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

447 lines (423 loc) 16.7 kB
import {Plugin} from "../../viewer/Plugin.js"; import {AngleMeasurement} from "./AngleMeasurement.js"; import {AngleMeasurementsMouseControl} from "./AngleMeasurementsMouseControl.js"; /** * {@link Viewer} plugin for measuring angles. * * [<img src="https://user-images.githubusercontent.com/83100/63641903-61488180-c6b6-11e9-8e00-895b9d16dc4b.gif">](https://xeokit.github.io/xeokit-sdk/examples/index.html#measurements_angle_createWithMouse) * * * [[Example 1: Model with angle measurements](https://xeokit.github.io/xeokit-sdk/examples/index.html#measurements_angle_modelWithMeasurements)] * * [[Example 2: Create angle measurements with mouse](https://xeokit.github.io/xeokit-sdk/examples/measurement/#angle_createWithMouse_snapping)] * * ## Overview * * * An {@link AngleMeasurement} shows the angle between two connected 3D line segments, given * as three positions on the surface(s) of one or more {@link Entity}s. * * As shown on the screen capture above, a AngleMeasurement has two wires that show the line segments, with a label that shows the angle between them. * * Create AngleMeasurements programmatically with {@link AngleMeasurementsPlugin#createMeasurement}. * * Create AngleMeasurements interactively using a {@link AngleMeasurementsControl}. * * Existing AngleMeasurements are registered by ID in {@link AngleMeasurementsPlugin#measurements}. * * Destroy AngleMeasurements using {@link AngleMeasurementsPlugin#destroyMeasurement}. * * Configure global measurement units and scale via {@link Metrics}, located at {@link Scene#metrics} * * ## Example 1: Creating AngleMeasurements Programmatically * * In our first example, we'll use an {@link XKTLoaderPlugin} to load a model, and then use a AngleMeasurementsPlugin to programmatically create two {@link AngleMeasurement}s. * * Note how each AngleMeasurement has ````origin````, ````corner```` and ````target````, which each indicate a 3D World-space * position on the surface of an {@link Entity}. These can be aon the same Entity, or on different Entitys. * * [[Run example](https://xeokit.github.io/xeokit-sdk/examples/index.html#measurements_angle_modelWithMeasurements)] * * ````JavaScript * import {Viewer, XKTLoaderPlugin, AngleMeasurementsPlugin} from "xeokit-sdk.es.js"; * * const viewer = new Viewer({ * canvasId: "myCanvas", * transparent: true * }); * * viewer.scene.camera.eye = [-2.37, 18.97, -26.12]; * viewer.scene.camera.look = [10.97, 5.82, -11.22]; * viewer.scene.camera.up = [0.36, 0.83, 0.40]; * * const xktLoader = new XKTLoaderPlugin(viewer); * * const angleMeasurements = new AngleMeasurementsPlugin(viewer); * * const model = xktLoader.load({ * src: "./models/xkt/duplex/duplex.xkt" * }); * * model.on("loaded", () => { * * const myMeasurement1 = angleMeasurements.createMeasurement({ * id: "myAngleMeasurement1", * origin: { * entity: viewer.scene.objects["2O2Fr$t4X7Zf8NOew3FLOH"], * worldPos: [0.044, 5.998, 17.767] * }, * corner: { * entity: viewer.scene.objects["2O2Fr$t4X7Zf8NOew3FLOH"], * worldPos: [0.044, 5.998, 17.767] * }, * target: { * entity: viewer.scene.objects["2O2Fr$t4X7Zf8NOew3FLOH"], * worldPos: [4.738, 3.172, 17.768] * }, * visible: true * }); * * const myMeasurement2 = angleMeasurements.createMeasurement({ * id: "myAngleMeasurement2", * origin: { * entity: viewer.scene.objects["2O2Fr$t4X7Zf8NOew3FNr2"], * worldPos: [0.457, 2.532, 17.766] * }, * corner: { * entity: viewer.scene.objects["2O2Fr$t4X7Zf8NOew3FNr2"], * worldPos: [0.457, 2.532, 17.766] * }, * target: { * entity: viewer.scene.objects["1CZILmCaHETO8tf3SgGEXu"], * worldPos: [0.436, 0.001, 22.135] * }, * visible: true * }); * }); * ```` * * ## Example 2: Creating AngleMeasurements with Mouse Input * * In our second example, we'll use an {@link XKTLoaderPlugin} to load a model, then we'll use the AngleMeasurementsPlugin's {@link AngleMeasurementsTouchControl} to interactively create {@link AngleMeasurement}s with mouse or touch input. * * After we've activated the AngleMeasurementsControl, the first click on any {@link Entity} begins constructing a AngleMeasurement, fixing its * origin to that Entity. The next click on any Entity will fix the AngleMeasurement's corner, and the next click after * that will fix its target and complete the AngleMeasurement. * * The AngleMeasurementControl will then wait for the next click on any Entity, to begin constructing * another AngleMeasurement, and so on, until deactivated again. * * [[Run example](https://xeokit.github.io/xeokit-sdk/examples/measurement/#angle_createWithMouse_snapping)] * * ````JavaScript * import {Viewer, XKTLoaderPlugin, AngleMeasurementsPlugin, AngleMeasurementsMouseControl, PointerLens} from "xeokit-sdk.es.js"; * * const viewer = new Viewer({ * canvasId: "myCanvas", * transparent: true * }); * * viewer.scene.camera.eye = [-2.37, 18.97, -26.12]; * viewer.scene.camera.look = [10.97, 5.82, -11.22]; * viewer.scene.camera.up = [0.36, 0.83, 0.40]; * * const xktLoader = new XKTLoaderPlugin(viewer); * * cconst angleMeasurementsMouseControl = new AngleMeasurementsMouseControl(angleMeasurements, { * pointerLens : new PointerLens(viewer) * }) * * angleMeasurementsMouseControl.snapToVertex = true; * angleMeasurementsMouseControl.snapToEdge = true; * * angleMeasurementsMouseControl.activate(); * ```` * * ## Example 4: Attaching Mouse Handlers * * In our fourth example, we'll attach even handlers to our plugin, to catch when the user * hovers or right-clicks over our measurements. * * [[Run example](/examples/measurement/#angle_modelWithMeasurements)] * * ````javascript * import {Viewer, XKTLoaderPlugin, AngleMeasurementsPlugin} from "xeokit-sdk.es.js"; * * const viewer = new Viewer({ * canvasId: "myCanvas", * transparent: true * }); * * viewer.scene.camera.eye = [-2.37, 18.97, -26.12]; * viewer.scene.camera.look = [10.97, 5.82, -11.22]; * viewer.scene.camera.up = [0.36, 0.83, 0.40]; * * const xktLoader = new XKTLoaderPlugin(viewer); * * const angleMeasurements = new AngleMeasurementsPlugin(viewer); * * angleMeasurements.on("mouseOver", (e) => { * e.measurement.setHighlighted(true); * }); * * angleMeasurements.on("mouseLeave", (e) => { * e.measurement.setHighlighted(false); * }); * * angleMeasurements.on("contextMenu", (e) => { * // Show context menu * e.event.preventDefault(); * }); * * const model = xktLoader.load({ * src: "./models/xkt/duplex/duplex.xkt" * }); * * model.on("loaded", () => { * * angleMeasurementsPlugin.createMeasurement({ * id: "angleMeasurement1", * origin: { * entity: viewer.scene.objects["1CZILmCaHETO8tf3SgGEXu"], * worldPos: [0.4158603637281142, 2.5193106917110457, 17.79972838299403] * }, * corner: { * entity: viewer.scene.objects["1CZILmCaHETO8tf3SgGEXu"], * worldPos: [0.41857741956197625,0.0987169929481646,17.799763071093395] * }, * target: { * entity: viewer.scene.objects["1CZILmCaHETO8tf3SgGEXu"], * worldPos: [5.235526066859247, 0.11580773869801986, 17.824891550941565] * }, * visible: true * }); * * angleMeasurementsPlugin.createMeasurement({ * id: "angleMeasurement2", * origin: { * entity: viewer.scene.objects["2O2Fr$t4X7Zf8NOew3FNr2"], * worldPos: [-0.00003814187850181838, 5.9996748076205115,17.79996871551525] * }, * corner: { * entity: viewer.scene.objects["2O2Fr$t4X7Zf8NOew3FNqI"], * worldPos: [-0.0005214119318139865, 3.1010044228517595, 17.787656604483363] * * }, * target: { * entity: viewer.scene.objects["1s1jVhK8z0pgKYcr9jt7AB"], * worldPos: [ 8.380657312957396, 3.1055697628459553, 17.799220108187185] * }, * visible: true * }); * }); * ```` * * ## Example 5: Creating AngleMeasurements with Touch Input * * In our fifth example, we'll show how to create angle measurements with touch input, with snapping * to the nearest vertex or edge. While creating the measurements, a long-touch when setting the * start, corner or end point will cause the point to snap to the nearest vertex or edge. A quick * touch-release will immediately set the point at the tapped position on the object surface. * * [[Run example](https://xeokit.github.io/xeokit-sdk/examples/measurement/#angle_createWithTouch_snapping)] * * ````javascript * import {Viewer, XKTLoaderPlugin, AngleMeasurementsPlugin, AngleMeasurementsTouchControl} from "xeokit-sdk.es.js"; * * const viewer = new Viewer({ * canvasId: "myCanvas", * transparent: true * }); * * viewer.scene.camera.eye = [-2.37, 18.97, -26.12]; * viewer.scene.camera.look = [10.97, 5.82, -11.22]; * viewer.scene.camera.up = [0.36, 0.83, 0.40]; * * const xktLoader = new XKTLoaderPlugin(viewer); * * const angleMeasurements = new AngleMeasurementsPlugin(viewer); * * const model = xktLoader.load({ * src: "./models/xkt/duplex/duplex.xkt" * }); * * const angleMeasurements = new AngleMeasurementsPlugin(viewer); * * const angleMeasurementsTouchControl = new AngleMeasurementsTouchControl(angleMeasurements, { * pointerLens : new PointerLens(viewer), * snapToVertex: true, * snapToEdge: true * }) * * angleMeasurementsTouchControl.activate(); * ```` */ export class AngleMeasurementsPlugin extends Plugin { /** * @constructor * @param {Viewer} viewer The Viewer. * @param {Object} [cfg] Plugin configuration. * @param {String} [cfg.id="AngleMeasurements"] Optional ID for this plugin, so that we can find it within {@link Viewer#plugins}. * @param {HTMLElement} [cfg.container] Container DOM element for markers and labels. Defaults to ````document.body````. * @param {string} [cfg.defaultColor=null] The default color of the dots, wire and label. * @param {boolean} [cfg.defaultLabelsVisible=true] The default value of {@link AngleMeasurement.labelsVisible}. * @param {number} [cfg.zIndex] If set, the wires, dots and labels will have this zIndex (+1 for dots and +2 for labels). * @param {PointerCircle} [cfg.pointerLens] A PointerLens to help the user position the pointer. This can be shared with other plugins. */ constructor(viewer, cfg = {}) { super("AngleMeasurements", viewer); this._container = cfg.container || document.body; this._defaultControl = null; this._measurements = {}; this.defaultColor = cfg.defaultColor !== undefined ? cfg.defaultColor : "#00BBFF"; this.defaultLabelsVisible = cfg.defaultLabelsVisible !== false; this.zIndex = cfg.zIndex || 10000; this._onMouseOver = (event, measurement) => { this.fire("mouseOver", { plugin: this, angleMeasurement: measurement, measurement, event }); } this._onMouseLeave = (event, measurement) => { this.fire("mouseLeave", { plugin: this, angleMeasurement: measurement, measurement, event }); }; this._onContextMenu = (event, measurement) => { this.fire("contextMenu", { plugin: this, angleMeasurement: measurement, measurement, event }); }; } /** * Gets the plugin's HTML container element, if any. * @returns {*|HTMLElement|HTMLElement} */ getContainerElement() { return this._container; } /** * @private */ send(name, value) { } /** * Gets the default {@link AngleMeasurementsMouseControl}. * * @type {AngleMeasurementsMouseControl} * @deprecated */ get control() { if (!this._defaultControl) { this._defaultControl = new AngleMeasurementsMouseControl(this, {}); } return this._defaultControl; } /** * Gets the existing {@link AngleMeasurement}s, each mapped to its {@link AngleMeasurement#id}. * * @type {{String:AngleMeasurement}} */ get measurements() { return this._measurements; } /** * Creates an {@link AngleMeasurement}. * * The AngleMeasurement is then registered by {@link AngleMeasurement#id} in {@link AngleMeasurementsPlugin#measurements}. * * @param {Object} params {@link AngleMeasurement} configuration. * @param {String} params.id Unique ID to assign to {@link AngleMeasurement#id}. The AngleMeasurement will be registered by this in {@link AngleMeasurementsPlugin#measurements} and {@link Scene.components}. Must be unique among all components in the {@link Viewer}. * @param {Number[]} params.origin.worldPos Origin World-space 3D position. * @param {Entity} params.origin.entity Origin Entity. * @param {Number[]} params.corner.worldPos Corner World-space 3D position. * @param {Entity} params.corner.entity Corner Entity. * @param {Number[]} params.target.worldPos Target World-space 3D position. * @param {Entity} params.target.entity Target Entity. * @param {Boolean} [params.visible=true] Whether to initially show the {@link AngleMeasurement}. * @returns {AngleMeasurement} The new {@link AngleMeasurement}. */ createMeasurement(params = {}) { if (this.viewer.scene.components[params.id]) { this.error("Viewer scene component with this ID already exists: " + params.id); delete params.id; } const origin = params.origin; const corner = params.corner; const target = params.target; const measurement = new AngleMeasurement(this, { id: params.id, plugin: this, container: this._container, origin: { entity: origin.entity, worldPos: origin.worldPos }, corner: { entity: corner.entity, worldPos: corner.worldPos }, target: { entity: target.entity, worldPos: target.worldPos }, visible: params.visible, originVisible: true, originWireVisible: true, cornerVisible: true, targetWireVisible: true, targetVisible: true, onMouseOver: this._onMouseOver, onMouseLeave: this._onMouseLeave, onContextMenu: this._onContextMenu }); this._measurements[measurement.id] = measurement; measurement.on("destroyed", () => { delete this._measurements[measurement.id]; }); measurement.clickable = true; this.fire("measurementCreated", measurement); return measurement; } /** * Destroys a {@link AngleMeasurement}. * * @param {String} id ID of AngleMeasurement to destroy. */ destroyMeasurement(id) { const measurement = this._measurements[id]; if (!measurement) { this.log("AngleMeasurement not found: " + id); return; } measurement.destroy(); this.fire("measurementDestroyed", measurement); } /** * Shows all or hides the angle label of each {@link AngleMeasurement}. * * @param {Boolean} labelsShown Whether or not to show the labels. */ setLabelsShown(labelsShown) { for (const [key, measurement] of Object.entries(this.measurements)) { measurement.labelShown = labelsShown; } } /** * Destroys all {@link AngleMeasurement}s. */ clear() { const ids = Object.keys(this._measurements); for (var i = 0, len = ids.length; i < len; i++) { this.destroyMeasurement(ids[i]); } } /** * Destroys this AngleMeasurementsPlugin. * * Destroys all {@link AngleMeasurement}s first. */ destroy() { this.clear(); super.destroy(); } }