# Human-in-the-Loop 碰撞优化功能 - 实现完成报告

## 当前状态（2026-02-09）

### 已完成功能

#### 第一阶段（基础分析）✅

1. **碰撞热点自动分析** - 预计算完成后自动分析碰撞结果
2. **自动高亮显示** - 自动高亮所有预计算碰撞结果（紫色 #9C27B0）
3. **分析对话框** - 显示详细的碰撞统计和建议排除选项
4. **光标修复** - 对话框显示时光标恢复正常

#### 第二阶段（排除列表集成）✅

1. **排除列表数据结构** - `PathAnimationManager` 中使用 `HashSet<ModelItem>`
2. **管理方法** - `SetExcludedObjectsAndClearCache`、`AddExcludedObjects`、`ClearExcludedObjects`
3. **预计算过滤** - 在 `PrecomputeAnimationFrames()` 中应用排除列表过滤
4. **对话框集成** - 分析结果自动合并到排除列表
5. **UI区域** - 新增"检测排除对象"区域，支持手工添加和显示排除列表

#### 第三阶段（UI优化）✅

1. **碰撞分析对话框增强**
   - 序号列显示
   - 行点击自动高亮对应物体（紫色预计算风格）
   - 候选碰撞数字黑色粗体显示
   - 预计检测时间显示（xxx秒/xx.x分钟）
   - 智能自动选中（碰撞>100次 或 占比>50%）
   - 单一"继续生成动画"按钮（自动判断是否有排除对象）

2. **排除列表UI完善**
   - 添加"清除高亮"按钮
   - 删除列表项时自动清除高亮
   - 使用 `ModelItemEquals` 正确比较对象（修复 InstanceGuid 问题）

#### 第四阶段（数据库持久化）✅

1. **数据库表结构**
   - `ExcludedObjects` - 存储排除对象（PathId、DisplayName、ModelIndex等）
   - `CollisionReportExcludedObjects` - 碰撞报告与排除对象关联表
   - `ClashDetectiveExcludedObjects` - ClashDetective结果与排除对象关联表

2. **数据持久化**
   - 排除对象随碰撞报告一起保存到数据库
   - 支持从数据库加载排除对象（通过PathId查找）
   - 碰撞历史和报告中显示当时使用的排除对象列表

3. **核心方法**
   - `SaveExcludedObjectsToDatabase()` - 保存排除对象到数据库
   - `LoadExcludedObjectsFromDatabase()` - 从数据库加载排除对象
   - `LinkExcludedObjectsToCollisionReport()` - 关联排除对象到碰撞报告

---

## 新增/修改文件

| 文件 | 说明 |
|------|------|
| `src/UI/WPF/Views/CollisionAnalysisDialog.xaml` | 分析对话框UI（已统一项目风格） |
| `src/UI/WPF/Views/CollisionAnalysisDialog.xaml.cs` | 分析对话框逻辑（序号、高亮、智能选中） |
| `src/UI/WPF/ViewModels/AnimationControlViewModel.cs` | 添加分析逻辑、排除列表管理、数据库保存 |
| `src/Core/Animation/PathAnimationManager.cs` | 排除列表字段、预计算过滤、数据库加载/保存 |
| `src/UI/WPF/Views/AnimationControlView.xaml` | "检测排除对象"UI区域 |
| `src/Utils/ModelItemAnalysisHelper.cs` | 添加 `ModelItemEquals` 正确比较方法 |
| `src/Core/PathDatabase.cs` | 添加排除列表相关的数据库表和方法 |

---

## 技术实现细节

### 排除列表存储与比较

```csharp
// 使用 HashSet<ModelItem> 存储排除对象
private HashSet<ModelItem> _excludedObjects = new HashSet<ModelItem>();

// 正确的 ModelItem 比较（使用底层原生对象比较）
public static bool ModelItemEquals(ModelItem item1, ModelItem item2)
{
    if (item1 == null && item2 == null) return true;
    if (item1 == null || item2 == null) return false;
    return item1.Equals(item2);  // 使用 Navisworks API 的 Equals
}
```

**重要修复**：之前使用 `InstanceGuid` 比较会导致不同对象被误判为相同，现在统一使用 `ModelItemEquals`。

### 预计算过滤点

```csharp
// 在 PrecomputeAnimationFrames 的循环中
var nearbyObjects = spatialIndexManager.FindInAABB(searchBounds, excludeObject: _animatedObject);

// 应用排除列表过滤
nearbyObjects = nearbyObjects.Where(obj => !_excludedObjects.Contains(obj));
```

### 智能自动选中逻辑

```csharp
// 碰撞分析对话框中的智能选中条件
IsExcluded = h.CollisionCount > 100 || h.Percentage > 50
```

### 行点击高亮

```csharp
// 使用预计算碰撞结果高亮类别（紫色 #9C27B0）
private const string PrecomputedHighlightCategory = ModelHighlightHelper.PrecomputeCollisionResultsCategory;

// 点击行时高亮对应物体
ModelHighlightHelper.HighlightItems(PrecomputedHighlightCategory, items);
```

### UI 绑定

```csharp
// ViewModel 属性
public ThreadSafeObservableCollection<ExcludedObjectViewModel> ExcludedObjects { get; }
public string ExcludedObjectSummary { get; set; }
public bool HasExcludedObjects { get; }

// 命令
public ICommand AddExcludedObjectsFromSelectionCommand { get; }
public ICommand ClearExcludedObjectsCommand { get; }
public ICommand RemoveExcludedObjectCommand { get; }
public ICommand HighlightAllExcludedObjectsCommand { get; }
public ICommand ClearExcludedHighlightCommand { get; }
```

### 数据库持久化

#### 数据库表结构

```sql
-- 排除对象表
CREATE TABLE ExcludedObjects (
    Id INTEGER PRIMARY KEY AUTOINCREMENT,
    RouteId TEXT,  -- 关联路径ID（可为空表示全局排除）
    ModelIndex INTEGER NOT NULL,
    PathId TEXT NOT NULL,  -- 格式: "模型索引:路径索引数组"
    DisplayName TEXT,
    ObjectName TEXT,
    ExcludedTime DATETIME DEFAULT CURRENT_TIMESTAMP,
    Reason TEXT,
    IsGlobal INTEGER DEFAULT 0
);

-- 碰撞报告与排除对象关联表
CREATE TABLE CollisionReportExcludedObjects (
    Id INTEGER PRIMARY KEY AUTOINCREMENT,
    ReportId INTEGER NOT NULL,
    ExcludedObjectId INTEGER NOT NULL
);
```

#### 保存排除对象到数据库

```csharp
// 在生成碰撞报告时保存排除对象
public void SaveExcludedObjectsToDatabase(PathDatabase database, string routeId)
{
    var records = new List<ExcludedObjectRecord>();
    foreach (var obj in _excludedObjects)
    {
        var record = new ExcludedObjectRecord
        {
            RouteId = routeId,
            ModelIndex = GetModelIndex(obj),
            PathId = GetModelItemPathId(obj),  // 格式: "0:1,2,3"
            DisplayName = obj.DisplayName,
            ObjectName = GetModelItemObjectName(obj),
            ExcludedTime = DateTime.Now,
            Reason = "用户排除"
        };
        records.Add(record);
    }
    database.SaveExcludedObjects(records);
}
```

#### 从数据库加载排除对象

```csharp
// 根据PathId查找ModelItem
private ModelItem FindModelItemByPathId(Document doc, string pathId)
{
    // PathId格式: "模型索引:路径索引数组"
    // 例如: "0:1,2,3" 表示第0个模型的第1->2->3个节点
    var parts = pathId.Split(':');
    int modelIndex = int.Parse(parts[0]);
    var pathIndices = parts[1].Split(',').Select(int.Parse).ToArray();
    
    var model = doc.Models[modelIndex];
    ModelItem current = model.RootItem;
    foreach (var index in pathIndices)
    {
        var childrenList = current.Children.ToList();
        current = childrenList[index];
    }
    return current;
}
```

---

## 工作流程

### 1. 动画生成流程

```
用户点击"生成动画"
    ↓
预计算碰撞检测
    ↓
分析碰撞热点（>5次或>10%）
    ↓
显示分析对话框（自动选中高频物体）
    ↓
用户调整勾选/点击行查看高亮
    ↓
点击"继续生成动画"
    ↓
[如果有排除对象] 添加到排除列表 → 清除缓存 → 重新生成
[如果无排除对象] 直接继续
```

### 2. 排除列表管理流程

```
手工添加：选择物体 → 点击"从选择添加" → 添加到列表
分析添加：分析对话框勾选 → 点击"继续生成动画" → 合并到列表
删除单个：点击列表项的删除 → 清除高亮 → 同步到Manager
清除所有：点击"清除所有" → 清空列表 → 清除高亮
高亮显示：点击"全部高亮"/"清除高亮"
```

### 3. 数据库持久化流程

```
生成碰撞报告
    ↓
保存排除对象到数据库（ExcludedObjects表）
    ↓
关联排除对象到碰撞报告（CollisionReportExcludedObjects表）
    ↓
查看碰撞历史时
    ↓
加载碰撞报告关联的排除对象列表
    ↓
显示"本次检测排除了X个对象"
```

---

## 使用指南

### 1. 通过分析对话框排除

1. 生成动画时自动触发预计算分析
2. 分析对话框显示高频碰撞物体（已自动选中>100次或>50%的）
3. 点击表格行可在3D视图中高亮对应物体（紫色）
4. 调整勾选状态后点击"继续生成动画"
5. 勾选的对象会被加入排除列表并重新生成

### 2. 手工管理排除列表

1. 在动画控制面板的"检测排除对象"区域
2. 在模型中选择要排除的物体
3. 点击"从选择添加"
4. 已排除物体在碰撞检测中会被忽略
5. 可随时"全部高亮"查看或"清除所有"清空

---

## 预期效果

### 减少 ClashDetective 测试数量

- 典型场景：379 次碰撞 -> 排除地面后可能减少到 50 次以下
- 按每次测试 133ms 计算：50s -> 6.6s，节省 86% 时间

### 提高结果质量

- 减少地面/楼板假阳性碰撞
- 让用户专注于真实障碍物
- 更准确的碰撞报告

---

## 后续可能的改进方向

### 智能建议增强

- [ ] 基于包围盒大小识别大型物体（>1000立方米）
- [ ] 基于位置识别：位于路径下方的物体可能是地面
- [ ] 学习用户历史排除选择，优先建议

### 碰撞类型分类

- [ ] 区分"疑似假阳性"（地面/楼板接触）
- [ ] 区分"真实碰撞"（墙体/障碍物）

### 性能优化

- [ ] 增量预计算（仅重新计算受影响的帧）
- [ ] 分析结果持久化到数据库

---

## 注意事项

1. **排除列表生命周期**：当前为运行时管理，切换路径后保留，关闭文档后失效
2. **ModelItem比较**：必须使用 `ModelItemEquals` 方法，不能用 `InstanceGuid`
3. **缓存机制**：排除列表变更会触发 `SetExcludedObjectsAndClearCache` 清除动画缓存
4. **高亮颜色**：
   - 排除对象：绿色 (#4CAF50)
   - 预计算碰撞：紫色 (#9C27B0)
   - 手动目标：橙色 (#FFAA00)
