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

437 lines (393 loc) 16.2 kB
import {math} from "../../viewer/scene/math/math.js"; import {Plugin} from "../../viewer/Plugin.js"; import {SectionPlane} from "../../viewer/scene/sectionPlane/SectionPlane.js"; import {FaceAlignedSectionPlanesControl} from "./FaceAlignedSectionPlanesControl.js"; import {Overview} from "./Overview.js"; const tempAABB = math.AABB3(); const tempVec3 = math.vec3(); /** * FaceAlignedSectionPlanesPlugin is a {@link Viewer} plugin that creates and edits face-aligned {@link SectionPlane}s. * * [<img src="https://xeokit.github.io/xeokit-sdk/assets/images/FaceAlignedSectionPlanesPlugin.gif">](https://xeokit.github.io/xeokit-sdk/examples/index.html#gizmos_FaceAlignedSectionPlanesPlugin) * * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/slicing/#FaceAlignedSectionPlanesPlugin)] * * ## Overview * * * Use the FaceAlignedSectionPlanesPlugin to * create and edit {@link SectionPlane}s to slice portions off your models and reveal internal structures. * * * As shown in the screen capture above, FaceAlignedSectionPlanesPlugin shows an overview of all your SectionPlanes (on the right, in * this example). * * Click a plane in the overview to activate a 3D control with which you can interactively * reposition its SectionPlane in the main canvas. * * Configure the plugin with an HTML element that the user can click-and-drag on to reposition the SectionPlane for which the control is active. * * Use {@link BCFViewpointsPlugin} to save and load SectionPlanes in BCF viewpoints. * * ## Usage * * In the example below, we'll use a {@link GLTFLoaderPlugin} to load a model, and a FaceAlignedSectionPlanesPlugin * to slice it open with two {@link SectionPlane}s. We'll show the overview in the bottom right of the Viewer * canvas. Finally, we'll programmatically activate the 3D editing control, so that we can use it to interactively * reposition our second SectionPlane. * * ````JavaScript * import {Viewer, GLTFLoaderPlugin, FaceAlignedSectionPlanesPlugin} from "xeokit-sdk.es.js"; * * // Create a Viewer and arrange its Camera * * const viewer = new Viewer({ * canvasId: "myCanvas" * }); * * viewer.camera.eye = [-5.02, 2.22, 15.09]; * viewer.camera.look = [4.97, 2.79, 9.89]; * viewer.camera.up = [-0.05, 0.99, 0.02]; * * // Add a GLTFLoaderPlugin * * const gltfLoader = new GLTFLoaderPlugin(viewer); * * // Add a FaceAlignedSectionPlanesPlugin, with overview visible * * const faceAlignedSectionPlanes = new FaceAlignedSectionPlanesPlugin(viewer, { * overviewCanvasID: "myOverviewCanvas", * overviewVisible: true, * controlElementId: "myControlElement", // ID of element to capture drag events that move the SectionPlane * dragSensitivity: 1 // Sensitivity factor that governs the rate at which dragging moves the SectionPlane * }); * * // Load a model * * const model = gltfLoader.load({ * id: "myModel", * src: "./models/gltf/schependomlaan/scene.glb" * }); * * // Create a couple of section planes * // These will be shown in the overview * * faceAlignedSectionPlanes.createSectionPlane({ * id: "mySectionPlane", * pos: [1.04, 1.95, 9.74], * dir: [1.0, 0.0, 0.0] * }); * * faceAlignedSectionPlanes.createSectionPlane({ * id: "mySectionPlane2", * pos: [2.30, 4.46, 14.93], * dir: [0.0, -0.09, -0.79] * }); * * // Show the FaceAlignedSectionPlanesPlugin's 3D editing gizmo, * // to interactively reposition one of our SectionPlanes * * faceAlignedSectionPlanes.showControl("mySectionPlane2"); * * const mySectionPlane2 = faceAlignedSectionPlanes.sectionPlanes["mySectionPlane2"]; * * // Programmatically reposition one of our SectionPlanes * // This also updates its position as shown in the overview gizmo * * mySectionPlane2.pos = [11.0, 6.0, -12]; * mySectionPlane2.dir = [0.4, 0.0, 0.5]; * ```` */ export class FaceAlignedSectionPlanesPlugin extends Plugin { /** * @constructor * @param {Viewer} viewer The Viewer. * @param {Object} cfg Plugin configuration. * @param {String} [cfg.id="SectionPlanes"] Optional ID for this plugin, so that we can find it within {@link Viewer#plugins}. * @param {String} [cfg.overviewCanvasId] ID of a canvas element to display the overview. * @param {String} [cfg.overviewVisible=true] Initial visibility of the overview canvas. * @param {String} cfg.controlElementId ID of an HTML element that catches drag events to move the active SectionPlane. * @param {Number} [cfg.dragSensitivity=1] Sensitivity factor that governs the rate at which dragging on the control element moves SectionPlane. */ constructor(viewer, cfg = {}) { super("FaceAlignedSectionPlanesPlugin", viewer); this._freeControls = []; this._sectionPlanes = viewer.scene.sectionPlanes; this._controls = {}; this._shownControlId = null; this._dragSensitivity = cfg.dragSensitivity || 1; if (cfg.overviewCanvasId !== null && cfg.overviewCanvasId !== undefined) { const overviewCanvas = document.getElementById(cfg.overviewCanvasId); if (!overviewCanvas) { this.warn("Can't find overview canvas: '" + cfg.overviewCanvasId + "' - will create plugin without overview"); } else { this._overview = new Overview(this, { overviewCanvas: overviewCanvas, visible: cfg.overviewVisible, onHoverEnterPlane: ((id) => { this._overview.setPlaneHighlighted(id, true); }), onHoverLeavePlane: ((id) => { this._overview.setPlaneHighlighted(id, false); }), onClickedPlane: ((id) => { if (this.getShownControl() === id) { this.hideControl(); return; } this.showControl(id); const sectionPlane = this.sectionPlanes[id]; const sectionPlanePos = sectionPlane.pos; tempAABB.set(this.viewer.scene.aabb); math.getAABB3Center(tempAABB, tempVec3); tempAABB[0] += sectionPlanePos[0] - tempVec3[0]; tempAABB[1] += sectionPlanePos[1] - tempVec3[1]; tempAABB[2] += sectionPlanePos[2] - tempVec3[2]; tempAABB[3] += sectionPlanePos[0] - tempVec3[0]; tempAABB[4] += sectionPlanePos[1] - tempVec3[1]; tempAABB[5] += sectionPlanePos[2] - tempVec3[2]; this.viewer.cameraFlight.flyTo({ aabb: tempAABB, fitFOV: 65 }); }), onClickedNothing: (() => { this.hideControl(); }) }); } } if (cfg.controlElementId === null || cfg.controlElementId === undefined) { this.error("Parameter expected: controlElementId"); } else { this._controlElement = document.getElementById(cfg.controlElementId); if (!this._controlElement) { this.warn("Can't find control element: '" + cfg.controlElementId + "' - will create plugin without control element"); } } this._onSceneSectionPlaneCreated = viewer.scene.on("sectionPlaneCreated", (sectionPlane) => { // SectionPlane created, either via FaceAlignedSectionPlanesPlugin#createSectionPlane(), or by directly // instantiating a SectionPlane independently of FaceAlignedSectionPlanesPlugin, which can be done // by BCFViewpointsPlugin#loadViewpoint(). this._sectionPlaneCreated(sectionPlane); }); } /** * Sets the factor that governs how fast a SectionPlane moves as we drag on the control element. * * @param {Number} dragSensitivity The dragging sensitivity factor. */ setDragSensitivity(dragSensitivity) { this._dragSensitivity = dragSensitivity || 1; } /** * Gets the factor that governs how fast a SectionPlane moves as we drag on the control element. * * @return {Number} The dragging sensitivity factor. */ getDragSensitivity() { return this._dragSensitivity; } /** * Sets if the overview canvas is visible. * * @param {Boolean} visible Whether or not the overview canvas is visible. */ setOverviewVisible(visible) { if (this._overview) { this._overview.setVisible(visible); } } /** * Gets if the overview canvas is visible. * * @return {Boolean} True when the overview canvas is visible. */ getOverviewVisible() { if (this._overview) { return this._overview.getVisible(); } } /** * Returns a map of the {@link SectionPlane}s created by this FaceAlignedSectionPlanesPlugin. * * @returns {{String:SectionPlane}} A map containing the {@link SectionPlane}s, each mapped to its {@link SectionPlane#id}. */ get sectionPlanes() { return this._sectionPlanes; } /** * Creates a {@link SectionPlane}. * * The {@link SectionPlane} will be registered by {@link SectionPlane#id} in {@link FaceAlignedSectionPlanesPlugin#sectionPlanes}. * * @param {Object} params {@link SectionPlane} configuration. * @param {String} [params.id] Unique ID to assign to the {@link SectionPlane}. Must be unique among all components in the {@link Viewer}'s {@link Scene}. Auto-generated when omitted. * @param {Number[]} [params.pos=[0,0,0]] World-space position of the {@link SectionPlane}. * @param {Number[]} [params.dir=[0,0,-1]] World-space vector indicating the orientation of the {@link SectionPlane}. * @param {Boolean} [params.active=true] Whether the {@link SectionPlane} is initially active. Only clips while this is true. * @returns {SectionPlane} The new {@link SectionPlane}. */ createSectionPlane(params = {}) { if (params.id !== undefined && params.id !== null && this.viewer.scene.components[params.id]) { this.error("Viewer component with this ID already exists: " + params.id); delete params.id; } // Note that SectionPlane constructor fires "sectionPlaneCreated" on the Scene, // which FaceAlignedSectionPlanesPlugin handles and calls #_sectionPlaneCreated to create gizmo and add to overview canvas. const sectionPlane = new SectionPlane(this.viewer.scene, { id: params.id, pos: params.pos, dir: params.dir, active: true || params.active }); return sectionPlane; } _sectionPlaneCreated(sectionPlane) { const control = (this._freeControls.length > 0) ? this._freeControls.pop() : new FaceAlignedSectionPlanesControl(this); control._setSectionPlane(sectionPlane); control.setVisible(false); this._controls[sectionPlane.id] = control; if (this._overview) { this._overview.addSectionPlane(sectionPlane); } sectionPlane.once("destroyed", () => { this._sectionPlaneDestroyed(sectionPlane); }); } /** * Inverts the direction of {@link SectionPlane#dir} on every existing SectionPlane. * * Inverts all SectionPlanes, including those that were not created with FaceAlignedSectionPlanesPlugin. */ flipSectionPlanes() { const sectionPlanes = this.viewer.scene.sectionPlanes; for (let id in sectionPlanes) { const sectionPlane = sectionPlanes[id]; sectionPlane.flipDir(); } } /** * Shows the 3D editing gizmo for a {@link SectionPlane}. * * @param {String} id ID of the {@link SectionPlane}. */ showControl(id) { const control = this._controls[id]; if (!control) { this.error("Control not found: " + id); return; } this.hideControl(); control.setVisible(true); if (this._overview) { this._overview.setPlaneSelected(id, true); } this._shownControlId = id; } /** * Gets the ID of the {@link SectionPlane} that the 3D editing gizmo is shown for. * * Returns ````null```` when the editing gizmo is not shown. * * @returns {String} ID of the the {@link SectionPlane} that the 3D editing gizmo is shown for, if shown, else ````null````. */ getShownControl() { return this._shownControlId; } /** * Hides the 3D {@link SectionPlane} editing gizmo if shown. */ hideControl() { for (let id in this._controls) { if (this._controls.hasOwnProperty(id)) { this._controls[id].setVisible(false); if (this._overview) { this._overview.setPlaneSelected(id, false); } } } this._shownControlId = null; } /** * Destroys a {@link SectionPlane} created by this FaceAlignedSectionPlanesPlugin. * * @param {String} id ID of the {@link SectionPlane}. */ destroySectionPlane(id) { let sectionPlane = this.viewer.scene.sectionPlanes[id]; if (!sectionPlane) { this.error("SectionPlane not found: " + id); return; } this._sectionPlaneDestroyed(sectionPlane); sectionPlane.destroy(); if (id === this._shownControlId) { this._shownControlId = null; } } _sectionPlaneDestroyed(sectionPlane) { if (this._overview) { this._overview.removeSectionPlane(sectionPlane); } const control = this._controls[sectionPlane.id]; if (!control) { return; } control.setVisible(false); control._setSectionPlane(null); delete this._controls[sectionPlane.id]; this._freeControls.push(control); } /** * Destroys all {@link SectionPlane}s created by this FaceAlignedSectionPlanesPlugin. */ clear() { const ids = Object.keys(this._sectionPlanes); for (let i = 0, len = ids.length; i < len; i++) { this.destroySectionPlane(ids[i]); } } /** * @private */ send(name, value) { switch (name) { case "snapshotStarting": // Viewer#getSnapshot() about to take snapshot - hide controls for (let id in this._controls) { if (this._controls.hasOwnProperty(id)) { this._controls[id].setCulled(true); } } break; case "snapshotFinished": // Viewer#getSnapshot() finished taking snapshot - show controls again for (let id in this._controls) { if (this._controls.hasOwnProperty(id)) { this._controls[id].setCulled(false); } } break; case "clearSectionPlanes": this.clear(); break; } } /** * Destroys this FaceAlignedSectionPlanesPlugin. * * Also destroys each {@link SectionPlane} created by this FaceAlignedSectionPlanesPlugin. * * Does not destroy the canvas the FaceAlignedSectionPlanesPlugin was configured with. */ destroy() { this.clear(); if (this._overview) { this._overview.destroy(); } this._destroyFreeControls(); super.destroy(); } _destroyFreeControls() { let control = this._freeControls.pop(); while (control) { control._destroy(); control = this._freeControls.pop(); } this.viewer.scene.off(this._onSceneSectionPlaneCreated); } }