@inweb/viewer-three
Version:
JavaScript library for rendering CAD and BIM files in a browser using Three.js
211 lines (175 loc) • 7.68 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 {
EdgesGeometry,
LineBasicMaterial,
MeshPhongMaterial,
Object3D,
RGBAFormat,
UnsignedByteType,
Vector2,
WebGLRenderTarget,
} from "three";
import { LineSegmentsGeometry } from "three/examples/jsm/lines/LineSegmentsGeometry.js";
import { Wireframe } from "three/examples/jsm/lines/Wireframe.js";
import { LineMaterial } from "three/examples/jsm/lines/LineMaterial.js";
import { IComponent, ResizeEvent } from "@inweb/viewer-core";
import { Viewer } from "../Viewer";
import { HighlighterUtils } from "./HighlighterUtils";
export class HighlighterComponent implements IComponent {
protected viewer: Viewer;
public renderTarget: WebGLRenderTarget;
public facesMaterial: MeshPhongMaterial;
public edgesMaterial: LineMaterial;
public lineMaterial: LineBasicMaterial;
public lineGlowMaterial: LineMaterial;
constructor(viewer: Viewer) {
this.viewer = viewer;
const gl2 = viewer.canvas.getContext("webgl2");
if (gl2) {
const size = viewer.renderer.getSize(new Vector2());
this.renderTarget = new WebGLRenderTarget(size.x, size.y, {
format: RGBAFormat,
stencilBuffer: false,
samples: 4,
type: UnsignedByteType,
});
}
this.viewer.addEventListener("databasechunk", this.geometryEnd);
this.viewer.addEventListener("optionschange", this.optionsChange);
this.viewer.addEventListener("resize", this.viewerResize);
this.geometryEnd();
}
dispose() {
this.viewer.removeEventListener("databasechunk", this.geometryEnd);
this.viewer.removeEventListener("optionschange", this.optionsChange);
this.viewer.removeEventListener("resize", this.viewerResize);
}
highlight(objects: Object3D | Object3D[]) {
const { edgesVisibility } = this.viewer.options;
if (!Array.isArray(objects)) objects = [objects];
if (!objects.length) return;
objects
.filter((object) => !object.userData.isEdge) // <- filtering server generated edges
.forEach((object: any) => {
if (object.isHighlighted) return;
if (object.isLine || object.isLineSegments) {
const positions = object.geometry.attributes.position.array;
const indices = object.geometry.index ? object.geometry.index.array : null;
const lineGeometry = indices
? HighlighterUtils.fromIndexedLine(positions, indices)
: HighlighterUtils.fromNonIndexedLine(positions, object.isLineSegments);
const wireframe = new Wireframe(lineGeometry, this.lineGlowMaterial);
wireframe.position.copy(object.position);
wireframe.rotation.copy(object.rotation);
wireframe.scale.copy(object.scale);
wireframe.visible = edgesVisibility;
object.parent.add(wireframe);
object.userData.highlightWireframe = wireframe;
object.userData.originalMaterial = object.material;
object.material = this.lineMaterial;
object.isHighlighted = true;
} else if (object.isMesh) {
const edgesGeometry = new EdgesGeometry(object.geometry, 89);
const lineGeometry = new LineSegmentsGeometry().fromEdgesGeometry(edgesGeometry);
const wireframe = new Wireframe(lineGeometry, this.edgesMaterial);
wireframe.position.copy(object.position);
wireframe.rotation.copy(object.rotation);
wireframe.scale.copy(object.scale);
wireframe.visible = edgesVisibility;
object.parent.add(wireframe);
object.userData.highlightWireframe = wireframe;
object.userData.originalMaterial = object.material;
object.material = this.facesMaterial;
object.isHighlighted = true;
}
});
}
unhighlight(objects: Object3D | Object3D[]) {
if (!Array.isArray(objects)) objects = [objects];
if (!objects.length) return;
objects.forEach((object: any) => {
if (!object.isHighlighted) return;
object.isHighlighted = false;
object.material = object.userData.originalMaterial;
object.userData.highlightWireframe.removeFromParent();
delete object.userData.originalMaterial;
delete object.userData.highlightWireframe;
});
}
geometryEnd = () => {
this.facesMaterial = new MeshPhongMaterial({
transparent: true,
specular: 0x222222,
shininess: 10,
reflectivity: 0.05,
polygonOffset: true,
polygonOffsetFactor: 1,
polygonOffsetUnits: 1,
});
this.edgesMaterial = new LineMaterial({
linewidth: 1.5,
resolution: new Vector2(window.innerWidth, window.innerHeight),
});
this.lineMaterial = new LineBasicMaterial({
transparent: true,
depthTest: true,
depthWrite: true,
});
this.lineGlowMaterial = new LineMaterial({
linewidth: 1.5,
transparent: true,
opacity: 0.8,
resolution: new Vector2(window.innerWidth, window.innerHeight),
});
this.syncHighlightColors();
};
optionsChange = () => {
this.syncHighlightColors();
this.viewer.update();
};
syncHighlightColors() {
const { facesColor, facesTransparancy, facesOverlap } = this.viewer.options;
const { edgesColor, edgesVisibility, edgesOverlap } = this.viewer.options;
this.facesMaterial.color.setRGB(facesColor.r / 255, facesColor.g / 255, facesColor.b / 255);
this.facesMaterial.opacity = (255 - facesTransparancy) / 255;
this.facesMaterial.depthTest = !facesOverlap;
this.facesMaterial.depthWrite = !facesOverlap;
this.edgesMaterial.color.setRGB(edgesColor.r / 255, edgesColor.g / 255, edgesColor.b / 255);
this.edgesMaterial.depthTest = !edgesOverlap;
this.edgesMaterial.depthWrite = !edgesOverlap;
this.lineMaterial.color.setRGB(facesColor.r / 255, facesColor.g / 255, facesColor.b / 255);
this.lineGlowMaterial.color.setRGB(edgesColor.r / 255, edgesColor.g / 255, edgesColor.b / 255);
this.lineGlowMaterial.depthTest = !edgesOverlap;
this.lineGlowMaterial.depthWrite = !edgesOverlap;
this.viewer.selected.forEach((selected) => {
const wireframe = selected.userData.highlightWireframe;
if (wireframe) wireframe.visible = edgesVisibility;
});
}
viewerResize(event: ResizeEvent) {
this.renderTarget?.setSize(event.width, event.height);
this.edgesMaterial?.resolution.set(event.width, event.height);
this.lineGlowMaterial?.resolution.set(event.width, event.height);
}
}