src/viewer/scene/mesh/Mesh.js
/**
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 {createProgramVariablesState} from '../webgl/WebGLRenderer.js';
import {DrawShaderSource} from "./draw/DrawShaderSource.js";
import {LambertShaderSource} from "./draw/LambertShaderSource.js";
import {EmphasisShaderSource} from "./emphasis/EmphasisShaderSource.js";
import {PickMeshShaderSource} from "./pick/PickMeshShaderSource.js";
import {PickTriangleShaderSource} from "./pick/PickTriangleShaderSource.js";
import {OcclusionShaderSource} from "./occlusion/OcclusionShaderSource.js";
import {ShadowShaderSource} from "./shadow/ShadowShaderSource.js";
import {geometryCompressionUtils} from '../math/geometryCompressionUtils.js';
import {RenderFlags} from "../webgl/RenderFlags.js";
import {stats} from '../stats.js';
import {Map} from "../utils/Map.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();
const ids = new Map({});
const renderersCache = { };
/**
* @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}
*/
export 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: "",
pickOcclusionHash: "",
offset: math.vec3(),
origin: null,
originHash: null,
isUI: cfg.isUI
});
const material = cfg.material ? this._checkComponent2(["PhongMaterial", "MetallicMaterial", "SpecularMaterial", "LambertMaterial"], cfg.material) : this.scene.material;
const mesh = this;
const wrapRenderer = (getProgramSetup) => {
let instance = null;
const ensureInstance = () => {
if (! instance) {
const programVariablesState = createProgramVariablesState();
const createAttribute = programVariablesState.programVariables.createAttribute;
const geometryState = mesh._geometry._state;
const attributes = {
position: createAttribute("vec3", "position"),
color: geometryState.colorsBuf && createAttribute("vec4", "color"),
pickColor: createAttribute("vec4", "pickColor"),
uv: geometryState.uvBuf && createAttribute("vec2", "uv"),
normal: (geometryState.autoVertexNormals || geometryState.normalsBuf) && [ "triangles", "triangle-strip", "triangle-fan" ].includes(geometryState.primitiveName) && createAttribute("vec3", "normal")
};
const lazyShaderVariable = function(name) {
const variable = {
toString: () => {
variable.needed = true;
return name;
}
};
return variable;
};
const worldNormal = attributes.normal && lazyShaderVariable("worldNormal");
const viewNormal = worldNormal && lazyShaderVariable("viewNormal");
const decodedUv = attributes.uv && lazyShaderVariable("decodedUv");
const programSetup = getProgramSetup(
programVariablesState.programVariables,
{
attributes: {
position: {
world: "worldPosition",
view: "viewPosition"
},
color: attributes.color,
pickColor: attributes.pickColor,
uv: decodedUv,
normal: attributes.normal && {
world: worldNormal,
view: viewNormal
}
},
viewMatrix: "viewMatrix2"
});
const hash = [
programSetup.programName,
mesh.scene.canvas.canvas.id,
mesh.scene._sectionPlanesState.getHash(),
mesh._geometry._state.hash
].concat(programSetup.getHash()).join(";");
if (! (hash in renderersCache)) {
const renderer = instantiateMeshRenderer(mesh, attributes, { decodedUv: decodedUv, worldNormal: worldNormal, viewNormal: viewNormal }, programSetup, programVariablesState);
if (renderer.errors) {
console.log(renderer.errors.join("\n"));
return;
}
const id = ids.addItem({});
renderersCache[hash] = {
drawMesh: renderer.drawMesh,
id: id,
useCount: 0,
delete: () => {
ids.removeItem(id);
renderer.destroy();
delete renderersCache[hash];
stats.memory.programs--;
}
};
stats.memory.programs++;
}
instance = renderersCache[hash];
instance.useCount++;
}
};
return {
get: ensureInstance,
getId: () => instance.id,
put: () => { if (instance) { if (--instance.useCount === 0) { instance.delete(); } instance = null; } },
drawMesh: (frameCtx, mesh, material) => {
ensureInstance();
instance && instance.drawMesh(frameCtx, mesh, material);
}
};
};
const scene = mesh.scene;
const emphasisShaderSourceMaker = isFill => (vars, geo) => EmphasisShaderSource(mesh._state.hash, vars, geo, scene, isFill);
this._renderers = {
_drawRenderer: wrapRenderer((vars, geo) => ((material.type === "LambertMaterial")
? LambertShaderSource(mesh._state.drawHash, vars, geo, material, scene)
: DrawShaderSource (mesh._state.drawHash, vars, geo, material, scene))),
_shadowRenderer: wrapRenderer((vars) => ShadowShaderSource(mesh._state.hash, vars)),
_emphasisEdgesRenderer: wrapRenderer(emphasisShaderSourceMaker(false)),
_emphasisFillRenderer: wrapRenderer(emphasisShaderSourceMaker(true)),
_pickMeshRenderer: wrapRenderer((vars) => PickMeshShaderSource(mesh._state.hash, vars)),
_pickTriangleRenderer: wrapRenderer((vars, geo) => PickTriangleShaderSource(mesh._state.hash, vars, geo)),
_occlusionRenderer: wrapRenderer((vars) => OcclusionShaderSource(mesh._state.pickOcclusionHash, vars))
};
this._geometry = cfg.geometry ? this._checkComponent2(["ReadableGeometry", "VBOGeometry"], cfg.geometry) : this.scene.geometry;
this._material = 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 scene = this.scene;
const state = this._state;
const hash = [
scene.canvas.canvas.id,
scene._sectionPlanesState.getHash()
];
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(";");
const drawHash = hash.concat([
scene.gammaOutput ? "go" : "",
scene._lightsState.getHash(),
state.receivesShadow ? "/rs" : ""
]).join("");
if (this._state.drawHash !== drawHash) {
this._state.drawHash = drawHash;
this._renderers._drawRenderer.put();
this._renderers._shadowRenderer.put();
this._renderers._emphasisFillRenderer.put();
this._renderers._emphasisEdgesRenderer.put();
this._renderers._drawRenderer.get();
// this._renderers._shadowRenderer.get();
this._renderers._emphasisFillRenderer.get();
this._renderers._emphasisEdgesRenderer.get();
}
const pickOcclusionHash = hash.join("");
if (this._state.pickOcclusionHash !== pickOcclusionHash) {
this._state.pickOcclusionHash = pickOcclusionHash;
this._renderers._pickMeshRenderer.put();
this._renderers._pickTriangleRenderer.put();
this._renderers._pickMeshRenderer.get();
if (this._state.occluder) {
this._renderers._occlusionRenderer.put();
this._renderers._occlusionRenderer.get();
}
}
}
_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;
}
_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 Distance to translate along the vector.
*/
translate(axis, distance) {
math.vec3ApplyQuaternion(this.quaternion, axis, veca);
math.mulVec3Scalar(veca, distance, vecb);
math.addVec3(this.position, vecb, this.position);
this._setLocalMatrixDirty();
this._setAABBDirty();
this.glRedraw();
return this;
}
//------------------------------------------------------------------------------------------------------------------
// Drawable members
//------------------------------------------------------------------------------------------------------------------
/**
* Translates the Mesh along the local X-axis by the given increment.
*
* @param {Number} distance Distance to translate along the X-axis.
*/
translateX(distance) {
return this.translate(xAxis, distance);
}
/**
* Translates the Mesh along the local Y-axis by the given increment.
*
* @param {Number} distance Distance to translate along the Y-axis.
*/
translateY(distance) {
return this.translate(yAxis, distance);
}
/**
* Translates the Mesh along the local Z-axis by the given increment.
*
* @param {Number} distance Distance to translate along the Z-axis.
*/
translateZ(distance) {
return this.translate(zAxis, distance);
}
/**
* Comparison function used by the renderer to determine the order in which xeokit should render the Mesh, relative to to other Meshes.
*
* xeokit requires this method because Mesh implements {@link Drawable}.
*
* Sorting is essential for rendering performance, so that xeokit is able to avoid needlessly applying runs of the same rendering state changes to the GPU, ie. can collapse them.
*
* @param {Mesh} mesh1
* @param {Mesh} mesh2
* @returns {number}
*/
stateSortCompare(mesh1, mesh2) {
return (mesh1._state.layer - mesh2._state.layer)
|| (mesh1._renderers._drawRenderer.getId() - mesh2._renderers._drawRenderer.getId()) // Program state
|| (mesh1._material._state.id - mesh2._material._state.id) // Material state
|| (mesh1._geometry._state.id - mesh2._geometry._state.id); // Geometry state
}
/** @private */
rebuildRenderFlags() {
this.renderFlags.reset();
if (!this._getActiveSectionPlanes()) {
this.renderFlags.culled = true;
return;
}
this.renderFlags.numLayers = 1;
this.renderFlags.numVisibleLayers = 1;
this.renderFlags.visibleLayers[0] = 0;
this._updateRenderFlags();
}
/**
* @private
*/
_updateRenderFlags() {
const renderFlags = this.renderFlags;
const state = this._state;
if (state.xrayed) {
const xrayMaterial = this._xrayMaterial._state;
if (xrayMaterial.fill) {
if (xrayMaterial.fillAlpha < 1.0) {
renderFlags.xrayedSilhouetteTransparent = true;
} else {
renderFlags.xrayedSilhouetteOpaque = true;
}
}
if (xrayMaterial.edges) {
if (xrayMaterial.edgeAlpha < 1.0) {
renderFlags.xrayedEdgesTransparent = true;
} else {
renderFlags.xrayedEdgesOpaque = true;
}
}
} else {
const normalMaterial = this._material._state;
if (normalMaterial.alpha < 1.0 || state.colorize[3] < 1.0) {
renderFlags.colorTransparent = true;
} else {
renderFlags.colorOpaque = true;
}
if (state.edges) {
const edgeMaterial = this._edgeMaterial._state;
if (edgeMaterial.alpha < 1.0) {
renderFlags.edgesTransparent = true;
} else {
renderFlags.edgesOpaque = true;
}
}
if (state.selected) {
const selectedMaterial = this._selectedMaterial._state;
if (selectedMaterial.fill) {
if (selectedMaterial.fillAlpha < 1.0) {
renderFlags.selectedSilhouetteTransparent = true;
} else {
renderFlags.selectedSilhouetteOpaque = true;
}
}
if (selectedMaterial.edges) {
if (selectedMaterial.edgeAlpha < 1.0) {
renderFlags.selectedEdgesTransparent = true;
} else {
renderFlags.selectedEdgesOpaque = true;
}
}
} else if (state.highlighted) {
const highlightMaterial = this._highlightMaterial._state;
if (highlightMaterial.fill) {
if (highlightMaterial.fillAlpha < 1.0) {
renderFlags.highlightedSilhouetteTransparent = true;
} else {
renderFlags.highlightedSilhouetteOpaque = true;
}
}
if (highlightMaterial.edges) {
if (highlightMaterial.edgeAlpha < 1.0) {
renderFlags.highlightedEdgesTransparent = true;
} else {
renderFlags.highlightedEdgesOpaque = true;
}
}
}
}
}
_getActiveSectionPlanes() {
if (this._state.clippable) {
const sectionPlanes = this.scene._sectionPlanesState.sectionPlanes;
const numSectionPlanes = sectionPlanes.length;
if (numSectionPlanes > 0) {
for (let i = 0; i < numSectionPlanes; i++) {
const sectionPlane = sectionPlanes[i];
const renderFlags = this.renderFlags;
if (!sectionPlane.active) {
renderFlags.sectionPlanesActivePerLayer[i] = false;
} else {
if (this._state.origin) {
const intersect = math.planeAABB3Intersect(sectionPlane.dir, sectionPlane.dist, this.aabb);
const outside = (intersect === -1);
if (outside) {
return false;
}
const intersecting = (intersect === 0);
renderFlags.sectionPlanesActivePerLayer[i] = intersecting;
} else {
renderFlags.sectionPlanesActivePerLayer[i] = true;
}
}
}
}
}
return true;
}
// ---------------------- NORMAL RENDERING -----------------------------------
/** @private */
drawColorOpaque(frameCtx) {
this._renderers._drawRenderer.drawMesh(frameCtx, this, this._material);
}
/** @private */
drawColorTransparent(frameCtx) {
this._renderers._drawRenderer.drawMesh(frameCtx, this, this._material);
}
// ---------------------- RENDERING SAO POST EFFECT TARGETS --------------
// TODO
// ---------------------- EMPHASIS RENDERING -----------------------------------
/** @private */
drawSilhouetteXRayed(frameCtx) {
this._renderers._emphasisFillRenderer.drawMesh(frameCtx, this, this._xrayMaterial);
}
/** @private */
drawSilhouetteHighlighted(frameCtx) {
this._renderers._emphasisFillRenderer.drawMesh(frameCtx, this, this._highlightMaterial);
}
/** @private */
drawSilhouetteSelected(frameCtx) {
this._renderers._emphasisFillRenderer.drawMesh(frameCtx, this, this._selectedMaterial);
}
// ---------------------- EDGES RENDERING -----------------------------------
/** @private */
drawEdgesColorOpaque(frameCtx) {
this._renderers._emphasisEdgesRenderer.drawMesh(frameCtx, this, this._edgeMaterial);
}
/** @private */
drawEdgesColorTransparent(frameCtx) {
this._renderers._emphasisEdgesRenderer.drawMesh(frameCtx, this, this._edgeMaterial);
}
/** @private */
drawEdgesXRayed(frameCtx) {
this._renderers._emphasisEdgesRenderer.drawMesh(frameCtx, this, this._xrayMaterial);
}
/** @private */
drawEdgesHighlighted(frameCtx) {
this._renderers._emphasisEdgesRenderer.drawMesh(frameCtx, this, this._highlightMaterial);
}
/** @private */
drawEdgesSelected(frameCtx) {
this._renderers._emphasisEdgesRenderer.drawMesh(frameCtx, this, this._selectedMaterial);
}
// ---------------------- OCCLUSION CULL RENDERING -----------------------------------
/** @private */
drawOcclusion(frameCtx) {
this._renderers._occlusionRenderer.drawMesh(frameCtx, this, this._material);
}
// ---------------------- SHADOW BUFFER RENDERING -----------------------------------
/** @private */
drawShadow(frameCtx) {
this._renderers._shadowRenderer.drawMesh(frameCtx, this, this._material);
}
// ---------------------- PICKING RENDERING ----------------------------------
/** @private */
drawPickMesh(frameCtx) {
this._renderers._pickMeshRenderer.drawMesh(frameCtx, this, this._material);
}
/** @private
*/
canPickTriangle() {
return this._geometry.isReadableGeometry; // VBOGeometry does not support surface picking because it has no geometry data in browser memory
}
/** @private */
drawPickTriangles(frameCtx) {
this._renderers._pickTriangleRenderer.drawMesh(frameCtx, this, this._material);
}
/** @private */
pickTriangleSurface(pickViewMatrix, pickProjMatrix, projection, pickResult) {
pickTriangleSurface(this, pickViewMatrix, pickProjMatrix, projection, pickResult);
}
/** @private */
drawPickVertices(frameCtx) {
}
/**
* @private
* @returns {PerformanceNode}
*/
delegatePickedEntity() {
return this;
}
//------------------------------------------------------------------------------------------------------------------
// Component members
//------------------------------------------------------------------------------------------------------------------
/**
* Destroys this Mesh.
*/
destroy() {
super.destroy(); // xeokit.Object
Object.values(this._renderers).forEach(r => r.put());
this.scene._renderer.putPickID(this._state.pickID); // TODO: somehow puch this down into xeokit framework?
if (this._isObject) {
this.scene._deregisterObject(this);
if (this._visible) {
this.scene._objectVisibilityUpdated(this, false, false);
}
if (this._xrayed) {
this.scene._objectXRayedUpdated(this, false, false);
}
if (this._selected) {
this.scene._objectSelectedUpdated(this, false, false);
}
if (this._highlighted) {
this.scene._objectHighlightedUpdated(this, false, false);
}
this.scene._objectColorizeUpdated(this, false);
this.scene._objectOpacityUpdated(this, false);
if (this.offset.some((v) => v !== 0))
this.scene._objectOffsetUpdated(this, false);
}
if (this._isModel) {
this.scene._deregisterModel(this);
}
this.glRedraw();
}
}
const pickTriangleSurface = (function () {
// Cached vars to avoid garbage collection
const localRayOrigin = math.vec3();
const localRayDir = math.vec3();
const positionA = math.vec3();
const positionB = math.vec3();
const positionC = math.vec3();
const triangleVertices = math.vec3();
const position = math.vec4();
const worldPos = math.vec3();
const viewPos = math.vec3();
const bary = math.vec3();
const normalA = math.vec3();
const normalB = math.vec3();
const normalC = math.vec3();
const uva = math.vec3();
const uvb = math.vec3();
const uvc = math.vec3();
const tempVec4a = math.vec4();
const tempVec4b = math.vec4();
const tempVec4c = math.vec4();
const tempVec3 = math.vec3();
const tempVec3b = math.vec3();
const tempVec3c = math.vec3();
const tempVec3d = math.vec3();
const tempVec3e = math.vec3();
const tempVec3f = math.vec3();
const tempVec3g = math.vec3();
const tempVec3h = math.vec3();
const tempVec3i = math.vec3();
const tempVec3j = math.vec3();
const tempVec3k = math.vec3();
return function (mesh, pickViewMatrix, pickProjMatrix, projection, pickResult) {
var primIndex = pickResult.primIndex;
if (primIndex !== undefined && primIndex !== null && primIndex > -1) {
const geometry = mesh.geometry._state;
const scene = mesh.scene;
const camera = scene.camera;
const canvas = scene.canvas;
if (geometry.primitiveName === "triangles") {
// Triangle picked; this only happens when the
// Mesh has a Geometry that has primitives of type "triangle"
pickResult.primitive = "triangle";
// Get the World-space positions of the triangle's vertices
const i = primIndex; // Indicates the first triangle index in the indices array
const indices = geometry.indices; // Indices into geometry arrays, not into shared VertexBufs
const positions = geometry.positions;
let ia3;
let ib3;
let ic3;
if (indices) {
var ia = indices[i + 0];
var ib = indices[i + 1];
var ic = indices[i + 2];
triangleVertices[0] = ia;
triangleVertices[1] = ib;
triangleVertices[2] = ic;
pickResult.indices = triangleVertices;
ia3 = ia * 3;
ib3 = ib * 3;
ic3 = ic * 3;
} else {
ia3 = i * 3;
ib3 = ia3 + 3;
ic3 = ib3 + 3;
}
positionA[0] = positions[ia3 + 0];
positionA[1] = positions[ia3 + 1];
positionA[2] = positions[ia3 + 2];
positionB[0] = positions[ib3 + 0];
positionB[1] = positions[ib3 + 1];
positionB[2] = positions[ib3 + 2];
positionC[0] = positions[ic3 + 0];
positionC[1] = positions[ic3 + 1];
positionC[2] = positions[ic3 + 2];
if (geometry.compressGeometry) {
// Decompress vertex positions
const positionsDecodeMatrix = geometry.positionsDecodeMatrix;
if (positionsDecodeMatrix) {
geometryCompressionUtils.decompressPosition(positionA, positionsDecodeMatrix, positionA);
geometryCompressionUtils.decompressPosition(positionB, positionsDecodeMatrix, positionB);
geometryCompressionUtils.decompressPosition(positionC, positionsDecodeMatrix, positionC);
}
}
// Attempt to ray-pick the triangle in local space
if (pickResult.canvasPos) {
math.canvasPosToLocalRay(canvas.canvas, mesh.origin ? createRTCViewMat(pickViewMatrix, mesh.origin) : pickViewMatrix, pickProjMatrix, projection, mesh.worldMatrix, pickResult.canvasPos, localRayOrigin, localRayDir);
} else if (pickResult.origin && pickResult.direction) {
math.worldRayToLocalRay(mesh.worldMatrix, pickResult.origin, pickResult.direction, localRayOrigin, localRayDir);
}
math.normalizeVec3(localRayDir);
math.rayPlaneIntersect(localRayOrigin, localRayDir, positionA, positionB, positionC, position);
// Get Local-space cartesian coordinates of the ray-triangle intersection
pickResult.localPos = position;
pickResult.position = position;
// Get interpolated World-space coordinates
// Need to transform homogeneous coords
tempVec4a[0] = position[0];
tempVec4a[1] = position[1];
tempVec4a[2] = position[2];
tempVec4a[3] = 1;
// Get World-space cartesian coordinates of the ray-triangle intersection
math.transformVec4(mesh.worldMatrix, tempVec4a, tempVec4b);
worldPos[0] = tempVec4b[0];
worldPos[1] = tempVec4b[1];
worldPos[2] = tempVec4b[2];
if (pickResult.canvasPos && mesh.origin) {
worldPos[0] += mesh.origin[0];
worldPos[1] += mesh.origin[1];
worldPos[2] += mesh.origin[2];
}
pickResult.worldPos = worldPos;
// Get View-space cartesian coordinates of the ray-triangle intersection
math.transformVec4(camera.matrix, tempVec4b, tempVec4c);
viewPos[0] = tempVec4c[0];
viewPos[1] = tempVec4c[1];
viewPos[2] = tempVec4c[2];
pickResult.viewPos = viewPos;
// Get barycentric coordinates of the ray-triangle intersection
math.cartesianToBarycentric(position, positionA, positionB, positionC, bary);
pickResult.bary = bary;
// Get interpolated normal vector
const normals = geometry.normals;
if (normals) {
if (geometry.compressGeometry) {
// Decompress vertex normals
const ia2 = ia * 3;
const ib2 = ib * 3;
const ic2 = ic * 3;
geometryCompressionUtils.decompressNormal(normals.subarray(ia2, ia2 + 2), normalA);
geometryCompressionUtils.decompressNormal(normals.subarray(ib2, ib2 + 2), normalB);
geometryCompressionUtils.decompressNormal(normals.subarray(ic2, ic2 + 2), normalC);
} else {
normalA[0] = normals[ia3];
normalA[1] = normals[ia3 + 1];
normalA[2] = normals[ia3 + 2];
normalB[0] = normals[ib3];
normalB[1] = normals[ib3 + 1];
normalB[2] = normals[ib3 + 2];
normalC[0] = normals[ic3];
normalC[1] = normals[ic3 + 1];
normalC[2] = normals[ic3 + 2];
}
const normal = math.addVec3(math.addVec3(
math.mulVec3Scalar(normalA, bary[0], tempVec3),
math.mulVec3Scalar(normalB, bary[1], tempVec3b), tempVec3c),
math.mulVec3Scalar(normalC, bary[2], tempVec3d), tempVec3e);
pickResult.worldNormal = math.normalizeVec3(math.transformVec3(mesh.worldNormalMatrix, normal, tempVec3f));
}
// Get interpolated UV coordinates
const uvs = geometry.uv;
if (uvs) {
uva[0] = uvs[(ia * 2)];
uva[1] = uvs[(ia * 2) + 1];
uvb[0] = uvs[(ib * 2)];
uvb[1] = uvs[(ib * 2) + 1];
uvc[0] = uvs[(ic * 2)];
uvc[1] = uvs[(ic * 2) + 1];
if (geometry.compressGeometry) {
// Decompress vertex UVs
const uvDecodeMatrix = geometry.uvDecodeMatrix;
if (uvDecodeMatrix) {
geometryCompressionUtils.decompressUV(uva, uvDecodeMatrix, uva);
geometryCompressionUtils.decompressUV(uvb, uvDecodeMatrix, uvb);
geometryCompressionUtils.decompressUV(uvc, uvDecodeMatrix, uvc);
}
}
pickResult.uv = math.addVec3(
math.addVec3(
math.mulVec2Scalar(uva, bary[0], tempVec3g),
math.mulVec2Scalar(uvb, bary[1], tempVec3h), tempVec3i),
math.mulVec2Scalar(uvc, bary[2], tempVec3j), tempVec3k);
}
}
}
}
})();
const instantiateMeshRenderer = (mesh, attributes, auxVariables, programSetup, programVariablesState) => {
const programVariables = programVariablesState.programVariables;
const decodedUv = auxVariables.decodedUv;
const worldNormal = auxVariables.worldNormal;
const viewNormal = auxVariables.viewNormal;
const scene = mesh.scene;
const gl = scene.canvas.gl;
const meshStateBackground = mesh._state.background;
const geometryState = mesh._geometry._state;
const quantizedGeometry = geometryState.compressGeometry;
const isPoints = geometryState.primitiveName === "points";
const pointSize = programVariables.createUniform("float", "pointSize");
const modelMatrix = programVariables.createUniform("mat4", "modelMatrix");
const modelNormalMatrix = programVariables.createUniform("mat4", "modelNormalMatrix");
const offset = programVariables.createUniform("vec3", "offset");
const scale = programVariables.createUniform("vec3", "scale");
const positionsDecodeMatrix = programVariables.createUniform("mat4", "positionsDecodeMatrix");
const uvDecodeMatrix = programVariables.createUniform("mat3", "uvDecodeMatrix");
const viewMatrix = programVariables.createUniform("mat4", "viewMatrix");
const viewNormalMatrix = programVariables.createUniform("mat4", "viewNormalMatrix");
const projMatrix = programVariables.createUniform("mat4", "projMatrix");
const billboard = mesh.billboard;
const isBillboard = (! programSetup.dontBillboardAnything) && ((billboard === "spherical") || (billboard === "cylindrical"));
const stationary = mesh.stationary;
const defineBillboard = isBillboard && ((name, src) => [
`mat4 ${name}(in mat4 matIn) {`,
" mat4 mat = matIn;",
` mat[0].xyz = vec3(${scale}[0], 0.0, 0.0);`,
...((billboard === "spherical") ? [ ` mat[1].xyz = vec3(0.0, ${scale}[1], 0.0);` ] : [ ]),
" mat[2].xyz = vec3(0.0, 0.0, 1.0);",
" return mat;",
"}",
].forEach(l => src.push(l)));
const billboardIfApplicable = (function() {
const billboardVert = defineBillboard && programVariables.createVertexDefinition("billboard", defineBillboard);
return v => billboardVert ? `${billboardVert}(${v})` : v;
})();
const billboardIfApplicableFrag = (function() {
const billboardFrag = defineBillboard && programVariables.createFragmentDefinition("billboard", defineBillboard);
return v => billboardFrag ? `${billboardFrag}(${v})` : v;
})();
const fragmentOutputsSetup = [ ];
if (programSetup.dontBillboardAnything) {
fragmentOutputsSetup.push(`mat4 viewMatrix2 = ${viewMatrix};`);
} else {
fragmentOutputsSetup.push(`mat4 viewMatrix1 = ${viewMatrix};`);
if (stationary) {
fragmentOutputsSetup.push("viewMatrix1[3].xyz = vec3(0.0, 0.0, 0.0);");
} else if (meshStateBackground) {
fragmentOutputsSetup.push("viewMatrix1[3] = vec4(0.0, 0.0, 0.0, 1.0);");
}
fragmentOutputsSetup.push(`mat4 viewMatrix2 = ${billboardIfApplicableFrag("viewMatrix1")};`);
}
const clipPos = "clipPos";
const getVertexData = function() {
const viewNormalDefinition = viewNormal && viewNormal.needed && `vec3 ${viewNormal} = normalize((${billboardIfApplicable(viewNormalMatrix)} * vec4(${worldNormal}, 0.0)).xyz);`;
const src = [ ];
src.push(`vec4 localPosition = vec4(${attributes.position}, 1.0);`);
if (quantizedGeometry) {
src.push(`localPosition = ${positionsDecodeMatrix} * localPosition;`);
}
src.push(`vec4 worldPosition = ${billboardIfApplicable(modelMatrix)} * localPosition;`);
src.push(`worldPosition.xyz = worldPosition.xyz + ${offset};`);
if (programSetup.dontBillboardAnything) {
src.push(`vec4 viewPosition = ${viewMatrix} * worldPosition;`);
} else {
src.push(`mat4 viewMatrix1 = ${viewMatrix};`);
if (stationary) {
src.push("viewMatrix1[3].xyz = vec3(0.0, 0.0, 0.0);");
} else if (meshStateBackground) {
src.push("viewMatrix1[3] = vec4(0.0, 0.0, 0.0, 1.0);");
}
src.push(`mat4 viewMatrix2 = ${billboardIfApplicable("viewMatrix1")};`);
src.push(`vec4 viewPosition = ${(isBillboard
? `${billboardIfApplicable(`viewMatrix1 * ${modelMatrix}`)} * localPosition`
: "viewMatrix2 * worldPosition")};`);
}
decodedUv && decodedUv.needed && src.push(`vec2 ${decodedUv} = ${quantizedGeometry ? `(${uvDecodeMatrix} * vec3(${attributes.uv}, 1.0)).xy` : attributes.uv};`);
if (worldNormal && worldNormal.needed) {
const localNormal = quantizedGeometry ? `${programVariables.commonLibrary.octDecode}(${attributes.normal}.xy)` : attributes.normal;
src.push(`vec3 ${worldNormal} = (${billboardIfApplicable(modelNormalMatrix)} * vec4(${localNormal}, 0.0)).xyz;`);
}
viewNormalDefinition && src.push(viewNormalDefinition);
src.push(`vec4 ${clipPos} = ${projMatrix} * viewPosition;`);
return src;
};
const clippable = programVariables.createUniform("bool", "clippable");
const [ program, errors ] = programVariablesState.buildProgram(
gl,
programSetup.programName,
{
appendFragmentOutputs: (src, getGammaOutputExpression, gl_FragCoord, sliceColorOr) => {
fragmentOutputsSetup.forEach(line => src.push(line));
programSetup.appendFragmentOutputs(src, getGammaOutputExpression, gl_FragCoord, sliceColorOr);
},
clippableTest: () => clippable,
clippingCaps: programSetup.clippingCaps,
clipPos: meshStateBackground ? `${clipPos}.xyww` : clipPos,
crossSections: scene.crossSections,
discardPoints: isPoints && programSetup.discardPoints,
getGammaFactor: scene.gammaOutput && (() => scene.gammaFactor),
getLogDepth: (! programSetup.dontGetLogDepth) && scene.logarithmicDepthBufferEnabled && (vFragDepth => vFragDepth),
getPointSize: programSetup.setupPointSize && isPoints && (() => pointSize),
getVertexData: getVertexData,
projMatrix: projMatrix,
sectionPlanesState: scene._sectionPlanesState,
testPerspectiveForGl_FragDepth: true,
usePickClipPos: programSetup.isPick,
worldPositionAttribute: "worldPosition"
});
if (errors) {
return { errors: errors };
} else {
const inputSetters = program.inputSetters;
let lastMaterialId = null;
let lastGeometryId = null;
return {
destroy: () => program.destroy(),
drawMesh: (frameCtx, mesh, material) => {
if (programSetup.skipIfTransparent && (material.alpha < 1.0)) {
return;
}
const materialState = material._state;
const meshState = mesh._state;
const geometry = mesh._geometry;
const geometryState = geometry._state;
const viewParams = frameCtx.viewParams;
const actsAsBackground = programSetup.canActAsBackground && meshStateBackground;
if (frameCtx.lastProgramId !== program.id) {
frameCtx.lastProgramId = program.id;
program.bind();
frameCtx.useProgram++;
lastMaterialId = null;
lastGeometryId = null;
if (actsAsBackground) {
gl.depthFunc(gl.LEQUAL);
}
}
if (materialState.id !== lastMaterialId) {
if (frameCtx.backfaces !== materialState.backfaces) {
if (materialState.backfaces) {
gl.disable(gl.CULL_FACE);
} else {
gl.enable(gl.CULL_FACE);
}
frameCtx.backfaces = materialState.backfaces;
}
if ((! programSetup.dontSetFrontFace) && (frameCtx.frontface !== materialState.frontface)) {
gl.frontFace(materialState.frontface ? gl.CCW : gl.CW);
frameCtx.frontface = materialState.frontface;
}
if (programSetup.drawEdges && (frameCtx.lineWidth !== materialState.edgeWidth)) {
gl.lineWidth(materialState.edgeWidth);
frameCtx.lineWidth = materialState.edgeWidth;
}
if (programSetup.setsLineWidth && (frameCtx.lineWidth !== materialState.lineWidth)) {
gl.lineWidth(materialState.lineWidth);
frameCtx.lineWidth = materialState.lineWidth;
}
lastMaterialId = materialState.id;
}
const setUniforms = (projMat, viewMat) => {
const setUni = (u, v) => (u.setInputValue && u.setInputValue(v));
setUni(pointSize, material.pointSize);
setUni(modelMatrix, mesh.worldMatrix);
setUni(modelNormalMatrix, mesh.worldNormalMatrix);
setUni(offset, mesh.offset);
setUni(scale, mesh.scale);
setUni(positionsDecodeMatrix, geometryState.positionsDecodeMatrix);
setUni(uvDecodeMatrix, geometryState.uvDecodeMatrix);
setUni(viewMatrix, viewMat);
setUni(viewNormalMatrix, viewParams.viewNormalMatrix);
setUni(projMatrix, projMat);
setUni(clippable, mesh.clippable);
inputSetters.setUniforms(frameCtx, {
material: material,
meshColorize: mesh.colorize,
meshPickID: mesh._state.pickID,
mesh: {
origin: mesh.origin,
renderFlags: { sectionPlanesActivePerLayer: mesh.renderFlags.sectionPlanesActivePerLayer }
},
view: { far: viewParams.far }
});
};
const origin = (! programSetup.useShadowView) && mesh.origin;
setUniforms(viewParams.projMatrix, origin ? frameCtx.getRTCViewMatrix(meshState.originHash, origin) : viewParams.viewMatrix);
const setAttributes = (triangleGeometry) => {
const setAttr = (a, b) => {
if (a && a.setInputValue && b) {
a.setInputValue(b);
frameCtx.bindArray++;
}
};
setAttr(attributes.position, (triangleGeometry || geometryState).positionsBuf);
setAttr(attributes.color, geometryState.colorsBuf);
setAttr(attributes.pickColor, triangleGeometry && triangleGeometry.pickColorsBuf);
setAttr(attributes.uv, geometryState.uvBuf);
setAttr(attributes.normal, geometryState.normalsBuf);
};
if (programSetup.trianglePick) {
const positionsBuf = geometry._getPickTrianglePositions();
if (geometryState.id !== lastGeometryId) {
setAttributes({ positionsBuf: positionsBuf, pickColorsBuf: geometry._getPickTriangleColors() });
lastGeometryId = geometryState.id;
}
gl.drawArrays(geometryState.primitive, 0, positionsBuf.numItems / 3);
} else if (programSetup.drawEdges) {
const indicesBuf = ((geometryState.primitive === gl.TRIANGLES)
? geometry._getEdgeIndices()
: ((geometryState.primitive === gl.LINES) && geometryState.indicesBuf));
if (indicesBuf) {
if (geometryState.id !== lastGeometryId) {
setAttributes();
indicesBuf.bind();
frameCtx.bindArray++;
lastGeometryId = geometryState.id;
}
gl.drawElements(gl.LINES, indicesBuf.numItems, indicesBuf.itemType, 0);
frameCtx.drawElements++;
}
} else {
if (geometryState.id !== lastGeometryId) {
setAttributes();
if (geometryState.indicesBuf) {
geometryState.indicesBuf.bind();
frameCtx.bindArray++;
}
lastGeometryId = geometryState.id;
}
if (geometryState.indicesBuf) {
gl.drawElements(geometryState.primitive, geometryState.indicesBuf.numItems, geometryState.indicesBuf.itemType, 0);
frameCtx.drawElements++;
} else if (geometryState.positionsBuf) {
gl.drawArrays(gl.TRIANGLES, 0, geometryState.positionsBuf.numItems);
frameCtx.drawArrays++;
}
}
if (actsAsBackground) {
gl.depthFunc(gl.LESS);
}
}
};
}
};