src/plugins/DistanceMeasurementsPlugin/DistanceMeasurementsMouseControl.js
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();
}
}