Back to home page

EIC code displayed by LXR

 
 

    


Warning, /firebird/packages/firebird-ng/api/src/lib/geometry-pipeline.ts is written in an unsupported language. File is not indexed.

0001 /**
0002  * The extension seams of the geometry pipeline, in pipeline order:
0003  *
0004  * 1. Pre-build TGeo rules (`withRootGeometryRules`): serializable data,
0005  *    posted with each load request to the geometry worker, which prunes and
0006  *    edits the ROOT tree before jsroot builds three.js objects. The rules
0007  *    travel as data because the worker is a prebuilt module and cannot
0008  *    import pack modules.
0009  * 2. Themes (`withGeometryTheme`): rule sets that color, merge and outline
0010  *    subdetectors on the main thread, selected by `geometry.themeName`.
0011  * 3. Post-processors (`withGeometryPostProcessor`): main-thread code that runs
0012  *    once the geometry sits in the scene (scaled to mm), before the first
0013  *    frame shows it. May be async (an environment map needs the renderer).
0014  *
0015  * Plain TypeScript with type-only three.js imports: initial-bundle code
0016  * (tokens, feature functions, the config page) imports this module.
0017  */
0018 
0019 import type { Type } from '@angular/core';
0020 import type { Object3D, Plane, Scene } from 'three';
0021 import type { ClippingGroup, WebGPURenderer } from 'three/webgpu';
0022 import type { GeoNodeEditRule } from '@dexvis/root-geo-tree-editor';
0023 import type { DetectorThreeRuleSet } from '@dexvis/threejs-tree-editor';
0024 import { resolveRegistry } from '@dexvis/app-features';
0025 
0026 // ---------------------------------------------------------------------------
0027 // Pre-build TGeo rules
0028 // ---------------------------------------------------------------------------
0029 
0030 /**
0031  * Edit rules for one detector. The rules apply, one at a time and in order,
0032  * to every top-level TGeo node whose path matches the wildcard
0033  * `namePattern`. A top-level path is the world volume name, a slash and the
0034  * node name ('Default/Tracker_14'), so patterns start with a `*` segment.
0035  */
0036 export interface DetectorEditRules {
0037   namePattern: string;
0038   editRules: GeoNodeEditRule[];
0039 }
0040 
0041 /** A named set of detector edit rules; config `geometry.rootFilterName` selects one. */
0042 export interface RootGeometryEditRuleSet {
0043   /** Option text on the config page. */
0044   label?: string;
0045   detectors: DetectorEditRules[];
0046 }
0047 
0048 /** A named list of top-level detectors removed before the build; config `geometry.cutListName` selects one. */
0049 export interface RootGeometryCutList {
0050   /** Option text on the config page. */
0051   label?: string;
0052   /** Name prefixes of top-level TGeo nodes to remove ('Beam' removes 'BeamMagnet_32'). */
0053   remove: string[];
0054 }
0055 
0056 /**
0057  * The edit rule set name that the data selector's "Optimize geometry" toggle
0058  * selects. The toggle shows only when a contribution defines this name, with
0059  * the rule set's label as its hint.
0060  */
0061 export const OPTIMIZE_EDIT_RULE_SET = 'default';
0062 
0063 /** What `withRootGeometryRules()` contributes. Plain data: it is posted to the geometry worker. */
0064 export interface RootGeometryRules {
0065   /** Edit rule sets by name. `OPTIMIZE_EDIT_RULE_SET` ('default') is the one the data selector offers. */
0066   editRules?: Record<string, RootGeometryEditRuleSet>;
0067   /** Cut lists by name. */
0068   cutLists?: Record<string, RootGeometryCutList>;
0069 }
0070 
0071 /** Rules as data, or a dynamic import that resolves to them (keeps large rule data out of the initial bundle). */
0072 export type RootGeometryRulesSource = RootGeometryRules | (() => Promise<RootGeometryRules>);
0073 
0074 /** What one geometry load asks the worker to do, resolved from the config names. */
0075 export interface RootGeometryLoadRules {
0076   /** Name prefixes of top-level nodes to remove; empty removes nothing. */
0077   cutList: string[];
0078   /** Detector edit rules to apply; empty edits nothing. */
0079   editRules: DetectorEditRules[];
0080 }
0081 
0082 /**
0083  * Merges rule contributions in registration order. A later rule set or cut
0084  * list with the name of an earlier one replaces it (the registry rule of
0085  * every Firebird extension point).
0086  */
0087 export async function resolveRootGeometryRules(sources: readonly RootGeometryRulesSource[]): Promise<RootGeometryRules> {
0088   const resolved = await Promise.all(sources.map(source => typeof source === 'function' ? source() : source));
0089   const merged: Required<RootGeometryRules> = { editRules: {}, cutLists: {} };
0090   for (const rules of resolved) {
0091     Object.assign(merged.editRules, rules.editRules);
0092     Object.assign(merged.cutLists, rules.cutLists);
0093   }
0094   return merged;
0095 }
0096 
0097 /**
0098  * Picks what one load applies: the edit rule set and cut list named by the
0099  * config. 'off', and a name no contribution defines, select nothing.
0100  */
0101 export function selectRootGeometryLoadRules(rules: RootGeometryRules, editRulesName: string, cutListName: string): RootGeometryLoadRules {
0102   return {
0103     editRules: rules.editRules?.[editRulesName]?.detectors ?? [],
0104     cutList: rules.cutLists?.[cutListName]?.remove ?? [],
0105   };
0106 }
0107 
0108 // ---------------------------------------------------------------------------
0109 // Themes
0110 // ---------------------------------------------------------------------------
0111 
0112 /** One geometry theme: subdetector rule sets selected by config `geometry.themeName`. */
0113 export interface GeometryThemeRegistration {
0114   /** The `geometry.themeName` value that selects this theme. 'off' is reserved: no theme. */
0115   id: string;
0116   /** Option text on the config page. */
0117   label?: string;
0118   /** Resolves the rule sets. A dynamic import: rule sets carry three.js materials. */
0119   load: () => Promise<readonly DetectorThreeRuleSet[]>;
0120 }
0121 
0122 // ---------------------------------------------------------------------------
0123 // Post-processors
0124 // ---------------------------------------------------------------------------
0125 
0126 /** What a geometry post-processor works on. */
0127 export interface GeometryPostProcessContext {
0128   /** The loaded geometry's root object, already inside `sceneGeometry`. */
0129   geometry: Object3D;
0130   /**
0131    * The detector geometry container, scaled from ROOT cm to mm. Objects added
0132    * here render and clip like the geometry itself.
0133    */
0134   sceneGeometry: ClippingGroup;
0135   scene: Scene;
0136   /** For GPU work such as environment maps (PMREM). */
0137   renderer: WebGPURenderer;
0138   /** The main view's wedge clipping planes, for materials a processor creates. */
0139   clippingPlanes: Plane[];
0140   /** True when the user chose fast opaque materials (`geometry.FastDefaultMaterial`): skip costly effects. */
0141   fastMaterials: boolean;
0142 }
0143 
0144 /**
0145  * Main-thread code that adjusts a freshly loaded geometry: materials,
0146  * arrangement, helpers. Instantiated through a child injector, so `inject()`
0147  * works in its constructor.
0148  */
0149 export interface GeometryPostProcessor {
0150   process(context: GeometryPostProcessContext): void | Promise<void>;
0151 }
0152 
0153 /** One post-processor registration. */
0154 export interface GeometryPostProcessorRegistration {
0155   /** Identity: a later registration with this id replaces this one. */
0156   id: string;
0157   /** Ids of post-processors that must run before this one. Ids nobody registered are ignored. */
0158   after?: string[];
0159   /** Resolves the class. A dynamic import: post-processors usually pull three.js. */
0160   load: () => Promise<Type<GeometryPostProcessor>>;
0161 }
0162 
0163 /**
0164  * The run order of post-processors: registration order, except that each
0165  * runs after the ids in its `after`. Same-id registrations replace earlier
0166  * ones in place first.
0167  *
0168  * @throws Error naming the processors when the `after` constraints form a cycle.
0169  */
0170 export function orderGeometryPostProcessors(
0171   registrations: readonly GeometryPostProcessorRegistration[],
0172 ): GeometryPostProcessorRegistration[] {
0173   const pending = resolveRegistry(registrations, registration => registration.id);
0174   const known = new Set(pending.map(registration => registration.id));
0175   const ordered: GeometryPostProcessorRegistration[] = [];
0176   const placed = new Set<string>();
0177   while (pending.length > 0) {
0178     // The first registration whose predecessors are all placed goes next
0179     const index = pending.findIndex(registration =>
0180       (registration.after ?? []).every(id => placed.has(id) || !known.has(id)));
0181     if (index < 0) {
0182       throw new Error(`Geometry post-processors wait for each other: ${pending.map(registration => registration.id).join(', ')}`);
0183     }
0184     const [next] = pending.splice(index, 1);
0185     ordered.push(next);
0186     placed.add(next.id);
0187   }
0188   return ordered;
0189 }