Back to home page

EIC code displayed by LXR

 
 

    


Warning, /firebird/firebird-ng/src/app/utils/three.utils.ts is written in an unsupported language. File is not indexed.

0001 import outmatch from 'outmatch';
0002 
0003 import * as THREE from "three";
0004 import { GeoNodeWalkCallback, walkGeoNodes } from "../../lib-root-geometry/root-geo-navigation";
0005 import { MergeResult } from "./three-geometry-merge";
0006 
0007 /**
0008  * Callback function type for walking through Object3D nodes.
0009  *
0010  * @callback NodeWalkCallback
0011  * @param {any} node - The current node being processed.
0012  * @param {string} nodeFullPath - The full hierarchical path to the current node.
0013  * @param {number} level - The current depth level in the hierarchy.
0014  * @returns {boolean} - Determines whether to continue walking the tree.
0015  */
0016 export type NodeWalkCallback = (node: any, nodeFullPath: string, level: number) => boolean;
0017 
0018 /**
0019  * Options for walking through Object3D nodes.
0020  *
0021  * @interface NodeWalkOptions
0022  * @property {number} [maxLevel=Infinity] - The maximum depth level to traverse.
0023  * @property {number} [level=0] - The current depth level in the traversal.
0024  * @property {string} [parentPath=""] - The hierarchical path of the parent node.
0025  * @property {any} [pattern=null] - A pattern to match node paths against.
0026  */
0027 interface NodeWalkOptions {
0028   maxLevel?: number;
0029   level?: number;
0030   parentPath?: string;
0031   pattern?: any;
0032 }
0033 
0034 /**
0035  * Recursively walks through a THREE.Object3D hierarchy, invoking a callback on each node.
0036  *
0037  * @function walkObject3DNodes
0038  * @param {any} node - The current node in the Object3D hierarchy.
0039  * @param {NodeWalkCallback|null} callback - The function to execute on each node.
0040  * @param {NodeWalkOptions} [options={}] - Configuration options for the traversal.
0041  * @returns {number} - The total number of nodes processed.
0042  */
0043 export function walkObject3DNodes(node: any, callback: NodeWalkCallback | null, options: NodeWalkOptions = {}): number {
0044 
0045   // Destructure and set default values for options
0046   let { maxLevel = Infinity, level = 0, parentPath = "", pattern = null } = options;
0047 
0048   // Compile the pattern using outmatch if it's a string
0049   if (pattern) {
0050     pattern = typeof pattern === "string" ? outmatch(pattern) : pattern;
0051   }
0052 
0053   // Construct the full path of the current node
0054   const fullPath = parentPath ? `${parentPath}/${node.name}` : node.name;
0055   let processedNodes = 1;
0056 
0057   // Invoke the callback if the pattern matches or if no pattern is provided
0058   if (!pattern || pattern(fullPath)) {
0059     if (callback) {
0060       callback(node, fullPath, level);
0061     }
0062   }
0063 
0064   // Continue recursion if the node has children and the maximum level hasn't been reached
0065   if (node?.children && level < maxLevel) {
0066     // Iterate backwards to safely handle node removal during traversal
0067     for (let i = node.children.length - 1; i >= 0; i--) {
0068       let child = node.children[i];
0069       if (child) {
0070         processedNodes += walkObject3DNodes(child, callback, { maxLevel, level: level + 1, parentPath: fullPath, pattern });
0071       }
0072     }
0073   }
0074 
0075   return processedNodes;
0076 }
0077 
0078 /**
0079  * Represents the results of a node searching operation within a THREE.Object3D hierarchy.
0080  *
0081  * @interface FindResults
0082  * @property {any[]} nodes - An array of nodes that matched the search criteria. These nodes are part of the THREE.Object3D hierarchy.
0083  * @property {string[]} fullPaths - An array of strings, each representing the full path to a corresponding node in the `nodes` array. The full path is constructed by concatenating parent node names, providing a clear hierarchical structure.
0084  * @property {number} deepestLevel - The deepest level reached in the hierarchy during the search. This value helps understand the depth of the search and which level had the last matched node.
0085  * @property {number} totalWalked - The total number of nodes visited during the search process. This count includes all nodes checked, regardless of whether they matched the criteria.
0086  */
0087 export interface FindResults {
0088   nodes: any[];
0089   fullPaths: string[];
0090   deepestLevel: number;
0091   totalWalked: number;
0092 }
0093 
0094 /**
0095  * Searches for and collects nodes in a THREE.Object3D hierarchy based on a given pattern and type.
0096  *
0097  * @function findObject3DNodes
0098  * @param {any} parentNode - The root node of the hierarchy to search within.
0099  * @param {string} pattern - A string pattern to match node names against.
0100  * @param {string} [matchType=""] - Optional filter to restrict results to nodes of a specific type.
0101  * @param {number} [maxLevel=Infinity] - The maximum depth to search within the node hierarchy.
0102  * @returns {FindResults} - An object containing the results of the search:
0103  *                          - `nodes`: Array of nodes that match the criteria.
0104  *                          - `fullPaths`: Array of full path strings corresponding to each matched node.
0105  *                          - `deepestLevel`: The deepest level reached in the hierarchy during the search.
0106  *                          - `totalWalked`: Total number of nodes visited during the search.
0107  */
0108 export function findObject3DNodes(parentNode: any, pattern: string, matchType: string = "", maxLevel: number = Infinity): FindResults {
0109   let nodes: any[] = [];
0110   let fullPaths: string[] = [];
0111   let deepestLevel = 0;
0112 
0113   /**
0114    * Callback function to collect matching nodes.
0115    *
0116    * @param {any} node - The current node being processed.
0117    * @param {string} fullPath - The full hierarchical path to the current node.
0118    * @param {number} level - The current depth level in the hierarchy.
0119    * @returns {boolean} - Continues traversal.
0120    */
0121   const collectNodes: NodeWalkCallback = (node, fullPath, level) => {
0122     if (!matchType || matchType === node.type) {
0123       nodes.push(node);
0124       fullPaths.push(fullPath);
0125       if (level > deepestLevel) {
0126         deepestLevel = level;
0127       }
0128     }
0129     return true; // Continue traversal
0130   };
0131 
0132   // Execute the node walk with the collecting callback and the specified pattern
0133   let totalWalked = walkObject3DNodes(parentNode, collectNodes, { maxLevel, pattern });
0134 
0135   return {
0136     nodes,
0137     fullPaths,
0138     deepestLevel,
0139     totalWalked
0140   };
0141 }
0142 
0143 /**
0144  * Interface representing objects that have a color property.
0145  *
0146  * @interface Colorable
0147  * @property {THREE.Color} color - The color of the object.
0148  */
0149 export interface Colorable {
0150   color: THREE.Color;
0151 }
0152 
0153 /**
0154  * Type guard function to check if the material is colorable.
0155  *
0156  * @function isColorable
0157  * @param {any} material - The material to check.
0158  * @returns {material is Colorable} - Returns true if the material has a 'color' property, false otherwise.
0159  */
0160 export function isColorable(material: any): material is Colorable {
0161   return 'color' in material;
0162 }
0163 
0164 /**
0165  * Retrieves the color of a material if it is colorable; otherwise, returns a default color.
0166  *
0167  * @function getColorOrDefault
0168  * @param {any} material - The material whose color is to be retrieved.
0169  * @param {THREE.Color} defaultColor - The default color to return if the material is not colorable.
0170  * @returns {THREE.Color} - The color of the material if colorable, or the default color.
0171  */
0172 export function getColorOrDefault(material: any, defaultColor: THREE.Color): THREE.Color {
0173   if (isColorable(material)) {
0174     return material.color;
0175   } else {
0176     return defaultColor;
0177   }
0178 }
0179 
0180 /**
0181  * Custom error class thrown when a mesh or object does not contain geometry.
0182  *
0183  * @class NoGeometryError
0184  * @extends {Error}
0185  */
0186 class NoGeometryError extends Error {
0187   /**
0188    * The mesh or object that caused the error.
0189    *
0190    * @type {any}
0191    */
0192   mesh: any = undefined;
0193 
0194   /**
0195    * Creates an instance of NoGeometryError.
0196    *
0197    * @constructor
0198    * @param {any} mesh - The mesh or object that lacks geometry.
0199    * @param {string} [message="Mesh (or whatever is provided) does not contain geometry."] - The error message.
0200    */
0201   constructor(mesh: any, message: string = "Mesh (or whatever is provided) does not contain geometry.") {
0202     super(message);
0203     this.name = "NoGeometryError";
0204     this.mesh = mesh;
0205   }
0206 }
0207 
0208 /**
0209  * Options for creating an outline around a mesh.
0210  *
0211  * @interface CreateOutlineOptions
0212  * @property {THREE.ColorRepresentation} [color=0x555555] - The color of the outline.
0213  * @property {THREE.Material} [material] - The material to use for the outline.
0214  * @property {number} [thresholdAngle=40] - The angle threshold for edge detection.
0215  */
0216 export interface CreateOutlineOptions {
0217   color?: THREE.ColorRepresentation;
0218   material?: THREE.Material;
0219   thresholdAngle?: number;
0220   /** If true, marks the created outline with geometryEditingSkipRules flag */
0221   markAsProcessed?: boolean;
0222 }
0223 
0224 let globalOutlineCount = 0;
0225 
0226 /**
0227  * Applies an outline mesh from lines to a mesh and adds the outline to the mesh's parent.
0228  *
0229  * @function createOutline
0230  * @param {any} mesh - A THREE.Object3D (expected to be a Mesh) to process.
0231  * @param {CreateOutlineOptions} [options={}] - Configuration options for the outline.
0232  * @throws {NoGeometryError} - Throws an error if the mesh does not contain geometry.
0233  */
0234 export function createOutline(mesh: any, options: CreateOutlineOptions = {}): void {
0235   if (!mesh?.geometry) {
0236     throw new NoGeometryError(mesh);
0237   }
0238 
0239   let { color = 0x555555, material, thresholdAngle = 40, markAsProcessed = false } = options || {};
0240 
0241   // Generate edges geometry based on the threshold angle
0242   let edges = new THREE.EdgesGeometry(mesh.geometry, thresholdAngle);
0243   let lineMaterial = material as THREE.LineBasicMaterial;
0244 
0245   // If no material is provided, create a default LineBasicMaterial
0246   if (!lineMaterial) {
0247     lineMaterial = new THREE.LineBasicMaterial({
0248       color: color ?? new THREE.Color(0x555555),
0249       fog: false,
0250       clippingPlanes: mesh.material?.clippingPlanes ? mesh.material.clippingPlanes : [],
0251       clipIntersection: false,
0252       clipShadows: true,
0253       transparent: true
0254     });
0255   }
0256 
0257   // Create a LineSegments object for the outline
0258   const edgesLine = new THREE.LineSegments(edges, lineMaterial);
0259   edgesLine.name = (mesh.name ?? "") + "_outline";
0260   edgesLine.userData = {};
0261 
0262   // Mark the outline as processed so subsequent rules skip it
0263   if (markAsProcessed) {
0264     edgesLine.userData['geometryEditingSkipRules'] = true;
0265   }
0266 
0267   // Add the outline to the parent of the mesh
0268   mesh.updateMatrixWorld(true);
0269   mesh?.parent?.add(edgesLine);
0270   globalOutlineCount++;
0271   if(globalOutlineCount>0 && !(globalOutlineCount%10000)) {
0272     console.warn(`createOutline: Created: ${globalOutlineCount} outlines. (it is many)`);
0273   }
0274 }
0275 
0276 /**
0277  * Extended properties for THREE.Material to include various texture maps.
0278  *
0279  * @interface ExtendedMaterialProperties
0280  * @property {THREE.Texture | null} [map] - The main texture map.
0281  * @property {THREE.Texture | null} [lightMap] - The light map texture.
0282  * @property {THREE.Texture | null} [bumpMap] - The bump map texture.
0283  * @property {THREE.Texture | null} [normalMap] - The normal map texture.
0284  * @property {THREE.Texture | null} [specularMap] - The specular map texture.
0285  * @property {THREE.Texture | null} [envMap] - The environment map texture.
0286  * @property {THREE.Texture | null} [alphaMap] - The alpha map texture.
0287  * @property {THREE.Texture | null} [aoMap] - The ambient occlusion map texture.
0288  * @property {THREE.Texture | null} [displacementMap] - The displacement map texture.
0289  * @property {THREE.Texture | null} [emissiveMap] - The emissive map texture.
0290  * @property {THREE.Texture | null} [gradientMap] - The gradient map texture.
0291  * @property {THREE.Texture | null} [metalnessMap] - The metalness map texture.
0292  * @property {THREE.Texture | null} [roughnessMap] - The roughness map texture.
0293  */
0294 type ExtendedMaterialProperties = {
0295   map?: THREE.Texture | null,
0296   lightMap?: THREE.Texture | null,
0297   bumpMap?: THREE.Texture | null,
0298   normalMap?: THREE.Texture | null,
0299   specularMap?: THREE.Texture | null,
0300   envMap?: THREE.Texture | null,
0301   alphaMap?: THREE.Texture | null,
0302   aoMap?: THREE.Texture | null,
0303   displacementMap?: THREE.Texture | null,
0304   emissiveMap?: THREE.Texture | null,
0305   gradientMap?: THREE.Texture | null,
0306   metalnessMap?: THREE.Texture | null,
0307   roughnessMap?: THREE.Texture | null,
0308 };
0309 
0310 /**
0311  * Disposes of a THREE.Material and its associated texture maps.
0312  *
0313  * @function disposeMaterial
0314  * @param {any} material - The material to dispose of.
0315  */
0316 function disposeMaterial(material: any): void {
0317   const extMaterial = material as THREE.Material & ExtendedMaterialProperties;
0318 
0319   // Dispose of each texture map if it exists
0320   if (material?.map) material.map.dispose();
0321   if (material?.lightMap) material.lightMap.dispose();
0322   if (material?.bumpMap) material.bumpMap.dispose();
0323   if (material?.normalMap) material.normalMap.dispose();
0324   if (material?.specularMap) material.specularMap.dispose();
0325   if (material?.envMap) material.envMap.dispose();
0326   if (material?.alphaMap) material.alphaMap.dispose();
0327   if (material?.aoMap) material.aoMap.dispose();
0328   if (material?.displacementMap) material.displacementMap.dispose();
0329   if (material?.emissiveMap) material.emissiveMap.dispose();
0330   if (material?.gradientMap) material.gradientMap.dispose();
0331   if (material?.metalnessMap) material.metalnessMap.dispose();
0332   if (material?.roughnessMap) material.roughnessMap.dispose();
0333 
0334   // Dispose of the material itself
0335   if ('dispose' in material) {
0336     material.dispose();
0337   }
0338 }
0339 
0340 /**
0341  * Disposes of a THREE.Object3D node by disposing its geometry and materials.
0342  *
0343  * @function disposeNode
0344  * @param {any} node - The node to dispose of.
0345  */
0346 export function disposeNode(node: any): void {
0347   // Dispose of geometry if it exists
0348   if (node?.geometry) {
0349     node.geometry.dispose();
0350   }
0351 
0352   // Dispose of materials if they exist
0353   if (node?.material) {
0354     if (Array.isArray(node.material)) {
0355       node.material.forEach(disposeMaterial);
0356     } else {
0357       disposeMaterial(node.material);
0358     }
0359   }
0360 
0361   // Remove the node from its parent
0362   node.removeFromParent();
0363 }
0364 
0365 /**
0366  * Disposes of the original meshes after merging geometries.
0367  *
0368  * @function disposeOriginalMeshesAfterMerge
0369  * @param {MergeResult} mergeResult - The result of the geometry merge containing nodes to remove.
0370  */
0371 export function disposeOriginalMeshesAfterMerge(mergeResult: MergeResult): void {
0372   // Iterate through the children to remove in reverse order
0373   for (let i = mergeResult.childrenToRemove.length - 1; i >= 0; i--) {
0374     disposeNode(mergeResult.childrenToRemove[i]);
0375     mergeResult.childrenToRemove[i].removeFromParent();
0376   }
0377 }
0378 
0379 /**
0380  * Recursively disposes of a THREE.Object3D hierarchy.
0381  *
0382  * @function disposeHierarchy
0383  * @param {THREE.Object3D} node - The root node of the hierarchy to dispose of.
0384  * @param disposeSelf - disposes this node too (if false - only children and their hierarchies will be disposed)
0385  */
0386 export function disposeHierarchy(node: THREE.Object3D, disposeSelf=true): void {
0387   // Clone the children array and iterate in reverse order
0388   node.children.slice().reverse().forEach(child => {
0389     disposeHierarchy(child);
0390   });
0391 
0392   if(disposeSelf) {
0393     disposeNode(node);
0394   }
0395 }
0396 
0397 /**
0398  * Recursively removes empty branches from a THREE.Object3D tree.
0399  * An empty branch is a node without geometry and without any non-empty children.
0400  *
0401  * Removing useless nodes that were left without geometries speeds up overall rendering.
0402  *
0403  * @function pruneEmptyNodes
0404  * @param {THREE.Object3D} node - The starting node to prune empty branches from.
0405  */
0406 export function pruneEmptyNodes(node: THREE.Object3D): void {
0407   // Traverse children from last to first to avoid index shifting issues after removal
0408   for (let i = node.children.length - 1; i >= 0; i--) {
0409     pruneEmptyNodes(node.children[i]); // Recursively prune children first
0410   }
0411 
0412   // After pruning children, determine if the current node is now empty
0413   if (node.children.length === 0 && !((node as any)?.geometry)) {
0414     node.removeFromParent();
0415   }
0416 }