# `color` feature 実装調査報告 (2026-03-04)

修正案2（C++ が `from_a`/`from_b` を構築、Rust が色に反映）の実装過程で
判明した問題点と解決策をまとめる。

---

## 1. 実装した内容

### 修正案2 の骨格

`boolean_fuse` / `boolean_cut` / `boolean_common` の 3 関数内で
`BRepAlgoAPI_BooleanOperation::Modified()` / `IsDeleted()` を呼んでフェイスの由来を収集し、
deep copy 前後の TShape* 変化を `TopTools_IndexedMapOfShape` のインデックス一致で補正する。

収集結果は `BooleanShape::from_a` / `from_b` （`std::vector<uint64_t>`）に
`[post_copy_id, src_id, ...]` ペアとして格納し、
Rust 側の `#[cfg(feature = "color")]` ブロックが `merge_colormaps()` に渡す。

実装済みファイル:

| ファイル | 変更内容 |
|---------|---------|
| `Cargo.toml` | `color = []` feature 追加 |
| `cpp/wrapper.h` | `BooleanShape::from_a/from_b` フィールド、`boolean_shape_from_a/b`、`face_tshape_id` 宣言 |
| `cpp/wrapper.cpp` | `collect_relay_mapping` / `emit_from_pairs` ヘルパー、3 Boolean 関数に収集ロジック追加 |
| `src/ffi.rs` | `boolean_shape_from_a/b`、`face_tshape_id` FFI |
| `src/shape.rs` | `TShapeId`、`Rgb`、`Shape::colormap`、`merge_colormaps`、Boolean 演算後のカラーマップ合成 |
| `src/face.rs` | `Face::tshape_id()` |
| `src/solid.rs` | `From<Solid> for Shape` を `Shape::from_inner()` に修正 |
| `src/lib.rs` | `TShapeId`、`Rgb` を再エクスポート |
| `tests/integration_color.rs` | 統合テスト（後述の問題で修正中） |

---

## 2. `BRepBuilderAPI_Copy::ModifiedShape()` が使えない問題

### 問題

設計書（セクション 13、手順 ④）では

```
copier.ModifiedShape(pre_copy_face) → post_copy_face
```

でコピー前後の TShape* を対応させる想定だった。

しかし `BRepBuilderAPI_Copy` は `BRepBuilderAPI_MakeShape` を継承しており、
`BRepBuilderAPI_MakeShape::Modified()` のデフォルト実装は **空のリストを返す**。
したがって `copier.ModifiedShape(face)` は `face` 自身を返してしまい、
コピー後の TShape* への変換が機能しない。

### 解決策：`TopTools_IndexedMapOfShape` のインデックス一致

`BRepBuilderAPI_Copy` は内部でフェイスの **トポロジー走査順序を保持**する。
したがって：

```cpp
TopTools_IndexedMapOfShape pre_map, post_map;
TopExp::MapShapes(op.Shape(),     TopAbs_FACE, pre_map);
TopExp::MapShapes(copier.Shape(), TopAbs_FACE, post_map);
// インデックス i の face が pre_map と post_map で対応する
for (int i = 1; i <= pre_map.Size(); ++i) {
    uint64_t pre_id  = reinterpret_cast<uint64_t>(pre_map(i).TShape().get());
    uint64_t post_id = reinterpret_cast<uint64_t>(post_map(i).TShape().get());
    // relay[pre_id] が存在すれば (post_id, relay[pre_id]) をペアとして記録
}
```

この方法で pre→post の TShape* 対応が正しく取れることを
デバッグ出力で確認した。

---

## 3. `Shape::half_space()` の方向が設計と逆だった

### 問題

テストで

```rust
let half = Shape::half_space(DVec3::ZERO, DVec3::Z);
let result = cube.intersect(&half).expect("...");
```

と書いたとき、z > 0 の上半分が得られると期待していたが、
実際には z < 0 の **下半分** が返った。

### 原因

`Shape::half_space(origin, normal)` の C++ 実装：

```cpp
gp_Pnt ref_point(origin[0] - normal[0],
                 origin[1] - normal[1],
                 origin[2] - normal[2]);   // ref_point = origin - normal
BRepPrimAPI_MakeHalfSpace maker(face, ref_point);
```

`BRepPrimAPI_MakeHalfSpace` は **ref_point 側を solid とする**。
`normal = +Z` のとき `ref_point = (0, 0, -1)`（-Z 側）になるため、
solid は -Z 側 = z < 0 領域を占める。

つまり `half_space(origin, normal)` は **normal の向きとは逆の領域**を solid にする。

### 確認方法

デバッグ出力でフェイスごとに `op.IsDeleted(sf)` を確認したところ：

- `half_space(ZERO, +Z)` と `cube.intersect()` → top face（+Z 方向）が `IsDeleted = true`
- bottom face（-Z 方向）が unchanged → relay に入り、BLUE が正しく割り当てられる
- つまり「底面だけ残った下半分」が返っていた

### 影響

`from_a` / `from_b` のマッピング自体は正しく動作していた。
テストの期待値（上半分を想定）が誤っていただけ。

### テストへの修正方針

z > 0 の上半分を得るには `DVec3::NEG_Z` を使う：

```rust
let half = Shape::half_space(DVec3::ZERO, DVec3::NEG_Z);
// ref_point = (0, 0, 1) → +Z 側が solid → intersect は上半分を返す
```

または、`half_space` の API を直感的な方向（`normal` の向きを solid とする）に
修正する方法もある。

---

## 4. 現在の残課題

### テストの修正

`tests/integration_color.rs` を以下の通り修正する必要がある：

1. `DVec3::Z` → `DVec3::NEG_Z`（上半分を得るため）
2. デバッグ用 `eprintln!` を全て削除
3. テストコメントを実際の動作に合わせて更新

### デバッグコードの削除

| ファイル | 残留しているデバッグコード |
|---------|--------------------------|
| `src/shape.rs` | `intersect` 内の `eprintln!` ブロック |
| `tests/integration_color.rs` | 複数の `eprintln!` ブロック |
| `cpp/wrapper.cpp` | `#include <iostream>`（使用箇所なし） |

---

## 5. 動作確認済みの事項

- `from_a` / `from_b` の収集ロジック（`collect_relay_mapping` + `emit_from_pairs`）は正常動作
- `BRepBuilderAPI_Copy` によるコピー前後のインデックス対応は保持される
- `colormap` の合成 (`merge_colormaps`) は正常動作
- 変化なしのフェイス（unchanged）は元の色を保持できる
- トリムされたフェイス（trimmed, Modified() 非空）も元の色を保持できる
- 削除されたフェイス（IsDeleted = true）は colormap に含まれない
- 新規生成フェイス（交差断面）は colormap に含まれない（期待通り）

---

## 6. `half_space` API の設計ドキュメント修正案

現在のコードコメントが直感と逆のため、以下のどちらかを実施することを推奨する：

### 案A: API を修正（`ref_point = origin + normal` にする）

```cpp
gp_Pnt ref_point(origin[0] + normal[0],
                 origin[1] + normal[1],
                 origin[2] + normal[2]);  // solid は normal 向き側
```

`half_space(ZERO, +Z)` → z > 0 が solid → 直感と一致。
ただし既存ユーザーへの破壊的変更。

### 案B: ドキュメントに現在の動作を明記する

```rust
/// Creates an infinite half-space solid.
///
/// The solid occupies the region on the **opposite** side of `normal` from `origin`.
/// Equivalently, the solid contains all points `p` where `(p - origin) · normal < 0`.
///
/// Example: `half_space(DVec3::ZERO, DVec3::NEG_Z)` creates the z > 0 solid.
pub fn half_space(origin: DVec3, normal: DVec3) -> Shape { ... }
```

現時点では案A（修正）を推奨する。テストはまだ修正前なので破壊的変更のコストが低い。
