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

388 lines (360 loc) 13.6 kB
import {math} from '../math/math.js'; import {Component} from '../Component.js'; import {worldToRTCPos} from "../math/rtcCoords.js"; import {SceneModelEntity} from "../model/SceneModelEntity.js"; const tempVec4a = math.vec4(); const tempVec4b = math.vec4(); /** * @desc Tracks the World, View and Canvas coordinates, and visibility, of a position within a {@link Scene}. * * ## Position * * A Marker holds its position in the World, View and Canvas coordinate systems in three properties: * * * {@link Marker#worldPos} holds the Marker's 3D World-space coordinates. This property can be dynamically updated. The Marker will fire a "worldPos" event whenever this property changes. * * {@link Marker#viewPos} holds the Marker's 3D View-space coordinates. This property is read-only, and is automatically updated from {@link Marker#worldPos} and the current {@link Camera} position. The Marker will fire a "viewPos" event whenever this property changes. * * {@link Marker#canvasPos} holds the Marker's 2D Canvas-space coordinates. This property is read-only, and is automatically updated from {@link Marker#canvasPos} and the current {@link Camera} position and projection. The Marker will fire a "canvasPos" event whenever this property changes. * * ## Visibility * * {@link Marker#visible} indicates if the Marker is currently visible. The Marker will fire a "visible" event whenever {@link Marker#visible} changes. * * This property will be ````false```` when: * * * {@link Marker#entity} is set to an {@link Entity}, and {@link Entity#visible} is ````false````, * * {@link Marker#occludable} is ````true```` and the Marker is occluded by some {@link Entity} in the 3D view, or * * {@link Marker#canvasPos} is outside the boundary of the {@link Canvas}. * * ## Usage * * In the example below, we'll create a Marker that's associated with a {@link Mesh} (which a type of {@link Entity}). * * We'll configure our Marker to * become invisible whenever it's occluded by any Entities in the canvas. * * We'll also demonstrate how to query the Marker's visibility status and position (in the World, View and * Canvas coordinate systems), and how to subscribe to change events on those properties. * * ````javascript * import {Viewer, GLTFLoaderPlugin, Marker} from "xeokit-sdk.es.js"; * * const viewer = new Viewer({ * canvasId: "myCanvas" * }); * * // Create the torus Mesh * // Recall that a Mesh is an Entity * new Mesh(viewer.scene, { * geometry: new ReadableGeometry(viewer.scene, buildTorusGeometry({ * center: [0,0,0], * radius: 1.0, * tube: 0.5, * radialSegments: 32, * tubeSegments: 24, * arc: Math.PI * 2.0 * }), * material: new PhongMaterial(viewer.scene, { * diffuseMap: new Texture(viewer.scene, { * src: "textures/diffuse/uvGrid2.jpg" * }), * backfaces: true * }) * }); * * // Create the Marker, associated with our Mesh Entity * const myMarker = new Marker(viewer, { * entity: entity, * worldPos: [10,0,0], * occludable: true * }); * * // Get the Marker's current World, View and Canvas coordinates * const worldPos = myMarker.worldPos; // 3D World-space position * const viewPos = myMarker.viewPos; // 3D View-space position * const canvasPos = myMarker.canvasPos; // 2D Canvas-space position * * const visible = myMarker.visible; * * // Listen for change of the Marker's 3D World-space position * myMarker.on("worldPos", function(worldPos) { * //... * }); * * // Listen for change of the Marker's 3D View-space position, which happens * // when either worldPos was updated or the Camera was moved * myMarker.on("viewPos", function(viewPos) { * //... * }); * * // Listen for change of the Marker's 2D Canvas-space position, which happens * // when worldPos or viewPos was updated, or Camera's projection was updated * myMarker.on("canvasPos", function(canvasPos) { * //... * }); * * // Listen for change of Marker visibility. The Marker becomes invisible when it falls outside the canvas, * // has an Entity that is also invisible, or when an Entity occludes the Marker's position in the 3D view. * myMarker.on("visible", function(visible) { // Marker visibility has changed * if (visible) { * this.log("Marker is visible"); * } else { * this.log("Marker is invisible"); * } * }); * * // Listen for destruction of Marker * myMarker.on("destroyed", () => { * //... * }); * ```` */ class Marker extends Component { /** * @constructor * @param {Component} [owner] Owner component. When destroyed, the owner will destroy this Marker as well. * @param {*} [cfg] Marker configuration * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted. * @param {Entity} [cfg.entity] Entity to associate this Marker with. When the Marker has an Entity, then {@link Marker#visible} will always be ````false```` if {@link Entity#visible} is false. * @param {Boolean} [cfg.occludable=false] Indicates whether or not this Marker is hidden (ie. {@link Marker#visible} is ````false```` whenever occluded by {@link Entity}s in the {@link Scene}. * @param {Number[]} [cfg.worldPos=[0,0,0]] World-space 3D Marker position. */ constructor(owner, cfg) { super(owner, cfg); this._entity = null; this._visible = null; this._worldPos = math.vec3(); this._origin = math.vec3(); this._rtcPos = math.vec3(); this._viewPos = math.vec3(); this._canvasPos = math.vec2(); this._occludable = false; this._onCameraViewMatrix = this.scene.camera.on("matrix", () => { this._viewPosDirty = true; this._needUpdate(); }); this._onCameraProjMatrix = this.scene.camera.on("projMatrix", () => { this._canvasPosDirty = true; this._needUpdate(); }); this._onEntityDestroyed = null; this._onEntityModelDestroyed = null; this._renderer.addMarker(this); this.entity = cfg.entity; this.worldPos = cfg.worldPos; this.occludable = cfg.occludable; } _update() { // this._needUpdate() schedules this for next tick if (this._viewPosDirty) { math.transformPoint3(this.scene.camera.viewMatrix, this._worldPos, this._viewPos); this._viewPosDirty = false; this._canvasPosDirty = true; this.fire("viewPos", this._viewPos); } if (this._canvasPosDirty) { tempVec4a.set(this._viewPos); tempVec4a[3] = 1.0; math.transformPoint4(this.scene.camera.projMatrix, tempVec4a, tempVec4b); const aabb = this.scene.canvas.boundary; this._canvasPos[0] = Math.floor((1 + tempVec4b[0] / tempVec4b[3]) * aabb[2] / 2); this._canvasPos[1] = Math.floor((1 - tempVec4b[1] / tempVec4b[3]) * aabb[3] / 2); this._canvasPosDirty = false; this.fire("canvasPos", this._canvasPos); } } _setVisible(visible) { // Called by VisibilityTester and this._entity.on("destroyed"..) if (this._visible === visible) { // return; } this._visible = visible; this.fire("visible", this._visible); } /** * Sets the {@link Entity} this Marker is associated with. * * An Entity is optional. When the Marker has an Entity, then {@link Marker#visible} will always be ````false```` * if {@link Entity#visible} is false. * * @type {Entity} */ set entity(entity) { if (this._entity === entity) { return; } this._cleanupDestroyedHandlers(); this._entity = entity; if (this._entity) { if (this._entity.isSceneModelEntity) { this._onEntityModelDestroyed = this._entity.model.on("destroyed", () => { // SceneModelEntity does not fire events, and cannot exist beyond its VBOSceneModel this._entity = null; // Marker now may become visible, if it was synched to invisible Entity this._onEntityModelDestroyed = null; }); } else { this._onEntityDestroyed = this._entity.on("destroyed", () => { this._entity = null; this._onEntityDestroyed = null; }); } } this.fire("entity", this._entity, true /* forget */); } /** * Gets the {@link Entity} this Marker is associated with. * * @type {Entity} */ get entity() { return this._entity; } /** * Sets whether occlusion testing is performed for this Marker. * * When this is ````true````, then {@link Marker#visible} will be ````false```` whenever the Marker is occluded by an {@link Entity} in the 3D view. * * The {@link Scene} periodically occlusion-tests all Markers on every 20th "tick" (which represents a rendered frame). We * can adjust that frequency via property {@link Scene#ticksPerOcclusionTest}. * * @type {Boolean} */ set occludable(occludable) { occludable = !!occludable; if (occludable === this._occludable) { return; } this._occludable = occludable; if (this._occludable) { this._renderer.markerWorldPosUpdated(this); } } /** * Gets whether occlusion testing is performed for this Marker. * * When this is ````true````, then {@link Marker#visible} will be ````false```` whenever the Marker is occluded by an {@link Entity} in the 3D view. * * @type {Boolean} */ get occludable() { return this._occludable; } /** * Sets the World-space 3D position of this Marker. * * Fires a "worldPos" event with new World position. * * @type {Number[]} */ set worldPos(worldPos) { this._worldPos.set(worldPos || [0, 0, 0]); worldToRTCPos(this._worldPos, this._origin, this._rtcPos); if (this._occludable) { this._renderer.markerWorldPosUpdated(this); } this._viewPosDirty = true; this.fire("worldPos", this._worldPos); this._needUpdate(); } /** * Gets the World-space 3D position of this Marker. * * @type {Number[]} */ get worldPos() { return this._worldPos; } /** * Gets the RTC center of this Marker. * * This is automatically calculated from {@link Marker#worldPos}. * * @type {Number[]} */ get origin() { return this._origin; } /** * Gets the RTC position of this Marker. * * This is automatically calculated from {@link Marker#worldPos}. * * @type {Number[]} */ get rtcPos() { return this._rtcPos; } /** * View-space 3D coordinates of this Marker. * * This property is read-only and is automatically calculated from {@link Marker#worldPos} and the current {@link Camera} position. * * The Marker fires a "viewPos" event whenever this property changes. * * @type {Number[]} * @final */ get viewPos() { this._update(); return this._viewPos; } /** * Canvas-space 2D coordinates of this Marker. * * This property is read-only and is automatically calculated from {@link Marker#worldPos} and the current {@link Camera} position and projection. * * The Marker fires a "canvasPos" event whenever this property changes. * * @type {Number[]} * @final */ get canvasPos() { this._update(); return this._canvasPos; } /** * Indicates if this Marker is currently visible. * * This is read-only and is automatically calculated. * * The Marker is **invisible** whenever: * * * {@link Marker#canvasPos} is currently outside the canvas, * * {@link Marker#entity} is set to an {@link Entity} that has {@link Entity#visible} ````false````, or * * or {@link Marker#occludable} is ````true```` and the Marker is currently occluded by an Entity in the 3D view. * * The Marker fires a "visible" event whenever this property changes. * * @type {Boolean} * @final */ get visible() { return !!this._visible; } /** * Destroys this Marker. */ destroy() { this.fire("destroyed", true); this.scene.camera.off(this._onCameraViewMatrix); this.scene.camera.off(this._onCameraProjMatrix); this._cleanupDestroyedHandlers(); this._renderer.removeMarker(this); super.destroy(); } _cleanupDestroyedHandlers() { if (this._entity) { if (this._onEntityDestroyed !== null) { if (this._entity.model) { this._entity.model.off(this._onEntityDestroyed); } else { this._entity.off(this._onEntityDestroyed); } this._onEntityDestroyed = null; } if (this._onEntityModelDestroyed !== null) { if (this._entity.model) { this._entity.model.off(this._onEntityModelDestroyed); } this._onEntityModelDestroyed = null; } } } } export {Marker};