Files
Timeline_Generator/IMPROVEMENTS_v5.md
beabigegg 2d37d23bcf v9.5: 實作標籤完全不重疊算法
- 新增 _calculate_lane_conflicts_v2() 分開返回標籤重疊和線穿框分數
- 修改泳道選擇算法,優先選擇無標籤重疊的泳道
- 兩階段搜尋:優先側別無可用泳道則嘗試另一側
- 增強日誌輸出,顯示標籤範圍和詳細衝突分數

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-06 11:35:29 +08:00

323 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 時間軸標籤避碰改進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**:測試所有可能路徑 → **保證選擇最佳路徑**
這是從**被動避讓**到**主動檢測**的質的飛躍! 🚀