@inweb/viewer-three
Version:
JavaScript library for rendering CAD and BIM files in a browser using Three.js
215 lines (178 loc) • 7.97 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, Object3D, Vector3 } from "three";
import { ILoader } from "@inweb/viewer-core";
import { IModelImpl } from "./IModelImpl";
import { Viewer } from "../Viewer";
// Basic model implementation.
export class ModelImpl implements IModelImpl {
public handle: string;
public scene: Object3D;
public loader: ILoader;
public viewer: Viewer;
constructor(scene: Object3D) {
this.handle = "1";
this.scene = scene;
}
dispose() {
function disposeMaterial(material: any) {
// if (material.alphaMap) material.alphaMap.dispose();
// if (material.anisotropyMap) material.anisotropyMap.dispose();
// if (material.aoMap) material.aoMap.dispose();
// if (material.bumpMap) material.bumpMap.dispose();
// if (material.clearcoatMap) material.clearcoatMap.dispose();
// if (material.clearcoatNormalMap) material.clearcoatNormalMap.dispose();
// if (material.clearcoatRoughnessMap) material.clearcoatRoughnessMap.dispose();
// if (material.displacementMap) material.displacementMap.dispose();
// if (material.emissiveMap) material.emissiveMap.dispose();
// if (material.gradientMap) material.gradientMap.dispose();
// if (material.envMap) material.envMap.dispose();
// if (material.iridescenceMap) material.iridescenceMap.dispose();
// if (material.lightMap) material.lightMap.dispose();
// if (material.map) material.map.dispose();
// if (material.metalnessMap) material.metalnessMap.dispose();
// if (material.normalMap) material.normalMap.dispose();
// if (material.roughnessMap) material.roughnessMap.dispose();
// if (material.sheenColorMap) material.sheenColorMap.dispose();
// if (material.sheenRoughnessMap) material.sheenRoughnessMap.dispose();
// if (material.specularMap) material.specularMap.dispose();
// if (material.thicknessMap) material.thicknessMap.dispose();
// if (material.transmissionMap) material.transmissionMap.dispose();
material.dispose();
}
function disposeMaterials(material: any) {
const materials = Array.isArray(material) ? material : [material];
materials.forEach((material: any) => disposeMaterial(material));
}
function disposeObject(object: any) {
if (object.geometry) object.geometry.dispose();
if (object.material) disposeMaterials(object.material);
}
this.scene.traverse(disposeObject);
this.scene.clear();
}
getExtents(target: Box3): Box3 {
this.scene.traverseVisible((object) => !object.children.length && target.expandByObject(object));
return target;
}
getObjects(): Object3D[] {
const objects = [];
this.scene.traverse((object) => objects.push(object));
return objects;
}
getVisibleObjects(): Object3D[] {
const objects = [];
this.scene.traverseVisible((object) => objects.push(object));
return objects;
}
hasObject(object: Object3D): boolean {
while (object) {
if (object === this.scene) return true;
object = object.parent;
}
return false;
}
getOwnObjects(objects: Object3D | Object3D[]): Object3D[] {
if (!Array.isArray(objects)) objects = [objects];
return objects.filter((object) => this.hasObject(object));
}
getObjectsByHandles(handles: string | string[]): Object3D[] {
const handleSet = new Set(handles);
const objects = [];
this.scene.traverse((object) => handleSet.has(object.userData.handle) && objects.push(object));
return objects;
}
getHandlesByObjects(objects: Object3D | Object3D[]): string[] {
if (!Array.isArray(objects)) objects = [objects];
const handlesSet = new Set<string>();
this.getOwnObjects(objects).forEach((object) => handlesSet.add(object.userData.handle));
return Array.from(handlesSet);
}
hideObjects(objects: Object3D | Object3D[]): this {
if (!Array.isArray(objects)) objects = [objects];
this.getOwnObjects(objects).forEach((object) => (object.visible = false));
return this;
}
hideAllObjects(): this {
return this.isolateObjects([]);
}
isolateObjects(objects: Object3D | Object3D[]): this {
if (!Array.isArray(objects)) objects = [objects];
const visibleSet = new Set(objects);
this.getOwnObjects(objects).forEach((object) => object.traverseAncestors((parent) => visibleSet.add(parent)));
this.scene.traverse((object) => (object.visible = visibleSet.has(object)));
return this;
}
showObjects(objects: Object3D | Object3D[]): this {
if (!Array.isArray(objects)) objects = [objects];
this.getOwnObjects(objects).forEach((object) => {
object.visible = true;
object.traverseAncestors((parent) => (parent.visible = true));
});
return this;
}
showAllObjects(): this {
this.scene.traverse((object) => (object.visible = true));
return this;
}
showOriginalObjects(objects: Object3D | Object3D[]): this {
return this;
}
hideOriginalObjects(objects: Object3D | Object3D[]): this {
return this;
}
explode(scale = 0, coeff = 4): this {
function calcExplodeDepth(object: Object3D, depth: number): number {
let res = depth;
object.children.forEach((x: Object3D) => {
const objectDepth = calcExplodeDepth(x, depth + 1);
if (res < objectDepth) res = objectDepth;
});
object.userData.originalPosition = object.position.clone();
object.userData.originalCenter = new Box3().setFromObject(object).getCenter(new Vector3());
object.userData.isExplodeLocked = depth > 2 && object.children.length === 0;
return res;
}
scale /= 100;
if (!this.scene.userData.explodeDepth) this.scene.userData.explodeDepth = calcExplodeDepth(this.scene, 1);
const maxDepth = this.scene.userData.explodeDepth;
const scaledExplodeDepth = scale * maxDepth + 1;
const explodeDepth = 0 | scaledExplodeDepth;
const currentSegmentFraction = scaledExplodeDepth - explodeDepth;
function explodeObject(object: Object3D, depth: number) {
object.position.copy(object.userData.originalPosition);
if (depth > 0 && depth <= explodeDepth && !object.userData.isExplodeLocked) {
let objectScale = scale * 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);
}
object.children.forEach((x) => explodeObject(x, depth + 1));
}
explodeObject(this.scene, 0);
this.scene.updateMatrixWorld();
return this;
}
}