Warning, /firebird/firebird-ng/src/app/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} 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 * as THREE from 'three';
0010 import {MatTooltip} from "@angular/material/tooltip";
0011 import {NgIf} from "@angular/common";
0012
0013 @Component({
0014 selector: 'app-object-raycast',
0015 imports: [
0016 MatIcon,
0017 MatDialogClose,
0018 MatMenuItem,
0019 MatCheckbox,
0020 MatTooltip,
0021 MatIconButton,
0022 NgIf
0023 ],
0024 templateUrl: './object-raycast.component.html',
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 */
0034
0035 coordsEnabled = false;
0036 distanceEnabled = false;
0037
0038 coordsText = '';
0039 distanceText = '';
0040
0041 private coordsSub?: Subscription;
0042 private distSub?: Subscription;
0043 private distLine?: THREE.Line;
0044
0045
0046 /** internals */
0047 private firstPoint: THREE.Vector3 | null = null;
0048 private clickSub?: Subscription;
0049 private hoverSub?: Subscription;
0050
0051 constructor(
0052 private dialog: MatDialog,
0053 private three: ThreeService,
0054 private viewContainerRef: ViewContainerRef
0055 ) {}
0056
0057 /* ------------ UI ------------- */
0058 openRaycastDialog() {
0059 if (this.dialogRef) {
0060 this.dialogRef.close();
0061 return;
0062 }
0063
0064 const rect = this.openRayBtn.nativeElement.getBoundingClientRect();
0065 const dialogWidth = 320;
0066
0067 const left = Math.max(rect.right - dialogWidth, 8);
0068 const top = rect.bottom + 12;
0069
0070 this.dialogRef = this.dialog.open(this.raycastDialogTmpl, {
0071 position: {
0072 top: `${top}px`,
0073 left: `${left}px`
0074 },
0075 hasBackdrop: false,
0076 panelClass: 'custom-position-dialog',
0077 autoFocus: false,
0078 viewContainerRef: this.viewContainerRef
0079 });
0080
0081 this.dialogRef.afterClosed().subscribe(() => {
0082 this.dialogRef = null;
0083 });
0084 }
0085
0086 /* ---------- checkbox handlers ---------- */
0087
0088
0089 toggleShowCoords(e: MatCheckboxChange): void {
0090 this.coordsEnabled = e.checked;
0091 this.updateSubscriptions();
0092 this.updateRaycastActivation();
0093 }
0094
0095 toggleShowDistance(e: MatCheckboxChange): void {
0096 this.distanceEnabled = e.checked;
0097 this.three.measureMode = e.checked;
0098 if (!e.checked) this.firstPoint = null;
0099 this.updateSubscriptions();
0100 this.updateRaycastActivation();
0101 }
0102
0103 /* ---------- central switch ---------- */
0104 /** Ensures ThreeService raycast state matches UI needs */
0105 private updateRaycastActivation(): void {
0106 const needRaycast = this.coordsEnabled || this.distanceEnabled;
0107 const isOn = this.three.isRaycastEnabledState();
0108 if (needRaycast && !isOn) this.three.toggleRaycast();
0109 if (!needRaycast && isOn) this.three.toggleRaycast();
0110 }
0111
0112 /* ---------- RxJS subscriptions ---------- */
0113 private updateSubscriptions(): void {
0114
0115 /* XYZ overlay */
0116 if (this.coordsEnabled && !this.coordsSub) {
0117 this.coordsSub = this.three.pointHovered.subscribe(pt => {
0118 this.coordsText = `X:${pt.x.toFixed(2)} Y:${pt.y.toFixed(2)} Z:${pt.z.toFixed(2)}`;
0119 });
0120 } else if (!this.coordsEnabled && this.coordsSub) {
0121 this.coordsSub.unsubscribe();
0122 this.coordsSub = undefined;
0123 this.coordsText = '';
0124 }
0125
0126 /* distance overlay */
0127 if (this.distanceEnabled && !this.distSub) {
0128 this.distSub = this.three.distanceReady.subscribe(({ p1, p2, dist }) => {
0129 this.distanceText = `${dist.toFixed(2)} units`;
0130
0131 // draw / update line helper
0132 if (!this.distLine) {
0133 const g = new THREE.BufferGeometry().setFromPoints([p1, p2]);
0134 const m = new THREE.LineBasicMaterial({ color: 0xffff00 });
0135 this.distLine = new THREE.Line(g, m);
0136 this.three.sceneHelpers.add(this.distLine);
0137 } else {
0138 (this.distLine.geometry as THREE.BufferGeometry).setFromPoints([p1, p2]);
0139 }
0140 });
0141 } else if (!this.distanceEnabled && this.distSub) {
0142 this.distSub.unsubscribe();
0143 this.distSub = undefined;
0144 this.distanceText = '';
0145
0146 // remove helper line
0147 if (this.distLine) {
0148 this.three.sceneHelpers.remove(this.distLine);
0149 this.distLine.geometry.dispose();
0150 (this.distLine.material as THREE.Material).dispose();
0151 this.distLine = undefined!;
0152 }
0153 }
0154
0155 }
0156
0157 /* ---------- cleanup ---------- */
0158 ngOnDestroy(): void {
0159 this.hoverSub?.unsubscribe();
0160 this.clickSub?.unsubscribe();
0161 }
0162 }