UNPKG

@inweb/viewer-three

Version:

JavaScript library for rendering CAD and BIM files in a browser using Three.js

215 lines (164 loc) 7.42 kB
/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2002-2025, Open Design Alliance (the "Alliance"). // All rights reserved. // // This software and its documentation and related materials are owned by // the Alliance. The software may only be incorporated into application // programs owned by members of the Alliance, subject to a signed // Membership Agreement and Supplemental Software License Agreement with the // Alliance. The structure and organization of this software are the valuable // trade secrets of the Alliance and its suppliers. The software is also // protected by copyright law and international treaty provisions. Application // programs incorporating this software must include the following statement // with their copyright notices: // // This application incorporates Open Design Alliance software pursuant to a // license agreement with Open Design Alliance. // Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance. // All rights reserved. // // By use of this software, its documentation or related materials, you // acknowledge and accept the above terms. /////////////////////////////////////////////////////////////////////////////// import { Box3, Matrix4, Object3D, Vector3 } from "three"; import { IInfo, Info } from "@inweb/viewer-core"; import { ModelImpl } from "../../models/ModelImpl"; import { DynamicGltfLoader } from "./DynamicGltfLoader.js"; // Dynamic model implementation. export class DynamicModelImpl extends ModelImpl { public gltfLoader: DynamicGltfLoader; override getInfo(): IInfo { const stats = this.gltfLoader.getStats(); const info = new Info(); info.scene.objects = stats.scene.beforeOptimization.objects; info.scene.triangles = stats.scene.beforeOptimization.triangles; info.scene.lines = stats.scene.beforeOptimization.lines; info.scene.edges = stats.scene.beforeOptimization.edges; info.optimizedScene.objects = stats.scene.afterOptimization.objects; info.optimizedScene.triangles = stats.scene.afterOptimization.triangles; info.optimizedScene.lines = stats.scene.afterOptimization.lines; info.optimizedScene.edges = stats.scene.afterOptimization.edges; info.memory.geometries = stats.memory.geometries.count; info.memory.geometryBytes = stats.memory.geometries.bytes; info.memory.textures = stats.memory.textures.count; info.memory.materials = stats.memory.materials.count; info.memory.totalEstimatedGpuBytes = stats.memory.totalEstimatedGpuBytes; return info; } override getExtents(target: Box3): Box3 { return target.union(this.gltfLoader.getTotalGeometryExtent()); } override getObjects(): Object3D[] { const objects = []; this.gltfLoader.originalObjects.forEach((object: Object3D) => { objects.push(object); }); return objects; } override getVisibleObjects(): Object3D[] { return this.gltfLoader.getOriginalObjectForSelect(); } override getObjectsByHandles(handles: string | string[]): Object3D[] { if (!Array.isArray(handles)) handles = [handles]; const handlesSet = new Set(handles); const objects = []; handlesSet.forEach((handle) => { objects.push(this.gltfLoader.getObjectsByHandle(handle)); }); return objects.flat(); } override getHandlesByObjects(objects: Object3D | Object3D[]): string[] { if (!Array.isArray(objects)) objects = [objects]; const handleSet = new Set<string>(); objects .filter((object) => this.gltfLoader.originalObjects.has(object)) .forEach((object) => { handleSet.add(object.userData.handle); }); return Array.from(handleSet); } override hideObjects(objects: Object3D | Object3D[]): this { const handles = this.getHandlesByObjects(objects); this.gltfLoader.hideObjects(handles); return this; } override isolateObjects(objects: Object3D | Object3D[]): this { const handles = this.getHandlesByObjects(objects); this.gltfLoader.isolateObjects(new Set(handles)); return this; } override showObjects(objects: Object3D | Object3D[]): this { const handles = this.getHandlesByObjects(objects); this.gltfLoader.showObjects(handles); return this; } override showAllObjects(): this { this.gltfLoader.showAllHiddenObjects(); return this; } override highlightObjects(objects: Object3D | Object3D[]): this { this.gltfLoader.showOriginalObjects(objects); return this; } override unhighlightObjects(objects: Object3D | Object3D[]): this { this.gltfLoader.hideOriginalObjects(objects); return this; } override explode(scale = 0, coeff = 4): this { const centers = new Map(); const calcObjectCenter = (object: Object3D, target: Vector3): Vector3 => { const extents = new Box3().setFromObject(object); const handle = object.userData.handle; if (!handle) return extents.getCenter(target); const center = centers.get(handle); if (center) return target.copy(center); const objects = this.getObjectsByHandles(handle); objects.forEach((x: Object3D) => extents.expandByObject(x)); extents.getCenter(target); centers.set(handle, target.clone()); return target; }; function calcExplodeDepth(object: Object3D, depth: number): number { let result = depth; object.children .filter((x: Object3D) => !x.userData.isOptimized) .forEach((x: Object3D) => { const objectDepth = calcExplodeDepth(x, depth + 1); if (result < objectDepth) result = objectDepth; }); object.userData.originalPosition = object.position.clone(); object.userData.originalCenter = calcObjectCenter(object, new Vector3()); return result; } const explodeScale = scale / 100; const explodeRoot = this.scene.children[0]; if (!explodeRoot.userData.explodeDepth) explodeRoot.userData.explodeDepth = calcExplodeDepth(explodeRoot, 1); const maxDepth = explodeRoot.userData.explodeDepth; const scaledExplodeDepth = explodeScale * maxDepth + 1; const explodeDepth = 0 | scaledExplodeDepth; const currentSegmentFraction = scaledExplodeDepth - explodeDepth; const transformMap = new Map(); function explodeObject(object, depth: number) { if (object.isCamera) return; if (object.userData.isHighlightWireframe) return; object.position.copy(object.userData.originalPosition); if (depth > 0 && depth <= explodeDepth && !object.userData.isExplodeLocked) { let objectScale = explodeScale * coeff; if (depth === explodeDepth) objectScale *= currentSegmentFraction; const parentCenter = object.parent.userData.originalCenter; const objectCenter = object.userData.originalCenter; const objectOffset = objectCenter.clone().sub(parentCenter).multiplyScalar(objectScale); object.position.add(objectOffset); const matrix = new Matrix4().makeTranslation(objectOffset.x, objectOffset.y, objectOffset.z); transformMap.set(object, matrix); } object.children .filter((x: Object3D) => !x.userData.isOptimized) .forEach((x: Object3D) => explodeObject(x, depth + 1)); } explodeObject(explodeRoot, 0); this.scene.updateMatrixWorld(); this.gltfLoader.applyObjectTransforms(transformMap); return this; } }