Back to home page

EIC code displayed by LXR

 
 

    


Warning, /firebird/packages/firebird-ng/display/src/lib/services/selection.service.ts is written in an unsupported language. File is not indexed.

0001 import { Injectable, effect, inject, signal } from '@angular/core';
0002 import { EntityRef, entityRefOf } from '@dexvis/firebird-core';
0003 import { ThreeService } from './three.service';
0004 import { EventDisplayService } from './event-display.service';
0005 import { DataModelService } from './data-model.service';
0006 
0007 /**
0008  * The one selection of the event display: (piece name, entity index) — the
0009  * physics identity of the thing the user picked, not a scene object.
0010  *
0011  * Both directions run through here:
0012  * - a 3D click resolves the picked Object3D to its entity (painters stamp
0013  *   their objects; `entityRefOf` walks up to the stamp) and sets the signal;
0014  * - panels (model tree, inspectors) call `select()` and the owning painter
0015  *   highlights its scene objects.
0016  *
0017  * Hover over event data (opt-in hover picking) routes the same way: the
0018  * hovered entity is highlighted through its painter, and the selected
0019  * entity keeps its highlight while the pointer passes over it or others.
0020  *
0021  * Painters own the entity↔object mapping; this service only routes.
0022  */
0023 @Injectable({
0024   providedIn: 'root',
0025 })
0026 export class SelectionService {
0027   private three = inject(ThreeService);
0028   private eventDisplay = inject(EventDisplayService);
0029   private dataService = inject(DataModelService);
0030 
0031   private selectionSignal = signal<EntityRef | null>(null);
0032 
0033   /** The current selection, null when nothing is selected. */
0034   readonly selection = this.selectionSignal.asReadonly();
0035 
0036   private selectedPieceSignal = signal<string | null>(null);
0037 
0038   /** The entity under the pointer (hover picking), highlighted through its painter. */
0039   private hovered: EntityRef | null = null;
0040 
0041   /**
0042    * The piece the UI is focused on — set by entity selections (their piece)
0043    * and by piece-level clicks in the model tree. The painter-config panel
0044    * shows this piece's painter and knobs.
0045    */
0046   readonly selectedPiece = this.selectedPieceSignal.asReadonly();
0047 
0048   constructor() {
0049     // 3D pick → selection. trackClicked fires for any picked scene object;
0050     // only objects painters stamped resolve to an entity. The intersection
0051     // rides along for batched painters, which resolve the entity from the
0052     // picked segment instead of the object.
0053     this.three.trackClicked.subscribe(({ track, intersection }) => {
0054       const ref = entityRefOf(track, intersection);
0055       if (ref) {
0056         this.select(ref);
0057       }
0058     });
0059 
0060     // 3D hover → painter highlight (null: the pointer left event data).
0061     this.three.trackHovered.subscribe(picked => {
0062       this.hover(picked ? entityRefOf(picked.track, picked.intersection) : null);
0063     });
0064 
0065     // Event switches rebuild all painters — the old selection points into
0066     // disposed objects, so it resets.
0067     effect(() => {
0068       this.dataService.currentEntry();
0069       this.hovered = null;
0070       this.selectionSignal.set(null);
0071       this.selectedPieceSignal.set(null);
0072     });
0073   }
0074 
0075   /**
0076    * Moves the hover highlight to `ref` (null clears it). The selected entity
0077    * is never unhighlighted by hover, and is re-highlighted when the hover
0078    * leaves it: a painter may hold a single highlight slot.
0079    */
0080   private hover(ref: EntityRef | null): void {
0081     const previous = this.hovered;
0082     if (sameEntity(previous, ref)) return;
0083     this.hovered = ref;
0084     const selected = this.selectionSignal();
0085     if (previous && !sameEntity(previous, selected)) {
0086       this.eventDisplay.painterFor(previous.pieceName)?.unhighlightEntity(previous.entityIndex);
0087     }
0088     if (ref && !sameEntity(ref, selected)) {
0089       this.eventDisplay.painterFor(ref.pieceName)?.highlightEntity(ref.entityIndex);
0090     }
0091     if (selected && previous && !ref) {
0092       this.eventDisplay.painterFor(selected.pieceName)?.highlightEntity(selected.entityIndex);
0093     }
0094     // Highlights mutate materials directly — schedule a render.
0095     this.three.invalidate();
0096   }
0097 
0098   /** Focuses a piece without selecting an entity (model-tree piece click). */
0099   selectPiece(pieceName: string | null): void {
0100     this.selectedPieceSignal.set(pieceName);
0101   }
0102 
0103   /**
0104    * Selects one entity (or clears with null). The previously selected
0105    * entity is unhighlighted and the new one highlighted through its painter.
0106    */
0107   select(ref: EntityRef | null): void {
0108     const previous = this.selectionSignal();
0109     // The previous selection keeps its highlight while it is still hovered.
0110     if (previous && !sameEntity(previous, ref) && !sameEntity(previous, this.hovered)) {
0111       this.eventDisplay.painterFor(previous.pieceName)?.unhighlightEntity(previous.entityIndex);
0112     }
0113     if (ref) {
0114       this.eventDisplay.painterFor(ref.pieceName)?.highlightEntity(ref.entityIndex);
0115       this.selectedPieceSignal.set(ref.pieceName);
0116     }
0117     this.selectionSignal.set(ref);
0118     // Highlights mutate materials directly — schedule a render.
0119     this.three.invalidate();
0120   }
0121 
0122   clear(): void {
0123     this.select(null);
0124   }
0125 
0126   /** True when the given entity is the current selection. */
0127   isSelected(pieceName: string, entityIndex: number): boolean {
0128     const current = this.selectionSignal();
0129     return current !== null && current.pieceName === pieceName && current.entityIndex === entityIndex;
0130   }
0131 }
0132 
0133 /** True when both refs name the same entity (two nulls count as the same). */
0134 function sameEntity(a: EntityRef | null, b: EntityRef | null | undefined): boolean {
0135   if (!a || !b) return !a && !b;
0136   return a.pieceName === b.pieceName && a.entityIndex === b.entityIndex;
0137 }