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

692 lines (605 loc) 21.7 kB
import {Dot3D, Label3D, Wire3D} from "../lib/ui/index.js"; import {math} from "../../viewer/scene/math/math.js"; import {Component} from "../../viewer/scene/Component.js"; const tmpVec3a = math.vec3(); const tmpVec3b = math.vec3(); const tmpVec3c = math.vec3(); const tmpVec4a = math.vec4(); const tmpVec4b = math.vec4(); const tmpVec4c = math.vec4(); /** * @desc Measures the distance between two 3D points. * * See {@link DistanceMeasurementsPlugin} for more info. */ class DistanceMeasurement extends Component { /** * @private */ constructor(plugin, cfg = {}) { const scene = plugin.viewer.scene; super(scene, cfg); /** * The {@link DistanceMeasurementsPlugin} that owns this DistanceMeasurement. * @type {DistanceMeasurementsPlugin} */ this.plugin = plugin; const container = cfg.container; if (!container) { throw "config missing: container"; } this._color = cfg.color || plugin.defaultColor; const channel = function(v, defaultIfUndefined) { const listeners = [ ]; let value = v !== undefined ? Boolean(v) : defaultIfUndefined; return { reg: (l) => listeners.push(l), get: () => value, set: (v) => { value = v !== undefined ? Boolean(v) : defaultIfUndefined; listeners.forEach(l => l(value)); } }; }; this._visible = channel(cfg.visible, plugin.defaultVisible); this._originVisible = channel(cfg.originVisible, plugin.defaultOriginVisible); this._targetVisible = channel(cfg.targetVisible, plugin.defaultTargetVisible); this._axisVisible = channel(cfg.axisVisible, plugin.defaultAxisVisible); this._xAxisVisible = channel(cfg.xAxisVisible, plugin.defaultAxisVisible); this._yAxisVisible = channel(cfg.yAxisVisible, plugin.defaultAxisVisible); this._zAxisVisible = channel(cfg.zAxisVisible, plugin.defaultAxisVisible); this._axisEnabled = channel(true, plugin.defaultAxisVisible); this._wireVisible = channel(cfg.wireVisible, plugin.defaultWireVisible); this._xLabelEnabled = channel(cfg.xLabelEnabled, plugin.defaultXLabelEnabled); this._yLabelEnabled = channel(cfg.yLabelEnabled, plugin.defaultYLabelEnabled); this._zLabelEnabled = channel(cfg.zLabelEnabled, plugin.defaultZLabelEnabled); this._lengthLabelEnabled = channel(cfg.lengthLabelEnabled, plugin.defaultLengthLabelEnabled); this._labelsVisible = channel(cfg.labelsVisible, plugin.defaultLabelsVisible); this._clickable = channel(false, false); this._labelsOnWires = channel(cfg.labelsOnWires, plugin.defaultLabelsOnWires); this._useRotationAdjustment = channel(cfg.useRotationAdjustment, plugin.useRotationAdjustment); this._axesBasis = math.identityMat4(); this.approximate = cfg.approximate; const canvas = scene.canvas.canvas; const onMouseOver = cfg.onMouseOver ? (event) => { cfg.onMouseOver(event, this); canvas.dispatchEvent(new MouseEvent('mouseover', event)); } : null; const onMouseLeave = cfg.onMouseLeave ? (event) => { cfg.onMouseLeave(event, this); canvas.dispatchEvent(new MouseEvent('mouseleave', event)); } : null; const onContextMenu = cfg.onContextMenu ? (event) => { cfg.onContextMenu(event, this); } : null; const onMouseDown = (event) => canvas.dispatchEvent(new MouseEvent('mousedown', event)); const onMouseUp = (event) => canvas.dispatchEvent(new MouseEvent('mouseup', event)); const onMouseMove = (event) => canvas.dispatchEvent(new MouseEvent('mousemove', event)); const onMouseWheel = (event) => canvas.dispatchEvent(new WheelEvent('wheel', event)); this._cleanups = [ ]; [ "units", "scale" ].forEach(evt => { const handler = scene.metrics.on("units", () => this._update()); this._cleanups.push(() => scene.metrics.off(handler)); }); this._drawables = [ ]; const registerDrawable = (drawable, visibilityChannels) => { const updateVisibility = () => drawable.setVisible(visibilityChannels.every(ch => ch.get())); visibilityChannels.forEach(ch => ch.reg(updateVisibility)); this._drawables.push(drawable); this._cleanups.push(() => drawable.destroy()); }; const makeWire = (color, thickness, visibilityChannels) => { const wire = new Wire3D(scene, container, { color: color, thickness: thickness, thicknessClickable: 6, zIndex: plugin.zIndex !== undefined ? plugin.zIndex + 1 : undefined, onMouseOver, onMouseLeave, onMouseWheel, onMouseDown, onMouseUp, onMouseMove, onContextMenu }); registerDrawable(wire, visibilityChannels); return { setEnds: (p0, p1) => wire.setEnds(p0, p1), setColor: value => wire.setColor(value) }; }; this._lengthWire = makeWire(this._color, 2, [ this._visible, this._wireVisible ]); this._xAxisWire = makeWire("red", 1, [ this._visible, this._axisEnabled, this._axisVisible, this._xAxisVisible ]); this._yAxisWire = makeWire("green", 1, [ this._visible, this._axisEnabled, this._axisVisible, this._yAxisVisible ]); this._zAxisWire = makeWire("blue", 1, [ this._visible, this._axisEnabled, this._axisVisible, this._zAxisVisible ]); const makeLabel = (color, zIndexOffset, visibilityChannels) => { const label = new Label3D(scene, container, { fillColor: color, zIndex: plugin.zIndex !== undefined ? plugin.zIndex + zIndexOffset : undefined, onMouseOver, onMouseLeave, onMouseWheel, onMouseDown, onMouseUp, onMouseMove, onContextMenu }); registerDrawable(label, visibilityChannels); return { setFillColor: value => label.setFillColor(value), setPosOnWire: (p0, p1, offset, labelMinAxisLength) => label.setPosOnWire(p0, p1, offset, labelMinAxisLength), setPosBetween: (p0, p1, p2) => label.setPosBetween(p0, p1, p2), setText: str => label.setText(str.replace(/ /g, " ")) }; }; this._lengthLabel = makeLabel(this._color, 4, [ this._visible, this._wireVisible, this._labelsVisible, this._clickable, this._axisEnabled, this._lengthLabelEnabled ]); this._xAxisLabel = makeLabel("red", 3, [ this._visible, this._axisEnabled, this._axisVisible, this._xAxisVisible, this._labelsVisible, this._clickable, this._xLabelEnabled ]); this._yAxisLabel = makeLabel("green", 3, [ this._visible, this._axisEnabled, this._axisVisible, this._yAxisVisible, this._labelsVisible, this._clickable, this._yLabelEnabled ]); this._zAxisLabel = makeLabel("blue", 3, [ this._visible, this._axisEnabled, this._axisVisible, this._zAxisVisible, this._labelsVisible, this._clickable, this._zLabelEnabled ]); const makeDot = (cfg, visibilityChannels) => { const dot = new Dot3D(scene, cfg, container, { fillColor: this._color, zIndex: plugin.zIndex !== undefined ? plugin.zIndex + 2 : undefined, onMouseOver, onMouseLeave, onMouseWheel, onMouseDown, onMouseUp, onMouseMove, onContextMenu }); dot.on("worldPos", () => this._update()); registerDrawable(dot, visibilityChannels); return dot; }; this._originDot = makeDot(cfg.origin, [ this._visible, this._originVisible ]); this._targetDot = makeDot(cfg.target, [ this._visible, this._targetVisible ]); this._update(); } _update() { if (! this._targetDot) { return; } const p0 = this._originDot.worldPos; const p1 = this._targetDot.worldPos; const axesBasis = this._axesBasis; const delta = math.subVec3(p1, p0, tmpVec3a); const factors = math.transformVec3(axesBasis, delta, delta); const measurementOrientationVertical = this._useRotationAdjustment.get() && Math.abs(delta[1]) > 0; const setWireCoordinates = (xEnd, zStart) => { const metrics = this.plugin.viewer.scene.metrics; const scale = metrics.scale; const unit = metrics.unitsInfo[metrics.units].abbrev; const setAxisLabelCoords = (label, a, b, offsetIdx) => { if (this._labelsOnWires.get()) { label.setPosOnWire(a, b, 0, this.plugin.labelMinAxisLength); } else { label.setPosOnWire(p0, p1, offsetIdx * 35, 0); } }; const unitStr = len => (this._approximate ? " ~ " : " = ") + len.toFixed(2) + unit; this._xAxisWire.setEnds(p0, xEnd); setAxisLabelCoords(this._xAxisLabel, p0, xEnd, 1); this._xAxisLabel.setText("X" + unitStr(math.distVec3(p0, xEnd) * scale)); this._yAxisWire.setEnds(xEnd, zStart); setAxisLabelCoords(this._yAxisLabel, xEnd, zStart, 2); this._yAxisLabel.setText("Y" + unitStr(math.distVec3(xEnd, zStart) * scale)); this._zAxisWire.setEnds(zStart, p1); setAxisLabelCoords(this._zAxisLabel, zStart, p1, 3); this._zAxisLabel.setText((measurementOrientationVertical ? "" : "Z") + unitStr(math.distVec3(zStart, p1) * scale)); this._lengthWire.setEnds(p0, p1); setAxisLabelCoords(this._lengthLabel, p0, p1, 0); this._length = math.distVec3(p0, p1) * scale; this._lengthLabel.setText(unitStr(this._length)); }; if (measurementOrientationVertical) { tmpVec3c[0] = p0[0]; tmpVec3c[1] = p1[1]; tmpVec3c[2] = p0[2]; setWireCoordinates(p0, tmpVec3c); } else { tmpVec3b[0] = p0[0] + axesBasis[0] * factors[0]; tmpVec3b[1] = p0[1] + axesBasis[4] * factors[0]; tmpVec3b[2] = p0[2] + axesBasis[8] * factors[0]; tmpVec3c[0] = tmpVec3b[0] + axesBasis[1] * factors[1]; tmpVec3c[1] = tmpVec3b[1] + axesBasis[5] * factors[1]; tmpVec3c[2] = tmpVec3b[2] + axesBasis[9] * factors[1]; setWireCoordinates(tmpVec3b, tmpVec3c); } } /** * Sets the axes basis for the measurement. * * The value is a 4x4 matrix where each column-vector defines an axis and must have unit length. * * This is the ```identity``` matrix by default, meaning the measurement axes are the same as the world axes. * * @param {number[]} value */ set axesBasis(value) { this._axesBasis.set(value); this._update(); } /** * Gets the axes basis for the measurement. * * The value is a 4x4 matrix where each column-vector defines an axis and must have unit length. * * This is the ```identity``` matrix by default, meaning the measurement axes are the same as the world axes. * * @type {number[]} */ get axesBasis() { return this._axesBasis; } /** * Sets whether this DistanceMeasurement indicates that its measurement is approximate. * * This is ````true```` by default. * * @type {Boolean} */ set approximate(approximate) { approximate = approximate !== false; if (this._approximate === approximate) { return; } this._approximate = approximate; this._update(); } /** * Gets whether this DistanceMeasurement indicates that its measurement is approximate. * * This is ````true```` by default. * * @type {Boolean} */ get approximate() { return this._approximate; } /** * Gets the origin {@link Dot3D}. * * @type {Dot3D} */ get origin() { return this._originDot; } /** * Gets the target {@link Dot3D}. * * @type {Dot3D} */ get target() { return this._targetDot; } /** * Gets the World-space direct point-to-point distance between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target}. * * @type {Number} */ get length() { return this._length; } get color() { return this._color; } set color(value) { this._color = value; this._originDot.setFillColor(value); this._targetDot.setFillColor(value); this._lengthWire.setColor(value); this._lengthLabel.setFillColor(value); } /** * Sets whether this DistanceMeasurement is visible or not. * * @type {Boolean} */ set visible(value) { this._visible.set(value); } /** * Gets whether this DistanceMeasurement is visible or not. * * @type {Boolean} */ get visible() { return this._visible.get(); } /** * Sets if the origin {@link Dot3D} is visible. * * @type {Boolean} */ set originVisible(value) { this._originVisible.set(value); } /** * Gets if the origin {@link Dot3D} is visible. * * @type {Boolean} */ get originVisible() { return this._originVisible.get(); } /** * Sets if the target {@link Dot3D} is visible. * * @type {Boolean} */ set targetVisible(value) { this._targetVisible.set(value); } /** * Gets if the target {@link Dot3D} is visible. * * @type {Boolean} */ get targetVisible() { return this._targetVisible.get(); } /** * Sets if the measurement is adjusted based on rotation * * @type {Boolean} */ set useRotationAdjustment(value) { this._useRotationAdjustment.set(value); this._update(); } /** * Gets if the measurement is adjusted based on rotation * * @type {Boolean} */ get useRotationAdjustment() { return this._useRotationAdjustment.get(); } /** * Sets if the axis-aligned wires between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} are enabled. * * Wires are only shown if enabled and visible. * * @type {Boolean} */ set axisEnabled(value) { this._axisEnabled.set(value); } /** * Gets if the axis-aligned wires between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} are enabled. * * Wires are only shown if enabled and visible. * * @type {Boolean} */ get axisEnabled() { return this._axisEnabled.get(); } /** * Sets if the axis-aligned wires between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} are visible. * * Wires are only shown if enabled and visible. * * @type {Boolean} */ set axisVisible(value) { this._axisVisible.set(value); } /** * Gets if the axis-aligned wires between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} are visible. * * Wires are only shown if enabled and visible. * * @type {Boolean} */ get axisVisible() { return this._axisVisible.get(); } /** * Sets if the X-axis-aligned wire between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} is visible. * * Wires are only shown if enabled and visible. * * @type {Boolean} */ set xAxisVisible(value) { this._xAxisVisible.set(value); } /** * Gets if the X-axis-aligned wires between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} are visible. * * Wires are only shown if enabled and visible. * * @type {Boolean} */ get xAxisVisible() { return this._xAxisVisible.get(); } /** * Sets if the Y-axis-aligned wire between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} is visible. * * Wires are only shown if enabled and visible. * * @type {Boolean} */ set yAxisVisible(value) { this._yAxisVisible.set(value); } /** * Gets if the Y-axis-aligned wires between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} are visible. * * Wires are only shown if enabled and visible. * * @type {Boolean} */ get yAxisVisible() { return this._yAxisVisible.get(); } /** * Sets if the Z-axis-aligned wire between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} is visible. * * Wires are only shown if enabled and visible. * * @type {Boolean} */ set zAxisVisible(value) { this._zAxisVisible.set(value); } /** * Gets if the Z-axis-aligned wires between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} are visible. * * Wires are only shown if enabled and visible. * * @type {Boolean} */ get zAxisVisible() { return this._zAxisVisible.get(); } /** * Sets if the direct point-to-point wire between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} is visible. * * @type {Boolean} */ set wireVisible(value) { this._wireVisible.set(value); } /** * Gets if the direct point-to-point wire between {@link DistanceMeasurement#origin} and {@link DistanceMeasurement#target} is visible. * * @type {Boolean} */ get wireVisible() { return this._wireVisible.get(); } /** * Sets if the labels are visible except the length label. * * @type {Boolean} */ set labelsVisible(value) { this._labelsVisible.set(value); } /** * Gets if the labels are visible. * * @type {Boolean} */ get labelsVisible() { return this._labelsVisible.get(); } /** * Sets if the x label is enabled. * * @type {Boolean} */ set xLabelEnabled(value) { this._xLabelEnabled.set(value); } /** * Gets if the x label is enabled. * * @type {Boolean} */ get xLabelEnabled(){ return this._xLabelEnabled.get(); } /** * Sets if the y label is enabled. * * @type {Boolean} */ set yLabelEnabled(value) { this._yLabelEnabled.set(value); } /** * Gets if the y label is enabled. * * @type {Boolean} */ get yLabelEnabled(){ return this._yLabelEnabled.get(); } /** * Sets if the z label is enabled. * * @type {Boolean} */ set zLabelEnabled(value) { this._zLabelEnabled.set(value); } /** * Gets if the z label is enabled. * * @type {Boolean} */ get zLabelEnabled(){ return this._zLabelEnabled.get(); } /** * Sets if the length label is enabled. * * @type {Boolean} */ set lengthLabelEnabled(value) { this._lengthLabelEnabled.set(value); } /** * Gets if the length label is enabled. * * @type {Boolean} */ get lengthLabelEnabled(){ return this._lengthLabelEnabled.get(); } /** * Sets if labels should be positioned on the wires. * * @type {Boolean} */ set labelsOnWires(value) { this._labelsOnWires.set(value); this._update(); } /** * Gets if labels should be positioned on the wires. * * @type {Boolean} */ get labelsOnWires() { return this._labelsOnWires.get(); } /** * Sets if this DistanceMeasurement appears highlighted. * @param highlighted */ setHighlighted(highlighted) { this._drawables.forEach(d => d.setHighlighted(highlighted)); } /** * Sets if the wires, dots ad labels will fire "mouseOver" "mouseLeave" and "contextMenu" events, or ignore mouse events altogether. * * @type {Boolean} */ set clickable(value) { this._clickable.set(!!value); this._drawables.forEach(d => d.setClickable(this._clickable.get())); } /** * Gets if the wires, dots ad labels will fire "mouseOver" "mouseLeave" and "contextMenu" events. * * @type {Boolean} */ get clickable() { return this._clickable.get(); } /** * @private */ destroy() { this._cleanups.forEach(cleanup => cleanup()); super.destroy(); } } export {DistanceMeasurement};