UNPKG

@xeokit/xeokit-sdk

Version:

3D BIM IFC Viewer SDK for AEC engineering applications. Open Source JavaScript Toolkit based on pure WebGL for top performance, real-world coordinates and full double precision

400 lines (352 loc) 16.6 kB
import {math} from "../../viewer/scene/math/math.js"; import {transformToNode} from "../lib/ui/index.js"; import {DistanceMeasurementsControl} from "./DistanceMeasurementsControl.js"; const MOUSE_FIRST_CLICK_EXPECTED = 0; const MOUSE_SECOND_CLICK_EXPECTED = 1; /** * Creates {@link DistanceMeasurement}s in a {@link DistanceMeasurementsPlugin} from mouse input. * * ## Usage * * [[Run example](/examples/measurement/#distance_createWithMouse_snapping)] * * ````javascript * import {Viewer, XKTLoaderPlugin, DistanceMeasurementsPlugin, DistanceMeasurementsMouseControl, PointerLens} from "xeokit-sdk.es.js"; * * const viewer = new Viewer({ * canvasId: "myCanvas", * }); * * viewer.camera.eye = [-3.93, 2.85, 27.01]; * viewer.camera.look = [4.40, 3.72, 8.89]; * viewer.camera.up = [-0.01, 0.99, 0.039]; * * const xktLoader = new XKTLoaderPlugin(viewer); * * const sceneModel = xktLoader.load({ * id: "myModel", * src: "Duplex.xkt" * }); * * const distanceMeasurements = new DistanceMeasurementsPlugin(viewer); * * const distanceMeasurementsControl = new DistanceMeasurementsMouseControl(DistanceMeasurements, { * pointerLens: new PointerLens(viewer) * }) * * distanceMeasurementsControl.snapping = true; * * distanceMeasurementsControl.activate(); * ```` */ export class DistanceMeasurementsMouseControl extends DistanceMeasurementsControl { /** * Creates a DistanceMeasurementsMouseControl bound to the given DistanceMeasurementsPlugin. * * @param {DistanceMeasurementsPlugin} distanceMeasurementsPlugin The AngleMeasurementsPlugin to control. * @param [cfg] Configuration * @param {function} [cfg.canvasToPagePos] Optional function to map canvas-space coordinates to page coordinates. * @param {PointerLens} [cfg.pointerLens] A PointerLens to use to provide a magnified view of the cursor when snapping is enabled. * @param {boolean} [cfg.snapping=true] Whether to initially enable snap-to-vertex and snap-to-edge for this DistanceMeasurementsMouseControl. */ constructor(distanceMeasurementsPlugin, cfg = {}) { super(distanceMeasurementsPlugin.viewer.scene); this._canvasToPagePos = cfg.canvasToPagePos; this.pointerLens = cfg.pointerLens; this._active = false; this._currentDistanceMeasurement = null; this._currentDistanceMeasurementInitState = { wireVisible: null, axisVisible: null, xAxisVisible: null, yaxisVisible: null, zAxisVisible: null, targetVisible: null, } this._initMarkerDiv() this._snapping = cfg.snapping !== false; this._mouseState = MOUSE_FIRST_CLICK_EXPECTED; this._attachPlugin(distanceMeasurementsPlugin, cfg); } _initMarkerDiv() { const markerDiv = document.createElement('div'); const canvas = this.scene.canvas.canvas; canvas.parentNode.insertBefore(markerDiv, canvas); markerDiv.style.background = "black"; markerDiv.style.border = "2px solid blue"; markerDiv.style.borderRadius = "10px"; markerDiv.style.width = "5px"; markerDiv.style.height = "5px"; markerDiv.style.top = "-200px"; markerDiv.style.left = "-200px"; markerDiv.style.margin = "0 0"; markerDiv.style.zIndex = "100"; markerDiv.style.position = "absolute"; markerDiv.style.pointerEvents = "none"; this._markerDiv = markerDiv; } _destroyMarkerDiv() { if (this._markerDiv) { this._markerDiv.parentNode.removeChild(this._markerDiv); this._markerDiv = null; } } _attachPlugin(distanceMeasurementsPlugin, cfg = {}) { /** * The {@link DistanceMeasurementsPlugin} that owns this DistanceMeasurementsMouseControl. * @type {DistanceMeasurementsPlugin} */ this.distanceMeasurementsPlugin = distanceMeasurementsPlugin; /** * The {@link DistanceMeasurementsPlugin} that owns this DistanceMeasurementsMouseControl. * @type {DistanceMeasurementsPlugin} */ this.plugin = distanceMeasurementsPlugin; } /** * Gets if this DistanceMeasurementsMouseControl is currently active, where it is responding to input. * * @returns {boolean} True if this DistanceMeasurementsMouseControl is active. */ get active() { return this._active; } /** * Sets whether snap-to-vertex and snap-to-edge are enabled for this DistanceMeasurementsMouseControl. * * This is `true` by default. * * @param snapping */ set snapping(snapping) { this._snapping = snapping; } /** * Gets whether snap-to-vertex and snap-to-edge are enabled for this DistanceMeasurementsMouseControl. * * This is `true` by default. * @returns {*} */ get snapping() { return this._snapping; } /** * Activates this DistanceMeasurementsMouseControl, ready to respond to input. */ activate() { if (this._active) { return; } if (!this._markerDiv) { this._initMarkerDiv() } this.fire("activated", true); const distanceMeasurementsPlugin = this.distanceMeasurementsPlugin; const scene = this.scene; const cameraControl = distanceMeasurementsPlugin.viewer.cameraControl; const canvas = scene.canvas.canvas; const input = scene.input; let mouseHovering = false; const pointerWorldPos = math.vec3(); const pointerCanvasPos = math.vec2(); let pointerDownCanvasX; let pointerDownCanvasY; const clickTolerance = 20; let hoveredEntity = null; this._mouseState = MOUSE_FIRST_CLICK_EXPECTED; const pagePos = math.vec2(); const hoverOn = event => { const canvasPos = event.snappedCanvasPos || event.canvasPos; mouseHovering = true; pointerWorldPos.set(event.worldPos); pointerCanvasPos.set(event.canvasPos); if (this._mouseState === MOUSE_FIRST_CLICK_EXPECTED) { if (this._canvasToPagePos) { this._canvasToPagePos(canvas, canvasPos, pagePos); this._markerDiv.style.left = `${pagePos[0] - 5}px`; this._markerDiv.style.top = `${pagePos[1] - 5}px`; } else { const markerPos = math.vec2(canvasPos); transformToNode(canvas, this._markerDiv.parentNode, markerPos); this._markerDiv.style.left = `${markerPos[0] - 5}px`; this._markerDiv.style.top = `${markerPos[1] - 5}px`; } this._markerDiv.style.background = "pink"; if (event.snappedToVertex || event.snappedToEdge) { if (this.pointerLens) { this.pointerLens.visible = true; this.pointerLens.canvasPos = event.canvasPos; this.pointerLens.snappedCanvasPos = event.snappedCanvasPos || event.canvasPos; this.pointerLens.snapped = true; } this._markerDiv.style.background = "greenyellow"; this._markerDiv.style.border = "2px solid green"; } else { if (this.pointerLens) { this.pointerLens.visible = true; this.pointerLens.canvasPos = event.canvasPos; this.pointerLens.snappedCanvasPos = event.canvasPos; this.pointerLens.snapped = false; } this._markerDiv.style.background = "pink"; this._markerDiv.style.border = "2px solid red"; } hoveredEntity = event.entity; } else { this._markerDiv.style.left = `-10000px`; this._markerDiv.style.top = `-10000px`; } canvas.style.cursor = "pointer"; if (this._currentDistanceMeasurement) { this._currentDistanceMeasurement.wireVisible = this._currentDistanceMeasurementInitState.wireVisible; this._currentDistanceMeasurement.axisVisible = this._currentDistanceMeasurementInitState.axisVisible && this.distanceMeasurementsPlugin.defaultAxisVisible; this._currentDistanceMeasurement.xAxisVisible = this._currentDistanceMeasurementInitState.xAxisVisible && this.distanceMeasurementsPlugin.defaultXAxisVisible; this._currentDistanceMeasurement.yAxisVisible = this._currentDistanceMeasurementInitState.yAxisVisible && this.distanceMeasurementsPlugin.defaultYAxisVisible; this._currentDistanceMeasurement.zAxisVisible = this._currentDistanceMeasurementInitState.zAxisVisible && this.distanceMeasurementsPlugin.defaultZAxisVisible; this._currentDistanceMeasurement.targetVisible = this._currentDistanceMeasurementInitState.targetVisible; this._currentDistanceMeasurement.target.worldPos = pointerWorldPos.slice(); this._markerDiv.style.left = `-10000px`; this._markerDiv.style.top = `-10000px`; } }; this._onHoverSnapOrSurface = cameraControl.on("hoverSnapOrSurface", e => { if (this._snapping) hoverOn(e); }); this._onHoverSurface = cameraControl.on("hoverSurface", e => { if (! this._snapping) hoverOn(e); }); canvas.addEventListener('mousedown', this._onMouseDown = (e) => { if (e.which !== 1) { return; } pointerDownCanvasX = e.clientX; pointerDownCanvasY = e.clientY; }); canvas.addEventListener("mouseup", this._onMouseUp = (e) => { if (e.which !== 1) { return; } if (e.clientX > pointerDownCanvasX + clickTolerance || e.clientX < pointerDownCanvasX - clickTolerance || e.clientY > pointerDownCanvasY + clickTolerance || e.clientY < pointerDownCanvasY - clickTolerance) { return; } if (this._currentDistanceMeasurement) { if (mouseHovering) { this._currentDistanceMeasurement.target.entity = hoveredEntity; hoveredEntity = null; this._currentDistanceMeasurement.clickable = true; this.distanceMeasurementsPlugin.fire("measurementEnd", this._currentDistanceMeasurement); this._currentDistanceMeasurement = null; } else { this._currentDistanceMeasurement.destroy(); this.distanceMeasurementsPlugin.fire("measurementCancel", this._currentDistanceMeasurement); this._currentDistanceMeasurement = null; hoveredEntity = null; } } else { if (mouseHovering) { this._currentDistanceMeasurement = distanceMeasurementsPlugin.createMeasurement({ id: math.createUUID(), origin: { worldPos: pointerWorldPos.slice(), entity: hoveredEntity }, target: { worldPos: pointerWorldPos.slice(), entity: hoveredEntity }, approximate: true }); this._currentDistanceMeasurementInitState.axisVisible = this._currentDistanceMeasurement.axisVisible && this.distanceMeasurementsPlugin.defaultAxisVisible; this._currentDistanceMeasurementInitState.xAxisVisible = this._currentDistanceMeasurement.xAxisVisible && this.distanceMeasurementsPlugin.defaultXAxisVisible; this._currentDistanceMeasurementInitState.yAxisVisible = this._currentDistanceMeasurement.yAxisVisible && this.distanceMeasurementsPlugin.defaultYAxisVisible; this._currentDistanceMeasurementInitState.zAxisVisible = this._currentDistanceMeasurement.zAxisVisible && this.distanceMeasurementsPlugin.defaultZAxisVisible; this._currentDistanceMeasurementInitState.wireVisible = this._currentDistanceMeasurement.wireVisible; this._currentDistanceMeasurementInitState.targetVisible = this._currentDistanceMeasurement.targetVisible; this._currentDistanceMeasurement.clickable = false; this._currentDistanceMeasurement.origin.entity = hoveredEntity; hoveredEntity = null; this.distanceMeasurementsPlugin.fire("measurementStart", this._currentDistanceMeasurement); } } }); const hoverOff = event => { if (this.pointerLens) { this.pointerLens.visible = true; this.pointerLens.canvasPos = event.canvasPos; this.pointerLens.snappedCanvasPos = event.snappedCanvasPos || event.canvasPos; } mouseHovering = false; this._markerDiv.style.left = `-100px`; this._markerDiv.style.top = `-100px`; if (this._currentDistanceMeasurement) { this._currentDistanceMeasurement.wireVisible = false; this._currentDistanceMeasurement.targetVisible = false; this._currentDistanceMeasurement.axisVisible = false; } canvas.style.cursor = "default"; }; this._onHoverSnapOrSurfaceOff = cameraControl.on("hoverSnapOrSurfaceOff", e => { if (this._snapping) hoverOff(e); }); this._onHoverOff = cameraControl.on("hoverOff", e => { if (! this._snapping) hoverOff(e); }); this._active = true; } /** * Deactivates this DistanceMeasurementsMouseControl, making it unresponsive to input. * * Destroys any {@link DistanceMeasurement} under construction by this DistanceMeasurementsMouseControl. */ deactivate() { if (!this._active) { return; } this.fire("activated", false); if (this.pointerLens) { this.pointerLens.visible = false; } this.reset(); const canvas = this.scene.canvas.canvas; canvas.removeEventListener("mousedown", this._onMouseDown); canvas.removeEventListener("mouseup", this._onMouseUp); const cameraControl = this.distanceMeasurementsPlugin.viewer.cameraControl; cameraControl.off(this._onHoverSnapOrSurface); cameraControl.off(this._onHoverSurface); cameraControl.off(this._onHoverSnapOrSurfaceOff); cameraControl.off(this._onHoverOff); this._active = false; } /** * Resets this DistanceMeasurementsMouseControl. * * Destroys any {@link DistanceMeasurement} under construction by this DistanceMeasurementsMouseControl. * * Does nothing if the DistanceMeasurementsMouseControl is not active. */ reset() { if (!this._active) { return; } this._destroyMarkerDiv() this._initMarkerDiv() if (this._currentDistanceMeasurement) { this.distanceMeasurementsPlugin.fire("measurementCancel", this._currentDistanceMeasurement); this._currentDistanceMeasurement.destroy(); this._currentDistanceMeasurement = null; } this._mouseState = MOUSE_FIRST_CLICK_EXPECTED; } /** * Gets the {@link DistanceMeasurement} under construction by this DistanceMeasurementsMouseControl, if any. * * @returns {null|DistanceMeasurement} */ get currentMeasurement() { return this._currentDistanceMeasurement; } /** * Destroys this DistanceMeasurementsMouseControl. * * Destroys any {@link DistanceMeasurement} under construction by this DistanceMeasurementsMouseControl. */ destroy() { this.deactivate(); super.destroy(); } }