Back to home page

EIC code displayed by LXR

 
 

    


Warning, /firebird/firebird-ng/src/app/painters/point-trajectory.painter.ts is written in an unsupported language. File is not indexed.

0001 import { ComponentPainter } from "./component-painter";
0002 import { EntryComponent } from "../model/entry-component";
0003 import {
0004   PointTrajectoryComponent,
0005   TrackerLineSegment
0006 } from "../model/point-trajectory.event-component";
0007 
0008 import { Color, Object3D } from "three";
0009 import { LineMaterial } from "three/examples/jsm/lines/LineMaterial";
0010 import { LineGeometry } from "three/examples/jsm/lines/LineGeometry";
0011 import { Line2 } from "three/examples/jsm/lines/Line2";
0012 
0013 /** Example color set. Feel free to refine or expand. */
0014 export enum NeonTrackColors {
0015   Red      = 0xFF0007,
0016   Pink     = 0xCF00FF,
0017   Violet   = 0x5400FF,
0018   Blue     = 0x0097FF,
0019   DeepBlue = 0x003BFF,
0020   Teal     = 0x00FFD1,
0021   Green    = 0x13FF00,
0022   Salad    = 0x8CFF00,
0023   Yellow   = 0xFFEE00,
0024   Orange   = 0xFF3500,
0025   Gray     = 0xAAAAAA,
0026 }
0027 
0028 /**
0029  * We'll keep each line's full data in a small structure so we can rebuild partial geometry.
0030  */
0031 interface InternalLineData {
0032   lineObj: Line2;                    // the Line2 object in the scene
0033   points: number[][];                // the raw array of [x, y, z, t, dx, dy, dz, dt]
0034   lineMaterial: LineMaterial;        // the material used
0035   startTime: number;                 // The time of the first point
0036   endTime: number;                   // The end of the last point
0037   params: Record<string, any>;       // Track parameters
0038   lastPaintIndex: number;            // This is needed for partial track draw optimization
0039 }
0040 
0041 /**
0042  * Painter that draws lines for a "TrackerLinePointTrajectoryComponent",
0043  * supporting partial display based on time.
0044  */
0045 export class PointTrajectoryPainter extends ComponentPainter {
0046   /** A small array to store each line's data and references. */
0047   private trajectories: InternalLineData[] = [];
0048   private timeColumnIndex = 3;         // TODO check that line has time column
0049 
0050   /** Base materials that we clone for each line. */
0051   private baseSolidMaterial: LineMaterial;
0052   private baseDashedMaterial: LineMaterial;
0053 
0054   constructor(parentNode: Object3D, component: EntryComponent) {
0055     super(parentNode, component);
0056 
0057     if (component.type !== PointTrajectoryComponent.type) {
0058       throw new Error("Wrong component type given to TrackerLinePointTrajectoryPainter.");
0059     }
0060 
0061     // Create base materials
0062     this.baseSolidMaterial = new LineMaterial({
0063       color: 0xffffff,
0064       linewidth: 10,   // in world units
0065       worldUnits: true,
0066       dashed: false,
0067       alphaToCoverage: true
0068     });
0069 
0070     this.baseDashedMaterial = new LineMaterial({
0071       color: 0xffffff,
0072       linewidth: 10,
0073       worldUnits: true,
0074       dashed: true,
0075       dashSize: 100,
0076       gapSize: 100,
0077       alphaToCoverage: true
0078     });
0079 
0080     // Build lines at construction
0081     this.initLines();
0082   }
0083 
0084   /**
0085    * Builds the Line2 objects for each line in the data.
0086    * Initially, we set them fully visible (or we could set them invisible).
0087    */
0088   private initLines() {
0089     const component = this.component as PointTrajectoryComponent;
0090 
0091     // Let us see if paramColumns includes "pdg" or "charge" or something.
0092     const pdgIndex = component.paramColumns.indexOf("pdg");
0093     const chargeIndex = component.paramColumns.indexOf("charge");
0094     let paramsToColumnsMismatchWarned = false;
0095     let noPointsWarned = 0;
0096 
0097     for (const lineSegment of component.lines) {
0098 
0099       // Copy params
0100       const paramColumns = component.paramColumns;
0101       const params = lineSegment.params;
0102       if(params.length != paramColumns.length && !paramsToColumnsMismatchWarned) {
0103         // We do the warning only once!
0104         console.error(`params.length(${params.length})  != paramColumns.length(${paramColumns.length}) at '${component.name}'. This should never happen!`);
0105         paramsToColumnsMismatchWarned = true;
0106       }
0107 
0108       // We intentionally use the very dumb method but this method allows us do at least something if they mismatch
0109       const paramArrLen = Math.min(paramColumns.length, params.length);
0110       const paramsDict: Record<string, any> = {};
0111       for (let i = 0; i < paramArrLen; i++) {
0112         paramsDict[paramColumns[i]] = params[i];
0113       }
0114 
0115       // Check we have enough points to build at least something!
0116       if(lineSegment.points.length <= 1) {
0117         if(noPointsWarned < 10) {
0118           const result = Object.entries(paramsDict)
0119             .map(([key, value]) => `${key}:${value}`)
0120             .join(", ");
0121           console.warn(`Line has ${lineSegment.points.length} points. This can't be. Track parameters: ${result}`);
0122           noPointsWarned++;
0123         }
0124         continue;   // Skip this line!
0125       }
0126 
0127       // Create proper material
0128       const { lineMaterial } = this.createLineMaterial(lineSegment, pdgIndex, chargeIndex);
0129 
0130       // We'll start by building a geometry with *all* points, and rely on paint() to do partial logic.
0131       // We'll store the full set of points in linesData, then paint() can rebuild partial geometry.
0132       const geometry = new LineGeometry();
0133       const fullPositions = this.generateFlatXYZ(lineSegment.points);
0134       geometry.setPositions(fullPositions);
0135 
0136       const line2 = new Line2(geometry, lineMaterial);
0137       line2.computeLineDistances();
0138 
0139       // Add to the scene
0140       this.parentNode.add(line2);
0141 
0142       let startTime = 0;
0143       let endTime = 0;
0144       if(lineSegment.points[0].length > this.timeColumnIndex) {
0145         startTime = lineSegment.points[0][this.timeColumnIndex];
0146         endTime = lineSegment.points[lineSegment.points.length-1][this.timeColumnIndex]
0147       }
0148 
0149       const trajData: InternalLineData = {
0150         lineObj: line2,
0151         lineMaterial: lineMaterial,
0152         points: lineSegment.points,
0153         startTime: startTime,
0154         endTime: endTime,
0155         params: paramsDict,
0156         lastPaintIndex: 0,
0157       }
0158 
0159       trajData.lineObj.name = this.getNodeName(trajData);
0160       trajData.lineObj.userData["track_params"] = trajData.params;
0161 
0162       // Keep the data
0163       this.trajectories.push(trajData);
0164 
0165     }
0166   }
0167 
0168   /**
0169    * Creates or picks a line material based on PDG or charge, etc.
0170    */
0171   private createLineMaterial(line: TrackerLineSegment, pdgIndex: number, chargeIndex: number) {
0172     let colorVal = NeonTrackColors.Gray;
0173     let dashed = false;
0174 
0175     // Try to read PDG and/or charge from line.params
0176     // This assumes line.params matches paramColumns.
0177     let pdg = 0, charge = 0;
0178     if (pdgIndex >= 0 && pdgIndex < line.params.length) {
0179       pdg = Math.floor(line.params[pdgIndex]);
0180     }
0181     if (chargeIndex >= 0 && chargeIndex < line.params.length) {
0182       charge = line.params[chargeIndex];
0183     }
0184 
0185     // Minimal PDG-based color logic
0186     switch (pdg) {
0187       case 22: // gamma
0188         colorVal = NeonTrackColors.Yellow;
0189         dashed = true;
0190         break;
0191       case 11: // e-
0192         colorVal = NeonTrackColors.Blue;
0193         dashed = false;
0194         break;
0195       case -11: // e+
0196         colorVal = NeonTrackColors.Red;
0197         dashed = false;
0198         break;
0199       case 211: // pi+
0200         colorVal = NeonTrackColors.Pink;
0201         dashed = false;
0202         break;
0203       case -211: // pi-
0204         colorVal = NeonTrackColors.Teal;
0205         dashed = false;
0206         break;
0207       case 2212: // proton
0208         colorVal = NeonTrackColors.Violet;
0209         dashed = false;
0210         break;
0211       case 2112: // neutron
0212         colorVal = NeonTrackColors.Green;
0213         dashed = true;
0214         break;
0215       default:
0216         // fallback by charge
0217         if (charge > 0) colorVal = NeonTrackColors.Red;
0218         else if (charge < 0) colorVal = NeonTrackColors.DeepBlue;
0219         else colorVal = NeonTrackColors.Gray;
0220         break;
0221     }
0222 
0223     // clone base material
0224     const mat = dashed ? this.baseDashedMaterial.clone() : this.baseSolidMaterial.clone();
0225     mat.color = new Color(colorVal);
0226 
0227     return { lineMaterial: mat, dashed };
0228   }
0229 
0230   /**
0231    * Helper to flatten the [x, y, z, t, ...] points into [x0, y0, z0, x1, y1, z1, ...].
0232    * We skip anything beyond the first 3 indices in each point array, because
0233    * x=0,y=1,z=2 are the first three.
0234    */
0235   private generateFlatXYZ(points: number[][]): number[] {
0236     const flat: number[] = [];
0237     for (let i = 0; i < points.length; i++) {
0238       flat.push(points[i][0], points[i][1], points[i][2]); // x,y,z
0239     }
0240     return flat;
0241   }
0242 
0243   /**
0244    * Rebuild partial geometry for a line up to time `t`.
0245    * If the user wants interpolation, we do that for the "one extra" point beyond t.
0246    * Otherwise, we just up to the last point with time <= t.
0247    */
0248   private buildPartialXYZ(points: number[][], t: number): number[] {
0249     const flat: number[] = [];
0250 
0251     // We assume each "points[i]" = [x, y, z, time, dx, dy, dz, dt].
0252     // The time is at index 3 if it exists.
0253     const TIME_INDEX = 3;
0254     if (!points.length) return flat;
0255 
0256     let lastGoodIndex = -1;
0257     for (let i = 0; i < points.length; i++) {
0258       const p = points[i];
0259       if (p.length > TIME_INDEX) {
0260         if (p[TIME_INDEX] <= t) {
0261           // This entire point is included
0262           flat.push(p[0], p[1], p[2]);
0263           lastGoodIndex = i;
0264         } else {
0265           // we've found a point beyond t, let's see if we want to interpolate
0266           if (lastGoodIndex >= 0) {
0267             // Interpolate between points[lastGoodIndex] and points[i]
0268             const p0 = points[lastGoodIndex];
0269             const t0 = p0[TIME_INDEX];
0270             const t1 = p[TIME_INDEX];
0271             if (Math.abs(t1 - t0) > 1e-9) {
0272               const frac = (t - t0) / (t1 - t0);
0273               const xInterp = p0[0] + frac * (p[0] - p0[0]);
0274               const yInterp = p0[1] + frac * (p[1] - p0[1]);
0275               const zInterp = p0[2] + frac * (p[2] - p0[2]);
0276               flat.push(xInterp, yInterp, zInterp);
0277             } else {
0278               // times are effectively the same
0279               flat.push(p0[0], p0[1], p0[2]);
0280             }
0281           }
0282           break; // stop scanning
0283         }
0284       } else {
0285         // If there's no time column for that point, let's assume 0 or treat as instant
0286         // For simplicity, let's treat time as 0. So if t>0, we include it.
0287         flat.push(p[0], p[1], p[2]);
0288         lastGoodIndex = i;
0289       }
0290     }
0291     return flat;
0292   }
0293 
0294   /**
0295    * The main painting method, called each time the user updates "time."
0296    * If time is null, we show the entire track. Otherwise, we show partial up to that time.
0297    */
0298   public override paint(time: number | null): void {
0299     // If time===null => show all lines fully
0300     if (time === null) {
0301       this.paintNoTime();
0302       return;
0303     }
0304 
0305     // Otherwise, partial or none
0306     this.fastPaint(time);
0307   }
0308 
0309   private paintNoTime() {
0310     for (const track of this.trajectories) {
0311       // Rebuild geometry with *all* points
0312       track.lineObj.visible = true;
0313       track.lineObj.geometry.instanceCount=Infinity;
0314     }
0315   }
0316 
0317   public paintAtTime(time:number) {
0318     for (const ld of this.trajectories) {
0319       // Rebuild geometry up to time
0320       const partialPositions = this.buildPartialXYZ(ld.points, time);
0321       if (partialPositions.length < 2 * 3) {
0322         // fewer than 2 points => hide
0323         ld.lineObj.visible = false;
0324         continue;
0325       }
0326       ld.lineObj.visible = true;
0327 
0328       // Dispose old geometry
0329       const geom = ld.lineObj.geometry as LineGeometry;
0330       geom.dispose();
0331 
0332       // Set new geometry
0333       geom.setPositions(partialPositions);
0334       ld.lineObj.computeLineDistances();
0335     }
0336   }
0337 
0338   public fastPaint(time: number) {
0339 
0340     // pass1 select fully visible, partial and fully hidden tracks
0341 
0342     let partialTracks: InternalLineData[] = []; // Replace 'any' with the actual type
0343 
0344     for (const track of this.trajectories) {
0345       if (track.startTime > time) {
0346         track.lineObj.visible = false;
0347       } else {
0348         track.lineObj.visible = true;
0349         track.lineObj.geometry.instanceCount = track.points.length;
0350 
0351         if (track.endTime > time) {
0352           partialTracks.push(track);
0353         } else {
0354           // track should be visible fully
0355           track.lineObj.geometry.instanceCount = Infinity;
0356         }
0357       }
0358     }
0359 
0360      if (partialTracks.length > 0) {
0361        for (let track of partialTracks) {
0362          let geometryPosCount = track.points.length;
0363 
0364          //if (!geometryPosCount || geometryPosCount < 10) continue;
0365          //let trackProgress = (time - track.startTime) / (track.endTime - track.startTime);
0366          //let roundedProgress = Math.round(geometryPosCount * trackProgress * 2) / 2; // *2/2 to stick to 0.5 rounding
0367 
0368          if(track.lastPaintIndex<0 || track.lastPaintIndex>=track.points.length) {
0369            // In case of emergency set lastPointIndex to the center of array
0370            track.lastPaintIndex=track.points.length/2;
0371          }
0372 
0373          if(track.points[track.lastPaintIndex][this.timeColumnIndex] < time) {
0374            // Seek the correct point of time forward
0375            while(track.points[track.lastPaintIndex][this.timeColumnIndex] < time && track.lastPaintIndex<track.points.length) {
0376              track.lastPaintIndex++;
0377            }
0378          }
0379          else {
0380            // Seek the correct point of time backward
0381            while(track.points[track.lastPaintIndex][this.timeColumnIndex] > time && track.lastPaintIndex>=0) {
0382              track.lastPaintIndex--;
0383            }
0384          }
0385 
0386          track.lineObj.geometry.instanceCount = track.lastPaintIndex;
0387        }
0388      }
0389 
0390   }
0391 
0392   /**
0393    * Dispose all line objects, geometry, materials
0394    */
0395   public override dispose(): void {
0396     for (const ld of this.trajectories) {
0397       const geom = ld.lineObj.geometry as LineGeometry;
0398       geom.dispose();
0399       ld.lineMaterial.dispose();
0400 
0401       if (this.parentNode) {
0402         this.parentNode.remove(ld.lineObj);
0403       }
0404     }
0405     this.trajectories = [];
0406     super.dispose();
0407   }
0408 
0409   private getNodeName(trajData: InternalLineData) {
0410 
0411     let name = "track"
0412     if("type" in trajData.params) {
0413       name = trajData.params["type"]
0414     } else if ("pdg" in trajData.params) {
0415       name = trajData.params["pdg"]
0416     } else if ("charge" in trajData.params) {
0417       const charge = parseFloat(trajData.params["cahrge"]);
0418       if(Math.abs(charge) < 0.00001) {
0419         name = "NeuTrk";
0420       }
0421       if(charge>0) {
0422         name = "PosTrk";
0423       } else if (charge < 0) {
0424         name = "NegTrk";
0425       }
0426     }
0427     name="["+name+"]"
0428 
0429     let time = "no-t"
0430     if(Math.abs(trajData.startTime) > 0.000001 ||  Math.abs(trajData.endTime) > 0.000001) {
0431       time = `t:${trajData.startTime.toFixed(1)}-${trajData.endTime.toFixed(1)}`;
0432     }
0433 
0434     let momentum = "no-p"
0435     if("px" in trajData.params && "py" in trajData.params && "pz" in trajData.params) {
0436       let px = parseFloat(trajData.params["px"]);
0437       let py = parseFloat(trajData.params["py"]);
0438       let pz = parseFloat(trajData.params["pz"]);
0439       momentum = "p:"+(Math.sqrt(px*px+py*py+pz*pz)/1000.0).toFixed(3);
0440     }
0441 
0442     return `${name} ${momentum} ${time}`;
0443   }
0444 }