Back to home page

EIC code displayed by LXR

 
 

    


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