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 }