Warning, /firebird/firebird-ng/src/app/services/geometry.service.ts is written in an unsupported language. File is not indexed.
0001 import {Injectable, signal, WritableSignal} from '@angular/core';
0002 import {openFile} from 'jsroot';
0003 import {
0004 analyzeGeoNodes,
0005 findGeoManager, getGeoNodesByLevel
0006 } from '../../lib-root-geometry/root-geo-navigation';
0007 import {build} from 'jsroot/geom';
0008 import {pruneTopLevelDetectors, RootGeometryProcessor} from "../data-pipelines/root-geometry.processor";
0009 import {LocalStorageService} from "./local-storage.service";
0010 import {Subdetector} from "../model/subdetector";
0011 import {Color, DoubleSide, MeshLambertMaterial, NormalBlending, Object3D, Plane} from "three";
0012 import {UrlService} from "./url.service";
0013 import {DetectorThreeRuleSet, ThreeGeometryProcessor} from "../data-pipelines/three-geometry.processor";
0014 import * as THREE from "three";
0015 import {disposeHierarchy, getColorOrDefault} from "../utils/three.utils";
0016
0017 import {cool2ColorRules} from "../theme/cool2-geometry-ruleset";
0018 import {cadColorRules} from "../theme/cad-geometry-ruleset";
0019 import {monoColorRules} from "../theme/mono-geometry-ruleset";
0020 import {cool2NoOutlineColorRules} from "../theme/cool2no-geometry-ruleset";
0021
0022
0023 export const GROUP_CALORIMETRY = "Calorimeters";
0024 export const GROUP_TRACKING = "Tracking";
0025 export const GROUP_PID = "PID";
0026 export const GROUP_MAGNETS = "Magnets";
0027 export const GROUP_SUPPORT = "Beam pipe and support";
0028 export const ALL_GROUPS = [
0029 GROUP_CALORIMETRY,
0030 GROUP_TRACKING,
0031 GROUP_PID,
0032 GROUP_MAGNETS,
0033 GROUP_SUPPORT,
0034 ]
0035
0036 export const defaultRules: DetectorThreeRuleSet[] = [
0037 {
0038 names: ["FluxBarrel_env_25", "FluxEndcapP_26", "FluxEndcapN_28"],
0039 rules: [
0040 {
0041 color: 0x373766,
0042
0043 }
0044 ]
0045 },
0046 {
0047 name: "EcalEndcapN*",
0048 rules: [
0049 {
0050 patterns: ["**/crystal_vol_0"],
0051 color: 0xffef8b,
0052 material: new THREE.MeshStandardMaterial({
0053 color: 0xffef8b,
0054 roughness: 0.7,
0055 metalness: 0.869,
0056 transparent: true,
0057 opacity: 0.8,
0058 side: THREE.DoubleSide
0059 })
0060 },
0061 {
0062 patterns: ["**/inner_support*", "**/ring*"],
0063 material: new THREE.MeshStandardMaterial({
0064 color: 0x19a5f5,
0065 roughness: 0.7,
0066 metalness: 0.869,
0067 transparent: true,
0068 opacity: 0.8,
0069 side: THREE.DoubleSide
0070 })
0071 }
0072
0073 ]
0074 },
0075 {
0076 name: "InnerTrackerSupport_assembly_13",
0077 rules: [
0078 {
0079 material: new THREE.MeshStandardMaterial({
0080 color: 0xEEEEEE,
0081 roughness: 0.7,
0082 metalness: 0.3,
0083 transparent: true,
0084 opacity: 0.8,
0085 blending: THREE.NormalBlending,
0086 // premultipliedAlpha: true,
0087 depthWrite: false, // Ensures correct blending
0088 polygonOffset: true,
0089 polygonOffsetFactor: 1,
0090 side: THREE.DoubleSide
0091 }),
0092 outline: true,
0093 outlineColor: 0x666666,
0094 merge: true,
0095 newName: "InnerTrackerSupport"
0096 }
0097 ]
0098 },
0099 {
0100 name: "DIRC_14",
0101 rules: [
0102 {
0103 patterns: ["**/*box*", "**/*prism*"],
0104 material: new THREE.MeshPhysicalMaterial({
0105 color: 0xe5ba5d,
0106 metalness: .9,
0107 roughness: .05,
0108 envMapIntensity: 0.9,
0109 clearcoat: 1,
0110 transparent: true,
0111 //transmission: .60,
0112 opacity: .6,
0113 reflectivity: 0.2,
0114 //refr: 0.985,
0115 ior: 0.9,
0116 side: THREE.DoubleSide,
0117 }),
0118 newName: "DIRC_barAndPrisms"
0119 },
0120 {
0121 patterns: ["**/*rail*"],
0122 newName: "DIRC_rails",
0123 color: 0xAAAACC
0124 },
0125 {
0126 patterns: ["**/*mcp*"],
0127 newName: "DIRC_mcps"
0128 }
0129 ]
0130
0131 },
0132 {
0133 // This is when DIRC geometry is standalone
0134 name: "DIRC_0",
0135 rules: [
0136 {
0137 patterns: ["**/*box*", "**/*prism*"],
0138 material: new THREE.MeshPhysicalMaterial({
0139 color: 0xe5ba5d,
0140 metalness: .9,
0141 roughness: .05,
0142 envMapIntensity: 0.9,
0143 clearcoat: 1,
0144 transparent: true,
0145 //transmission: .60,
0146 opacity: .6,
0147 reflectivity: 0.2,
0148 //refr: 0.985,
0149 ior: 0.9,
0150 side: THREE.DoubleSide,
0151 }),
0152 newName: "DIRC_barAndPrisms",
0153 merge: false,
0154 outline: true
0155 },
0156 {
0157 patterns: ["**/*rail*"],
0158 newName: "DIRC_rails",
0159 color: 0xAAAACC
0160 },
0161 {
0162 patterns: ["**/*mcp*"],
0163 newName: "DIRC_mcps"
0164 }
0165 ]
0166
0167 },
0168 {
0169 name: "VertexBarrelSubAssembly_3",
0170 rules: [
0171 {
0172 merge: true,
0173 outline: true
0174 }
0175 ]
0176 },
0177 {
0178 name: "*",
0179 rules: [
0180 {
0181 merge: true,
0182 outline: true
0183 }
0184 ]
0185 }
0186 ]
0187
0188 /**
0189 * Detectors (top level TGeo nodes) to be removed.
0190 * (!) startsWith function is used for filtering (aka: detector.fName.startsWith(removeDetectorNames[i]) ... )
0191 */
0192 const removeDetectorNames: string[] = [
0193 "Lumi",
0194 //"Magnet",
0195 //"B0",
0196 "B1",
0197 "B2",
0198 //"Q0",
0199 //"Q1",
0200 "Q2",
0201 //"BeamPipe",
0202 //"Pipe",
0203 "ForwardOffM",
0204 "Forward",
0205 "Backward",
0206 "Vacuum",
0207 "SweeperMag",
0208 "AnalyzerMag",
0209 "ZDC",
0210 //"LFHCAL",
0211 "HcalFarForward",
0212 "InnerTrackingSupport"
0213 ];
0214
0215
0216 // constants.ts
0217 export const DEFAULT_GEOMETRY = 'builtin://epic-central-optimized';
0218
0219 @Injectable({
0220 providedIn: 'root'
0221 })
0222 export class GeometryService {
0223
0224 public rootGeometryProcessor = new RootGeometryProcessor();
0225
0226 /** Collection of subdetectors */
0227 public subdetectors: Subdetector[] = [];
0228
0229 /** TGeoManager if available */
0230 public rootGeometry: any|null = null;
0231
0232 /** Main/entry/root THREEJS geometry tree node with the whole geometry */
0233 // public geometry:
0234
0235 public groupsByDetName: Map<string, string>;
0236
0237 /** for geometry post-processing */
0238 private threeGeometryProcessor = new ThreeGeometryProcessor();
0239
0240 private defaultColor: Color = new Color(0x68698D);
0241
0242 public geometry:WritableSignal<Object3D|null> = signal(null)
0243
0244 constructor(private urlService: UrlService,
0245 private localStorage: LocalStorageService,
0246 ) {
0247 this.groupsByDetName = new Map<string,string> ([
0248 ["SolenoidBarrel_assembly_0", GROUP_MAGNETS],
0249 ["SolenoidEndcapP_1", GROUP_MAGNETS],
0250 ["SolenoidEndcapN_2", GROUP_MAGNETS],
0251 ["VertexBarrelSubAssembly_3", GROUP_TRACKING],
0252 ["InnerSiTrackerSubAssembly_4", GROUP_TRACKING],
0253 ["MiddleSiTrackerSubAssembly_5", GROUP_TRACKING],
0254 ["OuterSiTrackerSubAssembly_6", GROUP_TRACKING],
0255 ["EndcapMPGDSubAssembly_7", GROUP_TRACKING],
0256 ["InnerMPGDBarrelSubAssembly_8", GROUP_TRACKING],
0257 ["EndcapTOFSubAssembly_9", GROUP_PID],
0258 ["BarrelTOFSubAssembly_10", GROUP_PID],
0259 ["OuterBarrelMPGDSubAssembly_11", GROUP_TRACKING],
0260 ["B0TrackerSubAssembly_12", GROUP_TRACKING],
0261 ["InnerTrackerSupport_assembly_13", GROUP_SUPPORT],
0262 ["DIRC_14", GROUP_PID],
0263 ["RICHEndcapN_Vol_15", GROUP_PID],
0264 ["DRICH_16", GROUP_PID],
0265 ["EcalEndcapP_17", GROUP_CALORIMETRY],
0266 ["EcalEndcapPInsert_18", GROUP_CALORIMETRY],
0267 ["EcalBarrelImaging_19", GROUP_CALORIMETRY],
0268 ["EcalBarrelScFi_20", GROUP_CALORIMETRY],
0269 ["EcalEndcapN_21", GROUP_CALORIMETRY],
0270 ["LFHCAL_env_22", GROUP_CALORIMETRY],
0271 ["HcalEndcapPInsert_23", GROUP_CALORIMETRY],
0272 ["HcalBarrel_24", GROUP_CALORIMETRY],
0273 ["FluxBarrel_env_25", GROUP_SUPPORT],
0274 ["FluxEndcapP_26", GROUP_SUPPORT],
0275 ["HcalEndcapN_27", GROUP_CALORIMETRY],
0276 ["FluxEndcapN_28", GROUP_SUPPORT],
0277 ["BeamPipe_assembly_29", GROUP_SUPPORT],
0278 ["B0PF_BeamlineMagnet_assembly_30", GROUP_MAGNETS],
0279 ["B0APF_BeamlineMagnet_assembly_31", GROUP_MAGNETS],
0280 ["Q1APF_BeamlineMagnet_assembly_32", GROUP_MAGNETS],
0281 ["Q1BPF_BeamlineMagnet_assembly_33", GROUP_MAGNETS],
0282 ["BeamPipeB0_assembly_38", GROUP_SUPPORT],
0283 ["Pipe_cen_to_pos_assembly_39", GROUP_SUPPORT],
0284 ["Q0EF_assembly_40", GROUP_MAGNETS],
0285 ["Q0EF_vac_41", GROUP_MAGNETS],
0286 ["Q1EF_assembly_42", GROUP_MAGNETS],
0287 ["Q1EF_vac_43", GROUP_MAGNETS],
0288 ["B0ECal_44", GROUP_CALORIMETRY],
0289 ["Pipe_Q1eR_to_B2BeR_assembly_54", GROUP_SUPPORT],
0290 ["Magnet_Q1eR_assembly_55", GROUP_MAGNETS],
0291 ["Magnet_Q2eR_assembly_56", GROUP_MAGNETS],
0292 ["Magnet_B2AeR_assembly_57", GROUP_MAGNETS],
0293 ["Magnet_B2BeR_assembly_58", GROUP_MAGNETS],
0294 ["Magnets_Q3eR_assembly_59", GROUP_MAGNETS],
0295 ])
0296 }
0297
0298
0299 async loadGeometry(url:string): Promise<{rootGeometry: any|null, threeGeometry: Object3D|null}> {
0300
0301 this.subdetectors = [];
0302 //let url: string = 'assets/epic_pid_only.root';
0303 //let url: string = 'https://eic.github.io/epic/artifacts/tgeo/epic_dirc_only.root';
0304 // let url: string = 'https://eic.github.io/epic/artifacts/tgeo/epic_full.root';
0305 // >oO let objectName = 'default';
0306
0307 if(url === DEFAULT_GEOMETRY) {
0308 url = 'https://eic.github.io/epic/artifacts/tgeo/epic_full.root';
0309 }
0310 // TODO check aliases
0311
0312 const finalUrl = this.urlService.resolveDownloadUrl(url);
0313
0314 console.time('[GeometryService]: Total load geometry time');
0315 console.log(`[GeometryService]: Loading file ${finalUrl}`)
0316
0317 console.time('[GeometryService]: Open root file');
0318 const file = await openFile(finalUrl);
0319 // >oO debug console.log(file);
0320 console.timeEnd('[GeometryService]: Open root file');
0321
0322
0323 console.time('[GeometryService]: Reading geometry from file');
0324 this.rootGeometry = await findGeoManager(file) // await file.readObject(objectName);
0325 // >oO
0326 // console.log("Got TGeoManager. For inspection:")
0327 // console.log(this.rootGeometry);
0328 console.timeEnd('[GeometryService]: Reading geometry from file');
0329
0330
0331 // Getting main detector nodes
0332 if(this.localStorage.geometryCutListName.value === "central") {
0333 let result = pruneTopLevelDetectors(this.rootGeometry, removeDetectorNames);
0334 console.log(`[GeometryService]: Done prune geometry. Nodes left: ${result.nodes.length}, Nodes removed: ${result.removedNodes.length}`);
0335 } else {
0336 console.log("[GeometryService]: Prune geometry IS OFF");
0337
0338 }
0339
0340 if(this.localStorage.geometryRootFilterName.value === "default") {
0341 console.time('[GeometryService]: Root geometry pre-processing');
0342 this.rootGeometryProcessor.process(this.rootGeometry);
0343 console.timeEnd('[GeometryService]: Root geometry pre-processing');
0344 } else {
0345 console.log("[GeometryService]: Root geometry pre-processing IS OFF");
0346 }
0347
0348
0349 console.log("[GeometryService]: Number of tree elements analysis:");
0350 analyzeGeoNodes(this.rootGeometry, 1);
0351
0352 //
0353 console.time('[GeometryService]: Build geometry');
0354 const geometry = build(this.rootGeometry,
0355 {
0356 numfaces: 5000000000,
0357 numnodes: 5000000000,
0358 instancing:-1,
0359 dflt_colors: false,
0360 vislevel: 200,
0361 doubleside:true,
0362 transparency:true
0363 });
0364 console.timeEnd('[GeometryService]: Build geometry');
0365
0366 // Validate the geometry
0367 if(!geometry) {
0368 throw new Error("Geometry is null or undefined after TGeoPainter.build");
0369 }
0370
0371 if(!geometry.children.length) {
0372 throw new Error("Geometry is converted but empty. Anticipated 'world_volume' but got nothing");
0373 }
0374
0375 if(!geometry.children[0].children.length) {
0376 throw new Error("Geometry is converted but empty. Anticipated array of top level nodes (usually subdetectors) but got nothing");
0377 }
0378
0379 // We now know it is not empty array
0380 console.time('[GeometryService]: Map root geometry to threejs geometry');
0381 let topDetectorNodes = geometry.children[0].children;
0382 for(const topNode of topDetectorNodes) {
0383
0384 // Process name
0385 const originalName = topNode.name;
0386 const name = this.stripIdFromName(originalName); // Remove id in the end EcalN_21 => Ecal
0387
0388 const rootGeoNodes = getGeoNodesByLevel(this.rootGeometry, 1).map(obj=>obj.geoNode);
0389 const rootNode = rootGeoNodes.find(obj => obj.fName === originalName);
0390
0391 let subdetector: Subdetector = {
0392 sourceGeometry: rootNode,
0393 sourceGeometryName: rootNode?.fName ?? "",
0394 geometry: topNode,
0395 name: this.stripIdFromName(originalName),
0396 groupName: this.groupsByDetName.get(originalName) || ""
0397 }
0398 // console.log(subdetector.sourceGeometryName);
0399 this.subdetectors.push(subdetector);
0400 }
0401 console.timeEnd('[GeometryService]: Map root geometry to threejs geometry');
0402
0403 console.timeEnd('[GeometryService]: Total load geometry time');
0404
0405 this.geometry.set(geometry);
0406
0407 return {rootGeometry: this.rootGeometry, threeGeometry: geometry};
0408 }
0409
0410 public postProcessing(geometry: Object3D, clippingPlanes: Plane[]) {
0411 let threeGeometry = this.geometry();
0412 if (!threeGeometry) return;
0413
0414
0415 // Now we want to set default materials
0416 threeGeometry.traverse((child: any) => {
0417 if (child.type !== 'Mesh' || !child?.material?.isMaterial) {
0418 return;
0419 }
0420
0421 // Handle the material of the child
0422 const color = getColorOrDefault(child.material, this.defaultColor);
0423
0424 if(this.localStorage.geometryFastAndUgly.value) {
0425 child.material = new MeshLambertMaterial({
0426 color: color,
0427 side: DoubleSide, // you said you can’t change this
0428 transparent: false,
0429 opacity: 1, // false transparency; use 1 for full opacity
0430 blending: THREE.NoBlending, // since transparent is false
0431 depthTest: true,
0432 depthWrite: true,
0433 clippingPlanes,
0434 clipIntersection: true,
0435 clipShadows: false,
0436 fog: false, // disable fog math
0437 vertexColors: false, // disable vertex-color math
0438 flatShading: true, // simpler “flat” shading
0439 toneMapped: false // skip tone-mapping
0440 });
0441 } else {
0442 child.material = new MeshLambertMaterial({
0443 color: color,
0444 side: DoubleSide,
0445 transparent: true,
0446 opacity: 0.7,
0447 blending: NormalBlending,
0448 depthTest: true,
0449 depthWrite: true,
0450 clippingPlanes: clippingPlanes,
0451 clipIntersection: true,
0452 clipShadows: false,
0453 });
0454
0455 }
0456 });
0457
0458
0459 // HERE WE DO POSTPROCESSING STEP
0460 // TODO this.threeGeometryProcessor.processRuleSets(defaultRules, this.subdetectors);
0461 console.log(`[GeometryService]: Geometry theme name is set to '${this.localStorage.geometryThemeName.value}'`);
0462 if(this.localStorage.geometryThemeName.value === "cool2") {
0463 this.threeGeometryProcessor.processRuleSets(cool2ColorRules, this.subdetectors);
0464 }else if(this.localStorage.geometryThemeName.value === "cool2no") {
0465 this.threeGeometryProcessor.processRuleSets(cool2NoOutlineColorRules, this.subdetectors);
0466 } else if(this.localStorage.geometryThemeName.value === "cad") {
0467 this.threeGeometryProcessor.processRuleSets(cadColorRules, this.subdetectors);
0468 } else if(this.localStorage.geometryThemeName.value === "grey") {
0469 this.threeGeometryProcessor.processRuleSets(monoColorRules, this.subdetectors);
0470 }
0471
0472
0473
0474 // Now we want to change the materials
0475 threeGeometry.traverse((child: any) => {
0476 if (!child?.material?.isMaterial) {
0477 return;
0478 }
0479
0480 if (child.material?.clippingPlanes !== undefined) {
0481 child.material.clippingPlanes = clippingPlanes;
0482 }
0483
0484 if (child.material?.clipIntersection !== undefined) {
0485 child.material.clipIntersection = true;
0486 }
0487
0488 if (child.material?.clipShadows !== undefined) {
0489 child.material.clipShadows = false;
0490 }
0491 });
0492 }
0493
0494 private stripIdFromName(name: string) {
0495 return name.replace(/_\d+$/, '');
0496 }
0497
0498 toggleVisibility(object: Object3D) {
0499 if (object) {
0500 object.visible = !object.visible;
0501 console.log(`Visibility toggled for object: ${object.name}. Now visible: ${object.visible}`);
0502 }
0503 }
0504 }
0505