forked from Rowland/EG
169 lines
4.9 KiB
Markdown
169 lines
4.9 KiB
Markdown
# 选择功能修复说明
|
||
|
||
## 问题描述
|
||
|
||
用户反馈点击模型后没有反应,应该要能够选中模型并显示选择框和坐标轴。
|
||
|
||
## 问题分析
|
||
|
||
通过分析代码发现了几个关键问题:
|
||
|
||
### 1. 缺少碰撞检测设置
|
||
**问题**:模型导入时没有设置碰撞检测,导致射线检测无法检测到模型。
|
||
- `scene_manager.py` 中的 `importModel` 方法没有调用 `setupCollision`
|
||
- 所有模型加载方法都缺少碰撞设置
|
||
|
||
### 2. 默认工具未设置
|
||
**问题**:`tool_manager.py` 中默认工具为 `None`,而选择功能需要当前工具为"选择"。
|
||
- 事件处理器中的条件 `if self.world.currentTool == "选择"` 永远不成立
|
||
|
||
### 3. 碰撞掩码不匹配
|
||
**问题**:射线检测和模型碰撞体使用不同的碰撞掩码。
|
||
- 模型碰撞体使用 `BitMask32.bit(2)`
|
||
- 射线检测使用 `GeomNode.getDefaultCollideMask()`
|
||
|
||
## 修复方案
|
||
|
||
### 1. 为所有模型添加碰撞检测
|
||
|
||
**修复位置**:`scene/scene_manager.py`
|
||
|
||
在以下方法中添加碰撞检测设置:
|
||
- `importModel()` - 主要模型导入方法
|
||
- `loadScene()` - 场景加载时为每个模型设置碰撞
|
||
- `processLoadedModel()` - 异步加载回调
|
||
- `loadAnimatedModel()` - 动画模型加载
|
||
|
||
```python
|
||
# 设置碰撞检测(重要!用于选择功能)
|
||
print("\n=== 设置碰撞检测 ===")
|
||
self.setupCollision(model)
|
||
```
|
||
|
||
### 2. 设置默认工具为选择
|
||
|
||
**修复位置**:`core/tool_manager.py`
|
||
|
||
```python
|
||
def __init__(self, world):
|
||
"""初始化工具管理器"""
|
||
self.world = world
|
||
self.currentTool = "选择" # 默认工具为选择工具
|
||
```
|
||
|
||
### 3. 统一碰撞掩码设置
|
||
|
||
**修复位置**:`core/event_handler.py`
|
||
|
||
```python
|
||
# 设置射线的碰撞掩码,匹配模型的碰撞掩码(第2位)
|
||
from panda3d.core import BitMask32
|
||
pickerNode.setFromCollideMask(BitMask32.bit(2))
|
||
```
|
||
|
||
## 碰撞检测系统工作原理
|
||
|
||
### 模型碰撞体设置
|
||
每个模型都会创建一个包围球体作为碰撞体:
|
||
```python
|
||
def setupCollision(self, model):
|
||
# 创建碰撞节点
|
||
cNode = CollisionNode(f'modelCollision_{model.getName()}')
|
||
cNode.setIntoCollideMask(BitMask32.bit(2)) # 使用第2位
|
||
|
||
# 获取模型边界并创建碰撞球体
|
||
bounds = model.getBounds()
|
||
center = bounds.getCenter()
|
||
radius = bounds.getRadius()
|
||
cSphere = CollisionSphere(center, radius)
|
||
cNode.addSolid(cSphere)
|
||
|
||
# 附加到模型
|
||
cNodePath = model.attachNewNode(cNode)
|
||
```
|
||
|
||
### 射线检测机制
|
||
鼠标点击时创建射线进行碰撞检测:
|
||
```python
|
||
# 创建射线检测
|
||
picker = CollisionTraverser()
|
||
queue = CollisionHandlerQueue()
|
||
|
||
pickerNode = CollisionNode('mouseRay')
|
||
pickerNP = self.world.cam.attachNewNode(pickerNode)
|
||
pickerNode.setFromCollideMask(BitMask32.bit(2)) # 匹配模型掩码
|
||
|
||
# 创建射线几何体
|
||
direction = farPoint - nearPoint
|
||
direction.normalize()
|
||
pickerNode.addSolid(CollisionRay(nearPoint, direction))
|
||
|
||
# 执行碰撞检测
|
||
picker.addCollider(pickerNP, queue)
|
||
picker.traverse(self.world.render)
|
||
```
|
||
|
||
## 选择系统工作流程
|
||
|
||
1. **鼠标点击** → `mousePressEventLeft()`
|
||
2. **坐标转换** → 屏幕坐标转世界坐标
|
||
3. **创建射线** → 相机位置到点击位置
|
||
4. **碰撞检测** → 射线与模型碰撞体检测
|
||
5. **选择处理** → `_handleSelectionClick()`
|
||
6. **更新UI** → 选择框、坐标轴、属性面板
|
||
|
||
## 测试验证
|
||
|
||
创建了专用测试脚本 `demo/selection_test.py`:
|
||
|
||
### 测试功能
|
||
- 创建多个测试立方体模型
|
||
- 验证碰撞检测设置
|
||
- 测试点击选择功能
|
||
- 显示选择框和坐标轴
|
||
|
||
### 测试控制
|
||
- **鼠标左键**:点击选择模型
|
||
- **I键**:显示当前选择信息
|
||
- **C键**:显示碰撞检测信息
|
||
- **S键**:切换碰撞体显示(调试)
|
||
- **R键**:切换射线显示
|
||
- **Q键**:退出
|
||
|
||
### 运行测试
|
||
```bash
|
||
cd demo
|
||
python selection_test.py
|
||
```
|
||
|
||
## 修复效果
|
||
|
||
**修复前**:
|
||
- 点击模型无反应
|
||
- 无法选中任何物体
|
||
- 选择框和坐标轴不显示
|
||
|
||
**修复后**:
|
||
- ✅ 点击模型正确选中
|
||
- ✅ 显示橙色选择框
|
||
- ✅ 显示彩色坐标轴(红X、绿Y、蓝Z)
|
||
- ✅ 树形控件同步选择
|
||
- ✅ 属性面板更新显示
|
||
- ✅ 坐标轴支持拖拽变换
|
||
|
||
## 相关功能
|
||
|
||
选择功能修复后,以下功能也恢复正常:
|
||
- **坐标轴拖拽**:点击坐标轴可拖拽移动物体
|
||
- **属性编辑**:选中后可在属性面板编辑位置、旋转、缩放
|
||
- **场景树同步**:点击模型会在场景树中高亮对应项
|
||
- **射线调试**:按R键可显示点击射线用于调试
|
||
|
||
## 技术要点
|
||
|
||
1. **碰撞掩码一致性**:射线检测和模型碰撞体必须使用相同的掩码位
|
||
2. **碰撞体覆盖**:包围球体确保模型的所有部分都可点击
|
||
3. **工具状态管理**:默认工具状态影响事件处理逻辑
|
||
4. **模块化设计**:碰撞设置在所有模型加载路径中都要调用
|
||
|
||
这次修复确保了3D编辑器的核心交互功能正常工作,为后续的编辑操作打下了坚实基础。 |