UNPKG

xeokit-viewer

Version:

BIM viewer built on xeokit and Bootstrap

1,409 lines (1,256 loc) 51.3 kB
import {Component} from "../Component.js"; import {math} from "../math/math.js"; import {buildEdgeIndices} from '../math/buildEdgeIndices.js'; import {WEBGL_INFO} from '../webglInfo.js'; import {PerformanceMesh} from './lib/PerformanceMesh.js'; import {PerformanceNode} from './lib/PerformanceNode.js'; import {getBatchingBuffer, putBatchingBuffer} from "./lib/batching/batchingBuffer.js"; import {BatchingLayer} from './lib/batching/batchingLayer.js'; import {InstancingLayer} from './lib/instancing/instancingLayer.js'; import {RENDER_FLAGS} from './lib/renderFlags.js'; const instancedArraysSupported = WEBGL_INFO.SUPPORTED_EXTENSIONS["ANGLE_instanced_arrays"]; var tempMat4 = math.mat4(); const defaultScale = math.vec3([1, 1, 1]); const defaultPosition = math.vec3([0, 0, 0]); const defaultRotation = math.vec3([0, 0, 0]); const defaultQuaternion = math.identityQuaternion(); const DEFAULT_TILE_ID = "__default"; /** * @desc A high-performance model representation for efficient rendering and low memory usage. * * ## Examples * * * [PerformanceModel using geometry batching](http://xeokit.github.io/xeokit-sdk/examples/#sceneRepresentation_PerformanceModel_batching) * * [PerformanceModel using geometry instancing](http://xeokit.github.io/xeokit-sdk/examples/#sceneRepresentation_PerformanceModel_instancing) * * ## User Guide * * * [High Performance Model Representation](https://github.com/xeokit/xeokit-sdk/wiki/High-Performance-Model-Representation) * * @implements {Drawable} * @implements {Entity} */ class PerformanceModel 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 {Boolean} [cfg.isModel] Specify ````true```` if this PerformanceModel represents a model, in which case the PerformanceModel will be registered by {@link PerformanceModel#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 {Number[]} [cfg.position=[0,0,0]] Local 3D position. * @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 {Boolean} [cfg.visible=true] Indicates if the PerformanceModel is initially visible. * @param {Boolean} [cfg.culled=false] Indicates if the PerformanceModel is initially culled from view. * @param {Boolean} [cfg.pickable=true] Indicates if the PerformanceModel is initially pickable. * @param {Boolean} [cfg.clippable=true] Indicates if the PerformanceModel is initially clippable. * @param {Boolean} [cfg.collidable=true] Indicates if the PerformanceModel is initially included in boundary calculations. * @param {Boolean} [cfg.xrayed=false] Indicates if the PerformanceModel is initially xrayed. * @param {Boolean} [cfg.highlighted=false] Indicates if the PerformanceModel is initially highlighted. * @param {Boolean} [cfg.selected=false] Indicates if the PerformanceModel is initially selected. * @param {Boolean} [cfg.edges=false] Indicates if the PerformanceModel's edges are initially emphasized. * @param {Number[]} [cfg.colorize=[1.0,1.0,1.0]] PerformanceModel's initial RGB colorize color, multiplies by the rendered fragment colors. * @param {Number} [cfg.opacity=1.0] PerformanceModel's initial opacity factor, multiplies by the rendered fragment alpha. * @param {Boolean} [cfg.preCompressed=false] When this is ````true````, ````positions```` are assumed to be * quantized and in World-space, and ````normals```` are also assumed to be oct-encoded and in World-space. When ````true````, {@link PerformanceModel#createMesh} * will ignore ````matrix````, ````position````, ````scale```` and ````rotation```` parameters. */ constructor(owner, cfg = {}) { super(owner, cfg); this._preCompressed = !!cfg.preCompressed; this._tiles = {}; this._aabb = math.collapseAABB3(); this._layers = []; // For GL state efficiency when drawing, InstancingLayers are in first part, BatchingLayers are in second this._nodes = []; /** * @private */ this.numGeometries = 0; // Number of instance-able geometries created with createGeometry() // These counts are used to avoid unnecessary render passes // They are incremented or decremented exclusively by BatchingLayer and InstancingLayer /** * @private */ this.numPortions = 0; /** * @private */ this.numVisibleLayerPortions = 0; /** * @private */ this.numTransparentLayerPortions = 0; /** * @private */ this.numXRayedLayerPortions = 0; /** * @private */ this.numHighlightedLayerPortions = 0; /** * @private */ this.numSelectedLayerPortions = 0; /** * @private */ this.numEdgesLayerPortions = 0; /** * @private */ this.numPickableLayerPortions = 0; /** @private */ this.numEntities = 0; /** @private */ this.numTriangles = 0; 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.colorize = cfg.colorize; this.opacity = cfg.opacity; // Build static matrix this._position = new Float32Array(cfg.position || [0, 0, 0]); this._rotation = new Float32Array(cfg.rotation || [0, 0, 0]); this._quaternion = new Float32Array(cfg.quaternion || [0, 0, 0, 1]); if (cfg.rotation) { math.eulerToQuaternion(this._rotation, "XYZ", this._quaternion); } this._scale = new Float32Array(cfg.scale || [1, 1, 1]); this._worldMatrix = math.mat4(); math.composeMat4(this._position, this._quaternion, this._scale, this._worldMatrix); this._worldNormalMatrix = math.mat4(); if (cfg.matrix || cfg.position || cfg.rotation || cfg.scale || cfg.quaternion) { this._viewMatrix = math.mat4(); this._viewNormalMatrix = math.mat4(); this._viewMatrixDirty = true; this._worldMatrixNonIdentity = true; } this._opacity = 1.0; this._colorize = [1, 1, 1]; this._isModel = cfg.isModel; if (this._isModel) { this.scene._registerModel(this); } this.createTile({ // Create geometries, meshes and entities in this tile by default id: DEFAULT_TILE_ID }); this._onCameraViewMatrix = this.scene.camera.on("matrix", () => { this._viewMatrixDirty = true; }); } //------------------------------------------------------------------------------------------------------------------ // PerformanceModel members //------------------------------------------------------------------------------------------------------------------ /** * Returns true to indicate that this Component is a PerformanceModel. * @type {Boolean} */ get isPerformanceModel() { return true; } /** * Gets the PerformanceModel's local translation. * * Default value is ````[0,0,0]````. * * @type {Number[]} */ get position() { return this._position; } /** * Gets the PerformanceModel'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; } /** * Gets the PerformanceModels's local rotation quaternion. * * Default value is ````[0,0,0,1]````. * * @type {Number[]} */ get quaternion() { return this._quaternion; } /** * Gets the PerformanceModel's local scale. * * Default value is ````[1,1,1]````. * * @type {Number[]} */ get scale() { return this._scale; } /** * Gets the PerformanceModel'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() { return this._worldMatrix; } /** * Gets the PerformanceModel's World matrix. * * @property worldMatrix * @type {Number[]} */ get worldMatrix() { return this._worldMatrix; } /** * Gets the PerformanceModel's World normal matrix. * * @type {Number[]} */ get worldNormalMatrix() { return this._worldNormalMatrix; } /** * Called by private renderers in ./lib, returns the view matrix with which to * render this PerformanceModel. The view matrix is the concatenation of the * Camera view matrix with the Performance model's world (modeling) matrix. * * @private */ get viewMatrix() { if (!this._viewMatrix) { return this.scene.camera.viewMatrix; } if (this._viewMatrixDirty) { math.mulMat4(this.scene.camera.viewMatrix, this._worldMatrix, this._viewMatrix); math.inverseMat4(this._viewMatrix, this._viewNormalMatrix); math.transposeMat4(this._viewNormalMatrix); this._viewMatrixDirty = false; } return this._viewMatrix; } /** * Called by private renderers in ./lib, returns the picking view matrix with which to * ray-pick on this PerformanceModel. * * @private */ getPickViewMatrix(pickViewMatrix) { if (!this._viewMatrix) { return pickViewMatrix; } return this._viewMatrix; } /** * Called by private renderers in ./lib, returns the view normal matrix with which to render this PerformanceModel. * * @private */ get viewNormalMatrix() { if (!this._viewNormalMatrix) { return this.scene.camera.viewNormalMatrix; } if (this._viewMatrixDirty) { math.mulMat4(this.scene.camera.viewMatrix, this._worldMatrix, this._viewMatrix); math.inverseMat4(this._viewMatrix, this._viewNormalMatrix); math.transposeMat4(this._viewNormalMatrix); this._viewMatrixDirty = false; } return this._viewNormalMatrix; } /** * Starts building a tile. * * Tiles are used to incrementally show the {@link Entity}s within a PerformanceModel as it is being built. As each * geometry, mesh or {@link Entity} is created in the PerformanceModel, it may be optionally added to a tile. When that * tile is then finalized, then all its {@link Entity}s are immediately created within the {@link Scene}. * * @param {*} cfg Geometry properties. * @param {String|Number} cfg.id Mandatory ID for the tile, to refer to with {@link PerformanceModel#finalizeTile}. */ createTile(cfg) { if (this._tiles[cfg.id]) { this.warn("Tile already exists: " + cfg.id); return; } const tile = { id: cfg.id, layers: [], instancingLayers: {}, // InstancingLayer for each geometry - can build many of these concurrently currentBatchingLayer: null, // Current BatchingLayer - can only build one of these at a time due to its use of global geometry buffers buffer: getBatchingBuffer(), // Each PerformanceModel gets it's own batching buffer - allows multiple PerformanceModels to load concurrently meshes: {}, nodes: [] }; this._tiles[cfg.id] = tile; } /** * Creates a reusable geometry within this PerformanceModel. * * We can then supply the geometry ID to {@link PerformanceModel#createMesh} when we want to create meshes that instance the geometry. * * Note that positions, normals and indices are all required in geometry data. * * @param {*} cfg Geometry properties. * @param {String|Number} cfg.id Mandatory ID for the geometry, to refer to with {@link PerformanceModel#createMesh}. * @param {String} [cfg.tileId] Optional ID of a tile to add the geometry to. The tile must have been created with {@link PerformanceModel#createTile} and not yet finalized with {@link PerformanceModel#finalizeTile}. * @param {String} [cfg.primitive="triangles"] The primitive type. Accepted values are 'points', 'lines', 'line-loop', 'line-strip', 'triangles', 'triangle-strip' and 'triangle-fan'. * @param {Number[]} cfg.positions Flat array of positions. * @param {Number[]} cfg.normals Flat array of normal vectors. * @param {Number[]} cfg.indices Array of triangle indices. * @param {Number[]} cfg.edgeIndices Array of edge line indices. */ createGeometry(cfg) { if (!instancedArraysSupported) { this.error("WebGL instanced arrays not supported"); // TODO: Gracefully use batching? return; } const geometryId = cfg.id; if (geometryId === undefined || geometryId === null) { this.error("Config missing: id"); return; } const tileId = cfg.tileId || DEFAULT_TILE_ID; var tile = this._tiles[tileId]; if (!tile) { this.error("Tile not found: " + tileId + " - using default tile"); tile = this._tiles[DEFAULT_TILE_ID]; } if (tile.instancingLayers[geometryId]) { this.error("Geometry already created: " + geometryId); return; } cfg.preCompressed = this._preCompressed; var instancingLayer = new InstancingLayer(this, cfg); tile.layers.push(instancingLayer); tile.instancingLayers[geometryId] = instancingLayer; this.numGeometries++; this.numTriangles += cfg.indices ? cfg.indices.length / 3 : 0; } /** * Creates a mesh within this PerformanceModel. * * A mesh has a geometry, given either as the ID of a shared geometry created with {@link PerformanceModel#createGeometry}, or as * geometr data arrays to create a unique geometry belong to the mesh. * * When you provide a geometry ID, then the PerformanceModelMesh will instance the shared geometry for the mesh. * * When you provide arrays, then PerformanceModel will combine the geometry in a batch with the other non-shared unique geometries in the model. * * Note that positions, normals and indices are all required in geometry data. * * @param {object} cfg Object properties. * @param {String} cfg.id Mandatory ID for the new mesh. Must not clash with any existing components within the {@link Scene}. * @param {String} [cfg.tileId] Optional ID of a tile to add the mesh to. The tile must have been created with {@link PerformanceModel#createTile} and not yet finalized with {@link PerformanceModel#finalizeTile}. * @param {String|Number} [cfg.geometryId] ID of a geometry to instance, previously created with {@link PerformanceModel#createGeometry:method"}}createMesh(){{/crossLink}}. Overrides all other geometry parameters given to this method. If a tile ID is also given, then the geometry must exist within that tile. * @param {String} [cfg.primitive="triangles"] Geometry primitive type. Ignored when ````geometryId```` is given. Accepted values are 'points', 'lines', 'line-loop', 'line-strip', 'triangles', 'triangle-strip' and 'triangle-fan'. * @param {Number[]} [cfg.positions] Flat array of geometry positions. Ignored when ````geometryId```` is given. * @param {Number[]} [cfg.normals] Flat array of normal vectors. Ignored when ````geometryId```` is given. * @param {Number[]} [cfg.positionsDecodeMatrix] A 4x4 matrix for decompressing ````positions````. Only used when ````preCompressed```` is true. * @param {Number[]} [cfg.indices] Array of triangle indices. Ignored when ````geometryId```` is given. * @param {Number[]} [cfg.edgeIndices] Array of edge line indices. Ignored when ````geometryId```` is given. * @param {Number[]} [cfg.position=[0,0,0]] Local 3D position. of the mesh * @param {Number[]} [cfg.scale=[1,1,1]] Scale of the mesh. * @param {Number[]} [cfg.rotation=[0,0,0]] Rotation of the mesh 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]] Mesh modelling transform matrix. Overrides the ````position````, ````scale```` and ````rotation```` parameters. * @param {Number[]} [cfg.color=[1,1,1]] RGB color in range ````[0..1, 0..`, 0..1]````. * @param {Number} [cfg.opacity=1] Opacity in range ````[0..1]````. */ createMesh(cfg) { var id = cfg.id; if (id === undefined || id === null) { this.error("Config missing: id"); return; } if (this.scene.components[id]) { this.error("Scene already has a Component with this ID: " + id + " - will assign random ID"); id = math.createUUID(); } const geometryId = cfg.geometryId; const instancing = (geometryId !== undefined); const tileId = cfg.tileId || DEFAULT_TILE_ID; var tile = this._tiles[tileId]; if (!tile) { this.error("Tile not found: " + tileId + " - using default tile"); tile = this._tiles[DEFAULT_TILE_ID]; } if (instancing) { if (!instancedArraysSupported) { this.error("WebGL instanced arrays not supported"); // TODO: Gracefully use batching? return; } if (!tile.instancingLayers[geometryId]) { this.error("Geometry not found: " + geometryId + " - ensure that you create it first with createGeometry()"); return; } } var flags = 0; var layer; var portionId; const color = (cfg.color) ? new Uint8Array([Math.floor(cfg.color[0] * 255), Math.floor(cfg.color[1] * 255), Math.floor(cfg.color[2] * 255)]) : [255, 255, 255]; const opacity = (cfg.opacity !== undefined && cfg.opacity !== null) ? Math.floor(cfg.opacity * 255) : 255; if (opacity < 255) { this.numTransparentLayerPortions++; } var mesh = new PerformanceMesh(this, id, color, opacity); var pickId = mesh.pickId; const a = pickId >> 24 & 0xFF; const b = pickId >> 16 & 0xFF; const g = pickId >> 8 & 0xFF; const r = pickId & 0xFF; const pickColor = new Uint8Array([r, g, b, a]); // Quantized pick color const aabb = math.collapseAABB3(); if (instancing) { let meshMatrix; let worldMatrix = this._worldMatrixNonIdentity ? this._worldMatrix : null; if (cfg.matrix) { meshMatrix = cfg.matrix; } else { const scale = cfg.scale || defaultScale; const position = cfg.position || defaultPosition; const rotation = cfg.rotation || defaultRotation; math.eulerToQuaternion(rotation, "XYZ", defaultQuaternion); meshMatrix = math.composeMat4(position, defaultQuaternion, scale, tempMat4); } var instancingLayer = tile.instancingLayers[geometryId]; layer = instancingLayer; portionId = instancingLayer.createPortion(flags, color, opacity, meshMatrix, worldMatrix, aabb, pickColor); math.expandAABB3(this._aabb, aabb); this.numTriangles += instancingLayer.numIndices.length / 3; } else { var primitive = cfg.primitive || "triangles"; if (primitive !== "points" && primitive !== "lines" && primitive !== "line-loop" && primitive !== "line-strip" && primitive !== "triangles" && primitive !== "triangle-strip" && primitive !== "triangle-fan") { this.error(`Unsupported value for 'primitive': '${primitive}' - supported values are 'points', 'lines', 'line-loop', 'line-strip', 'triangles', 'triangle-strip' and 'triangle-fan'. Defaulting to 'triangles'.`); primitive = "triangles"; } var indices = cfg.indices; var edgeIndices = cfg.edgeIndices; var positions = cfg.positions; if (!positions) { this.error("Config missing: positions (no meshIds provided, so expecting geometry arrays instead)"); return null; } var normals = cfg.normals; if (!normals) { this.error("Config missing: normals (no meshIds provided, so expecting geometry arrays instead)"); return null; } if (!edgeIndices && !indices) { this.error("Config missing: must have one or both of indices and edgeIndices (no meshIds provided, so expecting geometry arrays instead)"); return null; } if (tile.currentBatchingLayer) { if (!tile.currentBatchingLayer.canCreatePortion(positions.length)) { tile.currentBatchingLayer.finalize(); tile.currentBatchingLayer = null; } } if (!tile.currentBatchingLayer) { tile.currentBatchingLayer = new BatchingLayer(this, { primitive: "triangles", buffer: tile.buffer, preCompressed: this._preCompressed, positionsDecodeMatrix: cfg.positionsDecodeMatrix, }); tile.layers.push(tile.currentBatchingLayer); } layer = tile.currentBatchingLayer; if (!edgeIndices && indices) { edgeIndices = buildEdgeIndices(positions, indices, null, 10); } let meshMatrix; let worldMatrix = this._worldMatrixNonIdentity ? this._worldMatrix : null; if (!this._preCompressed) { if (cfg.matrix) { meshMatrix = cfg.matrix; } else { const scale = cfg.scale || defaultScale; const position = cfg.position || defaultPosition; const rotation = cfg.rotation || defaultRotation; math.eulerToQuaternion(rotation, "XYZ", defaultQuaternion); meshMatrix = math.composeMat4(position, defaultQuaternion, scale, tempMat4); } } portionId = tile.currentBatchingLayer.createPortion(positions, normals, indices, edgeIndices, flags, color, opacity, meshMatrix, worldMatrix, aabb, pickColor); math.expandAABB3(this._aabb, aabb); this.numGeometries++; this.numTriangles += indices.length / 3; } mesh.parent = null; // Will be set within PerformanceModelNode constructor mesh._layer = layer; mesh._portionId = portionId; mesh.aabb = aabb; tile.meshes[id] = mesh; } /** * Creates an {@link Entity} within this PerformanceModel, giving it one or more meshes previously created with {@link PerformanceModel#createMesh}. * * A mesh can only belong to one {@link Entity}, so you'll get an error if you try to reuse a mesh among multiple {@link Entity}s. * * @param {Object} cfg Entity configuration. * @param {String} cfg.id Optional ID for the new Entity. Must not clash with any existing components within the {@link Scene}. * @param {String} [cfg.tileId] Optional ID of a tile to add the Entity to. The tile must have been created with {@link PerformanceModel#createTile} and not yet finalized with {@link PerformanceModel#finalizeTile}. * @param {Boolean} [cfg.isObject] Set ````true```` if the {@link Entity} represents an object, in which case it will be registered by {@link Entity#id} in {@link Scene#objects} and can also have a corresponding {@link MetaObject} with matching {@link MetaObject#id}, registered by that ID in {@link MetaScene#metaObjects}. * @param {Boolean} [cfg.visible=true] Indicates if the Entity is initially visible. * @param {Boolean} [cfg.culled=false] Indicates if the Entity is initially culled from view. * @param {Boolean} [cfg.pickable=true] Indicates if the Entity is initially pickable. * @param {Boolean} [cfg.clippable=true] Indicates if the Entity is initially clippable. * @param {Boolean} [cfg.collidable=true] Indicates if the Entity is initially included in boundary calculations. * @param {Boolean} [cfg.castsShadow=true] Indicates if the Entity initially casts shadows. * @param {Boolean} [cfg.receivesShadow=true] Indicates if the Entity initially receives shadows. * @param {Boolean} [cfg.xrayed=false] Indicates if the Entity is initially xrayed. XRayed appearance is configured by {@link PerformanceModel#xrayMaterial}. * @param {Boolean} [cfg.highlighted=false] Indicates if the Entity is initially highlighted. Highlighted appearance is configured by {@link PerformanceModel#highlightMaterial}. * @param {Boolean} [cfg.selected=false] Indicates if the Entity is initially selected. Selected appearance is configured by {@link PerformanceModel#selectedMaterial}. * @param {Boolean} [cfg.edges=false] Indicates if the Entity's edges are initially emphasized. Edges appearance is configured by {@link PerformanceModel#edgeMaterial}. * @returns {Entity} */ createEntity(cfg) { // Validate or generate Entity ID var id = cfg.id; if (id === undefined) { id = math.createUUID(); } else if (this.scene.components[id]) { this.error("Scene already has a Component with this ID: " + id + " - will assign random ID"); id = math.createUUID(); } // Collect PerformanceModelNode's PerformanceModelMeshes var meshIds = cfg.meshIds; if (meshIds === undefined) { this.error("Config missing: meshIds"); return; } const tileId = cfg.tileId || DEFAULT_TILE_ID; var tile = this._tiles[tileId]; if (!tile) { this.error("Tile not found: " + tileId + " - using default tile"); tile = this._tiles[DEFAULT_TILE_ID]; } var i; var len; var meshId; var mesh; var meshes = []; for (i = 0, len = meshIds.length; i < len; i++) { meshId = meshIds[i]; mesh = tile.meshes[meshId]; if (!mesh) { this.error("Mesh with this ID not found: " + meshId + " - ignoring this mesh"); continue; } if (mesh.parent) { this.error("Mesh with ID " + meshId + " already belongs to object with ID " + mesh.parent.id + " - ignoring this mesh"); continue; } delete tile.meshes[meshId]; meshes.push(mesh); } // Create PerformanceModelNode flags var flags = 0; if (this._visible && cfg.visible !== false) { flags = flags | RENDER_FLAGS.VISIBLE; } if (this._pickable && cfg.pickable !== false) { flags = flags | RENDER_FLAGS.PICKABLE; } if (this._clippable && cfg.clippable !== false) { flags = flags | RENDER_FLAGS.CLIPPABLE; } if (this._collidable && cfg.collidable !== false) { flags = flags | RENDER_FLAGS.COLLIDABLE; } if (this._edges && cfg.edges !== false) { flags = flags | RENDER_FLAGS.EDGES; } if (this._xrayed && cfg.xrayed !== false) { flags = flags | RENDER_FLAGS.XRAYED; } if (this._highlighted && cfg.highlighted !== false) { flags = flags | RENDER_FLAGS.HIGHLIGHTED; } if (this._selected && cfg.selected !== false) { flags = flags | RENDER_FLAGS.SELECTED; } // Create PerformanceModelNode AABB var aabb; if (meshes.length === 1) { aabb = meshes[0].aabb; } else { aabb = math.collapseAABB3(); for (i = 0, len = meshes.length; i < len; i++) { math.expandAABB3(aabb, meshes[i].aabb); } } var node = new PerformanceNode(this, cfg.isObject, id, meshes, flags, aabb); // Internally sets PerformanceModelMesh#parent to this PerformanceModelNode tile.nodes.push(node); this.numEntities++; return node; } /** * Finalizes a tile. * * Immediately creates the tile's {@link Entity}s within the {@link Scene}. * * Once finalized, you can't add anything more to the tile. * * @param {String} tileId ID of tile previously created with {@link PerformanceModel#createTile}. */ finalizeTile(tileId) { const tile = this._tiles[tileId]; if (!tile) { this.warn("Tile not found: " + tileId); return; } if (tile.currentBatchingLayer) { tile.currentBatchingLayer.finalize(); tile.currentBatchingLayer = null; } if (tile.buffer) { putBatchingBuffer(tile.buffer); tile.buffer = null; } for (const geometryId in tile.instancingLayers) { if (tile.instancingLayers.hasOwnProperty(geometryId)) { tile.instancingLayers[geometryId].finalize(); } } for (var i = 0, len = tile.nodes.length; i < len; i++) { const node = tile.nodes[i]; node._finalize(); this._nodes.push(node); } for (var i = 0, len = tile.layers.length; i < len; i++) { const layer = tile.layers[i]; if (layer instanceof InstancingLayer) { // For efficient GL state sorting, instancing layers are rendered before batching layers this._layers.unshift(layer); } else { this._layers.push(layer); } } delete this._tiles[tileId]; this.glRedraw(); this.scene._aabbDirty = true; } /** * Finalizes this PerformanceModel. * * Implicitly finalizes all tiles created with {#link PerformanceModel#createTile}. * * Immediately creates the PerformanceModel's {@link Entity}s within the {@link Scene}. * * Once finalized, you can't add anything more to this PerformanceModel. */ finalize() { for (var tileId in this._tiles) { if (this._tiles.hasOwnProperty(tileId)) { this.finalizeTile(tileId); } } } //------------------------------------------------------------------------------------------------------------------ // Entity members //------------------------------------------------------------------------------------------------------------------ /** * Returns true to indicate that PerformanceModel is an {@link Entity}. * @type {Boolean} */ get isEntity() { return true; } /** * Returns ````true```` if this PerformanceModel represents a model. * * When ````true```` the PerformanceModel will be registered by {@link PerformanceModel#id} in * {@link Scene#models} and may also have a {@link MetaObject} with matching {@link MetaObject#id}. * * @type {Boolean} */ get isModel() { return this._isModel; } /** * Returns ````false```` to indicate that PerformanceModel never represents an object. * * @type {Boolean} */ get isObject() { return false; } /** * Gets the PerformanceModel's World-space 3D axis-aligned bounding box. * * Represented by a six-element Float32Array containing the min/max extents of the * axis-aligned volume, ie. ````[xmin, ymin,zmin,xmax,ymax, zmax]````. * * @type {Number[]} */ get aabb() { return this._aabb; } /** * Sets if this PerformanceModel is visible. * * The PerformanceModel is only rendered when {@link PerformanceModel#visible} is ````true```` and {@link PerformanceModel#culled} is ````false````. ** * @type {Boolean} */ set visible(visible) { visible = visible !== false; this._visible = visible; for (var i = 0, len = this._nodes.length; i < len; i++) { this._nodes[i].visible = visible; } this.glRedraw(); } /** * Gets if any {@link Entity}s in this PerformanceModel are visible. * * The PerformanceModel is only rendered when {@link PerformanceModel#visible} is ````true```` and {@link PerformanceModel#culled} is ````false````. * * @type {Boolean} */ get visible() { return (this.numVisibleLayerPortions > 0); } /** * Sets if all {@link Entity}s in this PerformanceModel are xrayed. * * @type {Boolean} */ set xrayed(xrayed) { xrayed = !!xrayed; this._xrayed = xrayed; for (var i = 0, len = this._nodes.length; i < len; i++) { this._nodes[i].xrayed = xrayed; } this.glRedraw(); } /** * Gets if any {@link Entity}s in this PerformanceModel are xrayed. * * @type {Boolean} */ get xrayed() { return (this.numXRayedLayerPortions > 0); } /** * Sets if all {@link Entity}s in this PerformanceModel are highlighted. * * @type {Boolean} */ set highlighted(highlighted) { highlighted = !!highlighted; this._highlighted = highlighted; for (var i = 0, len = this._nodes.length; i < len; i++) { this._nodes[i].highlighted = highlighted; } this.glRedraw(); } /** * Gets if any {@link Entity}s in this PerformanceModel are highlighted. * * @type {Boolean} */ get highlighted() { return (this.numHighlightedLayerPortions > 0); } /** * Sets if all {@link Entity}s in this PerformanceModel are selected. * * @type {Boolean} */ set selected(selected) { selected = !!selected; this._selected = selected; for (var i = 0, len = this._nodes.length; i < len; i++) { this._nodes[i].selected = selected; } this.glRedraw(); } /** * Gets if any {@link Entity}s in this PerformanceModel are selected. * * @type {Boolean} */ get selected() { return (this.numSelectedLayerPortions > 0); } /** * Sets if all {@link Entity}s in this PerformanceModel have edges emphasised. * * @type {Boolean} */ set edges(edges) { edges = !!edges; this._edges = edges; for (var i = 0, len = this._nodes.length; i < len; i++) { this._nodes[i].edges = edges; } this.glRedraw(); } /** * Gets if any {@link Entity}s in this PerformanceModel have edges emphasised. * * @type {Boolean} */ get edges() { return (this.numEdgesLayerPortions > 0); } /** * Sets if this PerformanceModel is culled from view. * * The PerformanceModel is only rendered when {@link PerformanceModel#visible} is true and {@link PerformanceModel#culled} is false. * * @type {Boolean} */ set culled(culled) { culled = !!culled; this._culled = culled; // Whole PerformanceModel is culled this.glRedraw(); } /** * Gets if this PerformanceModel is culled from view. * * The PerformanceModel is only rendered when {@link PerformanceModel#visible} is true and {@link PerformanceModel#culled} is false. * * @type {Boolean} */ get culled() { return this._culled; } /** * Sets if {@link Entity}s in this PerformanceModel are clippable. * * Clipping is done by the {@link SectionPlane}s in {@link Scene#sectionPlanes}. * * @type {Boolean} */ set clippable(clippable) { clippable = clippable !== false; this._clippable = clippable; for (var i = 0, len = this._nodes.length; i < len; i++) { this._nodes[i].clippable = clippable; } this.glRedraw(); } /** * Gets if {@link Entity}s in this PerformanceModel are clippable. * * Clipping is done by the {@link SectionPlane}s in {@link Scene#sectionPlanes}. * * @type {Boolean} */ get clippable() { return this._clippable; } /** * Sets if {@link Entity}s in this PerformanceModel are collidable. * * @type {Boolean} */ set collidable(collidable) { collidable = collidable !== false; this._collidable = collidable; for (var i = 0, len = this._nodes.length; i < len; i++) { this._nodes[i].collidable = collidable; } } /** * Gets if this PerformanceModel is collidable. * * @type {Boolean} */ get collidable() { return this._collidable; } /** * Sets if {@link Entity}s in this PerformanceModel are pickable. * * Picking is done via calls to {@link Scene#pick}. * * @type {Boolean} */ set pickable(pickable) { pickable = pickable !== false; this._pickable = pickable; for (var i = 0, len = this._nodes.length; i < len; i++) { this._nodes[i].pickable = pickable; } } /** * Gets if this PerformanceModel is pickable. * * Picking is done via calls to {@link Scene#pick}. * * @type {Boolean} */ get pickable() { return this._pickable; } /** * Sets the RGB colorize color for this PerformanceModel. * * Multiplies by rendered fragment colors. * * Each element of the color is in range ````[0..1]````. * * @type {Number[]} */ set colorize(colorize) { this._colorize = colorize; for (var i = 0, len = this._nodes.length; i < len; i++) { this._nodes[i].colorize = colorize; } } /** * Gets the RGB colorize color for this PerformanceModel. * * Each element of the color is in range ````[0..1]````. * * @type {Number[]} */ get colorize() { return this._colorize; } /** * Sets the opacity factor for this PerformanceModel. * * This is a factor in range ````[0..1]```` which multiplies by the rendered fragment alphas. * * @type {Number} */ set opacity(opacity) { this._opacity = opacity; for (var i = 0, len = this._nodes.length; i < len; i++) { this._nodes[i].opacity = opacity; } } /** * Gets this PerformanceModel's opacity factor. * * This is a factor in range ````[0..1]```` which multiplies by the rendered fragment alphas. * * @type {Number} */ get opacity() { return this._opacity; } /** * Sets if this PerformanceModel casts a shadow. * * @type {Boolean} */ set castsShadow(castsShadow) { // TODO } /** * Gets if this PerformanceModel casts a shadow. * * @type {Boolean} */ get castsShadow() { // TODO return false; } /** * Sets if this PerformanceModel can have shadow cast upon it. * * @type {Boolean} */ set receivesShadow(receivesShadow) { // TODO } /** * Sets if this PerformanceModel can have shadow cast upon it. * * @type {Boolean} */ get receivesShadow() { // TODO return false; } //------------------------------------------------------------------------------------------------------------------ // Drawable members //------------------------------------------------------------------------------------------------------------------ /** * Returns true to indicate that PerformanceModel is implements {@link Drawable}. * * @type {Boolean} */ get isDrawable() { return true; } /** @private */ get isStateSortable() { return false } /** @private */ stateSortCompare(drawable1, drawable2) { } /** @private */ getRenderFlags(renderFlags) { renderFlags.reset(); if (this.numVisibleLayerPortions === 0) { return; } renderFlags.normalFillOpaque = true; if (this.numXRayedLayerPortions > 0) { const xrayMaterial = this.scene.xrayMaterial._state; if (xrayMaterial.fill) { if (xrayMaterial.fillAlpha < 1.0) { renderFlags.xrayedFillTransparent = true; } else { renderFlags.xrayedFillOpaque = true; } } if (xrayMaterial.edges) { if (xrayMaterial.edgeAlpha < 1.0) { renderFlags.xrayedEdgesTransparent = true; } else { renderFlags.xrayedEdgesOpaque = true; } } } if (this.numEdgesLayerPortions > 0) { const edgeMaterial = this.scene.edgeMaterial._state; if (edgeMaterial.alpha < 1.0) { renderFlags.normalEdgesTransparent = true; } else { renderFlags.normalEdgesOpaque = true; } } if (this.numTransparentLayerPortions > 0) { renderFlags.normalFillTransparent = true; } if (this.numSelectedLayerPortions > 0) { const selectedMaterial = this.scene.selectedMaterial._state; if (selectedMaterial.fill) { if (selectedMaterial.fillAlpha < 1.0) { renderFlags.selectedFillTransparent = true; } else { renderFlags.selectedFillOpaque = true; } } if (selectedMaterial.edges) { if (selectedMaterial.edgeAlpha < 1.0) { renderFlags.selectedEdgesTransparent = true; } else { renderFlags.selectedEdgesOpaque = true; } } } if (this.numHighlightedLayerPortions > 0) { const highlightMaterial = this.scene.highlightMaterial._state; if (highlightMaterial.fill) { if (highlightMaterial.fillAlpha < 1.0) { renderFlags.highlightedFillTransparent = true; } else { renderFlags.highlightedFillOpaque = true; } } if (highlightMaterial.edges) { if (highlightMaterial.edgeAlpha < 1.0) { renderFlags.highlightedEdgesTransparent = true; } else { renderFlags.highlightedEdgesOpaque = true; } } } } /** * Configures the appearance of xrayed {@link Entity}s within this PerformanceModel. * * This is the {@link Scene#xrayMaterial}. * * @type {EmphasisMaterial} */ get xrayMaterial() { return this.scene.xrayMaterial; } /** * Configures the appearance of highlighted {@link Entity}s within this PerformanceModel. * * This is the {@link Scene#highlightMaterial}. * * @type {EmphasisMaterial} */ get highlightMaterial() { return this.scene.highlightMaterial; } /** * Configures the appearance of selected {@link Entity}s within this PerformanceModel. * * This is the {@link Scene#selectedMaterial}. * * @type {EmphasisMaterial} */ get selectedMaterial() { return this.scene.selectedMaterial; } /** * Configures the appearance of edges of {@link Entity}s within this PerformanceModel. * * This is the {@link Scene#edgeMaterial}. * * @type {EdgeMaterial} */ get edgeMaterial() { return this.scene.edgeMaterial; } /** @private */ drawNormalFillOpaque(frameCtx) { for (var i = 0, len = this._layers.length; i < len; i++) { this._layers[i].drawNormalFillOpaque(frameCtx); } } /** @private */ drawNormalEdgesOpaque(frameCtx) { for (var i = 0, len = this._layers.length; i < len; i++) { this._layers[i].drawNormalEdgesOpaque(frameCtx); } } /** @private */ drawNormalFillTransparent(frameCtx) { for (var i = 0, len = this._layers.length; i < len; i++) { this._layers[i].drawNormalFillTransparent(frameCtx); } } /** @private */ drawNormalEdgesTransparent(frameCtx) { for (var i = 0, len = this._layers.length; i < len; i++) { this._layers[i].drawNormalEdgesTransparent(frameCtx); } } /** @private */ drawXRayedFillOpaque(frameCtx) { for (var i = 0, len = this._layers.length; i < len; i++) { this._layers[i].drawXRayedFillOpaque(frameCtx); } } /** @private */ drawXRayedEdgesOpaque(frameCtx) { for (var i = 0, len = this._layers.length; i < len; i++) { this._layers[i].drawXRayedEdgesOpaque(frameCtx); } } /** @private */ drawXRayedFillTransparent(frameCtx) { for (var i = 0, len = this._layers.length; i < len; i++) { this._layers[i].drawXRayedFillTransparent(frameCtx); } } /** @private */ drawXRayedEdgesTransparent(frameCtx) { for (var i = 0, len = this._layers.length; i < len; i++) { this._layers[i].drawXRayedEdgesTransparent(frameCtx); } } /** @private */ drawHighlightedFillOpaque(frameCtx) { for (var i = 0, len = this._layers.length; i < len; i++) { this._layers[i].drawHighlightedFillOpaque(frameCtx); } } /** @private */ drawHighlightedEdgesOpaque(frameCtx) { for (var i = 0, len = this._layers.length; i < len; i++) { this._layers[i].drawHighlightedEdgesOpaque(frameCtx); } } /** @private */ drawHighlightedFillTransparent(frameCtx) { for (var i = 0, len = this._layers.length; i < len; i++) { this._layers[i].drawHighlightedFillTransparent(frameCtx); } } /** @private */ drawHighlightedEdgesTransparent(frameCtx) { for (var i = 0, len = this._layers.length; i < len; i++) { this._layers[i].drawHighlightedEdgesTransparent(frameCtx); } } /** @private */ drawSelectedFillOpaque(frameCtx) { for (var i = 0, len = this._layers.length; i < len; i++) { this._layers[i].drawSelectedFillOpaque(frameCtx); } } /** @private */ drawSelectedEdgesOpaque(frameCtx) { for (var i = 0, len = this._layers.length; i < len; i++) { this._layers[i].drawSelectedEdgesOpaque(frameCtx); } } /** @private */ drawSelectedFillTransparent(frameCtx) { for (var i = 0, len = this._layers.length; i < len; i++) { this._layers[i].drawSelectedFillTransparent(frameCtx); } } /** @private */ drawSelectedEdgesTransparent(frameCtx) { for (var i = 0, len = this._layers.length; i < len; i++) { this._layers[i].drawSelectedEdgesTransparent(frameCtx); } } /** @private */ drawPickMesh(frameCtx) { if (this.numVisibleLayerPortions === 0) { return; } for (var i = 0, len = this._layers.length; i < len; i++) { this._layers[i].drawPickMesh(frameCtx); } } /** * Called by PerformanceMesh.drawPickDepths() * @private */ drawPickDepths(frameCtx) { if (this.numVisibleLayerPortions === 0) { return; } for (var i = 0, len = this._layers.length; i < len; i++) { this._layers[i].drawPickDepths(frameCtx); } } /** * Called by PerformanceMesh.drawPickNormals() * @private */ drawPickNormals(frameCtx) { if (this.numVisibleLayerPortions === 0) { return; } for (var i = 0, len = this._layers.length; i < len; i++) { this._layers[i].drawPickNormals(frameCtx); } } /** * @private */ drawOcclusion(frameCtx) { if (this.numVisibleLayerPortions === 0) { return; } for (var i = 0, len = this._layers.length; i < len; i++) { this._layers[i].drawOcclusion(frameCtx); } } /** * Called by xeokit to compile shaders for this PerformanceModel. * @private */ compile() { for (var i = 0, len = this._layers.leng