Back to home page

EIC code displayed by LXR

 
 

    


Warning, /firebird/packages/firebird-core/src/painters/event-piece-painter.ts is written in an unsupported language. File is not indexed.

0001 import { Object3D } from "three";
0002 import { Signal, signal } from "@angular/core";
0003 import { EventPiece } from "../model/event-piece";
0004 import {disposeHierarchy} from '@dexvis/threejs-tree-editor';
0005 
0006 
0007 /** Define the type for the constructor of EventPiecePainter subclasses */
0008 export type PiecePainterConstructor =
0009   new (node: Object3D, piece: EventPiece, config?: PainterConfigView) => EventPiecePainter;
0010 
0011 /**
0012  * One configurable knob of a painter, declared statically in `PainterMeta`.
0013  * The declaration drives the auto-rendered painter panel and the config keys
0014  * the knob is stored under (`painters.byPiece.<pieceName>.<key>`), so a knob
0015  * is settable from the UI, a URL, a server config file, and batch scripts
0016  * without painter code knowing any of those sources.
0017  */
0018 export interface PainterConfigDescriptor {
0019   /** The knob name. Must not be one of `RESERVED_PAINTER_KNOB_KEYS`. */
0020   key: string;
0021   default: unknown;
0022   label?: string;
0023   /** Enumerated choices; rendered as a picker. */
0024   options?: readonly unknown[];
0025   /** Numeric range; rendered as a slider. */
0026   min?: number;
0027   max?: number;
0028   step?: number;
0029 }
0030 
0031 /**
0032  * Static painter self-description: what it paints, how UI names it, and what
0033  * its knobs are. Declared as `static meta` on painter classes; the painter
0034  * registry uses `forPieceTypes` and `id` (painter selection is the config key
0035  * `painters.byPiece.<pieceName>` holding an id from the registered painters).
0036  */
0037 export interface PainterMeta {
0038   id: string;
0039   forPieceTypes: string[];
0040   label?: string;
0041   configs?: PainterConfigDescriptor[];
0042 }
0043 
0044 /**
0045  * The resolved configuration a painter instance reads its knobs through.
0046  * Values arrive resolved across all config sources; the painter neither knows
0047  * nor cares which source set them. Worker-safe: implementations are plain
0048  * objects over signals — no DI.
0049  */
0050 export interface PainterConfigView {
0051   /** Current value of a declared knob. */
0052   value<T>(key: string): T;
0053   /** Signal of a declared knob, for reactive bindings. */
0054   signal<T>(key: string): Signal<T>;
0055 }
0056 
0057 /**
0058  * Knob keys no painter may declare:
0059  * - `visible` is the piece visibility toggle
0060  *   (`painters.byPiece.<pieceName>.visible`). The display applies it to the
0061  *   painter's root node; painter code never reads it.
0062  * - `time` would share its storage key with the timestamp that the config
0063  *   storage keeps beside the painter-selection key
0064  *   (`painters.byPiece.<pieceName>` is stored with `painters.byPiece.<pieceName>.time`).
0065  */
0066 export const RESERVED_PAINTER_KNOB_KEYS: readonly string[] = ['time', 'visible'];
0067 
0068 /**
0069  * Throws when a painter's meta declares a reserved knob key. Painter
0070  * registration calls it, so the mistake surfaces when the painter is
0071  * registered instead of as a silently shared setting.
0072  */
0073 export function assertPainterKnobKeys(painterClass: PiecePainterConstructor): void {
0074   const meta = painterMetaOf(painterClass);
0075   for (const descriptor of meta?.configs ?? []) {
0076     if (RESERVED_PAINTER_KNOB_KEYS.includes(descriptor.key)) {
0077       throw new Error(`Painter '${painterIdOf(painterClass)}' declares the knob '${descriptor.key}', ` +
0078         `whose name is reserved (${RESERVED_PAINTER_KNOB_KEYS.join(', ')}). Rename the knob.`);
0079     }
0080   }
0081 }
0082 
0083 /** The `meta` declaration of a painter class, when it has one. */
0084 export function painterMetaOf(painterClass: PiecePainterConstructor): PainterMeta | undefined {
0085   return (painterClass as unknown as { meta?: PainterMeta }).meta;
0086 }
0087 
0088 /** The registry/config id of a painter class (meta id, class name fallback). */
0089 export function painterIdOf(painterClass: PiecePainterConstructor): string {
0090   return painterMetaOf(painterClass)?.id ?? painterClass.name;
0091 }
0092 
0093 /**
0094  * A config view holding the declared defaults — what a painter gets in
0095  * contexts without a config system (workers, scripts, tests).
0096  */
0097 export function defaultPainterConfigView(meta?: PainterMeta): PainterConfigView {
0098   const signals = new Map<string, Signal<unknown>>();
0099   for (const descriptor of meta?.configs ?? []) {
0100     signals.set(descriptor.key, signal(descriptor.default));
0101   }
0102   const emptySignal = signal(undefined);
0103   return {
0104     value<T>(key: string): T {
0105       return (signals.get(key)?.() as T)!;
0106     },
0107     signal<T>(key: string): Signal<T> {
0108       return (signals.get(key) ?? emptySignal) as Signal<T>;
0109     },
0110   };
0111 }
0112 
0113 /**
0114  * Identifies one entity inside one piece: the piece name (unique within the
0115  * event) plus the entity index (entity id ≡ index into the piece columns).
0116  */
0117 export interface EntityRef {
0118   pieceName: string;
0119   entityIndex: number;
0120 }
0121 
0122 /**
0123  * Resolves a scene object (or any descendant of one) back to the entity it
0124  * was built from, by walking up to the nearest ancestor a painter stamped
0125  * with `registerEntityObject`. Returns null for objects that do not belong
0126  * to any painted entity (geometry, helpers).
0127  *
0128  * Batched painters draw MANY entities in one object, so a per-object stamp
0129  * cannot name the entity — they stamp `userData['entityIndexResolver']`, a
0130  * function mapping the raycast intersection (its picked segment/instance)
0131  * to the entity index. Pass the intersection through when there is one.
0132  */
0133 export function entityRefOf(object: Object3D, intersection?: unknown): EntityRef | null {
0134   for (let node: Object3D | null = object; node; node = node.parent) {
0135     const pieceName = node.userData?.["pieceName"];
0136     if (typeof pieceName !== "string") continue;
0137 
0138     const resolver = node.userData?.["entityIndexResolver"];
0139     if (typeof resolver === "function" && intersection !== undefined) {
0140       const resolved = resolver(intersection);
0141       if (typeof resolved === "number") return { pieceName, entityIndex: resolved };
0142     }
0143     const entityIndex = node.userData?.["entityIndex"];
0144     if (typeof entityIndex === "number") {
0145       return { pieceName, entityIndex };
0146     }
0147   }
0148   return null;
0149 }
0150 
0151 /**
0152  * Paints all primitives for a given EventPiece.
0153  *
0154  * Painters own the entity↔object mapping: as a painter builds scene objects
0155  * it registers them per entity index (a plain array — entity id ≡ index, no
0156  * lookup maps). The mapping powers selection both ways: a 3D pick resolves
0157  * to (piece, entityIndex) via `entityRefOf`, and a model-tree selection
0158  * resolves to scene objects via `objectForEntity`.
0159  */
0160 export abstract class EventPiecePainter {
0161 
0162   /** Scene object per entity index; holes where no object was built. */
0163   protected entityObjects: (Object3D | null)[] = [];
0164 
0165   /**
0166    * The painter's resolved knobs. Always usable: without an injected view the
0167    * declared defaults from `static meta` apply (workers, scripts, tests).
0168    */
0169   protected config: PainterConfigView;
0170 
0171   /** Constructor is public since we can instantiate directly */
0172   constructor(protected parentNode: Object3D, protected piece: EventPiece, config?: PainterConfigView) {
0173     this.config = config ?? defaultPainterConfigView(painterMetaOf(this.constructor as PiecePainterConstructor));
0174   }
0175 
0176   /** Gets the `type` identifier for the piece this class works with */
0177   public get pieceType() {
0178     return this.piece.type;
0179   }
0180 
0181   /** The name of the painted piece (unique within its event). */
0182   public get pieceName() {
0183     return this.piece.name;
0184   }
0185 
0186   /**
0187    * The scene group holding everything this painter builds. Piece-level
0188    * show/hide (the model tree eye, `painters.byPiece.<name>.visible`) flips
0189    * `visible` here instead of touching individual entity objects.
0190    */
0191   public get node(): Object3D {
0192     return this.parentNode;
0193   }
0194 
0195   /**
0196    * Records the scene object of one entity and stamps it so `entityRefOf`
0197    * can resolve the object (or its mesh children) back to the entity.
0198    * Painters call this as they build objects.
0199    */
0200   protected registerEntityObject(entityIndex: number, object: Object3D): void {
0201     this.entityObjects[entityIndex] = object;
0202     object.userData["pieceName"] = this.piece.name;
0203     object.userData["entityIndex"] = entityIndex;
0204   }
0205 
0206   /** The scene object built for an entity, or null when there is none. */
0207   public objectForEntity(entityIndex: number): Object3D | null {
0208     return this.entityObjects[entityIndex] ?? null;
0209   }
0210 
0211   /**
0212    * Applies the highlight visual of one entity. The default runs the
0213    * `highlightFunction` the painter stored on the entity's object; painters
0214    * with special highlight needs override this instead.
0215    */
0216   public highlightEntity(entityIndex: number): void {
0217     const object = this.objectForEntity(entityIndex);
0218     const highlight = object?.userData["highlightFunction"];
0219     if (typeof highlight === "function") highlight();
0220   }
0221 
0222   /** Reverts `highlightEntity` (default: the stored `unhighlightFunction`). */
0223   public unhighlightEntity(entityIndex: number): void {
0224     const object = this.objectForEntity(entityIndex);
0225     const unhighlight = object?.userData["unhighlightFunction"];
0226     if (typeof unhighlight === "function") unhighlight();
0227   }
0228 
0229   /**
0230    * Called after any of the painter's config knobs changed. Painters restyle
0231    * their existing objects here (colors, widths) — a knob change must not
0232    * require a rebuild or a data reload.
0233    */
0234   onConfigChanged(): void {}
0235 
0236   /**
0237    * Paints
0238    * @param time - time in [ns], null - draw in non-dynamic mode (all visible)
0239    */
0240   abstract paint(time: number | null): void;
0241 
0242   /**
0243    * Frees what the painter built and removes its node from the scene. The
0244    * base disposes the geometries and materials of the node's whole subtree,
0245    * so a painter that only adds objects under its node needs no override.
0246    * Override to free resources held outside the node (shared materials,
0247    * textures) and call `super.dispose()`.
0248    */
0249   public dispose(): void {
0250     this.entityObjects = [];
0251     if (this.parentNode) {
0252       disposeHierarchy(this.parentNode);
0253     }
0254   }
0255 }