Back to home page

EIC code displayed by LXR

 
 

    


Warning, /firebird/firebird-ng/src/app/components/scene-tree/scene-tree.component.ts is written in an unsupported language. File is not indexed.

0001 import {
0002   Component,
0003   EventEmitter,
0004   OnInit,
0005   Output,
0006 } from '@angular/core';
0007 
0008 import { FlatTreeControl } from '@angular/cdk/tree';
0009 import {
0010   MatTreeFlatDataSource,
0011   MatTreeFlattener,
0012 } from '@angular/material/tree';
0013 
0014 import {
0015   MatTree,
0016   MatTreeNode,
0017   MatTreeNodeDef,
0018   MatTreeNodePadding,
0019   MatTreeNodeToggle,
0020 } from '@angular/material/tree';
0021 import { MatIcon } from '@angular/material/icon';
0022 import { MatIconButton } from '@angular/material/button';
0023 import { MatTooltip } from '@angular/material/tooltip';
0024 
0025 import * as THREE from 'three';
0026 import { Mesh, Object3D} from 'three';
0027 
0028 import { Line, LineSegments } from 'three';
0029 import { Line2 } from 'three/examples/jsm/lines/Line2.js';
0030 import { ThreeService } from '../../services/three.service';
0031 
0032 
0033 
0034 interface TreeNodeFlat {
0035   expandable: boolean;
0036   name: string;
0037   level: number;
0038   type: string;
0039   object3D: Object3D;
0040   visible: boolean;
0041 }
0042 
0043 
0044 
0045 @Component({
0046   selector: 'app-scene-tree',
0047   standalone: true,
0048   imports: [
0049     MatTree,
0050     MatTreeNode,
0051     MatTreeNodeToggle,
0052     MatTreeNodeDef,
0053     MatTreeNodePadding,
0054     MatIcon,
0055     MatTooltip,
0056     MatIconButton,
0057   ],
0058   templateUrl: './scene-tree.component.html',
0059   styleUrls: ['./scene-tree.component.scss'],
0060 })
0061 export class SceneTreeComponent implements OnInit {
0062 
0063   @Output() configureItem = new EventEmitter<string>();
0064 
0065   /** Enable/disable generic geometry highlighting. */
0066   public isHighlightingEnabled = false;
0067 
0068   /** Enable/disable event-track highlighting. */
0069   public isTrackHighlightingEnabled = false;
0070 
0071 
0072   /* ---------------- Highlight materials for regular geometry ---------------- */
0073 
0074   private readonly geometryHighlightMaterial = new THREE.MeshBasicMaterial({
0075     color: 0xffff00,
0076     wireframe: true,
0077   });
0078 
0079   /* ---------------- Tree helpers ---------------- */
0080 
0081   public treeControl = new FlatTreeControl<TreeNodeFlat>(
0082     node => node.level,
0083     node => node.expandable,
0084   );
0085 
0086   private treeFlattener = new MatTreeFlattener<Object3D, TreeNodeFlat>(
0087     (node: Object3D, level: number): TreeNodeFlat => ({
0088       expandable: !!node.children && node.children.length > 0,
0089       name: node.name || '(untitled)',
0090       level,
0091       type: node.type,
0092       object3D: node,
0093       visible: node.visible,
0094     }),
0095     node => node.level,
0096     node => node.expandable,
0097     node => node.children,
0098   );
0099 
0100   public dataSource = new MatTreeFlatDataSource(
0101     this.treeControl,
0102     this.treeFlattener,
0103   );
0104 
0105   public hasChild = (_: number, node: TreeNodeFlat) => node.expandable;
0106 
0107   /* ---------------- Constructor / init ---------------- */
0108 
0109   constructor(
0110     private threeService: ThreeService,
0111   ) {}
0112 
0113   ngOnInit(): void {
0114     this.refreshSceneTree();
0115   }
0116 
0117   /* ---------------- UI callbacks ---------------- */
0118 
0119 
0120   public get isAnyHighlightingEnabled(): boolean {
0121     return this.isHighlightingEnabled || this.isTrackHighlightingEnabled;
0122   }
0123 
0124 
0125   public toggleHighlighting(): void {
0126     const newState = !this.isAnyHighlightingEnabled;
0127 
0128     this.isHighlightingEnabled = newState;
0129     this.isTrackHighlightingEnabled = newState;
0130   }
0131 
0132   /* ---------------- Tree population ---------------- */
0133 
0134   public refreshSceneTree(): void {
0135     this.dataSource.data = [];
0136     const scene = this.threeService.scene;
0137     if (!scene) {
0138       console.warn('No scene present in ThreeService.');
0139       return;
0140     }
0141     this.dataSource.data = scene.children;
0142     this.treeControl.collapseAll();
0143   }
0144 
0145   /* ---------------- Visibility toggle ---------------- */
0146 
0147   public isEffectivelyVisible(obj: Object3D): boolean {
0148     let cur: Object3D | null = obj;
0149     while (cur) {
0150       if (!cur.visible) return false;
0151       cur = cur.parent!;
0152     }
0153     return true;
0154   }
0155 
0156 
0157   private revealPath(target: Object3D, full = false): void {
0158     //  enabling each invisible parent and hiding its other children
0159     let branch: Object3D = target;
0160     let parent: Object3D | null = branch.parent;
0161     while (parent) {
0162       if (!parent.visible) {
0163         parent.visible = true;
0164         parent.children.forEach(child => {
0165           if (child !== branch) child.visible = false;
0166         });
0167       }
0168       branch = parent;
0169       parent = parent.parent;
0170     }
0171 
0172     // show every descendant of the original target
0173     if (full) target.traverse(child => (child.visible = true));
0174   }
0175 
0176 
0177   public toggleVisibility(node: TreeNodeFlat): void {
0178     const obj = node.object3D;
0179 
0180     if (obj.visible) {
0181       obj.visible = false;
0182     } else {
0183       const hiddenAncestor = (() => {
0184         for (let p = obj.parent; p; p = p.parent) if (!p.visible) return true;
0185         return false;
0186       })();
0187 
0188       if (hiddenAncestor) {
0189         this.revealPath(obj, /* full = */ false);
0190         obj.visible = true;
0191       } else {
0192         this.revealPath(obj, /* full = */ true);
0193         obj.visible = true;
0194       }
0195     }
0196 
0197     node.visible = this.isEffectivelyVisible(obj);
0198   }
0199 
0200 
0201   /* ---------------- Mouse-over handlers ---------------- */
0202 
0203   public onMouseEnterNode(node: TreeNodeFlat): void {
0204     if (this.isHighlightingEnabled && !this.isTrackNode(node)) {
0205       this.highlightNode(node);
0206     }
0207 
0208     if (this.isTrackHighlightingEnabled && this.isTrackNode(node)) {
0209       this.highlightTrack(node);
0210     }
0211   }
0212 
0213   public onMouseLeaveNode(node: TreeNodeFlat): void {
0214     if (this.isHighlightingEnabled && !this.isTrackNode(node)) {
0215       this.unhighlightNode(node);
0216     }
0217 
0218     if (this.isTrackHighlightingEnabled && this.isTrackNode(node)) {
0219       this.unhighlightTrack(node);
0220     }
0221   }
0222 
0223   /* ---------------- Geometry highlight (meshes) ---------------- */
0224 
0225   public highlightNode(node: TreeNodeFlat): void {
0226     node.object3D.traverse(child => {
0227       if (child instanceof Mesh) {
0228         if (!child.userData['origMaterial']) {
0229           child.userData['origMaterial'] = child.material;
0230         }
0231         child.material = this.geometryHighlightMaterial;
0232       }
0233     });
0234   }
0235 
0236   public unhighlightNode(node: TreeNodeFlat): void {
0237     node.object3D.traverse(child => {
0238       if (child instanceof Mesh && child.userData['origMaterial']) {
0239         child.material = child.userData['origMaterial'];
0240         delete child.userData['origMaterial'];
0241       }
0242     });
0243   }
0244 
0245 
0246   /** Returns true if node belongs to the "event track" hierarchy. */
0247   public isTrackNode(node: TreeNodeFlat): boolean {
0248     return (
0249       this.isUnderEventParent(node) &&
0250       (node.name.toLowerCase().includes('track') ||
0251         this.hasLineGeometry(node.object3D) ||
0252         node.object3D.userData?.['isTrack'] === true)
0253     );
0254   }
0255 
0256   /** Detect "Event" parent upwards in scene graph. */
0257   private isUnderEventParent(node: TreeNodeFlat): boolean {
0258     if (
0259       node.name.toLowerCase() === 'event' ||
0260       node.object3D.userData?.['isEvent'] === true
0261     ) {
0262       return true;
0263     }
0264     let cur: Object3D | null = node.object3D;
0265     while (cur && cur.parent) {
0266       if (
0267         cur.parent.name.toLowerCase() === 'event' ||
0268         cur.parent.userData?.['isEvent'] === true
0269       ) {
0270         return true;
0271       }
0272       cur = cur.parent;
0273     }
0274     return false;
0275   }
0276 
0277   /** Quick geometry test for lines. */
0278   private hasLineGeometry(object: Object3D): boolean {
0279     let found = false;
0280     object.traverse(o => {
0281       if (
0282         o instanceof Line ||
0283         o instanceof LineSegments ||
0284         o instanceof Line2 ||
0285         (o instanceof Mesh &&
0286           o.geometry &&
0287           o.geometry.type.includes('Line'))
0288       ) {
0289         found = true;
0290       }
0291     });
0292     return found;
0293   }
0294 
0295 
0296   public highlightTrack(node: TreeNodeFlat): void {
0297     // Using the highlight function stored in userData
0298     node.object3D.traverse(obj3d => {
0299       if (obj3d.userData['highlightFunction']) {
0300         obj3d.userData['highlightFunction']();
0301       }
0302     });
0303   }
0304 
0305   public unhighlightTrack(node: TreeNodeFlat): void {
0306     // Using the unhighlight function stored in userData
0307     node.object3D.traverse(obj3d => {
0308       if (obj3d.userData['unhighlightFunction']) {
0309         obj3d.userData['unhighlightFunction']();
0310       }
0311     });
0312   }
0313 }