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 }