--- name: fire-visual-placement-mode description: "fire-visual Placement-Mode (snap-to-point) — Architektur, Settings, Algorithmus, DropPoints, Commands; Pfade im threed/interaction- und threed/pmi-Modul" metadata: node_type: memory type: project originSessionId: 733f7054-ac76-49cd-ba04-516ef015d4f3 --- **WICHTIG: `fire-visual` ist KEIN eigener Code.** Es ist eine Kopie fremden Codes (Drittanbieter), die nur zum Reverse Engineering / Verstehen dient — nicht ändern, nicht als unser Produkt behandeln. Daraus gebaut wird das Paket `@ad/fire` (Repo `~/projects/geko-fire`, Remote `git.heron.at/pzm/geko/geko-fire.git`, nur Build: `browser/index.js` + `types/`, u.a. `import-fire-visual-modules.d.ts`). `fire-visual` selbst ist kein Git-Repo. adesso-cpq konsumiert `@ad/fire` als Dependency (`packages/frontend/package.json: "@ad/fire": "^1.0.6"`). Änderungen an der Visualisierung/Placement gehören NICHT hier hinein. Placement-Mode = snap-to-point-Platzierung eines Subjects über Drag-Points/Drop-Points. Teil des Moduls `@fire-visual/interaction`. Quelle: `~/projects/fire-visual`. ## Pfade - Mode: `threed/interaction/module/src/interactionMode/placementMode.ts` - Verdrahtung/Command: `threed/interaction/module/src/feature/interactionFeature.ts` (ab Zeile 259) - Options-Typen: `threed/interaction/module/src/options/modeOptions.ts` - Modul-Doku: `threed/interaction/MODULE.md` - Test: `threed/interaction/module/tests/placementMode.test.ts` - DropPoint-Implementierungen (PMI): `threed/pmi/module/src/dropPoint/{pmiDropPoint,planeDropPoint,pointDropPoint,lineDropPoint}.ts` - DropPoint-Factory: `threed/pmi/module/src/component/pmiComponent.ts` → `getDropPoint()` (Zeile 422) ## Grundbegriffe - **Subject**: das zu platzierende Objekt (`Handle`). - **Drag-Point**: Punkt am Subject (lokaler `transform: Matrix4_4`), an dem „angefasst" wird. Visualisiert als Kugel (Sphere). Aktiv = gelb, inaktiv = blau. - **Drop-Point**: Ziel-Snap-Geometrie in der Szene; liefert über `assessRay` eine Bewertung. Implementiert Interface `DropPoint`. - **activeDragPointIndex**: aktiver Drag-Point; `-1` = „alle Drag-Points" (nur erlaubt wenn `allowAllDragPoints !== false`). Start: `-1`, oder `0` falls `allowAllDragPoints === false`. ## PlacementModeSettings - `camera: CameraComponent`, `subject: Handle` - `dragPoints[]`: je `{ transform, dropPoints: Partial[], selector? }` - `defaultCursorTransform?` (Fallback = `dragPoints[0].transform`) - `allowAllDragPoints?` (Default true) — false: Index -1 verboten, Cycling hält immer einen konkreten DP aktiv - `allowDragPointCycling?` (Default true) — false: User kann aktiven DP nicht ändern, „alle DP" bleibt aktiv - `maxJumpDistance?` — überschreibt globalen Wert aus `ModeOptionProvider` ## Globale Optionen (ModeOptionProvider.options.placement, Typ PlacementModeOptions) `maxJumpDistance`, `highlightColor` (Default Color.yellow), `dragPointActiveColor` (Default yellow), `dragPointInactiveColor` (Default blue), `dragPointRadius` (Default 0.1). Plus `gizmoScale` (Default 1) skaliert Kugelradius. ## Algorithmus processInput(input) je Frame Input: `pointerNormalizedPosition`, `nextDragPoint`, `previousDragPoint`, `interactionClick`. 1. Ohne Pointer/Camera → false. 2. **Grab-Gating**: Rising edge von `interactionClick` setzt `_grabActive = true`. Solange nicht grab-aktiv: nur prevClick merken, return true (Subject bewegt sich nicht). 3. Cycling: bei `nextDragPoint`/`previousDragPoint` Index zyklen (wenn Cycling erlaubt). 4. Alle Drop-Point-Visualisierungen zurücksetzen (erst `false`+Highlight null), dann die zum aktiven DP gehörenden aktivieren (Index -1 ⇒ alle). 5. Ray von Kamera durch Pointer; Schnitt mit XZ-Ebene (`Plane` durch Ursprung, Normale up) → `intersectionPoint`. 6. Für jeden aktiven Drag-Point × dessen Drop-Points: Ray um `dropPoint.predictDragPointRotation(ray) * (dragPoint.transform⁻¹ * cursorTransform) * 0` versetzen, dann `dropPoint.assessRay(offsetRay)` → RayAssessment. 7. Beste Assessment über `betterRayAssessment`: kleinste `jumpDistance`; bei Gleichstand (Toleranz 0.05) kleinste `rayDistance`. 8. Wenn `best.jumpDistance < maxJumpDistance`: Drop-Point highlighten und Subject snappen: `subject.matrix = compose(best.position, best.rotation, 1) * dragPointTransform⁻¹`. Sonst: Subject auf `intersectionPoint` auf XZ-Ebene setzen (`* cursorTransform⁻¹`), rotation identity. 9. `_lastPlacement` (PlacementUpdateData) merken: position, rotation, dragPointSelector, dropPointSelector (jeweils null bei XZ-Fallback). 10. Drag-Point-Kugeln neu positionieren (`subjectMatrix * dragPoint.transform * 0`), Material gelb/blau je aktiv. 11. **Falling edge** von `interactionClick`: `_grabActive = false`, `_lastPlacement` per `emitUpdateEvent` (subject als selectorElementReferenceRepresentation) ausgeben. ## Cycling-Logik - allowAllDragPoints true: Reihenfolge … last → -1 (alle) → 0 → … (wrap über -1). - allowAllDragPoints false: -1 wird übersprungen, Wrap last → 0. ## Lifecycle - `startMode`: Drop-Points je Drag-Point aus Providern extrahieren (`getDropPoint()`), Drag-Point-Visuals (Sphere-Handles, BasicMaterial transparent opacity 0.75) per `addInteractionScene` erzeugen, Handles via `find('.drag-point-i')`. - `stopMode`: dropPoints/dragPointHandles leeren. ## Command (Environment): start_placement_mode(params) params: `subject: string`, `cursorTransform?`, `allowAllDragPoints?`, `allowDragPointCycling?`, `maxJumpDistance?`, `dragPoints: Array<{ selector?, transform?:{position,rotation}, dropPoints: string[] }>`. Auflösung in interactionFeature: - Subject-Selector → Handle (wirft wenn nicht gefunden). - Pro Drag-Point: `dropPoints`-Selektoren via `selectorEngine.findAll` → DropPointProvider. - Ohne `selector`: ein Drag-Point mit `transform` (aus position/rotation, sonst identity), selector undefined. - Mit `selector`: `findAll(selector, subjectHandle)` → je Sub-Entity ein Drag-Point mit `transform = subject.LTW⁻¹ * dpe.LTW * transform`, selector = `selectorEngine.selector(dpe)`. Weitere Commands: `placement_mode_next_drag_point`, `placement_mode_previous_drag_point` (werfen TypeError wenn aktueller Mode kein PlacementMode), `stop_interaction_mode`, `observe_interaction_events`. ## DropPoint-Interface `assessRay(ray): RayAssessment {jumpDistance, rayDistance, position, rotation}`, `predictDragPointRotation(ray): Matrix4_4`, `setVisualization(state)`, `setVisualizationHighlight(color|null)`, `selector(): string|null`. PMI-Implementierungen (`PmiDropPoint` Basis, hält `LTW`, `dragPointRotation = decompose(LTW)[1]`, predictRotation = konstant): - **PointDropPoint** (PMIType point/matrix): position = LTW·0; jumpDistance = Abstand Punkt↔nächster Punkt auf Strahl. - **LineDropPoint** (line): Segment LTW·0 → parentLTW·endpoint; closest segment-on-line. - **PlaneDropPoint** (plane): Ebene aus LTW; Ray-Schnitt, in Ebenen-Koords auf `size`/2 geclampt, zurück nach Welt; jumpDistance = clampedPoint↔rayPoint. `PMIComponent.getDropPoint()` mappt PMIType → Klasse (point/matrix→Point, line→Line, plane→Plane, sonst null). Verwandt: [[cpq-verbau-architektur]] (CPQ-seitige Verbau/Platzierung).