@xeokit/xeokit-sdk
Version:
3D BIM IFC Viewer SDK for AEC engineering applications. Open Source JavaScript Toolkit based on pure WebGL for top performance, real-world coordinates and full double precision
1,320 lines (1,267 loc) • 142 kB
JavaScript
import {Component} from "../Component.js";
import {math} from "../math/math.js";
import {buildEdgeIndices} from '../math/buildEdgeIndices.js';
import {SceneModelMesh} from './SceneModelMesh.js';
import {DTXLayer} from './layer/DTXLayer.js';
import {VBOLayer} from './layer/VBOLayer.js';
import {ENTITY_FLAGS} from './ENTITY_FLAGS.js';
import {RenderFlags} from "../webgl/RenderFlags.js";
import {worldToRTCPositions} from "../math/rtcCoords.js";
import {SceneModelTextureSet} from "./SceneModelTextureSet.js";
import {SceneModelTexture} from "./SceneModelTexture.js";
import {Texture2D} from "../webgl/Texture2D.js";
import {utils} from "../utils.js";
import {getKTX2TextureTranscoder} from "../utils/textureTranscoders/KTX2TextureTranscoder/KTX2TextureTranscoder.js";
import {
ClampToEdgeWrapping,
LinearEncoding,
LinearFilter,
LinearMipmapLinearFilter,
LinearMipMapNearestFilter,
MirroredRepeatWrapping,
NearestFilter,
NearestMipMapLinearFilter,
NearestMipMapNearestFilter,
RepeatWrapping,
sRGBEncoding
} from "../constants/constants.js";
import {createPositionsDecodeMatrix, quantizePositions} from "./compression.js";
import {uniquifyPositions} from "./calculateUniquePositions.js";
import {rebucketPositions} from "./rebucketPositions.js";
import {SceneModelEntity} from "./SceneModelEntity.js";
import {geometryCompressionUtils} from "../math/geometryCompressionUtils.js";
import {SceneModelTransform} from "./SceneModelTransform.js";
const tempVec3a = math.vec3();
const tempOBB3 = math.OBB3();
const tempQuaternion = math.vec4();
const DEFAULT_SCALE = math.vec3([1, 1, 1]);
const DEFAULT_POSITION = math.vec3([0, 0, 0]);
const DEFAULT_ROTATION = math.vec3([0, 0, 0]);
const DEFAULT_QUATERNION = math.identityQuaternion();
const DEFAULT_MATRIX = math.identityMat4();
const DEFAULT_COLOR_TEXTURE_ID = "defaultColorTexture";
const DEFAULT_METAL_ROUGH_TEXTURE_ID = "defaultMetalRoughTexture";
const DEFAULT_NORMALS_TEXTURE_ID = "defaultNormalsTexture";
const DEFAULT_EMISSIVE_TEXTURE_ID = "defaultEmissiveTexture";
const DEFAULT_OCCLUSION_TEXTURE_ID = "defaultOcclusionTexture";
const DEFAULT_TEXTURE_SET_ID = "defaultTextureSet";
const defaultCompressedColor = new Uint8Array([255, 255, 255]);
const VBO_INSTANCED = 0;
const VBO_BATCHED = 1;
const DTX = 2;
/**
* @desc A high-performance model representation for efficient rendering and low memory usage.
*
* # Examples
*
* Internally, SceneModel uses a combination of several different techniques to render and represent
* the different parts of a typical model. Each of the live examples at these links is designed to "unit test" one of these
* techniques, in isolation. If some bug occurs in SceneModel, we use these tests to debug, but they also
* serve to demonstrate how to use the capabilities of SceneModel programmatically.
*
* * [Loading building models into SceneModels](/examples/buildings)
* * [Loading city models into SceneModels](/examples/cities)
* * [Loading LiDAR scans into SceneModels](/examples/lidar)
* * [Loading CAD models into SceneModels](/examples/cad)
* * [SceneModel feature tests](/examples/scenemodel)
*
* # Overview
*
* While xeokit's standard [scene graph](https://github.com/xeokit/xeokit-sdk/wiki/Scene-Graphs) is great for gizmos and medium-sized models, it doesn't scale up to millions of objects in terms of memory and rendering efficiency.
*
* For huge models, we have the ````SceneModel```` representation, which is optimized to pack large amounts of geometry into memory and render it efficiently using WebGL.
*
* ````SceneModel```` is the default model representation loaded by (at least) {@link GLTFLoaderPlugin}, {@link XKTLoaderPlugin} and {@link WebIFCLoaderPlugin}.
*
* In this tutorial you'll learn how to use ````SceneModel```` to create high-detail content programmatically. Ordinarily you'd be learning about ````SceneModel```` if you were writing your own model loader plugins.
*
* # Contents
*
* - [SceneModel](#DataTextureSceneModel)
* - [GPU-Resident Geometry](#gpu-resident-geometry)
* - [Picking](#picking)
* - [Example 1: Geometry Instancing](#example-1--geometry-instancing)
* - [Finalizing a SceneModel](#finalizing-a-DataTextureSceneModel)
* - [Finding Entities](#finding-entities)
* - [Example 2: Geometry Batching](#example-2--geometry-batching)
* - [Classifying with Metadata](#classifying-with-metadata)
* - [Querying Metadata](#querying-metadata)
* - [Metadata Structure](#metadata-structure)
* - [RTC Coordinates](#rtc-coordinates-for-double-precision)
* - [Example 3: RTC Coordinates with Geometry Instancing](#example-2--rtc-coordinates-with-geometry-instancing)
* - [Example 4: RTC Coordinates with Geometry Batching](#example-2--rtc-coordinates-with-geometry-batching)
*
* ## SceneModel
*
* ````SceneModel```` uses two rendering techniques internally:
*
* 1. ***Geometry batching*** for unique geometries, combining those into a single WebGL geometry buffer, to render in one draw call, and
* 2. ***geometry instancing*** for geometries that are shared by multiple meshes, rendering all instances of each shared geometry in one draw call.
*
* <br>
* These techniques come with certain limitations:
*
* * Non-realistic rendering - while scene graphs can use xeokit's full set of material workflows, ````SceneModel```` uses simple Lambertian shading without textures.
* * Static transforms - transforms within a ````SceneModel```` are static and cannot be dynamically translated, rotated and scaled the way {@link Node}s and {@link Mesh}es in scene graphs can.
* * Immutable model representation - while scene graph {@link Node}s and
* {@link Mesh}es can be dynamically plugged together, ````SceneModel```` is immutable,
* since it packs its geometries into buffers and instanced arrays.
*
* ````SceneModel````'s API allows us to exploit batching and instancing, while exposing its elements as
* abstract {@link Entity} types.
*
* {@link Entity} is the abstract base class for
* the various xeokit components that represent models, objects, or anonymous visible elements. An Entity has a unique ID and can be
* individually shown, hidden, selected, highlighted, ghosted, culled, picked and clipped, and has its own World-space boundary.
*
* * A ````SceneModel```` is an {@link Entity} that represents a model.
* * A ````SceneModel```` represents each of its objects with an {@link Entity}.
* * Each {@link Entity} has one or more meshes that define its shape.
* * Each mesh has either its own unique geometry, or shares a geometry with other meshes.
*
* ## GPU-Resident Geometry
*
* For a low memory footprint, ````SceneModel```` stores its geometries in GPU memory only, compressed (quantized) as integers. Unfortunately, GPU-resident geometry is
* not readable by JavaScript.
*
*
* ## Example 1: Geometry Instancing
*
* In the example below, we'll use a ````SceneModel````
* to build a simple table model using geometry instancing.
*
* We'll start by adding a reusable box-shaped geometry to our ````SceneModel````.
*
* Then, for each object in our model we'll add an {@link Entity}
* that has a mesh that instances our box geometry, transforming and coloring the instance.
*
* [](https://xeokit.github.io/xeokit-sdk/examples/index.html#sceneRepresentation_SceneModel_instancing)
*
* ````javascript
* import {Viewer, SceneModel} from "xeokit-sdk.es.js";
*
* const viewer = new Viewer({
* canvasId: "myCanvas",
* transparent: true
* });
*
* 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];
*
* // Build a SceneModel representing a table
* // with four legs, using geometry instancing
*
* const sceneModel = new SceneModel(viewer.scene, {
* id: "table",
* isModel: true, // <--- Registers SceneModel in viewer.scene.models
* position: [0, 0, 0],
* scale: [1, 1, 1],
* rotation: [0, 0, 0]
* });
*
* // Create a reusable geometry within the SceneModel
* // We'll instance this geometry by five meshes
*
* sceneModel.createGeometry({
*
* id: "myBoxGeometry",
*
* // The primitive type - allowed values are "points", "lines" and "triangles".
* // See the OpenGL/WebGL specification docs
* // for how the coordinate arrays are supposed to be laid out.
* primitive: "triangles",
*
* // The vertices - eight for our cube, each
* // one spanning three array elements for X,Y and Z
* positions: [
* 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, // v0-v1-v2-v3 front
* 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, // v0-v3-v4-v1 right
* 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, // v0-v1-v6-v1 top
* -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, // v1-v6-v7-v2 left
* -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, // v7-v4-v3-v2 bottom
* 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1 // v4-v7-v6-v1 back
* ],
*
* // Normal vectors, one for each vertex
* normals: [
* 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, // v0-v1-v2-v3 front
* 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v3-v4-v5 right
* 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, // v0-v5-v6-v1 top
* -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, // v1-v6-v7-v2 left
* 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, // v7-v4-v3-v2 bottom
* 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1 // v4-v7-v6-v5 back
* ],
*
* // Indices - these organise the positions and and normals
* // into geometric primitives in accordance with the "primitive" parameter,
* // in this case a set of three indices for each triangle.
* //
* // Note that each triangle is specified in counter-clockwise winding order.
* //
* indices: [
* 0, 1, 2, 0, 2, 3, // front
* 4, 5, 6, 4, 6, 7, // right
* 8, 9, 10, 8, 10, 11, // top
* 12, 13, 14, 12, 14, 15, // left
* 16, 17, 18, 16, 18, 19, // bottom
* 20, 21, 22, 20, 22, 23
* ]
* });
*
* // Red table leg
*
* sceneModel.createMesh({
* id: "redLegMesh",
* geometryId: "myBoxGeometry",
* position: [-4, -6, -4],
* scale: [1, 3, 1],
* rotation: [0, 0, 0],
* color: [1, 0.3, 0.3]
* });
*
* sceneModel.createEntity({
* id: "redLeg",
* meshIds: ["redLegMesh"],
* isObject: true // <---- Registers Entity by ID on viewer.scene.objects
* });
*
* // Green table leg
*
* sceneModel.createMesh({
* id: "greenLegMesh",
* geometryId: "myBoxGeometry",
* position: [4, -6, -4],
* scale: [1, 3, 1],
* rotation: [0, 0, 0],
* color: [0.3, 1.0, 0.3]
* });
*
* sceneModel.createEntity({
* id: "greenLeg",
* meshIds: ["greenLegMesh"],
* isObject: true // <---- Registers Entity by ID on viewer.scene.objects
* });
*
* // Blue table leg
*
* sceneModel.createMesh({
* id: "blueLegMesh",
* geometryId: "myBoxGeometry",
* position: [4, -6, 4],
* scale: [1, 3, 1],
* rotation: [0, 0, 0],
* color: [0.3, 0.3, 1.0]
* });
*
* sceneModel.createEntity({
* id: "blueLeg",
* meshIds: ["blueLegMesh"],
* isObject: true // <---- Registers Entity by ID on viewer.scene.objects
* });
*
* // Yellow table leg
*
* sceneModel.createMesh({
* id: "yellowLegMesh",
* geometryId: "myBoxGeometry",
* position: [-4, -6, 4],
* scale: [1, 3, 1],
* rotation: [0, 0, 0],
* color: [1.0, 1.0, 0.0]
* });
*
* sceneModel.createEntity({
* id: "yellowLeg",
* meshIds: ["yellowLegMesh"],
* isObject: true // <---- Registers Entity by ID on viewer.scene.objects
* });
*
* // Purple table top
*
* sceneModel.createMesh({
* id: "purpleTableTopMesh",
* geometryId: "myBoxGeometry",
* position: [0, -3, 0],
* scale: [6, 0.5, 6],
* rotation: [0, 0, 0],
* color: [1.0, 0.3, 1.0]
* });
*
* sceneModel.createEntity({
* id: "purpleTableTop",
* meshIds: ["purpleTableTopMesh"],
* isObject: true // <---- Registers Entity by ID on viewer.scene.objects
* });
* ````
*
* ## Finalizing a SceneModel
*
* Before we can view and interact with our ````SceneModel````, we need to **finalize** it. Internally, this causes the ````SceneModel```` to build the
* vertex buffer objects (VBOs) that support our geometry instances. When using geometry batching (see next example),
* this causes ````SceneModel```` to build the VBOs that combine the batched geometries. Note that you can do both instancing and
* batching within the same ````SceneModel````.
*
* Once finalized, we can't add anything more to our ````SceneModel````.
*
* ```` javascript
* SceneModel.finalize();
* ````
*
* ## Finding Entities
*
* As mentioned earlier, {@link Entity} is
* the abstract base class for components that represent models, objects, or just
* anonymous visible elements.
*
* Since we created configured our ````SceneModel```` with ````isModel: true````,
* we're able to find it as an Entity by ID in ````viewer.scene.models````. Likewise, since
* we configured each of its Entities with ````isObject: true````, we're able to
* find them in ````viewer.scene.objects````.
*
*
* ````javascript
* // Get the whole table model Entity
* const table = viewer.scene.models["table"];
*
* // Get some leg object Entities
* const redLeg = viewer.scene.objects["redLeg"];
* const greenLeg = viewer.scene.objects["greenLeg"];
* const blueLeg = viewer.scene.objects["blueLeg"];
* ````
*
* ## Example 2: Geometry Batching
*
* Let's once more use a ````SceneModel````
* to build the simple table model, this time exploiting geometry batching.
*
* [](https://xeokit.github.io/xeokit-sdk/examples/index.html#sceneRepresentation_SceneModel_batching)
*
* ````javascript
* import {Viewer, SceneModel} from "xeokit-sdk.es.js";
*
* const viewer = new Viewer({
* canvasId: "myCanvas",
* transparent: true
* });
*
* 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];
*
* // Create a SceneModel representing a table with four legs, using geometry batching
* const sceneModel = new SceneModel(viewer.scene, {
* id: "table",
* isModel: true, // <--- Registers SceneModel in viewer.scene.models
* position: [0, 0, 0],
* scale: [1, 1, 1],
* rotation: [0, 0, 0]
* });
*
* // Red table leg
*
* sceneModel.createMesh({
* id: "redLegMesh",
*
* // Geometry arrays are same as for the earlier batching example
* primitive: "triangles",
* positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],
* normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],
* indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],
* position: [-4, -6, -4],
* scale: [1, 3, 1],
* rotation: [0, 0, 0],
* color: [1, 0.3, 0.3]
* });
*
* sceneModel.createEntity({
* id: "redLeg",
* meshIds: ["redLegMesh"],
* isObject: true // <---- Registers Entity by ID on viewer.scene.objects
* });
*
* // Green table leg
*
* sceneModel.createMesh({
* id: "greenLegMesh",
* primitive: "triangles",
* primitive: "triangles",
* positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],
* normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],
* indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],
* position: [4, -6, -4],
* scale: [1, 3, 1],
* rotation: [0, 0, 0],
* color: [0.3, 1.0, 0.3]
* });
*
* sceneModel.createEntity({
* id: "greenLeg",
* meshIds: ["greenLegMesh"],
* isObject: true // <---- Registers Entity by ID on viewer.scene.objects
* });
*
* // Blue table leg
*
* sceneModel.createMesh({
* id: "blueLegMesh",
* primitive: "triangles",
* positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],
* normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],
* indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],
* position: [4, -6, 4],
* scale: [1, 3, 1],
* rotation: [0, 0, 0],
* color: [0.3, 0.3, 1.0]
* });
*
* sceneModel.createEntity({
* id: "blueLeg",
* meshIds: ["blueLegMesh"],
* isObject: true // <---- Registers Entity by ID on viewer.scene.objects
* });
*
* // Yellow table leg object
*
* sceneModel.createMesh({
* id: "yellowLegMesh",
* positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],
* normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],
* indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],
* position: [-4, -6, 4],
* scale: [1, 3, 1],
* rotation: [0, 0, 0],
* color: [1.0, 1.0, 0.0]
* });
*
* sceneModel.createEntity({
* id: "yellowLeg",
* meshIds: ["yellowLegMesh"],
* isObject: true // <---- Registers Entity by ID on viewer.scene.objects
* });
*
* // Purple table top
*
* sceneModel.createMesh({
* id: "purpleTableTopMesh",
* positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],
* normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],
* indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],
* position: [0, -3, 0],
* scale: [6, 0.5, 6],
* rotation: [0, 0, 0],
* color: [1.0, 0.3, 1.0]
* });
*
* sceneModel.createEntity({
* id: "purpleTableTop",
* meshIds: ["purpleTableTopMesh"],
* isObject: true // <---- Registers Entity by ID on viewer.scene.objects
* });
*
* // Finalize the SceneModel.
*
* SceneModel.finalize();
*
* // Find BigModelNodes by their model and object IDs
*
* // Get the whole table model
* const table = viewer.scene.models["table"];
*
* // Get some leg objects
* const redLeg = viewer.scene.objects["redLeg"];
* const greenLeg = viewer.scene.objects["greenLeg"];
* const blueLeg = viewer.scene.objects["blueLeg"];
* ````
*
* ## Classifying with Metadata
*
* In the previous examples, we used ````SceneModel```` to build
* two versions of the same table model, to demonstrate geometry batching and geometry instancing.
*
* We'll now classify our {@link Entity}s with metadata. This metadata
* will work the same for both our examples, since they create the exact same structure of {@link Entity}s
* to represent their models and objects. The abstract Entity type is, after all, intended to provide an abstract interface through which differently-implemented scene content can be accessed uniformly.
*
* To create the metadata, we'll create a {@link MetaModel} for our model,
* with a {@link MetaObject} for each of it's objects. The MetaModel and MetaObjects
* get the same IDs as the {@link Entity}s that represent their model and objects within our scene.
*
* ```` javascript
* const furnitureMetaModel = viewer.metaScene.createMetaModel("furniture", { // Creates a MetaModel in the MetaScene
*
* "projectId": "myTableProject",
* "revisionId": "V1.0",
*
* "metaObjects": [
* { // Creates a MetaObject in the MetaModel
* "id": "table",
* "name": "Table", // Same ID as an object Entity
* "type": "furniture", // Arbitrary type, could be IFC type
* "properties": { // Arbitrary properties, could be IfcPropertySet
* "cost": "200"
* }
* },
* {
* "id": "redLeg",
* "name": "Red table Leg",
* "type": "leg",
* "parent": "table", // References first MetaObject as parent
* "properties": {
* "material": "wood"
* }
* },
* {
* "id": "greenLeg", // Node with corresponding id does not need to exist
* "name": "Green table leg", // and MetaObject does not need to exist for Node with an id
* "type": "leg",
* "parent": "table",
* "properties": {
* "material": "wood"
* }
* },
* {
* "id": "blueLeg",
* "name": "Blue table leg",
* "type": "leg",
* "parent": "table",
* "properties": {
* "material": "wood"
* }
* },
* {
* "id": "yellowLeg",
* "name": "Yellow table leg",
* "type": "leg",
* "parent": "table",
* "properties": {
* "material": "wood"
* }
* },
* {
* "id": "tableTop",
* "name": "Purple table top",
* "type": "surface",
* "parent": "table",
* "properties": {
* "material": "formica",
* "width": "60",
* "depth": "60",
* "thickness": "5"
* }
* }
* ]
* });
* ````
*
* ## Querying Metadata
*
* Having created and classified our model (either the instancing or batching example), we can now find the {@link MetaModel}
* and {@link MetaObject}s using the IDs of their
* corresponding {@link Entity}s.
*
* ````JavaScript
* const furnitureMetaModel = scene.metaScene.metaModels["furniture"];
*
* const redLegMetaObject = scene.metaScene.metaObjects["redLeg"];
* ````
*
* In the snippet below, we'll log metadata on each {@link Entity} we click on:
*
* ````JavaScript
* viewer.scene.input.on("mouseclicked", function (coords) {
*
* const hit = viewer.scene.pick({
* canvasPos: coords
* });
*
* if (hit) {
* const entity = hit.entity;
* const metaObject = viewer.metaScene.metaObjects[entity.id];
* if (metaObject) {
* console.log(JSON.stringify(metaObject.getJSON(), null, "\t"));
* }
* }
* });
* ````
*
* ## Metadata Structure
*
* The {@link MetaModel}
* organizes its {@link MetaObject}s in
* a tree that describes their structural composition:
*
* ````JavaScript
* // Get metadata on the root object
* const tableMetaObject = furnitureMetaModel.rootMetaObject;
*
* // Get metadata on the leg objects
* const redLegMetaObject = tableMetaObject.children[0];
* const greenLegMetaObject = tableMetaObject.children[1];
* const blueLegMetaObject = tableMetaObject.children[2];
* const yellowLegMetaObject = tableMetaObject.children[3];
* ````
*
* Given an {@link Entity}, we can find the object or model of which it is a part, or the objects that comprise it. We can also generate UI
* components from the metadata, such as the tree view demonstrated in [this demo](https://xeokit.github.io/xeokit-sdk/examples/index.html#BIMOffline_glTF_OTCConferenceCenter).
*
* This hierarchy allows us to express the hierarchical structure of a model while representing it in
* various ways in the 3D scene (such as with ````SceneModel````, which
* has a non-hierarchical scene representation).
*
* Note also that a {@link MetaObject} does not need to have a corresponding
* {@link Entity} and vice-versa.
*
* # RTC Coordinates for Double Precision
*
* ````SceneModel```` can emulate 64-bit precision on GPUs using relative-to-center (RTC) coordinates.
*
* Consider a model that contains many small objects, but with such large spatial extents that 32 bits of GPU precision (accurate to ~7 digits) will not be sufficient to render all of the the objects without jittering.
*
* To prevent jittering, we could spatially subdivide the objects into "tiles". Each tile would have a center position, and the positions of the objects within the tile would be relative to that center ("RTC coordinates").
*
* While the center positions of the tiles would be 64-bit values, the object positions only need to be 32-bit.
*
* Internally, when rendering an object with RTC coordinates, xeokit first temporarily translates the camera viewing matrix by the object's tile's RTC center, on the CPU, using 64-bit math.
*
* Then xeokit loads the viewing matrix into its WebGL shaders, where math happens at 32-bit precision. Within the shaders, the matrix is effectively down-cast to 32-bit precision, and the object's 32-bit vertex positions are transformed by the matrix.
*
* We see no jittering, because with RTC a detectable loss of GPU accuracy only starts happening to objects as they become very distant from the camera viewpoint, at which point they are too small to be discernible anyway.
*
* ## RTC Coordinates with Geometry Instancing
*
* To use RTC with ````SceneModel```` geometry instancing, we specify an RTC center for the geometry via its ````origin```` parameter. Then ````SceneModel```` assumes that all meshes that instance that geometry are within the same RTC coordinate system, ie. the meshes ````position```` and ````rotation```` properties are assumed to be relative to the geometry's ````origin````.
*
* For simplicity, our example's meshes all instance the same geometry. Therefore, our example model has only one RTC center.
*
* Note that the axis-aligned World-space boundary (AABB) of our model is ````[ -6, -9, -6, 1000000006, -2.5, 1000000006]````.
*
* [](https://xeokit.github.io/xeokit-sdk/examples/index.html#sceneRepresentation_SceneModel_batching)
*
* ````javascript
* const origin = [100000000, 0, 100000000];
*
* sceneModel.createGeometry({
* id: "box",
* primitive: "triangles",
* positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],
* normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],
* indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],
* });
*
* sceneModel.createMesh({
* id: "leg1",
* geometryId: "box",
* position: [-4, -6, -4],
* scale: [1, 3, 1],
* rotation: [0, 0, 0],
* color: [1, 0.3, 0.3],
* origin: origin
* });
*
* sceneModel.createEntity({
* meshIds: ["leg1"],
* isObject: true
* });
*
* sceneModel.createMesh({
* id: "leg2",
* geometryId: "box",
* position: [4, -6, -4],
* scale: [1, 3, 1],
* rotation: [0, 0, 0],
* color: [0.3, 1.0, 0.3],
* origin: origin
* });
*
* sceneModel.createEntity({
* meshIds: ["leg2"],
* isObject: true
* });
*
* sceneModel.createMesh({
* id: "leg3",
* geometryId: "box",
* position: [4, -6, 4],
* scale: [1, 3, 1],
* rotation: [0, 0, 0],
* color: [0.3, 0.3, 1.0],
* origin: origin
* });
*
* sceneModel.createEntity({
* meshIds: ["leg3"],
* isObject: true
* });
*
* sceneModel.createMesh({
* id: "leg4",
* geometryId: "box",
* position: [-4, -6, 4],
* scale: [1, 3, 1],
* rotation: [0, 0, 0],
* color: [1.0, 1.0, 0.0],
* origin: origin
* });
*
* sceneModel.createEntity({
* meshIds: ["leg4"],
* isObject: true
* });
*
* sceneModel.createMesh({
* id: "top",
* geometryId: "box",
* position: [0, -3, 0],
* scale: [6, 0.5, 6],
* rotation: [0, 0, 0],
* color: [1.0, 0.3, 1.0],
* origin: origin
* });
*
* sceneModel.createEntity({
* meshIds: ["top"],
* isObject: true
* });
* ````
*
* ## RTC Coordinates with Geometry Batching
*
* To use RTC with ````SceneModel```` geometry batching, we specify an RTC center (````origin````) for each mesh. For performance, we try to have as many meshes share the same value for ````origin```` as possible. Each mesh's ````positions````, ````position```` and ````rotation```` properties are assumed to be relative to ````origin````.
*
* For simplicity, the meshes in our example all share the same RTC center.
*
* The axis-aligned World-space boundary (AABB) of our model is ````[ -6, -9, -6, 1000000006, -2.5, 1000000006]````.
*
* [](https://xeokit.github.io/xeokit-sdk/examples/index.html#sceneRepresentation_SceneModel_batching)
*
* ````javascript
* const origin = [100000000, 0, 100000000];
*
* sceneModel.createMesh({
* id: "leg1",
* origin: origin, // This mesh's positions and transforms are relative to the RTC center
* primitive: "triangles",
* positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],
* normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],
* indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],
* position: [-4, -6, -4],
* scale: [1, 3, 1],
* rotation: [0, 0, 0],
* color: [1, 0.3, 0.3]
* });
*
* sceneModel.createEntity({
* meshIds: ["leg1"],
* isObject: true
* });
*
* sceneModel.createMesh({
* id: "leg2",
* origin: origin, // This mesh's positions and transforms are relative to the RTC center
* primitive: "triangles",
* positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],
* normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],
* indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],
* position: [4, -6, -4],
* scale: [1, 3, 1],
* rotation: [0, 0, 0],
* color: [0.3, 1.0, 0.3]
* });
*
* sceneModel.createEntity({
* meshIds: ["leg2"],
* isObject: true
* });
*
* sceneModel.createMesh({
* id: "leg3",
* origin: origin, // This mesh's positions and transforms are relative to the RTC center
* primitive: "triangles",
* positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],
* normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],
* indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],
* position: [4, -6, 4],
* scale: [1, 3, 1],
* rotation: [0, 0, 0],
* color: [0.3, 0.3, 1.0]
* });
*
* sceneModel.createEntity({
* meshIds: ["leg3"],
* isObject: true
* });
*
* sceneModel.createMesh({
* id: "leg4",
* origin: origin, // This mesh's positions and transforms are relative to the RTC center
* primitive: "triangles",
* positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],
* normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],
* indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],
* position: [-4, -6, 4],
* scale: [1, 3, 1],
* rotation: [0, 0, 0],
* color: [1.0, 1.0, 0.0]
* });
*
* sceneModel.createEntity({
* meshIds: ["leg4"],
* isObject: true
* });
*
* sceneModel.createMesh({
* id: "top",
* origin: origin, // This mesh's positions and transforms are relative to the RTC center
* primitive: "triangles",
* positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],
* normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],
* indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],
* position: [0, -3, 0],
* scale: [6, 0.5, 6],
* rotation: [0, 0, 0],
* color: [1.0, 0.3, 1.0]
* });
*
* sceneModel.createEntity({
* meshIds: ["top"],
* isObject: true
* });
* ````
*
* ## Positioning at World-space coordinates
*
* To position a SceneModel at given double-precision World coordinates, we can
* configure the ````origin```` of the SceneModel itself. The ````origin```` is a double-precision
* 3D World-space position at which the SceneModel will be located.
*
* Note that ````position```` is a single-precision offset relative to ````origin````.
*
* ````javascript
* const origin = [100000000, 0, 100000000];
*
* const sceneModel = new SceneModel(viewer.scene, {
* id: "table",
* isModel: true,
* origin: origin, // Everything in this SceneModel is relative to this RTC center
* position: [0, 0, 0],
* scale: [1, 1, 1],
* rotation: [0, 0, 0]
* });
*
* sceneModel.createGeometry({
* id: "box",
* primitive: "triangles",
* positions: [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 ... ],
* normals: [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, ... ],
* indices: [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, ... ],
* });
*
* sceneModel.createMesh({
* id: "leg1",
* geometryId: "box",
* position: [-4, -6, -4],
* scale: [1, 3, 1],
* rotation: [0, 0, 0],
* color: [1, 0.3, 0.3]
* });
*
* sceneModel.createEntity({
* meshIds: ["leg1"],
* isObject: true
* });
*
* sceneModel.createMesh({
* id: "leg2",
* geometryId: "box",
* position: [4, -6, -4],
* scale: [1, 3, 1],
* rotation: [0, 0, 0],
* color: [0.3, 1.0, 0.3]
* });
*
* sceneModel.createEntity({
* meshIds: ["leg2"],
* isObject: true
* });
*
* sceneModel.createMesh({
* id: "leg3",
* geometryId: "box",
* position: [4, -6, 4],
* scale: [1, 3, 1],
* rotation: [0, 0, 0],
* color: [0.3, 0.3, 1.0]
* });
*
* sceneModel.createEntity({
* meshIds: ["leg3"],
* isObject: true
* });
*
* sceneModel.createMesh({
* id: "leg4",
* geometryId: "box",
* position: [-4, -6, 4],
* scale: [1, 3, 1],
* rotation: [0, 0, 0],
* color: [1.0, 1.0, 0.0]
* });
*
* sceneModel.createEntity({
* meshIds: ["leg4"],
* isObject: true
* });
*
* sceneModel.createMesh({
* id: "top",
* geometryId: "box",
* position: [0, -3, 0],
* scale: [6, 0.5, 6],
* rotation: [0, 0, 0],
* color: [1.0, 0.3, 1.0]
* });
*
* sceneModel.createEntity({
* meshIds: ["top"],
* isObject: true
* });
* ````
*
* # Textures
*
* ## Loading KTX2 Texture Files into a SceneModel
*
* A {@link SceneModel} that is configured with a {@link KTX2TextureTranscoder} will
* allow us to load textures into it from KTX2 buffers or files.
*
* In the example below, we'll create a {@link Viewer}, containing a {@link SceneModel} configured with a
* {@link KTX2TextureTranscoder}. We'll then programmatically create a simple object within the SceneModel, consisting of
* a single mesh with a texture loaded from a KTX2 file, which our SceneModel internally transcodes, using
* its {@link KTX2TextureTranscoder}. Note how we configure our {@link KTX2TextureTranscoder} with a path to the Basis Universal
* transcoder WASM module.
*
* ````javascript
* const viewer = new Viewer({
* canvasId: "myCanvas",
* transparent: true
* });
*
* 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 textureTranscoder = new KTX2TextureTranscoder({
* viewer,
* transcoderPath: "https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/" // <------ Path to BasisU transcoder module
* });
*
* const sceneModel = new SceneModel(viewer.scene, {
* id: "myModel",
* textureTranscoder // <<-------------------- Configure model with our transcoder
* });
*
* sceneModel.createTexture({
* id: "myColorTexture",
* src: "../assets/textures/compressed/sample_uastc_zstd.ktx2" // <<----- KTX2 texture asset
* });
*
* sceneModel.createTexture({
* id: "myMetallicRoughnessTexture",
* src: "../assets/textures/alpha/crosshatchAlphaMap.jpg" // <<----- JPEG texture asset
* });
*
* sceneModel.createTextureSet({
* id: "myTextureSet",
* colorTextureId: "myColorTexture",
* metallicRoughnessTextureId: "myMetallicRoughnessTexture"
* });
*
* sceneModel.createMesh({
* id: "myMesh",
* textureSetId: "myTextureSet",
* primitive: "triangles",
* positions: [1, 1, 1, ...],
* normals: [0, 0, 1, 0, ...],
* uv: [1, 0, 0, ...],
* indices: [0, 1, 2, ...],
* });
*
* sceneModel.createEntity({
* id: "myEntity",
* meshIds: ["myMesh"]
* });
*
* sceneModel.finalize();
* ````
*
* ## Loading KTX2 Textures from ArrayBuffers into a SceneModel
*
* A SceneModel that is configured with a {@link KTX2TextureTranscoder} will allow us to load textures into
* it from KTX2 ArrayBuffers.
*
* In the example below, we'll create a {@link Viewer}, containing a {@link SceneModel} configured with a
* {@link KTX2TextureTranscoder}. We'll then programmatically create a simple object within the SceneModel, consisting of
* a single mesh with a texture loaded from a KTX2 ArrayBuffer, which our SceneModel internally transcodes, using
* its {@link KTX2TextureTranscoder}.
*
* ````javascript
* const viewer = new Viewer({
* canvasId: "myCanvas",
* transparent: true
* });
*
* 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 textureTranscoder = new KTX2TextureTranscoder({
* viewer,
* transcoderPath: "https://cdn.jsdelivr.net/npm/@xeokit/xeokit-sdk/dist/basis/" // <------ Path to BasisU transcoder module
* });
*
* const sceneModel = new SceneModel(viewer.scene, {
* id: "myModel",
* textureTranscoder // <<-------------------- Configure model with our transcoder
* });
*
* utils.loadArraybuffer("../assets/textures/compressed/sample_uastc_zstd.ktx2",(arrayBuffer) => {
*
* sceneModel.createTexture({
* id: "myColorTexture",
* buffers: [arrayBuffer] // <<----- KTX2 texture asset
* });
*
* sceneModel.createTexture({
* id: "myMetallicRoughnessTexture",
* src: "../assets/textures/alpha/crosshatchAlphaMap.jpg" // <<----- JPEG texture asset
* });
*
* sceneModel.createTextureSet({
* id: "myTextureSet",
* colorTextureId: "myColorTexture",
* metallicRoughnessTextureId: "myMetallicRoughnessTexture"
* });
*
* sceneModel.createMesh({
* id: "myMesh",
* textureSetId: "myTextureSet",
* primitive: "triangles",
* positions: [1, 1, 1, ...],
* normals: [0, 0, 1, 0, ...],
* uv: [1, 0, 0, ...],
* indices: [0, 1, 2, ...],
* });
*
* sceneModel.createEntity({
* id: "myEntity",
* meshIds: ["myMesh"]
* });
*
* sceneModel.finalize();
* });
* ````
*
* @implements {Entity}
*/
export class SceneModel 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 SceneModel represents a model, in which case the SceneModel will be registered by {@link SceneModel#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.origin=[0,0,0]] World-space double-precision 3D origin.
* @param {Number[]} [cfg.position=[0,0,0]] Local, single-precision 3D position, relative to the origin parameter.
* @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 SceneModel is initially visible.
* @param {Boolean} [cfg.culled=false] Indicates if the SceneModel is initially culled from view.
* @param {Boolean} [cfg.pickable=true] Indicates if the SceneModel is initially pickable.
* @param {Boolean} [cfg.clippable=true] Indicates if the SceneModel is initially clippable.
* @param {Boolean} [cfg.collidable=true] Indicates if the SceneModel is initially included in boundary calculations.
* @param {Boolean} [cfg.xrayed=false] Indicates if the SceneModel is initially xrayed.
* @param {Boolean} [cfg.highlighted=false] Indicates if the SceneModel is initially highlighted.
* @param {Boolean} [cfg.selected=false] Indicates if the SceneModel is initially selected.
* @param {Boolean} [cfg.edges=false] Indicates if the SceneModel's edges are initially emphasized.
* @param {Number[]} [cfg.colorize=[1.0,1.0,1.0]] SceneModel's initial RGB colorize color, multiplies by the rendered fragment colors.
* @param {Number} [cfg.opacity=1.0] SceneModel's initial opacity factor, multiplies by the rendered fragment alpha.
* @param {Number} [cfg.backfaces=false] When we set this ````true````, then we force rendering of backfaces for this SceneModel. When
* we leave this ````false````, then we allow the Viewer to decide when to render backfaces. In that case, the
* Viewer will hide backfaces on watertight meshes, show backfaces on open meshes, and always show backfaces on meshes when we slice them open with {@link SectionPlane}s.
* @param {Boolean} [cfg.saoEnabled=true] Indicates if Scalable Ambient Obscurance (SAO) will apply to this SceneModel. SAO is configured by the Scene's {@link SAO} component.
* @param {Boolean} [cfg.pbrEnabled=true] Indicates if physically-based rendering (PBR) will apply to the SceneModel when {@link Scene#pbrEnabled} is ````true````.
* @param {Boolean} [cfg.colorTextureEnabled=true] Indicates if base color textures will be rendered for the SceneModel when {@link Scene#colorTextureEnabled} is ````true````.
* @param {Number} [cfg.edgeThreshold=10] When xraying, highlighting, selecting or edging, this is the threshold angle between normals of adjacent triangles, below which their shared wireframe edge is not drawn.
* @param {Number} [cfg.maxGeometryBatchSize=50000000] Maximum geometry batch size, as number of vertices. This is optionally supplied
* to limit the size of the batched geometry arrays that SceneModel internally creates for batched geometries.
* A lower value means less heap allocation/de-allocation while creating/loading batched geometries, but more draw calls and
* slower rendering speed. A high value means larger heap allocation/de-allocation while creating/loading, but less draw calls
* and faster rendering speed. It's recommended to keep this somewhere roughly between ````50000```` and ````50000000```.
* @param {TextureTranscoder} [cfg.textureTranscoder] Transcoder that will be used internally by {@link SceneModel#createTexture}
* to convert transcoded texture data. Only required when we'll be providing transcoded data
* to {@link SceneModel#createTexture}. We assume that all transcoded texture data added to a ````SceneModel````
* will then in a format supported by this transcoder.
* @param {Boolean} [cfg.dtxEnabled=true] When ````true```` (default) use data textures (DTX), where appropriate, to
* represent the returned model. Set false to always use vertex buffer objects (VBOs). Note that DTX is only applicable
* to non-textured triangle meshes, and that VBOs are always used for meshes that have textures, line segments, or point
* primitives. Only works while {@link DTX#enabled} is also ````true````.
* @param {Number} [cfg.renderOrder=0] Specifies the rendering order for this SceneModel. This is used to control the order in which
* SceneModels 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;
this._dtxEnabled = this.scene.dtxEnabled && (cfg.dtxEnabled !== false);
this._enableVertexWelding = false; // Not needed for most objects, and very expensive, so disabled
this._enableIndexBucketing = false; // Until fixed: https://github.com/xeokit/xeokit-sdk/issues/1204
this._textureTranscoder = cfg.textureTranscoder || getKTX2TextureTranscoder(this.scene.viewer);
this._maxGeometryBatchSize = cfg.maxGeometryBatchSize ?? 5000000;
this._aabb = math.collapseAABB3();
this._aabbDirty = true;
this._quantizationRanges = {};
this._vboLayers = {};
this._dtxLayers = {};
this._meshList = [];
this.layerList = []; // For GL state efficiency when drawing, InstancingLayers are in first part, BatchingLayers are in second
this._layersToFinalize = [];
this._entityList = [];
this._entitiesToFinalize = [];
this._geometries = {};
this._dtxBuckets = {}; // Geometries with optimizations used for data texture representation
this._textures = {};
this._textureSets = {};
this._transforms = {};
this._meshes = {};
this._unusedMeshes = {};
this._entities = {};
/** @private **/
this.renderFlags = new RenderFlags();
/**
* @private
*/
this.numGeometries = 0; // Number of 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.numClippableLayerPortions = 0;
/**
* @private
*/
this.numCulledLayerPortions = 0;
this.numEntities = 0;
this._numTriangles = 0;
this._numLines = 0;
this._numPoints = 0;
this._layersFinalized = false;
this._edgeThreshold = cfg.edgeThreshold || 10;
// Build static matrix
this._origin = math.vec3(cfg.origin || [0, 0, 0]);
this._position = math.vec3(cfg.position || [0, 0, 0]);
this._rotation = math.vec3(cfg.rotation || [0, 0, 0]);
this._quaternion = math.vec4(cfg.quaternion || [0, 0, 0, 1]);
this._conjugateQuaternion = math.vec4(cfg.quaternion || [0, 0, 0, 1]);
if (cfg.rotation) {
math.eulerToQuaternion(this._rotation, "XYZ", this._quaternion);
}
this._scale = math.vec3(cfg.scale || [1, 1, 1]);
this._worldRotationMatrix = math.mat4();
this._worldRotationMatrixConjugate = math.mat4();
this._matrix = math.mat4();
this._matrixDirty = true;
this._rebuildMatrices();
this._worldNormalMatrix = math.mat4();
math.inverseMat4(this._matrix, this._worldNormalMatrix);
math.transposeMat4(this._worldNormalMatrix);
if (cfg.matrix || cfg.position || cfg.rotation || cfg.scale || cfg.quaternion) {
this._viewMatrix = math.mat4();
this._viewNormalMatrix = math.mat4();
this._viewMatrixDirty = true;
this._matrixNonIdentity = true;
}
this._opacity = 1.0;
this._colorize = [1, 1, 1];
this._saoEnabled = (cfg.saoEnabled !== false);
this._pbrEnabled = (cfg.pbrEnabled !== false);
this._colorTextureEnabled = (cfg.colorTextureEnabled !== false);
this._isModel = cfg.isModel;
if (this._isModel) {
this.scene._registerModel(this);
}
this._onCameraViewMatrix = this.scene.camera.on("matrix", () => {
this._viewMatrixDirty = true;
});
this._meshesWithDirtyMatrices = [];
this._numMeshesWithDirtyMatrices = 0;
this._onTick = this.scene.on("tick", () => {
while (this._numMeshesWithDirtyMatrices > 0) {
this._meshesWithDirtyMatrices[--this._numMeshesWithDirtyMatrices]._updateMatrix();
}
});
// Every SceneModelMesh gets at least the default TextureSet,
// which contains empty default textures filled with color
const defineTex = (texId, preloadColor) => {
return this._textures[texId] = new SceneModelTexture({
id: texId,
texture: new Texture2D({
gl: this.scene.canvas.gl,
preloadColor: preloadColor
})
});
};
this._textureSets[DEFAULT_TEXTURE_SET_ID] = new SceneModelTextureSet({
id: DEFAULT_TEXTURE_SET_ID,
colorTexture: defineTex(DEFAULT_COLOR_TEXTURE_ID, [1, 1, 1, 1]), // [r, g, b, a]}),
metallicRoughnessTexture: defineTex(DEFAULT_METAL_ROUGH_TEXTURE_ID, [0, 1, 1, 1]), // [_, roughness, metalness, _],
normalsTexture: defineTex(DEFAULT_NORMALS_TEXTURE_ID, [0, 0, 0, 0]), // [x, y, z, _] - these must be zeros
emissiveTexture: defineTex(DEFAULT_EMISSIVE_TEXTURE_ID, [0, 0, 0, 1]), // [x, y, z, _]
occlusionTexture: defineTex(DEFAULT_OCCLUSION_TEXTURE_ID, [1, 1, 1, 1]) // [x, y, z, _]
});
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.co