Back to home page

EIC code displayed by LXR

 
 

    


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

0001 import { Injectable } from '@angular/core';
0002 import * as THREE from 'three';
0003 import { ThreeService } from './three.service';
0004 
0005 /**
0006  * Keeps references to "helper" objects and toggles or moves them:
0007  * - Axis lines
0008  * - EtaPhi grids
0009  * - Cartesian grids
0010  * - 3D points picking
0011  * Every setter that changes what the scene shows schedules a render
0012  * (`ThreeService.invalidate()`): the on-demand render loop draws nothing
0013  * new without one.
0014  */
0015 @Injectable({ providedIn: 'root' })
0016 export class SceneHelpersService {
0017   private cartesianGridGroup: THREE.Group | null = null;
0018   private etaPhiGroup: THREE.Group | null = null;
0019   private labelsGroup: THREE.Group | null = null;
0020 
0021   // For "show distance" or "show 3D coords" logic:
0022   private onPointerMoveFn?: (event: MouseEvent) => void;
0023   private onPointerClickFn?: (event: MouseEvent) => void;
0024 
0025   constructor(private threeService: ThreeService) {}
0026 
0027   /**
0028    * Toggles the existing axes helper created in ThreeService.
0029    */
0030   setShowAxis(show: boolean) {
0031     if (this.threeService.axesHelper) {
0032       this.threeService.axesHelper.visible = show;
0033       this.threeService.invalidate();
0034     }
0035   }
0036 
0037   /**
0038    * Show/hide a cartesian grid on the XZ plane at Y = -4000.
0039    * Grid squares are 1 meter (1000 units) on a side.
0040    */
0041   setShowCartesianGrid(show: boolean) {
0042     if (!this.cartesianGridGroup) {
0043       const gridSize = 10000; // total extent in units (10 m)
0044       const divisions = gridSize / 1000; // 1 m per square
0045       this.cartesianGridGroup = new THREE.Group();
0046       this.cartesianGridGroup.name = 'CartesianGrid';
0047       // GridHelper creates a grid on the XZ plane by default
0048       const grid = new THREE.GridHelper(gridSize, divisions, 0xffffff, 0x888888);
0049       grid.material = new THREE.LineBasicMaterial({ color: 0x888888, transparent: true, opacity: 0.4 });
0050       this.cartesianGridGroup.add(grid);
0051       // Position at Y = -4000
0052       this.cartesianGridGroup.position.set(0, -4000, 0);
0053       this.threeService.sceneHelpers.add(this.cartesianGridGroup);
0054     }
0055     this.cartesianGridGroup.visible = show;
0056     this.threeService.invalidate();
0057   }
0058 
0059   /**
0060    * Show/hide eta lines with labels for common HEP pseudorapidity values.
0061    * Eta lines are drawn as cones emanating from the origin in the XZ plane (beam axis = Z).
0062    */
0063   setShowEtaPhiGrid(show: boolean) {
0064     if (!this.etaPhiGroup) {
0065       this.etaPhiGroup = new THREE.Group();
0066       this.etaPhiGroup.name = 'EtaPhi';
0067       this.buildEtaLines(this.etaPhiGroup);
0068       this.threeService.sceneHelpers.add(this.etaPhiGroup);
0069     }
0070     this.etaPhiGroup.visible = show;
0071     this.threeService.invalidate();
0072   }
0073 
0074   /**
0075    * Build lines for common eta values.
0076    * Pseudorapidity eta = -ln(tan(theta/2)), so theta = 2*atan(exp(-eta)).
0077    * In HEP convention: beam axis = Z, so a particle at angle theta from Z
0078    * travels in direction (sin(theta), 0, cos(theta)).
0079    */
0080   private buildEtaLines(group: THREE.Group) {
0081     // Common eta values for HEP collider detectors
0082     const etaValues = [-4, -3, -2, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2, 3, 4];
0083     const lineLength = 4000; // mm, extent of lines
0084 
0085     const material = new THREE.LineBasicMaterial({
0086       color: 0xffcc00,
0087       transparent: true,
0088       opacity: 0.6,
0089     });
0090 
0091     for (const eta of etaValues) {
0092       const theta = 2 * Math.atan(Math.exp(-eta));
0093       // Direction in (x, y, z) with beam along Z:
0094       // x = sin(theta), y = 0, z = cos(theta)
0095       const sinTheta = Math.sin(theta);
0096       const cosTheta = Math.cos(theta);
0097 
0098       const points = [
0099         new THREE.Vector3(0, 0, 0),
0100         new THREE.Vector3(sinTheta * lineLength, 0, cosTheta * lineLength),
0101       ];
0102       const geometry = new THREE.BufferGeometry().setFromPoints(points);
0103       const line = new THREE.Line(geometry, material);
0104       line.name = `eta_${eta}`;
0105       group.add(line);
0106 
0107       // Add label sprite at the end of the line
0108       const label = this.createTextSprite(`η=${eta}`, 0xffcc00);
0109       label.position.set(
0110         sinTheta * (lineLength + 200),
0111         0,
0112         cosTheta * (lineLength + 200)
0113       );
0114       label.name = `eta_label_${eta}`;
0115       group.add(label);
0116     }
0117   }
0118 
0119   /**
0120    * Show/hide X, Y, Z labels at the end of the axes helper.
0121    */
0122   showLabels(show: boolean) {
0123     if (!this.labelsGroup) {
0124       this.labelsGroup = new THREE.Group();
0125       this.labelsGroup.name = 'AxisLabels';
0126 
0127       const axisLength = 1500; // matches AxesHelper size in three.service
0128       const offset = 150; // offset past the axis end
0129 
0130       const xLabel = this.createTextSprite('X', 0xff0000);
0131       xLabel.position.set(axisLength + offset, 0, 0);
0132       this.labelsGroup.add(xLabel);
0133 
0134       const yLabel = this.createTextSprite('Y', 0x00ff00);
0135       yLabel.position.set(0, axisLength + offset, 0);
0136       this.labelsGroup.add(yLabel);
0137 
0138       const zLabel = this.createTextSprite('Z', 0x0000ff);
0139       zLabel.position.set(0, 0, axisLength + offset);
0140       this.labelsGroup.add(zLabel);
0141 
0142       this.threeService.sceneHelpers.add(this.labelsGroup);
0143     }
0144     this.labelsGroup.visible = show;
0145     this.threeService.invalidate();
0146   }
0147 
0148   /**
0149    * Creates a sprite with text rendered on a canvas texture.
0150    */
0151   private createTextSprite(text: string, color: number): THREE.Sprite {
0152     const canvas = document.createElement('canvas');
0153     const size = 256;
0154     canvas.width = size;
0155     canvas.height = size;
0156     const ctx = canvas.getContext('2d')!;
0157 
0158     ctx.clearRect(0, 0, size, size);
0159 
0160     // Convert hex color to CSS string
0161     const cssColor = `#${color.toString(16).padStart(6, '0')}`;
0162 
0163     ctx.font = 'Bold 120px Arial';
0164     ctx.textAlign = 'center';
0165     ctx.textBaseline = 'middle';
0166     ctx.fillStyle = cssColor;
0167     ctx.fillText(text, size / 2, size / 2);
0168 
0169     const texture = new THREE.CanvasTexture(canvas);
0170     texture.needsUpdate = true;
0171 
0172     const spriteMaterial = new THREE.SpriteMaterial({
0173       map: texture,
0174       transparent: true,
0175       depthTest: false,
0176     });
0177     const sprite = new THREE.Sprite(spriteMaterial);
0178     sprite.scale.set(200, 200, 1);
0179     sprite.name = `label_${text}`;
0180     return sprite;
0181   }
0182 
0183   /**
0184    * Show/hide 3D mouse coordinates by hooking pointer events, running a Raycaster, etc.
0185    */
0186   show3DMousePoints(show: boolean) {
0187     if (show) {
0188       this.onPointerClickFn = (evt) => this.handle3DPointClick(evt);
0189       window.addEventListener('click', this.onPointerClickFn);
0190     } else {
0191       if (this.onPointerClickFn) {
0192         window.removeEventListener('click', this.onPointerClickFn);
0193         this.onPointerClickFn = undefined;
0194       }
0195     }
0196   }
0197 
0198   private handle3DPointClick(evt: MouseEvent) {
0199     const rect = this.threeService.renderer.domElement.getBoundingClientRect();
0200     const x = ((evt.clientX - rect.left) / rect.width) * 2 - 1;
0201     const y = -((evt.clientY - rect.top) / rect.height) * 2 + 1;
0202     const raycaster = new THREE.Raycaster();
0203     raycaster.setFromCamera(new THREE.Vector2(x, y), this.threeService.camera);
0204 
0205     const intersects = raycaster.intersectObjects(this.threeService.scene.children, true);
0206     if (intersects.length > 0) {
0207       const point = intersects[0].point;
0208       console.log('Clicked 3D coords:', point);
0209     }
0210   }
0211 
0212   /**
0213    * Toggle "show 3D distance" by hooking pointer events for measuring two points, etc.
0214    */
0215   show3DDistance(show: boolean) {
0216     console.warn('show3DDistance not implemented yet.');
0217   }
0218 
0219   /**
0220    * SHIFT cartesian grid by pointer or by values.
0221    */
0222   shiftCartesianGridByPointer() {
0223     console.warn('shiftCartesianGridByPointer not implemented.');
0224   }
0225 
0226   translateCartesianGrid(shift: THREE.Vector3) {
0227     if (this.cartesianGridGroup) {
0228       this.cartesianGridGroup.position.add(shift);
0229       this.threeService.invalidate();
0230     }
0231   }
0232 
0233   translateCartesianLabels(shift: THREE.Vector3) {
0234     // If you keep labels in a separate group or do 2D overlay, handle that here
0235   }
0236 
0237   setCameraView(targetPos: THREE.Vector3, cameraPos: THREE.Vector3) {
0238     this.threeService.camera.position.copy(cameraPos);
0239     this.threeService.controls.target.copy(targetPos);
0240     this.threeService.controls.update();
0241     this.threeService.invalidate();
0242   }
0243 }