src/plugins/AngleMeasurementsPlugin/AngleMeasurement.js
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();
/**
* @desc Measures the angle indicated by three 3D points.
*
* See {@link AngleMeasurementsPlugin} for more info.
*/
class AngleMeasurement extends Component {
/**
* @private
*/
constructor(plugin, cfg = {}) {
const scene = plugin.viewer.scene;
super(scene, cfg);
/**
* The {@link AngleMeasurementsPlugin} that owns this AngleMeasurement.
* @type {AngleMeasurementsPlugin}
*/
this.plugin = plugin;
const container = cfg.container;
if (!container) {
throw "config missing: container";
}
this._color = cfg.color || plugin.defaultColor;
const channel = function(v) {
const listeners = [ ];
let value = v !== false;
return {
reg: (l) => listeners.push(l),
get: () => value,
set: (v) => {
value = v !== false;
listeners.forEach(l => l(value));
}
};
};
this._visible = channel(cfg.visible);
this._originVisible = channel(cfg.originVisible);
this._cornerVisible = channel(cfg.cornerVisible);
this._targetVisible = channel(cfg.targetVisible);
this._originWireVisible = channel(cfg.originWireVisible);
this._targetWireVisible = channel(cfg.targetWireVisible);
this._angleVisible = channel(cfg.angleVisible);
this._labelsVisible = channel();
this.labelsVisible = cfg.labelsVisible;
this._clickable = channel(false);
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 = [ ];
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._originWire = makeWire(this._color || "blue", 1, [ this._visible, this._originWireVisible ]);
this._targetWire = makeWire(this._color || "red", 1, [ this._visible, this._targetWireVisible ]);
const makeLabel = (color, zIndexOffset, visibilityChannels) => {
const label = new Label3D(scene, container, {
fillColor: color,
zIndex: plugin.zIndex + zIndexOffset,
onMouseOver,
onMouseLeave,
onMouseWheel,
onMouseDown,
onMouseUp,
onMouseMove,
onContextMenu
});
registerDrawable(label, visibilityChannels);
return {
setFillColor: value => label.setFillColor(value),
setPosOnWire: (p0, p1, offset) => label.setPosOnWire(p0, p1, offset),
setPosBetween: (p0, p1, p2) => label.setPosBetween(p0, p1, p2),
setText: str => label.setText(str)
};
};
this._angleLabel = makeLabel(this._color || "#00BBFF", 2, [ this._visible, this._angleVisible, this._labelsVisible ]);
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._cornerDot = makeDot(cfg.corner, [ this._visible, this._cornerVisible ]);
this._targetDot = makeDot(cfg.target, [ this._visible, this._targetVisible ]);
this._update();
}
_update() {
if (! this._targetDot) {
return;
}
const p0 = this._originDot.worldPos;
const p1 = this._cornerDot.worldPos;
const p2 = this._targetDot.worldPos;
this._originWire.setEnds(p0, p1);
this._targetWire.setEnds(p1, p2);
this._angleLabel.setPosBetween(p0, p1, p2);
math.subVec3(p0, p1, tmpVec3a);
math.subVec3(p2, p1, tmpVec3b);
if ((math.lenVec3(tmpVec3a) > 0) && (math.lenVec3(tmpVec3b) > 0)) {
math.normalizeVec3(tmpVec3a);
math.normalizeVec3(tmpVec3b);
this._angle = Math.abs(math.angleVec3(tmpVec3a, tmpVec3b)) * math.RADTODEG;
this._angleLabel.setText((this._approximate ? " ~ " : " = ") + this._angle.toFixed(2) + "°");
} else {
this._angle = undefined;
this._angleLabel.setText("");
}
}
/**
* Sets whether this AngleMeasurement 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 AngleMeasurement 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 corner {@link Dot3D}.
*
* @type {Dot3D}
*/
get corner() {
return this._cornerDot;
}
/**
* Gets the target {@link Dot3D}.
*
* @type {Dot3D}
*/
get target() {
return this._targetDot;
}
/**
* Gets the angle between two connected 3D line segments, given
* as three positions on the surface(s) of one or more {@link Entity}s.
*
* @type {Number}
*/
get angle() {
return this._angle;
}
/**
* Gets the color of the angle measurement.
*
* The color is an HTML string representation, eg. "#00BBFF" and "blue".
*
* @type {String}
*/
get color() {
return this._color;
}
/** Sets the color of the angle measurement.
*
* The color is given as an HTML string representation, eg. "#00BBFF" and "blue".
*
* @type {String}
*/
set color(value) {
this._color = value;
this._originDot.setFillColor(value);
this._cornerDot.setFillColor(value);
this._targetDot.setFillColor(value);
this._originWire.setColor(value || "blue");
this._targetWire.setColor(value || "red");
this._angleLabel.setFillColor(value || "#00BBFF");
}
/**
* Sets whether this AngleMeasurement is visible or not.
*
* @type {Boolean}
*/
set visible(value) {
this._visible.set(value);
}
/**
* Gets whether this AngleMeasurement 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 corner {@link Dot3D} is visible.
*
* @type {Boolean}
*/
set cornerVisible(value) {
this._cornerVisible.set(value);
}
/**
* Gets if the corner {@link Dot3D} is visible.
*
* @type {Boolean}
*/
get cornerVisible() {
return this._cornerVisible.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 wire between the origin and the corner is visible.
*
* @type {Boolean}
*/
set originWireVisible(value) {
this._originWireVisible.set(value);
}
/**
* Gets if the wire between the origin and the corner is visible.
*
* @type {Boolean}
*/
get originWireVisible() {
return this._originWireVisible.get();
}
/**
* Sets if the wire between the target and the corner is visible.
*
* @type {Boolean}
*/
set targetWireVisible(value) {
this._targetWireVisible.set(value);
}
/**
* Gets if the wire between the target and the corner is visible.
*
* @type {Boolean}
*/
get targetWireVisible() {
return this._targetWireVisible.get();
}
/**
* Sets if the angle label is visible.
*
* @type {Boolean}
*/
set angleVisible(value) {
this._angleVisible.set(value);
}
/**
* Gets if the angle label is visible.
*
* @type {Boolean}
*/
get angleVisible() {
return this._angleVisible.get();
}
/**
* Sets if the labels are visible.
*
* @type {Boolean}
*/
set labelsVisible(value) {
this._labelsVisible.set(value !== undefined ? Boolean(value) : this.plugin.defaultLabelsVisible);
}
/**
* Gets if the labels are visible.
*
* @type {Boolean}
*/
get labelsVisible() {
return this._labelsVisible.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.
*
* @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 {AngleMeasurement};