- 新增 _calculate_lane_conflicts_v2() 分開返回標籤重疊和線穿框分數 - 修改泳道選擇算法,優先選擇無標籤重疊的泳道 - 兩階段搜尋:優先側別無可用泳道則嘗試另一側 - 增強日誌輸出,顯示標籤範圍和詳細衝突分數 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
323 lines
8.6 KiB
Markdown
323 lines
8.6 KiB
Markdown
# 時間軸標籤避碰改進(v5.0) - 真正的碰撞預防系統
|
||
|
||
## 新增改進(v5.0)
|
||
|
||
### 核心概念:從靜態分層到動態碰撞檢測
|
||
|
||
**v4.1 的問題**:
|
||
- ❌ 只是根據層級靜態計算路徑高度
|
||
- ❌ 沒有真正檢測線條之間的碰撞
|
||
- ❌ 沒有檢測線條與文字框的碰撞
|
||
- ❌ 仍然會出現嚴重的重疊
|
||
|
||
**v5.0 的解決方案**:
|
||
- ✅ **真正的碰撞檢測算法**
|
||
- ✅ **動態路徑優化**
|
||
- ✅ **20個候選高度,選擇最佳路徑**
|
||
- ✅ **實時追蹤已繪製的線條和文字框**
|
||
|
||
---
|
||
|
||
## 技術實現
|
||
|
||
### 1. **碰撞檢測算法**
|
||
|
||
#### 線段與線段碰撞檢測
|
||
```python
|
||
def check_collision(x_start_sec, x_end_sec, y_height, margin=0.05):
|
||
collision_score = 0
|
||
|
||
# 檢查與已繪製線段的碰撞
|
||
for seg_start, seg_end, seg_y in drawn_horizontal_segments:
|
||
# Y 座標是否接近(在 margin 範圍內)
|
||
if abs(y_height - seg_y) < margin:
|
||
# X 範圍是否重疊
|
||
if not (x_end_sec < seg_start or x_start_sec > seg_end):
|
||
overlap = min(x_end_sec, seg_end) - max(x_start_sec, seg_start)
|
||
collision_score += overlap / (x_end_sec - x_start_sec + 1)
|
||
|
||
return collision_score
|
||
```
|
||
|
||
**邏輯**:
|
||
- 檢查新線段的水平部分是否與已有線段在同一高度(±5%範圍內)
|
||
- 計算 X 軸重疊的比例
|
||
- 重疊越多,碰撞分數越高
|
||
|
||
#### 線段與文字框碰撞檢測
|
||
```python
|
||
# 檢查與文字框的碰撞
|
||
for box_x, box_y, box_w, box_h in text_boxes:
|
||
# Y 座標是否在文字框範圍內
|
||
if abs(y_height - box_y) < box_h / 2 + margin:
|
||
# X 範圍是否穿過文字框
|
||
box_left = box_x - box_w / 2
|
||
box_right = box_x + box_w / 2
|
||
if not (x_end_sec < box_left or x_start_sec > box_right):
|
||
overlap = min(x_end_sec, box_right) - max(x_start_sec, box_left)
|
||
collision_score += overlap / (x_end_sec - x_start_sec + 1) * 2 # 權重 x2
|
||
```
|
||
|
||
**邏輯**:
|
||
- 檢查線段是否穿過文字框的垂直範圍
|
||
- 計算與文字框的 X 軸重疊
|
||
- 文字框碰撞的權重是線段碰撞的2倍(更嚴重)
|
||
|
||
### 2. **最佳路徑選擇**
|
||
|
||
```python
|
||
def find_best_path_height(event_x_sec, label_x_sec, label_y, layer):
|
||
is_upper = label_y > 0
|
||
|
||
# 生成20個候選高度
|
||
candidates = []
|
||
if is_upper:
|
||
# 上方:從 20% 到 90% (每次增加 3.5%)
|
||
for i in range(20):
|
||
ratio = 0.20 + (i * 0.035)
|
||
candidates.append(ratio)
|
||
else:
|
||
# 下方:從 90% 到 20% (每次減少 3.5%)
|
||
for i in range(20):
|
||
ratio = 0.90 - (i * 0.035)
|
||
candidates.append(ratio)
|
||
|
||
# 計算每個高度的碰撞分數
|
||
best_ratio = candidates[layer % len(candidates)] # 默認值
|
||
min_collision = float('inf')
|
||
|
||
x_start = min(event_x_sec, label_x_sec)
|
||
x_end = max(event_x_sec, label_x_sec)
|
||
|
||
for ratio in candidates:
|
||
test_y = label_y * ratio
|
||
score = check_collision(x_start, x_end, test_y)
|
||
if score < min_collision:
|
||
min_collision = score
|
||
best_ratio = ratio
|
||
|
||
return best_ratio
|
||
```
|
||
|
||
**邏輯**:
|
||
1. 根據標籤位置(上方/下方)生成20個候選高度
|
||
2. 對每個候選高度計算碰撞分數
|
||
3. 選擇碰撞分數最低的高度
|
||
4. 如果所有高度碰撞分數相同(都是0),使用層級對應的默認高度
|
||
|
||
### 3. **實時追蹤系統**
|
||
|
||
```python
|
||
# 初始化追蹤列表
|
||
drawn_horizontal_segments = [] # [(x_start, x_end, y), ...]
|
||
text_boxes = [] # [(x_center, y_center, width, height), ...]
|
||
|
||
# 繪製後記錄
|
||
if not is_directly_above:
|
||
drawn_horizontal_segments.append((x_start_sec, x_end_sec, mid_y))
|
||
|
||
text_boxes.append((label_x_sec, label_y, label_width_sec, label_height))
|
||
```
|
||
|
||
**效果**:
|
||
- 每繪製一條線段,立即記錄其位置
|
||
- 每繪製一個文字框,立即記錄其範圍
|
||
- 後續線條會避開已記錄的所有障礙物
|
||
|
||
---
|
||
|
||
## 效果對比
|
||
|
||
### v4.1(靜態分層)
|
||
```python
|
||
# 只根據層級計算高度
|
||
if is_upper_side:
|
||
base_ratio = 0.25
|
||
layer_offset = layer_group * 0.06
|
||
mid_y_ratio = base_ratio + layer_offset
|
||
|
||
❌ 問題:無法知道這個高度是否會碰撞
|
||
```
|
||
|
||
### v5.0(動態碰撞檢測)
|
||
```python
|
||
# 測試20個候選高度
|
||
for ratio in candidates:
|
||
test_y = label_y * ratio
|
||
score = check_collision(x_start, x_end, test_y)
|
||
if score < min_collision:
|
||
best_ratio = ratio
|
||
|
||
✅ 優勢:保證選擇碰撞最少的路徑
|
||
```
|
||
|
||
---
|
||
|
||
## 性能分析
|
||
|
||
### 時間複雜度
|
||
- **單條線路徑選擇**:O(候選數 × (已繪線段數 + 文字框數))
|
||
- **全部線條**:O(事件數 × 候選數 × 事件數) = O(20n²)
|
||
- **實際情況**:因為是按順序繪製,平均複雜度約為 O(10n²)
|
||
|
||
### 空間複雜度
|
||
- **線段追蹤**:O(事件數)
|
||
- **文字框追蹤**:O(事件數)
|
||
- **總計**:O(事件數)
|
||
|
||
### 性能表現
|
||
- 10 個事件:~2000 次碰撞檢測
|
||
- 50 個事件:~50000 次碰撞檢測
|
||
- 100 個事件:~200000 次碰撞檢測
|
||
|
||
**優化空間**:
|
||
- 可以使用空間索引(R-tree)降低到 O(n log n)
|
||
- 可以減少候選數量(從20降到10)
|
||
- 可以使用啟發式策略減少檢測次數
|
||
|
||
---
|
||
|
||
## 參數配置
|
||
|
||
```python
|
||
# 碰撞檢測參數
|
||
margin = 0.05 # Y 軸碰撞容忍度(5%)
|
||
text_box_weight = 2.0 # 文字框碰撞權重(x2)
|
||
|
||
# 候選高度參數
|
||
candidates_count = 20 # 候選高度數量
|
||
upper_range = (0.20, 0.90) # 上方高度範圍 20%-90%
|
||
lower_range = (0.90, 0.20) # 下方高度範圍 90%-20%
|
||
step = 0.035 # 每次增減 3.5%
|
||
|
||
# 文字框估算參數
|
||
label_width_ratio = 0.15 # 文字框寬度 = 15% 時間軸
|
||
label_height = 0.3 # 文字框高度 = 0.3 單位
|
||
```
|
||
|
||
---
|
||
|
||
## 調整建議
|
||
|
||
### 如果仍有碰撞
|
||
|
||
1. **增加候選高度數量**
|
||
```python
|
||
for i in range(30): # 從 20 增加到 30
|
||
ratio = 0.20 + (i * 0.024) # 調整步長
|
||
```
|
||
|
||
2. **增加碰撞容忍度**
|
||
```python
|
||
margin = 0.08 # 從 0.05 增加到 0.08
|
||
```
|
||
|
||
3. **增加文字框尺寸估算**
|
||
```python
|
||
label_width_sec = time_range_seconds * 0.18 # 從 0.15 增加到 0.18
|
||
label_height = 0.4 # 從 0.3 增加到 0.4
|
||
```
|
||
|
||
### 如果性能太慢
|
||
|
||
1. **減少候選數量**
|
||
```python
|
||
for i in range(10): # 從 20 減少到 10
|
||
```
|
||
|
||
2. **使用啟發式優先級**
|
||
```python
|
||
# 優先測試層級對應的高度附近的候選
|
||
priority_candidates = [
|
||
candidates[layer % len(candidates)], # 優先級1:層級對應
|
||
candidates[(layer-1) % len(candidates)], # 優先級2:相鄰
|
||
candidates[(layer+1) % len(candidates)], # 優先級3:相鄰
|
||
# ... 然後測試其他候選
|
||
]
|
||
```
|
||
|
||
---
|
||
|
||
## 視覺效果
|
||
|
||
### 碰撞檢測過程示意
|
||
|
||
```
|
||
測試候選高度 ratio=0.20 (20%):
|
||
████████████ 線段1 (已存在)
|
||
────────────────── 測試線段 ← 碰撞! score=0.8
|
||
|
||
測試候選高度 ratio=0.35 (35%):
|
||
|
||
────────────────── 測試線段 ← 無碰撞! score=0.0 ✓
|
||
|
||
████████████ 線段1 (已存在)
|
||
|
||
選擇 ratio=0.35,碰撞分數最低
|
||
```
|
||
|
||
### 文字框避讓示意
|
||
|
||
```
|
||
┌──────────┐
|
||
│ 文字框A │ (已存在)
|
||
└──────────┘
|
||
↓
|
||
────────── 測試路徑1 ← 穿過文字框! score=1.5
|
||
↓
|
||
|
||
─────────── 測試路徑2 ← 避開文字框! score=0.0 ✓
|
||
|
||
●
|
||
時間軸
|
||
```
|
||
|
||
---
|
||
|
||
## 版本改進總結
|
||
|
||
| 版本 | 方法 | 線條交錯 | 線條穿框 | 性能 |
|
||
|------|------|----------|----------|------|
|
||
| v3.2 | 增加間距 | ❌ 嚴重 | ❌ 嚴重 | ⚡ 快 |
|
||
| v4.0 | 層級偏移 | ⚠️ 存在 | ⚠️ 偶爾 | ⚡ 快 |
|
||
| v4.1 | 鏡像分布 | ⚠️ 仍有 | ⚠️ 仍有 | ⚡ 快 |
|
||
| **v5.0** | **碰撞檢測** | **✅ 最小** | **✅ 極少** | **⚡ 中等** |
|
||
|
||
---
|
||
|
||
## 未來改進方向
|
||
|
||
### 1. **空間索引優化**
|
||
使用 R-tree 或 KD-tree 加速碰撞檢測:
|
||
- 當前:O(n) 檢測每個障礙物
|
||
- 優化後:O(log n) 查詢相關障礙物
|
||
|
||
### 2. **貝茲曲線**
|
||
使用平滑曲線代替直角折線:
|
||
- 更自然的視覺效果
|
||
- 更容易避開障礙物
|
||
|
||
### 3. **A* 路徑規劃**
|
||
使用圖搜索算法找到最優路徑:
|
||
- 可以繞過複雜的障礙物布局
|
||
- 保證找到全局最優解
|
||
|
||
### 4. **分組優化**
|
||
對事件進行分組,組內使用相似的路徑高度:
|
||
- 減少視覺混亂
|
||
- 突出事件的邏輯關係
|
||
|
||
---
|
||
|
||
**版本**: v5.0
|
||
**更新日期**: 2025-11-05
|
||
**作者**: Claude AI
|
||
|
||
## 關鍵突破
|
||
|
||
從**靜態規則**到**動態智能**:
|
||
- v1-v4:根據規則計算路徑 → 希望不會碰撞
|
||
- **v5**:測試所有可能路徑 → **保證選擇最佳路徑**
|
||
|
||
這是從**被動避讓**到**主動檢測**的質的飛躍! 🚀
|