# 虚拟物体立方体碰撞检测功能设计方案

## 需求背景

当前项目中，检测动画用的移动物体是手工选择的。需要增加一种方式，按照路径编辑中物体的长宽高，动态生成一个立方体，用它来进行碰撞检测。

**需求来源**：`doc/requirement/todo_features.md` - 2025/12/08 功能点3
> （功能）动画检测，增加使用模拟物流构件立方体选项

## 方案选择

### 方案A（已弃用）：虚拟包围盒
- 仅使用虚拟包围盒进行碰撞检测
- **问题**：Clash Detective需要两个真实的ModelItem进行精确几何体验证，虚拟包围盒无法满足

### 方案B（采用）：创建真实几何体
- 在Navisworks中动态创建一个真实的立方体几何体
- 将其作为动画对象，复用现有的全部碰撞检测逻辑
- **优势**：完全复用现有代码，支持Clash Detective精确验证

## 核心思路

**关键洞察**：虚拟物体本质上就是一个动态创建的立方体ModelItem，一旦创建完成，后续流程与手动选择物体**完全一致**。

```
用户选择"虚拟物体模式"
       ↓
根据物体尺寸创建立方体几何体（NWD/NWC文件或内存几何体）
       ↓
将创建的立方体作为 _animatedObject
       ↓
【完全复用现有流程】
  - SetupAnimation()
  - PrecomputeAnimationFrames()
  - StartAnimation()
  - CreateClashTestAfterAnimation()
```

## 技术实现

### 1. 创建立方体几何体的方法

Navisworks API有以下几种方式创建几何体：

#### 方法1：使用临时NWD文件（推荐）
```csharp
// 1. 创建临时NWD文件包含立方体
// 2. 追加到当前文档
// 3. 获取追加后的ModelItem引用
// 4. 动画结束后移除临时模型
```

#### 方法2：使用Presenter API（如果可用）
```csharp
// 使用 Autodesk.Navisworks.Api.Presenter 命名空间
// 创建临时几何体用于可视化
```

#### 方法3：使用外部工具生成
```csharp
// 预先创建一个单位立方体NWC文件
// 运行时追加并缩放到目标尺寸
```

### 2. 实现方案详细设计

#### 2.1 创建 VirtualObjectManager 类

```csharp
/// <summary>
/// 虚拟物体管理器 - 负责创建和管理虚拟物体几何体
/// </summary>
public class VirtualObjectManager
{
    private static VirtualObjectManager _instance;
    public static VirtualObjectManager Instance => _instance ?? (_instance = new VirtualObjectManager());
    
    private ModelItem _virtualObjectModelItem;
    private string _tempFilePath;
    
    /// <summary>
    /// 创建虚拟物体立方体
    /// </summary>
    /// <param name="lengthMeters">长度（米）</param>
    /// <param name="widthMeters">宽度（米）</param>
    /// <param name="heightMeters">高度（米）</param>
    /// <param name="position">初始位置</param>
    /// <returns>创建的ModelItem</returns>
    public ModelItem CreateVirtualObject(double lengthMeters, double widthMeters, double heightMeters, Point3D position)
    {
        try
        {
            // 1. 清理之前的虚拟物体
            RemoveVirtualObject();
            
            // 2. 创建临时NWD文件包含立方体
            _tempFilePath = CreateCubeNwdFile(lengthMeters, widthMeters, heightMeters);
            
            // 3. 追加到当前文档
            var doc = Application.ActiveDocument;
            doc.AppendFile(_tempFilePath);
            
            // 4. 获取追加后的ModelItem（最后一个模型的根节点）
            _virtualObjectModelItem = GetLastAppendedModel();
            
            // 5. 移动到指定位置
            MoveToPosition(_virtualObjectModelItem, position);
            
            LogManager.Info($"虚拟物体创建成功: {lengthMeters:F1}m × {widthMeters:F1}m × {heightMeters:F1}m");
            return _virtualObjectModelItem;
        }
        catch (Exception ex)
        {
            LogManager.Error($"创建虚拟物体失败: {ex.Message}");
            throw;
        }
    }
    
    /// <summary>
    /// 移除虚拟物体
    /// </summary>
    public void RemoveVirtualObject()
    {
        if (_virtualObjectModelItem != null)
        {
            // 从文档中移除模型
            // ...
            _virtualObjectModelItem = null;
        }
        
        // 清理临时文件
        if (!string.IsNullOrEmpty(_tempFilePath) && File.Exists(_tempFilePath))
        {
            File.Delete(_tempFilePath);
            _tempFilePath = null;
        }
    }
    
    /// <summary>
    /// 获取当前虚拟物体
    /// </summary>
    public ModelItem CurrentVirtualObject => _virtualObjectModelItem;
}
```

#### 2.2 修改 AnimationControlViewModel

```csharp
private void ExecuteGenerateAnimation()
{
    try
    {
        ModelItem animatedObject;
        
        if (UseVirtualObject)
        {
            // 创建虚拟物体几何体
            var startPosition = CurrentPathRoute.Points.First();
            animatedObject = VirtualObjectManager.Instance.CreateVirtualObject(
                VirtualObjectLength,
                VirtualObjectWidth,
                VirtualObjectHeight,
                new Point3D(startPosition.X, startPosition.Y, startPosition.Z)
            );
            
            LogManager.Info($"使用虚拟物体: {VirtualObjectLength:F1}m × {VirtualObjectWidth:F1}m × {VirtualObjectHeight:F1}m");
        }
        else
        {
            animatedObject = SelectedAnimatedObject;
        }
        
        // 【以下完全复用现有逻辑】
        var pathPoints = CurrentPathRoute.Points.Select(p => new Point3D(p.X, p.Y, p.Z)).ToList();
        
        _pathAnimationManager.CreateAnimation(
            animatedObject,  // 无论是手动选择的还是虚拟物体，都是真实的ModelItem
            pathPoints,
            AnimationDuration,
            CurrentPathRoute.Name,
            CurrentPathRoute.Id
        );
        
        // ... 其余现有逻辑不变 ...
    }
    catch (Exception ex)
    {
        // ...
    }
}
```

#### 2.3 动画结束后清理

```csharp
// 在动画完成后的处理中
private void OnAnimationCompleted()
{
    // ... 现有的报告生成和Clash测试创建 ...
    
    // 如果使用了虚拟物体，询问用户是否保留
    if (UseVirtualObject && VirtualObjectManager.Instance.CurrentVirtualObject != null)
    {
        // 可选：提示用户是否保留虚拟物体
        // 或者自动移除
        // VirtualObjectManager.Instance.RemoveVirtualObject();
    }
}
```

### 3. 创建立方体NWD文件的技术细节

由于Navisworks API不直接支持创建几何体，可以采用以下方案：

#### 方案A：预制单位立方体 + 缩放
1. 预先准备一个1m×1m×1m的立方体NWC文件（放在插件资源目录）
2. 运行时追加这个文件
3. 使用`Transform3D.CreateScale`缩放到目标尺寸

```csharp
private ModelItem CreateScaledCube(double length, double width, double height)
{
    // 追加预制的单位立方体
    var unitCubePath = Path.Combine(PluginResourcesPath, "unit_cube.nwc");
    doc.AppendFile(unitCubePath);
    
    var cubeItem = GetLastAppendedModel();
    
    // 缩放到目标尺寸
    var scaleTransform = Transform3D.CreateScale(length, width, height);
    doc.Models.OverridePermanentTransform(new ModelItemCollection { cubeItem }, scaleTransform, true);
    
    return cubeItem;
}
```

#### 方案B：使用FBX/OBJ中间格式
1. 运行时生成OBJ格式的立方体文件
2. 使用Navisworks的文件转换功能导入

### 4. 关于预制立方体文件

需要准备一个`unit_cube.nwc`文件：
- 尺寸：1m × 1m × 1m
- 原点：底面中心在原点
- 材质：半透明（便于观察碰撞）

可以使用以下工具创建：
1. AutoCAD/Revit 导出
2. 3ds Max 导出
3. 简单的OBJ文件手动编写后转换

## 修改文件清单

| 文件 | 修改类型 | 主要改动 |
|------|----------|----------|
| `VirtualObjectManager.cs` | **新增** | 虚拟物体几何体创建和管理 |
| `AnimationControlView.xaml` | 修改 | 添加RadioButton选择组和虚拟物体尺寸显示 |
| `AnimationControlViewModel.cs` | 修改 | 调用VirtualObjectManager创建虚拟物体 |
| `LogisticsControlPanel.xaml.cs` | 修改 | 添加物体参数同步逻辑 |
| `resources/unit_cube.nwc` | **新增** | 预制的单位立方体模型文件 |

## 关键优势

1. **完全复用现有代码**：创建真实ModelItem后，所有现有逻辑（动画、碰撞检测、Clash Detective测试、报告生成）无需任何修改
2. **真实的碰撞检测结果**：Clash Detective可以进行精确几何体验证
3. **最小改动原则**：只需添加创建几何体的逻辑，其他代码不变

## 测试要点

1. **几何体创建**
   - 虚拟物体立方体正确创建
   - 尺寸和位置正确

2. **碰撞检测**
   - 与手动选择物体的检测结果一致
   - Clash Detective测试正常生成

3. **清理**
   - 动画结束后正确清理临时文件
   - 用户可选择是否保留虚拟物体

## 实现步骤

1. 准备预制的单位立方体NWC文件
2. 创建VirtualObjectManager类
3. 修改AnimationControlViewModel调用VirtualObjectManager
4. 测试完整流程
