UNPKG

@inweb/viewer-three

Version:

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

174 lines (137 loc) 6.33 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 { Intersection, Object3D, Raycaster, Vector2 } from "three"; import type { IComponent } from "@inweb/viewer-core"; import type { Viewer } from "../Viewer"; import type { IModelImpl } from "../models/IModelImpl"; import type { HighlighterComponent } from "./HighlighterComponent"; export class SelectionComponent implements IComponent { protected viewer: Viewer; protected raycaster: Raycaster; protected downPosition: Vector2; protected highlighter: HighlighterComponent; constructor(viewer: Viewer) { this.viewer = viewer; this.raycaster = new Raycaster(); this.downPosition = new Vector2(); this.viewer.addEventListener("pointerdown", this.onPointerDown); this.viewer.addEventListener("pointerup", this.onPointerUp); this.viewer.addEventListener("dblclick", this.onDoubleClick); this.viewer.addEventListener("initialize", this.initHighlighter); } dispose() { this.viewer.removeEventListener("pointerdown", this.onPointerDown); this.viewer.removeEventListener("pointerup", this.onPointerUp); this.viewer.removeEventListener("dblclick", this.onDoubleClick); this.viewer.removeEventListener("initialize", this.initHighlighter); } onPointerDown = (event: PointerEvent) => { if (!event.isPrimary || event.button !== 0) return; this.getMousePosition(event, this.downPosition); }; onPointerUp = (event: PointerEvent) => { if (!event.isPrimary) return; const upPosition = this.getMousePosition(event, new Vector2()); if (upPosition.distanceTo(this.downPosition) !== 0) return; let intersections = []; this.viewer.models.forEach((model) => { const objects = model.getVisibleObjects(); const intersects = this.getPointerIntersects(upPosition, objects); if (intersects.length > 0) intersections.push({ ...intersects[0], model }); }); intersections = intersections.sort((a, b) => a.distance - b.distance); if (!event.shiftKey) this.clearSelection(); if (intersections.length > 0) { const model = intersections[0].model; const handles = model.getHandlesByObjects(intersections[0].object); const objects = model.getObjectsByHandles(handles); if (!event.shiftKey) this.select(objects, model); else this.toggleSelection(objects, model); } this.viewer.update(); this.viewer.emitEvent({ type: "select", data: undefined, handles: this.viewer.getSelected() }); }; onDoubleClick = (event: MouseEvent) => { if (event.button !== 0) return; this.viewer.executeCommand("zoomToSelected"); }; getMousePosition(event: MouseEvent, target: Vector2): Vector2 { return target.set(event.clientX, event.clientY); } getPointerIntersects(mouse: Vector2, objects: Object3D[]): Array<Intersection<Object3D>> { const rect = this.viewer.canvas.getBoundingClientRect(); const x = ((mouse.x - rect.left) / rect.width) * 2 - 1; const y = (-(mouse.y - rect.top) / rect.height) * 2 + 1; const coords = new Vector2(x, y); this.raycaster.setFromCamera(coords, this.viewer.camera); this.raycaster.params = this.raycaster.params = { Mesh: {}, Line: { threshold: 0.25 }, Line2: { threshold: 0.25 }, LOD: {}, Points: { threshold: 0.1 }, Sprite: {}, }; return this.raycaster.intersectObjects(objects, false); } select(objects: Object3D | Object3D[], model?: IModelImpl) { if (!model) { this.viewer.models.forEach((model) => this.select(objects, model)); return; } if (!Array.isArray(objects)) objects = [objects]; if (!objects.length) return; model.showOriginalObjects(objects); this.highlighter.highlight(objects); objects.forEach((object: any) => this.viewer.selected.push(object)); objects.forEach((object: any) => (object.isSelected = true)); } deselect(objects: Object3D | Object3D[], model?: IModelImpl) { if (!model) { this.viewer.models.forEach((model) => this.select(objects, model)); return; } if (!Array.isArray(objects)) objects = [objects]; if (!objects.length) return; this.highlighter.unhighlight(objects); model.hideOriginalObjects(objects); this.viewer.selected = this.viewer.selected.filter((x) => !objects.includes(x)); objects.forEach((object: any) => (object.isSelected = false)); } toggleSelection(objects: Object3D | Object3D[], model?: IModelImpl) { if (!Array.isArray(objects)) objects = [objects]; if (!objects.length) return; if ((objects[0] as any).isSelected) this.deselect(objects, model); else this.select(objects, model); } clearSelection() { if (!this.viewer.selected.length) return; this.highlighter.unhighlight(this.viewer.selected); this.viewer.models.forEach((model) => model.hideOriginalObjects(this.viewer.selected)); this.viewer.selected.forEach((object: any) => (object.isSelected = false)); this.viewer.selected.length = 0; } initHighlighter = () => { this.highlighter = this.viewer.getComponent("HighlighterComponent") as HighlighterComponent; }; }