Warning, /firebird/firebird-ng/src/app/painters/box-tracker-hit.painter.ts is written in an unsupported language. File is not indexed.
0001 import {
0002 Object3D,
0003 InstancedMesh,
0004 BoxGeometry,
0005 ShaderMaterial,
0006 Object3D as THREEObject3D,
0007 InstancedBufferAttribute,
0008 Color,
0009 Matrix4,
0010 } from 'three';
0011 import { ComponentPainter } from './component-painter';
0012 import { BoxTrackerHitComponent } from '../model/box-tracker-hit.component';
0013 import {EntryComponent} from "../model/entry-component";
0014
0015 /**
0016 * Painter class for rendering BoxTrackerHitComponent using InstancedMesh.
0017 */
0018 export class BoxTrackerHitPainter extends ComponentPainter {
0019 /** The InstancedMesh object to store multiple hit boxes. */
0020 private instancedMesh: InstancedMesh;
0021
0022 /** Number of hits */
0023 private count: number;
0024
0025 /** Shader material for the instanced mesh */
0026 private material: ShaderMaterial;
0027
0028 /** Geometry for the instanced boxes */
0029 private geometry: BoxGeometry;
0030
0031
0032 private boxComponent: BoxTrackerHitComponent;
0033
0034 /**
0035 * Constructs a new BoxHitPainter.
0036 *
0037 * @param node - The Object3D node where the instanced mesh will be added.
0038 * @param component - The BoxTrackerHitComponent containing the hit data.
0039 */
0040 constructor(node: Object3D, component: EntryComponent) {
0041 super(node, component);
0042
0043 // Runtime type check
0044 if (component.type !== BoxTrackerHitComponent.type) {
0045 throw new Error('Invalid component type for BoxTrackerHitPainter');
0046 }
0047
0048 this.boxComponent = component as BoxTrackerHitComponent;
0049
0050 this.count = this.boxComponent.hits.length;
0051
0052 // Define geometry for a unit box
0053 this.geometry = new BoxGeometry(1, 1, 1);
0054
0055 // Define material for the boxes using shaders for time-based animation
0056 this.material = new ShaderMaterial({
0057 uniforms: {
0058 uTime: { value: 0 },
0059 },
0060 vertexShader: this.vertexShader(),
0061 fragmentShader: this.fragmentShader(),
0062 // Enable per-instance coloring
0063 vertexColors: true,
0064 });
0065
0066 // Create the instanced mesh
0067 this.instancedMesh = new InstancedMesh(this.geometry, this.material, this.count);
0068
0069 // Set up each instance (position, scale, color, appearance time)
0070 this.setupInstances(this.boxComponent);
0071
0072 // Add the instanced mesh to the node
0073 node.add(this.instancedMesh);
0074 }
0075
0076 /**
0077 * Sets up the instances of the hits, including position, scale, color, and time.
0078 *
0079 * @param component - The BoxTrackerHitComponent with the hit data.
0080 */
0081 private setupInstances(component: BoxTrackerHitComponent): void {
0082 const instanceColors: number[] = [];
0083 const instanceTimes: number[] = [];
0084 const dummy = new Object3D();
0085
0086 for (let i = 0; i < component.hits.length; i++) {
0087 const hit = component.hits[i];
0088
0089 // Set position from hit data
0090 dummy.position.set(hit.position[0], hit.position[1], hit.position[2]);
0091
0092 // Set non-uniform scale from hit dimensions
0093 dummy.scale.set(hit.dimensions[0], hit.dimensions[1], hit.dimensions[2]);
0094
0095 // Update the transformation matrix for this instance
0096 dummy.updateMatrix();
0097 this.instancedMesh.setMatrixAt(i, dummy.matrix);
0098
0099 // Set color (e.g., based on energy deposit)
0100 // For example, mapping energy deposit to color intensity
0101 const edep = hit.energyDeposit[0];
0102 const color = new Color().setHSL(0.0, 1.0, Math.min(1.0, edep));
0103
0104 instanceColors.push(color.r, color.g, color.b);
0105
0106 // Set appearance time for this hit
0107 instanceTimes.push(hit.time[0]);
0108 }
0109
0110 // Add per-instance color attribute
0111 const instanceColorAttr = new InstancedBufferAttribute(new Float32Array(instanceColors), 3);
0112 this.instancedMesh.instanceColor = instanceColorAttr;
0113
0114 // Add per-instance appearance time attribute
0115 const instanceTimeAttr = new InstancedBufferAttribute(new Float32Array(instanceTimes), 1);
0116 this.instancedMesh.geometry.setAttribute('instanceAppearanceTime', instanceTimeAttr);
0117 }
0118
0119 /**
0120 * Custom vertex shader to control the visibility based on time and transform the instance.
0121 */
0122 private vertexShader(): string {
0123 return `
0124 uniform float uTime;
0125 attribute float instanceAppearanceTime;
0126 attribute vec3 instanceColor;
0127 varying float vVisible;
0128 varying vec3 vColor;
0129
0130 void main() {
0131 // Check if this instance should be visible at the current time
0132 vVisible = step(instanceAppearanceTime, uTime);
0133
0134 // Pass the instance color to the fragment shader
0135 vColor = instanceColor;
0136
0137 // Apply the usual transformations
0138 gl_Position = projectionMatrix * modelViewMatrix * instanceMatrix * vec4(position, 1.0);
0139 }
0140 `;
0141 }
0142
0143 /**
0144 * Custom fragment shader to render each box with its color and control its visibility.
0145 */
0146 private fragmentShader(): string {
0147 return `
0148 varying float vVisible;
0149 varying vec3 vColor;
0150
0151 void main() {
0152 if (vVisible < 1.0) discard; // Skip rendering if not visible
0153
0154 gl_FragColor = vec4(vColor, 1.0); // Apply the instance color
0155 }
0156 `;
0157 }
0158
0159 /**
0160 * Paint method to update the time-based animation of the hits.
0161 *
0162 * @param time - The current time in nanoseconds or null for static rendering.
0163 */
0164 public paint(time: number | null): void {
0165 if (time !== null) {
0166 this.material.uniforms["uTime"].value = time; // Assuming time is in nanoseconds
0167 }
0168
0169 // If instances move or change over time, update instanceMatrix
0170 // this.instancedMesh.instanceMatrix.needsUpdate = true;
0171 }
0172
0173 /**
0174 * Dispose of resources used by the BoxHitPainter.
0175 */
0176 override dispose(): void {
0177 if (this.instancedMesh) {
0178 // Dispose of geometry
0179 this.instancedMesh.geometry.dispose();
0180
0181 // Dispose of material(s)
0182 const materials = Array.isArray(this.instancedMesh.material)
0183 ? this.instancedMesh.material
0184 : [this.instancedMesh.material];
0185 materials.forEach((material) => material.dispose());
0186
0187 // Remove the instanced mesh from the scene
0188 this.instancedMesh.parent?.remove(this.instancedMesh);
0189 }
0190 super.dispose();
0191 }
0192 }