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

1,563 lines (1,442 loc) 73.4 kB
/** Fired when this Mesh is picked via a call to {@link Scene/pick:method"}}Scene#pick(){{/crossLink}}. The event parameters will be the hit result returned by the {@link Scene/pick:method"}}Scene#pick(){{/crossLink}} method. @event picked */ import {math} from '../math/math.js'; import {createRTCViewMat} from '../math/rtcCoords.js'; import {Component} from '../Component.js'; import {RenderState} from '../webgl/RenderState.js'; import {DrawRenderer} from "./draw/DrawRenderer.js"; import {EmphasisFillRenderer} from "./emphasis/EmphasisFillRenderer.js"; import {EmphasisEdgesRenderer} from "./emphasis/EmphasisEdgesRenderer.js"; import {PickMeshRenderer} from "./pick/PickMeshRenderer.js"; import {PickTriangleRenderer} from "./pick/PickTriangleRenderer.js"; import {OcclusionRenderer} from "./occlusion/OcclusionRenderer.js"; import {ShadowRenderer} from "./shadow/ShadowRenderer.js"; import {geometryCompressionUtils} from '../math/geometryCompressionUtils.js'; import {RenderFlags} from "../webgl/RenderFlags.js"; const obb = math.OBB3(); const angleAxis = math.vec4(); const q1 = math.vec4(); const q2 = math.vec4(); const xAxis = math.vec3([1, 0, 0]); const yAxis = math.vec3([0, 1, 0]); const zAxis = math.vec3([0, 0, 1]); const veca = math.vec3(3); const vecb = math.vec3(3); const identityMat = math.identityMat4(); /** * @desc An {@link Entity} that is a drawable element, with a {@link Geometry} and a {@link Material}, that can be * connected into a scene graph using {@link Node}s. * * ## Usage * * The example below is the same as the one given for {@link Node}, since the two classes work together. In this example, * we'll create a scene graph in which a root {@link Node} represents a group and the Meshes are leaves. * * Since {@link Node} implements {@link Entity}, we can designate the root {@link Node} as a model, causing it to be registered by its * ID in {@link Scene#models}. * * Since Mesh also implements {@link Entity}, we can designate the leaf Meshes as objects, causing them to * be registered by their IDs in {@link Scene#objects}. * * We can then find those {@link Entity} types in {@link Scene#models} and {@link Scene#objects}. * * We can also update properties of our object-Meshes via calls to {@link Scene#setObjectsHighlighted} etc. * * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/scenegraph/#sceneGraph)] * * ````javascript * import {Viewer, Mesh, Node, PhongMaterial, buildBoxGeometry, ReadableGeometry} from "xeokit-sdk.es.js"; * * const viewer = new Viewer({ * canvasId: "myCanvas" * }); * * viewer.scene.camera.eye = [-21.80, 4.01, 6.56]; * viewer.scene.camera.look = [0, -5.75, 0]; * viewer.scene.camera.up = [0.37, 0.91, -0.11]; * * const boxGeometry = new ReadableGeometry(viewer.scene, buildBoxGeometry({ * xSize: 1, * ySize: 1, * zSize: 1 * })); * * new Node(viewer.scene, { * id: "table", * isModel: true, // <---------- Node represents a model, so is registered by ID in viewer.scene.models * rotation: [0, 50, 0], * position: [0, 0, 0], * scale: [1, 1, 1], * * children: [ * * new Mesh(viewer.scene, { // Red table leg * id: "redLeg", * isObject: true, // <------ Node represents an object, so is registered by ID in viewer.scene.objects * position: [-4, -6, -4], * scale: [1, 3, 1], * rotation: [0, 0, 0], * material: new PhongMaterial(viewer.scene, { * diffuse: [1, 0.3, 0.3] * }), * geometry: boxGeometry * }), * * new Mesh(viewer.scene, { // Green table leg * id: "greenLeg", * isObject: true, // <------ Node represents an object, so is registered by ID in viewer.scene.objects * position: [4, -6, -4], * scale: [1, 3, 1], * rotation: [0, 0, 0], * material: new PhongMaterial(viewer.scene, { * diffuse: [0.3, 1.0, 0.3] * }), * geometry: boxGeometry * }), * * new Mesh(viewer.scene, {// Blue table leg * id: "blueLeg", * isObject: true, // <------ Node represents an object, so is registered by ID in viewer.scene.objects * position: [4, -6, 4], * scale: [1, 3, 1], * rotation: [0, 0, 0], * material: new PhongMaterial(viewer.scene, { * diffuse: [0.3, 0.3, 1.0] * }), * geometry: boxGeometry * }), * * new Mesh(viewer.scene, { // Yellow table leg * id: "yellowLeg", * isObject: true, // <------ Node represents an object, so is registered by ID in viewer.scene.objects * position: [-4, -6, 4], * scale: [1, 3, 1], * rotation: [0, 0, 0], * material: new PhongMaterial(viewer.scene, { * diffuse: [1.0, 1.0, 0.0] * }), * geometry: boxGeometry * }), * * new Mesh(viewer.scene, { // Purple table top * id: "tableTop", * isObject: true, // <------ Node represents an object, so is registered by ID in viewer.scene.objects * position: [0, -3, 0], * scale: [6, 0.5, 6], * rotation: [0, 0, 0], * material: new PhongMaterial(viewer.scene, { * diffuse: [1.0, 0.3, 1.0] * }), * geometry: boxGeometry * }) * ] * }); * * // Find Nodes and Meshes by their IDs * * var table = viewer.scene.models["table"]; // Since table Node has isModel == true * * var redLeg = viewer.scene.objects["redLeg"]; // Since the Meshes have isObject == true * var greenLeg = viewer.scene.objects["greenLeg"]; * var blueLeg = viewer.scene.objects["blueLeg"]; * * // Highlight one of the table leg Meshes * * viewer.scene.setObjectsHighlighted(["redLeg"], true); // Since the Meshes have isObject == true * * // Periodically update transforms on our Nodes and Meshes * * viewer.scene.on("tick", function () { * * // Rotate legs * redLeg.rotateY(0.5); * greenLeg.rotateY(0.5); * blueLeg.rotateY(0.5); * * // Rotate table * table.rotateY(0.5); * table.rotateX(0.3); * }); * ```` * * ## Metadata * * As mentioned, we can also associate {@link MetaModel}s and {@link MetaObject}s with our {@link Node}s and Meshes, * within a {@link MetaScene}. See {@link MetaScene} for an example. * * @implements {Entity} * @implements {Drawable} */ class Mesh extends Component { /** * @constructor * @param {Component} owner Owner component. When destroyed, the owner will destroy this component as well. * @param {*} [cfg] Configs * @param {String} [cfg.id] Optional ID, unique among all components in the parent scene, generated automatically when omitted. * @param {String} [cfg.originalSystemId] ID of the corresponding object within the originating system, if any. * @param {Boolean} [cfg.isModel] Specify ````true```` if this Mesh represents a model, in which case the Mesh will be registered by {@link Mesh#id} in {@link Scene#models} and may also have a corresponding {@link MetaModel} with matching {@link MetaModel#id}, registered by that ID in {@link MetaScene#metaModels}. * @param {Boolean} [cfg.isObject] Specify ````true```` if this Mesh represents an object, in which case the Mesh will be registered by {@link Mesh#id} in {@link Scene#objects} and may also have a corresponding {@link MetaObject} with matching {@link MetaObject#id}, registered by that ID in {@link MetaScene#metaObjects}. * @param {Node} [cfg.parent] The parent Node. * @param {Number[]} [cfg.origin] World-space origin for this Mesh. When this is given, then ````matrix````, ````position```` and ````geometry```` are all assumed to be relative to this center. * @param {Number[]} [cfg.rtcCenter] Deprecated - renamed to ````origin````. * @param {Number[]} [cfg.position=[0,0,0]] 3D position of this Mesh, relative to ````origin````. * @param {Number[]} [cfg.scale=[1,1,1]] Local scale. * @param {Number[]} [cfg.rotation=[0,0,0]] Local rotation, as Euler angles given in degrees, for each of the X, Y and Z axis. * @param {Number[]} [cfg.matrix=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]] Local modelling transform matrix. Overrides the position, scale and rotation parameters. * @param {Number[]} [cfg.offset=[0,0,0]] World-space 3D translation offset. Translates the Mesh in World space, after modelling transforms. * @param {Boolean} [cfg.occluder=true] Indicates if the Mesh is able to occlude {@link Marker}s. * @param {Boolean} [cfg.visible=true] Indicates if the Mesh is initially visible. * @param {Boolean} [cfg.culled=false] Indicates if the Mesh is initially culled from view. * @param {Boolean} [cfg.pickable=true] Indicates if the Mesh is initially pickable. * @param {Boolean} [cfg.clippable=true] Indicates if the Mesh is initially clippable. * @param {Boolean} [cfg.collidable=true] Indicates if the Mesh is initially included in boundary calculations. * @param {Boolean} [cfg.castsShadow=true] Indicates if the Mesh initially casts shadows. * @param {Boolean} [cfg.receivesShadow=true] Indicates if the Mesh initially receives shadows. * @param {Boolean} [cfg.xrayed=false] Indicates if the Mesh is initially xrayed. * @param {Boolean} [cfg.highlighted=false] Indicates if the Mesh is initially highlighted. * @param {Boolean} [cfg.selected=false] Indicates if the Mesh is initially selected. * @param {Boolean} [cfg.edges=false] Indicates if the Mesh's edges are initially emphasized. * @param {Boolean} [cfg.background=false] Indicates if the Mesh should act as background, e.g., it can be used for a skybox. * @param {Number[]} [cfg.colorize=[1.0,1.0,1.0]] Mesh's initial RGB colorize color, multiplies by the rendered fragment colors. * @param {Number} [cfg.opacity=1.0] Mesh's initial opacity factor, multiplies by the rendered fragment alpha. * @param {String} [cfg.billboard="none"] Mesh's billboarding behaviour. Options are "none" for no billboarding, "spherical" to always directly face {@link Camera.eye}, rotating both vertically and horizontally, or "cylindrical" to face the {@link Camera#eye} while rotating only about its vertically axis (use that mode for things like trees on a landscape). * @param {Geometry} [cfg.geometry] {@link Geometry} to define the shape of this Mesh. Inherits {@link Scene#geometry} by default. * @param {Material} [cfg.material] {@link Material} to define the normal rendered appearance for this Mesh. Inherits {@link Scene#material} by default. * @param {EmphasisMaterial} [cfg.xrayMaterial] {@link EmphasisMaterial} to define the xrayed appearance for this Mesh. Inherits {@link Scene#xrayMaterial} by default. * @param {EmphasisMaterial} [cfg.highlightMaterial] {@link EmphasisMaterial} to define the xrayed appearance for this Mesh. Inherits {@link Scene#highlightMaterial} by default. * @param {EmphasisMaterial} [cfg.selectedMaterial] {@link EmphasisMaterial} to define the selected appearance for this Mesh. Inherits {@link Scene#selectedMaterial} by default. * @param {EmphasisMaterial} [cfg.edgeMaterial] {@link EdgeMaterial} to define the appearance of enhanced edges for this Mesh. Inherits {@link Scene#edgeMaterial} by default. * @param {Number} [cfg.renderOrder=0] Specifies the rendering order for this mESH. This is used to control the order in which * mESHES are drawn when they have transparent objects, to give control over the order in which those objects are blended within the transparent * render pass. */ constructor(owner, cfg = {}) { super(owner, cfg); this.renderOrder = cfg.renderOrder || 0; /** * ID of the corresponding object within the originating system, if any. * * @type {String} * @abstract */ this.originalSystemId = (cfg.originalSystemId || this.id); /** @private **/ this.renderFlags = new RenderFlags(); this._state = new RenderState({ // NOTE: Renderer gets modeling and normal matrices from Mesh#matrix and Mesh.#normalWorldMatrix visible: true, culled: false, pickable: null, clippable: null, collidable: null, occluder: (cfg.occluder !== false), castsShadow: null, receivesShadow: null, xrayed: false, highlighted: false, selected: false, edges: false, stationary: !!cfg.stationary, background: !!cfg.background, billboard: this._checkBillboard(cfg.billboard), layer: null, colorize: null, pickID: this.scene._renderer.getPickID(this), drawHash: "", pickHash: "", offset: math.vec3(), origin: null, originHash: null, isUI: cfg.isUI }); this._drawRenderer = null; this._shadowRenderer = null; this._emphasisFillRenderer = null; this._emphasisEdgesRenderer = null; this._pickMeshRenderer = null; this._pickTriangleRenderer = null; this._occlusionRenderer = null; this._geometry = cfg.geometry ? this._checkComponent2(["ReadableGeometry", "VBOGeometry"], cfg.geometry) : this.scene.geometry; this._material = cfg.material ? this._checkComponent2(["PhongMaterial", "MetallicMaterial", "SpecularMaterial", "LambertMaterial"], cfg.material) : this.scene.material; this._xrayMaterial = cfg.xrayMaterial ? this._checkComponent("EmphasisMaterial", cfg.xrayMaterial) : this.scene.xrayMaterial; this._highlightMaterial = cfg.highlightMaterial ? this._checkComponent("EmphasisMaterial", cfg.highlightMaterial) : this.scene.highlightMaterial; this._selectedMaterial = cfg.selectedMaterial ? this._checkComponent("EmphasisMaterial", cfg.selectedMaterial) : this.scene.selectedMaterial; this._edgeMaterial = cfg.edgeMaterial ? this._checkComponent("EdgeMaterial", cfg.edgeMaterial) : this.scene.edgeMaterial; this._parentNode = null; this._aabb = null; this._aabbDirty = true; this._numTriangles = (this._geometry ? this._geometry.numTriangles : 0); this.scene._aabbDirty = true; this._scale = math.vec3(); this._quaternion = math.identityQuaternion(); this._rotation = math.vec3(); this._position = math.vec3(); this._worldMatrix = math.identityMat4(); this._worldNormalMatrix = math.identityMat4(); this._localMatrixDirty = true; this._worldMatrixDirty = true; this._worldNormalMatrixDirty = true; const origin = cfg.origin || cfg.rtcCenter; if (origin) { this._state.origin = math.vec3(origin); this._state.originHash = origin.join(); } if (cfg.matrix) { this.matrix = cfg.matrix; } else { this.scale = cfg.scale; this.position = cfg.position; if (cfg.quaternion) { } else { this.rotation = cfg.rotation; } } this._isObject = cfg.isObject; if (this._isObject) { this.scene._registerObject(this); } this._isModel = cfg.isModel; if (this._isModel) { this.scene._registerModel(this); } this.visible = cfg.visible; this.culled = cfg.culled; this.pickable = cfg.pickable; this.clippable = cfg.clippable; this.collidable = cfg.collidable; this.castsShadow = cfg.castsShadow; this.receivesShadow = cfg.receivesShadow; this.xrayed = cfg.xrayed; this.highlighted = cfg.highlighted; this.selected = cfg.selected; this.edges = cfg.edges; this.layer = cfg.layer; this.colorize = cfg.colorize; this.opacity = cfg.opacity; this.offset = cfg.offset; if (cfg.parentId) { const parentNode = this.scene.components[cfg.parentId]; if (!parentNode) { this.error("Parent not found: '" + cfg.parentId + "'"); } else if (!parentNode.isNode) { this.error("Parent is not a Node: '" + cfg.parentId + "'"); } else { parentNode.addChild(this); } this._parentNode = parentNode; } else if (cfg.parent) { if (!cfg.parent.isNode) { this.error("Parent is not a Node"); } cfg.parent.addChild(this); this._parentNode = cfg.parent; } this.compile(); } /** @private */ get type() { return "Mesh"; } //------------------------------------------------------------------------------------------------------------------ // Mesh members //------------------------------------------------------------------------------------------------------------------ /** * Returns true to indicate that this Component is a Mesh. * @final * @type {Boolean} */ get isMesh() { return true; } /** * The parent Node. * * The parent Node may also be set by passing the Mesh to the parent's {@link Node#addChild} method. * * @type {Node} */ get parent() { return this._parentNode; } /** * Defines the shape of this Mesh. * * Set to {@link Scene#geometry} by default. * * @type {Geometry} */ get geometry() { return this._geometry; } /** * Defines the appearance of this Mesh when rendering normally, ie. when not xrayed, highlighted or selected. * * Set to {@link Scene#material} by default. * * @type {Material} */ get material() { return this._material; } /** * Gets the Mesh's local translation. * * Default value is ````[0,0,0]````. * * @type {Number[]} */ get position() { return this._position; } /** * Sets the Mesh's local translation. * * Default value is ````[0,0,0]````. * * @type {Number[]} */ set position(value) { this._position.set(value || [0, 0, 0]); this._setLocalMatrixDirty(); this._setAABBDirty(); this.glRedraw(); } /** * Gets the Mesh's local rotation, as Euler angles given in degrees, for each of the X, Y and Z axis. * * Default value is ````[0,0,0]````. * * @type {Number[]} */ get rotation() { return this._rotation; } /** * Sets the Mesh's local rotation, as Euler angles given in degrees, for each of the X, Y and Z axis. * * Default value is ````[0,0,0]````. * * @type {Number[]} */ set rotation(value) { this._rotation.set(value || [0, 0, 0]); math.eulerToQuaternion(this._rotation, "XYZ", this._quaternion); this._setLocalMatrixDirty(); this._setAABBDirty(); this.glRedraw(); } /** * Gets the Mesh's local rotation quaternion. * * Default value is ````[0,0,0,1]````. * * @type {Number[]} */ get quaternion() { return this._quaternion; } /** * Sets the Mesh's local rotation quaternion. * * Default value is ````[0,0,0,1]````. * * @type {Number[]} */ set quaternion(value) { this._quaternion.set(value || [0, 0, 0, 1]); math.quaternionToEuler(this._quaternion, "XYZ", this._rotation); this._setLocalMatrixDirty(); this._setAABBDirty(); this.glRedraw(); } /** * Gets the Mesh's local scale. * * Default value is ````[1,1,1]````. * * @type {Number[]} */ get scale() { return this._scale; } /** * Sets the Mesh's local scale. * * Default value is ````[1,1,1]````. * * @type {Number[]} */ set scale(value) { this._scale.set(value || [1, 1, 1]); this._setLocalMatrixDirty(); this._setAABBDirty(); this.glRedraw(); } /** * Gets the Mesh's local modeling transform matrix. * * Default value is ````[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]````. * * @type {Number[]} */ get matrix() { if (this._localMatrixDirty) { if (!this.__localMatrix) { this.__localMatrix = math.identityMat4(); } math.composeMat4(this._position, this._quaternion, this._scale, this.__localMatrix); this._localMatrixDirty = false; } return this.__localMatrix; } /** * Sets the Mesh's local modeling transform matrix. * * Default value is ````[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]````. * * @type {Number[]} */ set matrix(value) { if (!this.__localMatrix) { this.__localMatrix = math.identityMat4(); } this.__localMatrix.set(value || identityMat); math.decomposeMat4(this.__localMatrix, this._position, this._quaternion, this._scale); this._localMatrixDirty = false; this._setWorldMatrixDirty(); this._setAABBDirty(); this.glRedraw(); } /** * Gets the Mesh's World matrix. * * @property worldMatrix * @type {Number[]} */ get worldMatrix() { if (this._worldMatrixDirty) { this._buildWorldMatrix(); } return this._worldMatrix; } /** * Gets the Mesh's World normal matrix. * * @type {Number[]} */ get worldNormalMatrix() { if (this._worldNormalMatrixDirty) { this._buildWorldNormalMatrix(); } return this._worldNormalMatrix; } /** * Returns true to indicate that Mesh implements {@link Entity}. * * @returns {Boolean} */ get isEntity() { return true; } /** * Returns ````true```` if this Mesh represents a model. * * When this returns ````true````, the Mesh will be registered by {@link Mesh#id} in {@link Scene#models} and * may also have a corresponding {@link MetaModel}. * * @type {Boolean} */ get isModel() { return this._isModel; } /** * Returns ````true```` if this Mesh represents an object. * * When this returns ````true````, the Mesh will be registered by {@link Mesh#id} in {@link Scene#objects} and * may also have a corresponding {@link MetaObject}. * * @type {Boolean} */ get isObject() { return this._isObject; } /** * Returns ````true```` if this Mesh is an UI object. * * @type {Boolean} */ get isUI() { return this._state.isUI; } /** * Gets the Mesh's World-space 3D axis-aligned bounding box. * * Represented by a six-element Float64Array containing the min/max extents of the * axis-aligned volume, ie. ````[xmin, ymin,zmin,xmax,ymax, zmax]````. * * @type {Number[]} */ get aabb() { if (this._aabbDirty) { this._updateAABB(); } return this._aabb; } /** * Gets the 3D origin of the Mesh's {@link Geometry}'s vertex positions. * * When this is given, then {@link Mesh#matrix}, {@link Mesh#position} and {@link Mesh#geometry} are all assumed to be relative to this center position. * * @type {Float64Array} */ get origin() { return this._state.origin; } /** * Sets the 3D origin of the Mesh's {@link Geometry}'s vertex positions. * * When this is given, then {@link Mesh#matrix}, {@link Mesh#position} and {@link Mesh#geometry} are all assumed to be relative to this center position. * * @type {Float64Array} */ set origin(origin) { if (origin) { if (!this._state.origin) { this._state.origin = math.vec3(); } this._state.origin.set(origin); this._state.originHash = origin.join(); this._setAABBDirty(); this.scene._aabbDirty = true; } else { if (this._state.origin) { this._state.origin = null; this._state.originHash = null; this._setAABBDirty(); this.scene._aabbDirty = true; } } } /** * Gets the World-space origin for this Mesh. * * Deprecated and replaced by {@link Mesh#origin}. * * @deprecated * @type {Float64Array} */ get rtcCenter() { return this.origin; } /** * Sets the World-space origin for this Mesh. * * Deprecated and replaced by {@link Mesh#origin}. * * @deprecated * @type {Float64Array} */ set rtcCenter(rtcCenter) { this.origin = rtcCenter; } /** * The approximate number of triangles in this Mesh. * * @type {Number} */ get numTriangles() { return this._numTriangles; } /** * Gets if this Mesh is visible. * * Only rendered when {@link Mesh#visible} is ````true```` and {@link Mesh#culled} is ````false````. * * When {@link Mesh#isObject} and {@link Mesh#visible} are both ````true```` the Mesh will be * registered by {@link Mesh#id} in {@link Scene#visibleObjects}. * * @type {Boolean} */ get visible() { return this._state.visible; } /** * Sets if this Mesh is visible. * * Only rendered when {@link Mesh#visible} is ````true```` and {@link Mesh#culled} is ````false````. * * When {@link Mesh#isObject} and {@link Mesh#visible} are both ````true```` the Mesh will be * registered by {@link Mesh#id} in {@link Scene#visibleObjects}. * * @type {Boolean} */ set visible(visible) { visible = visible !== false; this._state.visible = visible; if (this._isObject) { this.scene._objectVisibilityUpdated(this, visible); } this.glRedraw(); } /** * Gets if this Mesh is xrayed. * * XRayed appearance is configured by the {@link EmphasisMaterial} referenced by {@link Mesh#xrayMaterial}. * * When {@link Mesh#isObject} and {@link Mesh#xrayed} are both ````true``` the Mesh will be * registered by {@link Mesh#id} in {@link Scene#xrayedObjects}. * * @type {Boolean} */ get xrayed() { return this._state.xrayed; } /** * Sets if this Mesh is xrayed. * * XRayed appearance is configured by the {@link EmphasisMaterial} referenced by {@link Mesh#xrayMaterial}. * * When {@link Mesh#isObject} and {@link Mesh#xrayed} are both ````true``` the Mesh will be * registered by {@link Mesh#id} in {@link Scene#xrayedObjects}. * * @type {Boolean} */ set xrayed(xrayed) { xrayed = !!xrayed; if (this._state.xrayed === xrayed) { return; } this._state.xrayed = xrayed; if (this._isObject) { this.scene._objectXRayedUpdated(this, xrayed); } this.glRedraw(); } /** * Gets if this Mesh is highlighted. * * Highlighted appearance is configured by the {@link EmphasisMaterial} referenced by {@link Mesh#highlightMaterial}. * * When {@link Mesh#isObject} and {@link Mesh#highlighted} are both ````true```` the Mesh will be * registered by {@link Mesh#id} in {@link Scene#highlightedObjects}. * * @type {Boolean} */ get highlighted() { return this._state.highlighted; } /** * Sets if this Mesh is highlighted. * * Highlighted appearance is configured by the {@link EmphasisMaterial} referenced by {@link Mesh#highlightMaterial}. * * When {@link Mesh#isObject} and {@link Mesh#highlighted} are both ````true```` the Mesh will be * registered by {@link Mesh#id} in {@link Scene#highlightedObjects}. * * @type {Boolean} */ set highlighted(highlighted) { highlighted = !!highlighted; if (highlighted === this._state.highlighted) { return; } this._state.highlighted = highlighted; if (this._isObject) { this.scene._objectHighlightedUpdated(this, highlighted); } this.glRedraw(); } /** * Gets if this Mesh is selected. * * Selected appearance is configured by the {@link EmphasisMaterial} referenced by {@link Mesh#selectedMaterial}. * * When {@link Mesh#isObject} and {@link Mesh#selected} are both ````true``` the Mesh will be * registered by {@link Mesh#id} in {@link Scene#selectedObjects}. * * @type {Boolean} */ get selected() { return this._state.selected; } /** * Sets if this Mesh is selected. * * Selected appearance is configured by the {@link EmphasisMaterial} referenced by {@link Mesh#selectedMaterial}. * * When {@link Mesh#isObject} and {@link Mesh#selected} are both ````true``` the Mesh will be * registered by {@link Mesh#id} in {@link Scene#selectedObjects}. * * @type {Boolean} */ set selected(selected) { selected = !!selected; if (selected === this._state.selected) { return; } this._state.selected = selected; if (this._isObject) { this.scene._objectSelectedUpdated(this, selected); } this.glRedraw(); } /** * Gets if this Mesh is edge-enhanced. * * Edge appearance is configured by the {@link EdgeMaterial} referenced by {@link Mesh#edgeMaterial}. * * @type {Boolean} */ get edges() { return this._state.edges; } /** * Sets if this Mesh is edge-enhanced. * * Edge appearance is configured by the {@link EdgeMaterial} referenced by {@link Mesh#edgeMaterial}. * * @type {Boolean} */ set edges(edges) { edges = !!edges; if (edges === this._state.edges) { return; } this._state.edges = edges; this.glRedraw(); } /** * Gets if this Mesh is culled. * * Only rendered when {@link Mesh#visible} is ````true```` and {@link Mesh#culled} is ````false````. * * @type {Boolean} */ get culled() { return this._state.culled; } /** * Sets if this Mesh is culled. * * Only rendered when {@link Mesh#visible} is ````true```` and {@link Mesh#culled} is ````false````. * * @type {Boolean} */ set culled(value) { this._state.culled = !!value; this.glRedraw(); } /** * Gets if this Mesh is clippable. * * Clipping is done by the {@link SectionPlane}s in {@link Scene#sectionPlanes}. * * @type {Boolean} */ get clippable() { return this._state.clippable; } /** * Sets if this Mesh is clippable. * * Clipping is done by the {@link SectionPlane}s in {@link Scene#sectionPlanes}. * * @type {Boolean} */ set clippable(value) { value = value !== false; if (this._state.clippable === value) { return; } this._state.clippable = value; this.glRedraw(); } /** * Gets if this Mesh included in boundary calculations. * * @type {Boolean} */ get collidable() { return this._state.collidable; } /** * Sets if this Mesh included in boundary calculations. * * @type {Boolean} */ set collidable(value) { value = value !== false; if (value === this._state.collidable) { return; } this._state.collidable = value; this._setAABBDirty(); this.scene._aabbDirty = true; } //------------------------------------------------------------------------------------------------------------------ // Entity members //------------------------------------------------------------------------------------------------------------------ /** * Gets if this Mesh is pickable. * * Picking is done via calls to {@link Scene#pick}. * * @type {Boolean} */ get pickable() { return this._state.pickable; } /** * Sets if this Mesh is pickable. * * Picking is done via calls to {@link Scene#pick}. * * @type {Boolean} */ set pickable(value) { value = value !== false; if (this._state.pickable === value) { return; } this._state.pickable = value; // No need to trigger a render; // state is only used when picking } /** * Gets if this Mesh casts shadows. * * @type {Boolean} */ get castsShadow() { return this._state.castsShadow; } /** * Sets if this Mesh casts shadows. * * @type {Boolean} */ set castsShadow(value) { value = value !== false; if (value === this._state.castsShadow) { return; } this._state.castsShadow = value; this.glRedraw(); } /** * Gets if this Mesh can have shadows cast upon it. * * @type {Boolean} */ get receivesShadow() { return this._state.receivesShadow; } /** * Sets if this Mesh can have shadows cast upon it. * * @type {Boolean} */ set receivesShadow(value) { value = value !== false; if (value === this._state.receivesShadow) { return; } this._state.receivesShadow = value; this._state.hash = value ? "/mod/rs;" : "/mod;"; this.fire("dirty", this); // Now need to (re)compile objectRenderers to include/exclude shadow mapping } /** * Gets if this Mesh can have Scalable Ambient Obscurance (SAO) applied to it. * * SAO is configured by {@link SAO}. * * @type {Boolean} * @abstract */ get saoEnabled() { return false; // TODO: Support SAO on Meshes } /** * Gets the RGB colorize color for this Mesh. * * Multiplies by rendered fragment colors. * * Each element of the color is in range ````[0..1]````. * * @type {Number[]} */ get colorize() { return this._state.colorize; } /** * Sets the RGB colorize color for this Mesh. * * Multiplies by rendered fragment colors. * * Each element of the color is in range ````[0..1]````. * * @type {Number[]} */ set colorize(value) { let colorize = this._state.colorize; if (!colorize) { colorize = this._state.colorize = new Float32Array(4); colorize[3] = 1; } if (value) { colorize[0] = value[0]; colorize[1] = value[1]; colorize[2] = value[2]; } else { colorize[0] = 1; colorize[1] = 1; colorize[2] = 1; } const colorized = (!!value); this.scene._objectColorizeUpdated(this, colorized); this.glRedraw(); } /** * Gets the opacity factor for this Mesh. * * This is a factor in range ````[0..1]```` which multiplies by the rendered fragment alphas. * * @type {Number} */ get opacity() { return this._state.colorize[3]; } /** * Sets the opacity factor for this Mesh. * * This is a factor in range ````[0..1]```` which multiplies by the rendered fragment alphas. * * @type {Number} */ set opacity(opacity) { let colorize = this._state.colorize; if (!colorize) { colorize = this._state.colorize = new Float32Array(4); colorize[0] = 1; colorize[1] = 1; colorize[2] = 1; } const opacityUpdated = (opacity !== null && opacity !== undefined); colorize[3] = opacityUpdated ? opacity : 1.0; this.scene._objectOpacityUpdated(this, opacityUpdated); this.glRedraw(); } /** * Gets if this Mesh is transparent. * @returns {Boolean} */ get transparent() { return this._material.alphaMode === 2 /* blend */ || this._state.colorize[3] < 1 } /** * Gets the Mesh's rendering order relative to other Meshes. * * Default value is ````0````. * * This can be set on multiple transparent Meshes, to make them render in a specific order for correct alpha blending. * * @type {Number} */ get layer() { return this._state.layer; } /** * Sets the Mesh's rendering order relative to other Meshes. * * Default value is ````0````. * * This can be set on multiple transparent Meshes, to make them render in a specific order for correct alpha blending. * * @type {Number} */ set layer(value) { // TODO: Only accept rendering layer in range [0...MAX_layer] value = value || 0; value = Math.round(value); if (value === this._state.layer) { return; } this._state.layer = value; this._renderer.needStateSort(); } /** * Gets if the Node's position is stationary. * * When true, will disable the effect of {@link Camera} translations for this Mesh, while still allowing it to rotate. This is useful for skyboxes. * * @type {Boolean} */ get stationary() { return this._state.stationary; } /** * Gets the Node's billboarding behaviour. * * Options are: * * ````"none"```` - (default) - No billboarding. * * ````"spherical"```` - Mesh is billboarded to face the viewpoint, rotating both vertically and horizontally. * * ````"cylindrical"```` - Mesh is billboarded to face the viewpoint, rotating only about its vertically axis. Use this mode for things like trees on a landscape. * @type {String} */ get billboard() { return this._state.billboard; } /** * Gets the Mesh's 3D World-space offset. * * Default value is ````[0,0,0]````. * * @type {Number[]} */ get offset() { return this._state.offset; } /** * Sets the Mesh's 3D World-space offset. * * The offset dynamically translates the Mesh in World-space. * * Default value is ````[0, 0, 0]````. * * Provide a null or undefined value to reset to the default value. * * @type {Number[]} */ set offset(value) { this._state.offset.set(value || [0, 0, 0]); this._setAABBDirty(); this.glRedraw(); } /** * Returns true to indicate that Mesh implements {@link Drawable}. * @final * @type {Boolean} */ get isDrawable() { return true; } /** * Property with final value ````true```` to indicate that xeokit should render this Mesh in sorted order, relative to other Meshes. * * The sort order is determined by {@link Mesh#stateSortCompare}. * * Sorting is essential for rendering performance, so that xeokit is able to avoid applying runs of the same state changes to the GPU, ie. can collapse them. * * @type {Boolean} */ get isStateSortable() { return true; } /** * Defines the appearance of this Mesh when xrayed. * * Mesh is xrayed when {@link Mesh#xrayed} is ````true````. * * Set to {@link Scene#xrayMaterial} by default. * * @type {EmphasisMaterial} */ get xrayMaterial() { return this._xrayMaterial; } /** * Defines the appearance of this Mesh when highlighted. * * Mesh is xrayed when {@link Mesh#highlighted} is ````true````. * * Set to {@link Scene#highlightMaterial} by default. * * @type {EmphasisMaterial} */ get highlightMaterial() { return this._highlightMaterial; } /** * Defines the appearance of this Mesh when selected. * * Mesh is xrayed when {@link Mesh#selected} is ````true````. * * Set to {@link Scene#selectedMaterial} by default. * * @type {EmphasisMaterial} */ get selectedMaterial() { return this._selectedMaterial; } /** * Defines the appearance of this Mesh when edges are enhanced. * * Mesh is xrayed when {@link Mesh#edges} is ````true````. * * Set to {@link Scene#edgeMaterial} by default. * * @type {EdgeMaterial} */ get edgeMaterial() { return this._edgeMaterial; } _checkBillboard(value) { value = value || "none"; if (value !== "spherical" && value !== "cylindrical" && value !== "none") { this.error("Unsupported value for 'billboard': " + value + " - accepted values are " + "'spherical', 'cylindrical' and 'none' - defaulting to 'none'."); value = "none"; } return value; } /** * Called by xeokit to compile shaders for this Mesh. * @private */ compile() { const drawHash = this._makeDrawHash(); if (this._state.drawHash !== drawHash) { this._state.drawHash = drawHash; this._putDrawRenderers(); this._drawRenderer = DrawRenderer.get(this); // this._shadowRenderer = ShadowRenderer.get(this); this._emphasisFillRenderer = EmphasisFillRenderer.get(this); this._emphasisEdgesRenderer = EmphasisEdgesRenderer.get(this); } const pickHash = this._makePickHash(); if (this._state.pickHash !== pickHash) { this._state.pickHash = pickHash; this._putPickRenderers(); this._pickMeshRenderer = PickMeshRenderer.get(this); } if (this._state.occluder) { const occlusionHash = this._makeOcclusionHash(); if (this._state.occlusionHash !== occlusionHash) { this._state.occlusionHash = occlusionHash; this._putOcclusionRenderer(); this._occlusionRenderer = OcclusionRenderer.get(this); } } } _setLocalMatrixDirty() { this._localMatrixDirty = true; this._setWorldMatrixDirty(); } _setWorldMatrixDirty() { this._worldMatrixDirty = true; this._worldNormalMatrixDirty = true; } _buildWorldMatrix() { const localMatrix = this.matrix; if (!this._parentNode) { for (let i = 0, len = localMatrix.length; i < len; i++) { this._worldMatrix[i] = localMatrix[i]; } } else { math.mulMat4(this._parentNode.worldMatrix, localMatrix, this._worldMatrix); } this._worldMatrixDirty = false; } _buildWorldNormalMatrix() { if (this._worldMatrixDirty) { this._buildWorldMatrix(); } if (!this._worldNormalMatrix) { this._worldNormalMatrix = math.mat4(); } // Note: order of inverse and transpose doesn't matter math.transposeMat4(this._worldMatrix, this._worldNormalMatrix); math.inverseMat4(this._worldNormalMatrix); this._worldNormalMatrixDirty = false; } _setAABBDirty() { if (this.collidable) { for (let node = this; node; node = node._parentNode) { node._aabbDirty = true; } } } _updateAABB() { this.scene._aabbDirty = true; if (!this._aabb) { this._aabb = math.AABB3(); } this._buildAABB(this.worldMatrix, this._aabb); // Mesh or VBOSceneModel this._aabbDirty = false; } _webglContextRestored() { if (this._drawRenderer) { this._drawRenderer.webglContextRestored(); } if (this._shadowRenderer) { this._shadowRenderer.webglContextRestored(); } if (this._emphasisFillRenderer) { this._emphasisFillRenderer.webglContextRestored(); } if (this._emphasisEdgesRenderer) { this._emphasisEdgesRenderer.webglContextRestored(); } if (this._pickMeshRenderer) { this._pickMeshRenderer.webglContextRestored(); } if (this._pickTriangleRenderer) { this._pickMeshRenderer.webglContextRestored(); } if (this._occlusionRenderer) { this._occlusionRenderer.webglContextRestored(); } } _makeDrawHash() { const scene = this.scene; const hash = [ scene.canvas.canvas.id, (scene.gammaInput ? "gi;" : ";") + (scene.gammaOutput ? "go" : ""), scene._lightsState.getHash(), scene._sectionPlanesState.getHash() ]; const state = this._state; if (state.stationary) { hash.push("/s"); } if (state.billboard === "none") { hash.push("/n"); } else if (state.billboard === "spherical") { hash.push("/s"); } else if (state.billboard === "cylindrical") { hash.push("/c"); } if (state.receivesShadow) { hash.push("/rs"); } hash.push(";"); return hash.join(""); } _makePickHash() { const scene = this.scene; const hash = [ scene.canvas.canvas.id, scene._sectionPlanesState.getHash() ]; const state = this._state; if (state.stationary) { hash.push("/s"); } if (state.billboard === "none") { hash.push("/n"); } else if (state.billboard === "spherical") { hash.push("/s"); } else if (state.billboard === "cylindrical") { hash.push("/c"); } hash.push(";"); return hash.join(""); } _makeOcclusionHash() { const scene = this.scene; const hash = [ scene.canvas.canvas.id, scene._sectionPlanesState.getHash() ]; const state = this._state; if (state.stationary) { hash.push("/s"); } if (state.billboard === "none") { hash.push("/n"); } else if (state.billboard === "spherical") { hash.push("/s"); } else if (state.billboard === "cylindrical") { hash.push("/c"); } hash.push(";"); return hash.join(""); } _buildAABB(worldMatrix, aabb) { math.transformOBB3(worldMatrix, this._geometry.obb, obb); math.OBB3ToAABB3(obb, aabb); const offset = this._state.offset; aabb[0] += offset[0]; aabb[1] += offset[1]; aabb[2] += offset[2]; aabb[3] += offset[0]; aabb[4] += offset[1]; aabb[5] += offset[2]; if (this._state.origin) { const origin = this._state.origin; aabb[0] += origin[0]; aabb[1] += origin[1]; aabb[2] += origin[2]; aabb[3] += origin[0]; aabb[4] += origin[1]; aabb[5] += origin[2]; } } /** * Rotates the Mesh about the given local axis by the given increment. * * @param {Number[]} axis Local axis about which to rotate. * @param {Number} angle Angle increment in degrees. */ rotate(axis, angle) { angleAxis[0] = axis[0]; angleAxis[1] = axis[1]; angleAxis[2] = axis[2]; angleAxis[3] = angle * math.DEGTORAD; math.angleAxisToQuaternion(angleAxis, q1); math.mulQuaternions(this.quaternion, q1, q2); this.quaternion = q2; this._setLocalMatrixDirty(); this._setAABBDirty(); this.glRedraw(); return this; } /** * Rotates the Mesh about the given World-space axis by the given increment. * * @param {Number[]} axis Local axis about which to rotate. * @param {Number} angle Angle increment in degrees. */ rotateOnWorldAxis(axis, angle) { angleAxis[0] = axis[0]; angleAxis[1] = axis[1]; angleAxis[2] = axis[2]; angleAxis[3] = angle * math.DEGTORAD; math.angleAxisToQuaternion(angleAxis, q1); math.mulQuaternions(q1, this.quaternion, q1); //this.quaternion.premultiply(q1); return this; } /** * Rotates the Mesh about the local X-axis by the given increment. * * @param {Number} angle Angle increment in degrees. */ rotateX(angle) { return this.rotate(xAxis, angle); } /** * Rotates the Mesh about the local Y-axis by the given increment. * * @param {Number} angle Angle increment in degrees. */ rotateY(angle) { return this.rotate(yAxis, angle); } /** * Rotates the Mesh about the local Z-axis by the given increment. * * @param {Number} angle Angle increment in degrees. */ rotateZ(angle) { return this.rotate(zAxis, angle); } /** * Translates the Mesh along local space vector by the given increment. * * @param {Number[]} axis Normalized local space 3D vector along which to translate. * @param {Number} distance