Back to home page

EIC code displayed by LXR

 
 

    


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

0001 /**
0002  * ClippedGeometrySlice — a second, independently clipped copy of the detector
0003  * geometry, used by projection views (top / side / front) that need a
0004  * different cut than the main 3D view.
0005  *
0006  * Why a copy: the renderer caches an object's shader state by its clipping
0007  * plane COUNTS, and all views of one canvas share render objects. If the same
0008  * mesh had 2 wedge planes in the 3D view but 1 axis plane in a projection
0009  * view, its cached state would flip every view render — a full
0010  * dispose/rebuild of every object, every frame. Giving each clipping
0011  * STRUCTURE its own Object3D copy keeps every cached state valid forever.
0012  * Only the Object3D spine is copied; BufferGeometry, materials and textures
0013  * are shared references, so GPU memory does not duplicate.
0014  *
0015  * Why ONE slice serves all projection views: plane VALUES are re-projected
0016  * on every render call, so views that share a structure (one axis plane
0017  * each) share this slice and just write their own plane value before their
0018  * render (see RenderView.clipPlane). Per-view plane values on a shared
0019  * group are free; per-view plane counts are not.
0020  *
0021  * Layer routing (constants in @dexvis/firebird-ng/api): the original geometry
0022  * moves to GEOMETRY_MAIN_LAYER (seen by the main/3D camera), the slice spine
0023  * lives on GEOMETRY_SLICE_LAYER (seen by projection cameras). Event data
0024  * (EVENT_DATA_LAYER), helpers and lights are shared, never copied.
0025  */
0026 
0027 import { Object3D, Plane, Vector3 } from 'three';
0028 import { ClippingGroup } from 'three/webgpu';
0029 import { GEOMETRY_MAIN_LAYER, GEOMETRY_SLICE_LAYER, type GeometrySlice } from '@dexvis/firebird-ng/api';
0030 
0031 export class ClippedGeometrySlice implements GeometrySlice {
0032   /** The clipping group holding the spine copy. Added to the scene by ThreeService. */
0033   readonly group: ClippingGroup;
0034 
0035   /**
0036    * The one clip plane, in world space. Views sharing this slice write their
0037    * own plane value here right before their render call (plane values are
0038    * re-projected per render, so sequential views each get their own cut).
0039    */
0040   readonly plane = new Plane(new Vector3(0, -1, 0), 0);
0041 
0042   /** The geometry root the spine was built from (for layer restore on dispose). */
0043   private source: Object3D | null = null;
0044 
0045   constructor() {
0046     this.group = new ClippingGroup();
0047     this.group.name = 'GeometrySlice';
0048     // Intersection mode with a single plane clips exactly like union mode,
0049     // but gives this group the (1 intersection : 0 union) plane-count shape —
0050     // one the main clipping chain can never produce (the wedge is always 2
0051     // planes, Z clipping is always union). Through three r185 the renderer
0052     // cached built shader states by plane counts only, so two groups with the
0053     // same shape shared one shader bound to ONE group's plane array, and the
0054     // other group silently clipped with the wrong planes. Since r186 the
0055     // cache key also carries the clipping context id; the distinct shape
0056     // stays as a guard (pinned by three-clipping-internals.spec.ts).
0057     this.group.clipIntersection = true;
0058     this.group.clippingPlanes = [this.plane];
0059     this.group.enabled = true;
0060   }
0061 
0062   /**
0063    * (Re)builds the spine from the current content of `sourceGeometry` and
0064    * routes layers: originals to GEOMETRY_MAIN_LAYER, spine copies to
0065    * GEOMETRY_SLICE_LAYER. Call again after a geometry load replaces the
0066    * source content. Copies share geometry/material with the originals, so a
0067    * later visibility or material edit on originals does NOT propagate to the
0068    * spine — rebuild to resync.
0069    */
0070   rebuild(sourceGeometry: Object3D): void {
0071     this.source = sourceGeometry;
0072     this.group.clear();
0073 
0074     // Mirror the source root transform (geometry loading applies a cm→mm
0075     // scale on the geometry root; the spine must sit in the same frame).
0076     this.group.position.copy(sourceGeometry.position);
0077     this.group.quaternion.copy(sourceGeometry.quaternion);
0078     this.group.scale.copy(sourceGeometry.scale);
0079 
0080     for (const child of sourceGeometry.children) {
0081       this.group.add(child.clone(true));
0082     }
0083 
0084     this.group.traverse(node => {
0085       if (node !== this.group) {
0086         node.layers.set(GEOMETRY_SLICE_LAYER);
0087         // Spine transforms are frozen copies; skip per-frame matrix math.
0088         node.matrixAutoUpdate = false;
0089       }
0090     });
0091     sourceGeometry.traverse(node => {
0092       if (node !== sourceGeometry) node.layers.set(GEOMETRY_MAIN_LAYER);
0093     });
0094 
0095     this.group.updateMatrixWorld(true);
0096   }
0097 
0098   /**
0099    * Returns the original geometry to layer 0 (single-view pages render it
0100    * with any camera again) and drops the spine. The caller removes
0101    * `group` from the scene.
0102    */
0103   dispose(): void {
0104     this.source?.traverse(node => {
0105       if (node !== this.source) node.layers.set(0);
0106     });
0107     this.source = null;
0108     this.group.clear();
0109   }
0110 }