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 import { SimplifyModifier } from 'three/examples/jsm/modifiers/SimplifyModifier.js';
0023
0024 export const GROUP_CALORIMETRY = "Calorimeters";
0025 export const GROUP_TRACKING = "Tracking";
0026 export const GROUP_PID = "PID";
0027 export const GROUP_MAGNETS = "Magnets";
0028 export const GROUP_SUPPORT = "Beam pipe and support";
0029 export const ALL_GROUPS = [
0030 GROUP_CALORIMETRY,
0031 GROUP_TRACKING,
0032 GROUP_PID,
0033 GROUP_MAGNETS,
0034 GROUP_SUPPORT,
0035 ]
0036
0037 export const defaultRules: DetectorThreeRuleSet[] = [
0038 {
0039 names: ["FluxBarrel_env_25", "FluxEndcapP_26", "FluxEndcapN_28"],
0040 rules: [
0041 {
0042 color: 0x373766,
0043
0044 }
0045 ]
0046 },
0047 {
0048 name: "EcalEndcapN*",
0049 rules: [
0050 {
0051 patterns: ["**/crystal_vol_0"],
0052 color: 0xffef8b,
0053 material: new THREE.MeshStandardMaterial({
0054 color: 0xffef8b,
0055 roughness: 0.7,
0056 metalness: 0.869,
0057 transparent: true,
0058 opacity: 0.8,
0059 side: THREE.DoubleSide
0060 })
0061 },
0062 {
0063 patterns: ["**/inner_support*", "**/ring*"],
0064 material: new THREE.MeshStandardMaterial({
0065 color: 0x19a5f5,
0066 roughness: 0.7,
0067 metalness: 0.869,
0068 transparent: true,
0069 opacity: 0.8,
0070 side: THREE.DoubleSide
0071 })
0072 }
0073
0074 ]
0075 },
0076 {
0077 name: "InnerTrackerSupport_assembly_13",
0078 rules: [
0079 {
0080 material: new THREE.MeshStandardMaterial({
0081 color: 0xEEEEEE,
0082 roughness: 0.7,
0083 metalness: 0.3,
0084 transparent: true,
0085 opacity: 0.8,
0086 blending: THREE.NormalBlending,
0087 // premultipliedAlpha: true,
0088 depthWrite: false, // Ensures correct blending
0089 polygonOffset: true,
0090 polygonOffsetFactor: 1,
0091 side: THREE.DoubleSide
0092 }),
0093 outline: true,
0094 outlineColor: 0x666666,
0095 merge: true,
0096 newName: "InnerTrackerSupport"
0097 }
0098 ]
0099 },
0100 {
0101 name: "DIRC_14",
0102 rules: [
0103 {
0104 patterns: ["**/*box*", "**/*prism*"],
0105 material: new THREE.MeshPhysicalMaterial({
0106 color: 0xe5ba5d,
0107 metalness: .9,
0108 roughness: .05,
0109 envMapIntensity: 0.9,
0110 clearcoat: 1,
0111 transparent: true,
0112 //transmission: .60,
0113 opacity: .6,
0114 reflectivity: 0.2,
0115 //refr: 0.985,
0116 ior: 0.9,
0117 side: THREE.DoubleSide,
0118 }),
0119 newName: "DIRC_barAndPrisms"
0120 },
0121 {
0122 patterns: ["**/*rail*"],
0123 newName: "DIRC_rails",
0124 color: 0xAAAACC
0125 },
0126 {
0127 patterns: ["**/*mcp*"],
0128 newName: "DIRC_mcps"
0129 }
0130 ]
0131
0132 },
0133 {
0134 // This is when DIRC geometry is standalone
0135 name: "DIRC_0",
0136 rules: [
0137 {
0138 patterns: ["**/*box*", "**/*prism*"],
0139 material: new THREE.MeshPhysicalMaterial({
0140 color: 0xe5ba5d,
0141 metalness: .9,
0142 roughness: .05,
0143 envMapIntensity: 0.9,
0144 clearcoat: 1,
0145 transparent: true,
0146 //transmission: .60,
0147 opacity: .6,
0148 reflectivity: 0.2,
0149 //refr: 0.985,
0150 ior: 0.9,
0151 side: THREE.DoubleSide,
0152 }),
0153 newName: "DIRC_barAndPrisms",
0154 merge: false,
0155 outline: true
0156 },
0157 {
0158 patterns: ["**/*rail*"],
0159 newName: "DIRC_rails",
0160 color: 0xAAAACC
0161 },
0162 {
0163 patterns: ["**/*mcp*"],
0164 newName: "DIRC_mcps"
0165 }
0166 ]
0167
0168 },
0169 {
0170 name: "VertexBarrelSubAssembly_3",
0171 rules: [
0172 {
0173 merge: true,
0174 outline: true
0175 }
0176 ]
0177 },
0178 {
0179 name: "*",
0180 rules: [
0181 {
0182 merge: true,
0183 outline: true
0184 }
0185 ]
0186 }
0187 ]
0188
0189 /**
0190 * Detectors (top level TGeo nodes) to be removed.
0191 * (!) startsWith function is used for filtering (aka: detector.fName.startsWith(removeDetectorNames[i]) ... )
0192 */
0193 const removeDetectorNames: string[] = [
0194 "Lumi",
0195 //"Magnet",
0196 //"B0",
0197 "B1",
0198 "B2",
0199 //"Q0",
0200 //"Q1",
0201 "Q2",
0202 //"BeamPipe",
0203 //"Pipe",
0204 "ForwardOffM",
0205 "Forward",
0206 "Backward",
0207 "Vacuum",
0208 "SweeperMag",
0209 "AnalyzerMag",
0210 "ZDC",
0211 //"LFHCAL",
0212 "HcalFarForward",
0213 "InnerTrackingSupport"
0214 ];
0215
0216
0217 // constants.ts
0218 export const DEFAULT_GEOMETRY = 'builtin://epic-central-optimized';
0219
0220 @Injectable({
0221 providedIn: 'root'
0222 })
0223 export class GeometryService {
0224
0225 public rootGeometryProcessor = new RootGeometryProcessor();
0226
0227 /** Collection of subdetectors */
0228 public subdetectors: Subdetector[] = [];
0229
0230 /** TGeoManager if available */
0231 public rootGeometry: any|null = null;
0232
0233 /** Main/entry/root THREEJS geometry tree node with the whole geometry */
0234 // public geometry:
0235
0236 public groupsByDetName: Map<string, string>;
0237
0238 /** for geometry post-processing */
0239 private threeGeometryProcessor = new ThreeGeometryProcessor();
0240
0241 private defaultColor: Color = new Color(0x68698D);
0242
0243 public geometry:WritableSignal<Object3D|null> = signal(null)
0244
0245 constructor(private urlService: UrlService,
0246 private localStorage: LocalStorageService,
0247 ) {
0248 this.groupsByDetName = new Map<string,string> ([
0249 ["SolenoidBarrel_assembly_0", GROUP_MAGNETS],
0250 ["SolenoidEndcapP_1", GROUP_MAGNETS],
0251 ["SolenoidEndcapN_2", GROUP_MAGNETS],
0252 ["VertexBarrelSubAssembly_3", GROUP_TRACKING],
0253 ["InnerSiTrackerSubAssembly_4", GROUP_TRACKING],
0254 ["MiddleSiTrackerSubAssembly_5", GROUP_TRACKING],
0255 ["OuterSiTrackerSubAssembly_6", GROUP_TRACKING],
0256 ["EndcapMPGDSubAssembly_7", GROUP_TRACKING],
0257 ["InnerMPGDBarrelSubAssembly_8", GROUP_TRACKING],
0258 ["EndcapTOFSubAssembly_9", GROUP_PID],
0259 ["BarrelTOFSubAssembly_10", GROUP_PID],
0260 ["OuterBarrelMPGDSubAssembly_11", GROUP_TRACKING],
0261 ["B0TrackerSubAssembly_12", GROUP_TRACKING],
0262 ["InnerTrackerSupport_assembly_13", GROUP_SUPPORT],
0263 ["DIRC_14", GROUP_PID],
0264 ["RICHEndcapN_Vol_15", GROUP_PID],
0265 ["DRICH_16", GROUP_PID],
0266 ["EcalEndcapP_17", GROUP_CALORIMETRY],
0267 ["EcalEndcapPInsert_18", GROUP_CALORIMETRY],
0268 ["EcalBarrelImaging_19", GROUP_CALORIMETRY],
0269 ["EcalBarrelScFi_20", GROUP_CALORIMETRY],
0270 ["EcalEndcapN_21", GROUP_CALORIMETRY],
0271 ["LFHCAL_env_22", GROUP_CALORIMETRY],
0272 ["HcalEndcapPInsert_23", GROUP_CALORIMETRY],
0273 ["HcalBarrel_24", GROUP_CALORIMETRY],
0274 ["FluxBarrel_env_25", GROUP_SUPPORT],
0275 ["FluxEndcapP_26", GROUP_SUPPORT],
0276 ["HcalEndcapN_27", GROUP_CALORIMETRY],
0277 ["FluxEndcapN_28", GROUP_SUPPORT],
0278 ["BeamPipe_assembly_29", GROUP_SUPPORT],
0279 ["B0PF_BeamlineMagnet_assembly_30", GROUP_MAGNETS],
0280 ["B0APF_BeamlineMagnet_assembly_31", GROUP_MAGNETS],
0281 ["Q1APF_BeamlineMagnet_assembly_32", GROUP_MAGNETS],
0282 ["Q1BPF_BeamlineMagnet_assembly_33", GROUP_MAGNETS],
0283 ["BeamPipeB0_assembly_38", GROUP_SUPPORT],
0284 ["Pipe_cen_to_pos_assembly_39", GROUP_SUPPORT],
0285 ["Q0EF_assembly_40", GROUP_MAGNETS],
0286 ["Q0EF_vac_41", GROUP_MAGNETS],
0287 ["Q1EF_assembly_42", GROUP_MAGNETS],
0288 ["Q1EF_vac_43", GROUP_MAGNETS],
0289 ["B0ECal_44", GROUP_CALORIMETRY],
0290 ["Pipe_Q1eR_to_B2BeR_assembly_54", GROUP_SUPPORT],
0291 ["Magnet_Q1eR_assembly_55", GROUP_MAGNETS],
0292 ["Magnet_Q2eR_assembly_56", GROUP_MAGNETS],
0293 ["Magnet_B2AeR_assembly_57", GROUP_MAGNETS],
0294 ["Magnet_B2BeR_assembly_58", GROUP_MAGNETS],
0295 ["Magnets_Q3eR_assembly_59", GROUP_MAGNETS],
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
0404
0405 console.timeEnd('[GeometryService]: Total load geometry time');
0406
0407 this.geometry.set(geometry);
0408
0409 return {rootGeometry: this.rootGeometry, threeGeometry: geometry};
0410 }
0411
0412 public postProcessing(geometry: Object3D, clippingPlanes: Plane[]) {
0413 let threeGeometry = this.geometry();
0414 if (!threeGeometry) return;
0415
0416
0417 // Now we want to set default materials
0418 threeGeometry.traverse((child: any) => {
0419 if (child.type !== 'Mesh' || !child?.material?.isMaterial) {
0420 return;
0421 }
0422
0423 // Handle the material of the child
0424 const color = getColorOrDefault(child.material, this.defaultColor);
0425
0426 if(this.localStorage.geometryFastAndUgly.value) {
0427 child.material = new MeshLambertMaterial({
0428 color: color,
0429 side: DoubleSide, // you said you can’t change this
0430 transparent: false,
0431 opacity: 1, // false transparency; use 1 for full opacity
0432 blending: THREE.NoBlending, // since transparent is false
0433 depthTest: true,
0434 depthWrite: true,
0435 clippingPlanes,
0436 clipIntersection: true,
0437 clipShadows: false,
0438 fog: false, // disable fog math
0439 vertexColors: false, // disable vertex-color math
0440 flatShading: true, // simpler “flat” shading
0441 toneMapped: false // skip tone-mapping
0442 });
0443 } else {
0444 child.material = new MeshLambertMaterial({
0445 color: color,
0446 side: DoubleSide,
0447 transparent: true,
0448 opacity: 0.7,
0449 blending: NormalBlending,
0450 depthTest: true,
0451 depthWrite: true,
0452 clippingPlanes: clippingPlanes,
0453 clipIntersection: true,
0454 clipShadows: false,
0455 });
0456
0457 }
0458 });
0459
0460
0461 // HERE WE DO POSTPROCESSING STEP
0462 // TODO this.threeGeometryProcessor.processRuleSets(defaultRules, this.subdetectors);
0463 console.log(`[GeometryService]: Geometry theme name is set to '${this.localStorage.geometryThemeName.value}'`);
0464 if(this.localStorage.geometryThemeName.value === "cool2") {
0465 this.threeGeometryProcessor.processRuleSets(cool2ColorRules, this.subdetectors);
0466 }else if(this.localStorage.geometryThemeName.value === "cool2no") {
0467 this.threeGeometryProcessor.processRuleSets(cool2NoOutlineColorRules, this.subdetectors);
0468 } else if(this.localStorage.geometryThemeName.value === "cad") {
0469 this.threeGeometryProcessor.processRuleSets(cadColorRules, this.subdetectors);
0470 } else if(this.localStorage.geometryThemeName.value === "grey") {
0471 this.threeGeometryProcessor.processRuleSets(monoColorRules, this.subdetectors);
0472 }
0473
0474
0475
0476 // Now we want to change the materials
0477 threeGeometry.traverse((child: any) => {
0478 if (!child?.material?.isMaterial) {
0479 return;
0480 }
0481
0482 if (child.material?.clippingPlanes !== undefined) {
0483 child.material.clippingPlanes = clippingPlanes;
0484 }
0485
0486 if (child.material?.clipIntersection !== undefined) {
0487 child.material.clipIntersection = true;
0488 }
0489
0490 if (child.material?.clipShadows !== undefined) {
0491 child.material.clipShadows = false;
0492 }
0493 });
0494
0495 //this.simplifyAllMeshes(geometry, 0.5);
0496 }
0497
0498 private stripIdFromName(name: string) {
0499 return name.replace(/_\d+$/, '');
0500 }
0501
0502 toggleVisibility(object: Object3D) {
0503 if (object) {
0504 object.visible = !object.visible;
0505 console.log(`Visibility toggled for object: ${object.name}. Now visible: ${object.visible}`);
0506 }
0507 }
0508 }
0509