Warning, /firebird/firebird-ng/src/app/utils/three-geometry-editor.ts is written in an unsupported language. File is not indexed.
0001 import {Color, Material, Mesh, Object3D} from "three";
0002 import {createOutline, disposeOriginalMeshesAfterMerge, findObject3DNodes, pruneEmptyNodes} from "./three.utils";
0003 import {mergeBranchGeometries, mergeMeshList, MergeResult} from "./three-geometry-merge";
0004 import * as THREE from "three";
0005 import {ColorRepresentation} from "three/src/math/Color";
0006 import {SimplifyModifier} from "three/examples/jsm/modifiers/SimplifyModifier.js";
0007
0008 export enum EditThreeNodeActions {
0009
0010 Merge, /** Merge children matching patterns (if patterns are provided) or all meshes of the node*/
0011
0012 }
0013
0014 export interface EditThreeNodeRule {
0015
0016 patterns?: string[] | string;
0017 merge?: boolean;
0018 newName?: string;
0019 deleteOrigins?: boolean;
0020 cleanupNodes?: boolean;
0021 outline?: boolean;
0022 outlineThresholdAngle?: number;
0023 simplifyMeshes?: boolean;
0024 simplifyRatio?: number;
0025
0026 /** [degrees] */
0027 outlineColor?: ColorRepresentation;
0028 material?: Material;
0029 color?: ColorRepresentation;
0030
0031 }
0032
0033 function simplifyMeshTree(object: THREE.Object3D, simplifyRatio = 0.5): void {
0034 const simplifier = new SimplifyModifier();
0035 const minVerts = 10;
0036
0037 object.traverse((child: THREE.Object3D) => {
0038
0039 // Type coercions and type validations
0040 if (!(child as THREE.Mesh).isMesh) {
0041 return
0042 }
0043 const mesh = child as THREE.Mesh;
0044
0045 if(!(mesh.geometry as THREE.BufferGeometry).isBufferGeometry) {
0046 return;
0047 }
0048 const geom = mesh.geometry as THREE.BufferGeometry;
0049
0050 if (!geom.attributes['position']) {
0051 return;
0052 }
0053
0054 // Do we need to convert looking at the number of vertices?
0055 const verticeCount = geom.attributes['position'].count;
0056 const targetVerticeCount = Math.floor(verticeCount * simplifyRatio);
0057
0058 if (verticeCount < minVerts) {
0059 console.log(`[SimplifyMeshTree] Mesh "${mesh.name || '(unnamed)'}": skipped (too small, vertices=${verticeCount })`);
0060 return;
0061 }
0062
0063 if (verticeCount < targetVerticeCount) {
0064 console.log(`[SimplifyMeshTree] Mesh "${mesh.name || '(unnamed)'}": skipped (too small targetVerticeCount, targetVerticeCount=${targetVerticeCount})`);
0065 return;
0066 }
0067
0068
0069 // Actual simplification
0070 const timeStart = performance.now();
0071 console.log(`[SimplifyMeshTree] Processing "${mesh.name || '(unnamed)'}": vertices before=${verticeCount}, after=${targetVerticeCount}`);
0072
0073 mesh.geometry = simplifier.modify(geom, targetVerticeCount);
0074
0075 // Recompute bounding limits
0076 mesh.geometry.computeBoundingBox();
0077 mesh.geometry.computeBoundingSphere();
0078 mesh.geometry.computeVertexNormals();
0079
0080 // Make sure positions and normals will be updated
0081 mesh.geometry.attributes["position"]["needsUpdate"] = true;
0082 if (mesh.geometry.attributes["normal"]) {
0083 mesh.geometry.attributes["normal"]["needsUpdate"] = true;
0084 }
0085
0086 const timeEnd = performance.now()
0087 if (timeEnd - timeStart > 500) {
0088 console.warn(`[SimplifyMeshTree] Warn: mesh "${mesh.name || '(unnamed)'}" took ${Math.round(timeEnd-timeStart)}ms to simplify.`);
0089 }
0090 });
0091 }
0092
0093 function mergeWhatever(node: Object3D, rule: EditThreeNodeRule): MergeResult | undefined {
0094
0095 let newName = !rule.newName ? node.name + "_merged" : rule.newName;
0096
0097 if (!rule.patterns) {
0098 // If user provided patterns only children matching patterns (search goes over whole branch) will be merged,
0099 // But if no patterns given, we will merge whole node
0100 return mergeBranchGeometries(node, newName, rule.material); // Children auto removed
0101 }
0102
0103 // If we are here, we need to collect what to merge first
0104
0105 let mergeSubjects = [];
0106 // merge whole node
0107 if (typeof rule.patterns === "string") {
0108 rule.patterns = [rule.patterns];
0109 }
0110
0111 for (const pattern of rule.patterns) {
0112 mergeSubjects.push(...findObject3DNodes(node, pattern, "Mesh").nodes);
0113 }
0114
0115 let result = mergeMeshList(mergeSubjects, node, newName, rule.material);
0116 const deleteOrigins = rule?.deleteOrigins ?? true;
0117 if (result && deleteOrigins) {
0118 disposeOriginalMeshesAfterMerge(result);
0119 }
0120 return result;
0121 }
0122
0123
0124 export function editThreeNodeContent(node: Object3D, rule: EditThreeNodeRule) {
0125 let {
0126 patterns,
0127 deleteOrigins = true,
0128 cleanupNodes = true,
0129 outline = true,
0130 outlineThresholdAngle = 40,
0131 outlineColor,
0132 simplifyMeshes = false,
0133 simplifyRatio = 0.7,
0134 material,
0135 color,
0136 merge = true,
0137 newName = ""
0138 } = rule;
0139
0140 let targetMeshes: Mesh[] = [];
0141
0142 if (merge) {
0143 // Existing merge logic
0144 let result = mergeWhatever(node, rule);
0145 if (!result) {
0146 console.warn("didn't find children to merge. Patterns:");
0147 console.log(patterns)
0148 return;
0149 }
0150 targetMeshes = [result.mergedMesh];
0151 } else {
0152 // New logic for when merge is false
0153 // Find all meshes that match the patterns, similar to mergeWhatever
0154 if (!patterns) {
0155 // If no patterns given, collect all meshes with geometry in the node
0156 node.traverse((child) => {
0157 if ((child as any)?.geometry) {
0158 targetMeshes.push(child as Mesh);
0159 }
0160 });
0161 } else {
0162 // If patterns are given, find all meshes that match
0163 if (typeof patterns === "string") {
0164 patterns = [patterns];
0165 }
0166
0167 for (const pattern of patterns) {
0168 targetMeshes.push(...findObject3DNodes(node, pattern, "Mesh").nodes);
0169 }
0170 }
0171 }
0172
0173 // Apply operations to each target mesh
0174 for (const targetMesh of targetMeshes) {
0175
0176 // Change color
0177 if (color !== undefined && color !== null) {
0178 if (targetMesh.material) {
0179 (targetMesh.material as any).color = new Color(color);
0180 }
0181 }
0182
0183 // Change material
0184 if (material !== undefined && material !== null) {
0185 targetMesh.material = material;
0186 }
0187
0188 if (simplifyMeshes) {
0189 simplifyMeshTree(targetMesh, simplifyRatio);
0190 }
0191
0192 if (outline) {
0193 createOutline(targetMesh, {color: outlineColor, thresholdAngle: outlineThresholdAngle});
0194 }
0195 }
0196
0197 if (cleanupNodes) {
0198 pruneEmptyNodes(node);
0199 }
0200 }