Back to home page

EIC code displayed by LXR

 
 

    


Warning, /firebird/packages/firebird-ng/display/src/lib/components/object-raycast/object-raycast.component.ts is written in an unsupported language. File is not indexed.

0001 import {Component, ElementRef, OnDestroy, TemplateRef, ViewChild, ViewContainerRef, ChangeDetectionStrategy, signal} from '@angular/core';
0002 import {MatDialog, MatDialogClose, MatDialogRef} from "@angular/material/dialog";
0003 import {MatMenuItem} from "@angular/material/menu";
0004 import {MatCheckbox, MatCheckboxChange} from "@angular/material/checkbox";
0005 import {MatIcon} from "@angular/material/icon";
0006 import {MatIconButton} from "@angular/material/button";
0007 import {Subscription} from "rxjs";
0008 import {ThreeService} from "../../services/three.service";
0009 import {SelectionService} from "../../services/selection.service";
0010 import * as THREE from 'three';
0011 import {MatTooltip} from "@angular/material/tooltip";
0012 
0013 @Component({
0014   selector: 'app-object-raycast',
0015   imports: [
0016     MatIcon,
0017     MatDialogClose,
0018     MatMenuItem,
0019     MatCheckbox,
0020     MatTooltip,
0021     MatIconButton,
0022   ],
0023   templateUrl: './object-raycast.component.html',
0024   changeDetection: ChangeDetectionStrategy.OnPush,
0025   styleUrl: './object-raycast.component.scss'
0026 })
0027 export class ObjectRaycastComponent implements OnDestroy {
0028 
0029   @ViewChild('openRayBtn', { read: ElementRef }) openRayBtn!: ElementRef;
0030   @ViewChild('raycastDialogTmpl') raycastDialogTmpl!: TemplateRef<any>;
0031   dialogRef: MatDialogRef<any> | null = null;
0032 
0033   // UI state. Signals: the checkboxes live in the dialog, which renders this
0034   // component's template in another view; a plain field changed there would
0035   // never reach the overlay below in this component's own view.
0036   readonly coordsEnabled = signal(false);
0037   readonly distanceEnabled = signal(false);
0038 
0039   // Signals as well: updates arrive from native canvas listeners via
0040   // ThreeService, which schedule no change detection under zoneless.
0041   readonly coordsText = signal('');
0042   readonly distanceText = signal('');
0043 
0044   private coordsSub?: Subscription;
0045   private distSub?: Subscription;
0046   private distLine?: THREE.Line;
0047 
0048   /** True while this component holds ThreeService's measure mode on. */
0049   private measuring = false;
0050 
0051   constructor(
0052     private dialog: MatDialog,
0053     private three: ThreeService,
0054     private viewContainerRef: ViewContainerRef,
0055     /** Selection is resolved by SelectionService (3D pick → entity), not by
0056      * this component's own picking; the overlay shows the shared selection. */
0057     public selection: SelectionService,
0058   ) {}
0059 
0060   /* ------------ UI ------------- */
0061   openRaycastDialog() {
0062     if (this.dialogRef) {
0063       this.dialogRef.close();
0064       return;
0065     }
0066 
0067     const rect = this.openRayBtn.nativeElement.getBoundingClientRect();
0068     const dialogWidth = 320;
0069 
0070     const left = Math.max(rect.right - dialogWidth, 8);
0071     const top = rect.bottom + 12;
0072 
0073     this.dialogRef = this.dialog.open(this.raycastDialogTmpl, {
0074       position: {
0075         top: `${top}px`,
0076         left: `${left}px`
0077       },
0078       hasBackdrop: false,
0079       panelClass: 'custom-position-dialog',
0080       autoFocus: false,
0081       viewContainerRef: this.viewContainerRef
0082     });
0083 
0084     this.dialogRef.afterClosed().subscribe(() => {
0085       this.dialogRef = null;
0086     });
0087   }
0088 
0089   /* ---------- checkbox handlers ---------- */
0090 
0091 
0092   toggleShowCoords(e: MatCheckboxChange): void {
0093     this.coordsEnabled.set(e.checked);
0094     this.updateSubscriptions();
0095     this.updateRaycastActivation();
0096   }
0097 
0098   toggleShowDistance(e: MatCheckboxChange): void {
0099     this.distanceEnabled.set(e.checked);
0100     this.setMeasureMode(e.checked);
0101     this.updateSubscriptions();
0102     this.updateRaycastActivation();
0103   }
0104 
0105   /** Measure mode turns clicks into distance points, so click-to-select pauses while it is on. */
0106   private setMeasureMode(on: boolean): void {
0107     this.measuring = on;
0108     this.three.measureMode = on;
0109   }
0110 
0111   /* ---------- central switch ---------- */
0112   /** Ensures ThreeService raycast state matches UI needs */
0113   private updateRaycastActivation(): void {
0114     const needRaycast = this.coordsEnabled() || this.distanceEnabled();
0115     const isOn        = this.three.isRaycastEnabledState();
0116     if (needRaycast && !isOn) this.three.toggleRaycast();
0117     if (!needRaycast && isOn) this.three.toggleRaycast();
0118   }
0119 
0120   /* ---------- RxJS subscriptions ---------- */
0121   private updateSubscriptions(): void {
0122 
0123     /* XYZ overlay */
0124     if (this.coordsEnabled() && !this.coordsSub) {
0125       this.coordsSub = this.three.pointHovered.subscribe(pt => {
0126         this.coordsText.set(`X:${pt.x.toFixed(2)}  Y:${pt.y.toFixed(2)}  Z:${pt.z.toFixed(2)}`);
0127       });
0128     } else if (!this.coordsEnabled() && this.coordsSub) {
0129       this.coordsSub.unsubscribe();
0130       this.coordsSub = undefined;
0131       this.coordsText.set('');
0132     }
0133 
0134     /* distance overlay */
0135     if (this.distanceEnabled() && !this.distSub) {
0136       this.distSub = this.three.distanceReady.subscribe(({ p1, p2, dist }) => {
0137         this.distanceText.set(`${dist.toFixed(2)} units`);
0138 
0139         // draw / update line helper
0140         if (!this.distLine) {
0141           const g = new THREE.BufferGeometry().setFromPoints([p1, p2]);
0142           const m = new THREE.LineBasicMaterial({ color: 0xffff00 });
0143           this.distLine = new THREE.Line(g, m);
0144           this.three.sceneHelpers.add(this.distLine);
0145         } else {
0146           (this.distLine.geometry as THREE.BufferGeometry).setFromPoints([p1, p2]);
0147         }
0148         this.three.invalidate();
0149       });
0150     } else if (!this.distanceEnabled() && this.distSub) {
0151       this.distSub.unsubscribe();
0152       this.distSub = undefined;
0153       this.distanceText.set('');
0154       this.removeDistanceLine();
0155     }
0156 
0157   }
0158 
0159   private removeDistanceLine(): void {
0160     if (!this.distLine) return;
0161     this.three.sceneHelpers.remove(this.distLine);
0162     this.distLine.geometry.dispose();
0163     (this.distLine.material as THREE.Material).dispose();
0164     this.distLine = undefined;
0165     this.three.invalidate();
0166   }
0167 
0168   /* ---------- cleanup ---------- */
0169   /**
0170    * ThreeService outlives display pages: leaving the page must hand back
0171    * everything this component switched on there. Otherwise measure mode
0172    * stays on (click-to-select stops working on the next visit), hover
0173    * picking keeps running, and the distance line stays in the scene.
0174    */
0175   ngOnDestroy(): void {
0176     this.dialogRef?.close();
0177     // The page holds two instances (desktop and mobile toolbars): only the
0178     // one that switched hover picking on switches it off.
0179     const holdsRaycast = this.coordsEnabled() || this.distanceEnabled();
0180     this.coordsEnabled.set(false);
0181     this.distanceEnabled.set(false);
0182     if (this.measuring) {
0183       this.setMeasureMode(false);
0184     }
0185     this.updateSubscriptions();
0186     if (holdsRaycast) {
0187       this.updateRaycastActivation();
0188     }
0189     this.removeDistanceLine();
0190   }
0191 }