@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
text/typescript
///////////////////////////////////////////////////////////////////////////////
// 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;
}
}