src/viewer/scene/geometry/VBOGeometry.js
import {Geometry} from './Geometry.js';
import {RenderState} from '../webgl/RenderState.js';
import {ArrayBuf} from '../webgl/ArrayBuf.js';
import {math} from '../math/math.js';
import {stats} from '../stats.js';
import {buildEdgeIndices} from '../math/buildEdgeIndices.js';
import {geometryCompressionUtils} from '../math/geometryCompressionUtils.js';
const memoryStats = stats.memory;
const tempAABB = math.AABB3();
/**
* @desc A {@link Geometry} that keeps its geometry data solely in GPU memory, without retaining it in browser memory.
*
* VBOGeometry uses less memory than {@link ReadableGeometry}, which keeps its geometry data in both browser and GPU memory.
*
* ## Usage
*
* Creating a {@link Mesh} with a VBOGeometry that defines a single triangle, plus a {@link PhongMaterial} with diffuse {@link Texture}:
*
* [[Run this example](/examples/index.html#geometry_VBOGeometry)]
*
* ````javascript
* import {Viewer, Mesh, VBOGeometry, PhongMaterial, Texture} from "xeokit-sdk.es.js";
*
* const viewer = new Viewer({
* canvasId: "myCanvas"
* });
*
* new Mesh(viewer.scene, {
* geometry: new VBOGeometry(viewer.scene, {
* primitive: "triangles",
* positions: [0.0, 3, 0.0, -3, -3, 0.0, 3, -3, 0.0],
* normals: [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0],
* uv: [0.0, 0.0, 0.5, 1.0, 1.0, 0.0],
* indices: [0, 1, 2]
* }),
* material: new PhongMaterial(viewer.scene, {
* diffuseMap: new Texture(viewer.scene, {
* src: "textures/diffuse/uvGrid2.jpg"
* }),
* backfaces: true
* })
* });
* ````
*/
class VBOGeometry extends Geometry {
/**
@private
*/
get type() {
return "VBOGeometry";
}
/**
* @private
* @returns {Boolean}
*/
get isVBOGeometry() {
return true;
}
/**
* @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 {@link Scene}, generated automatically when omitted.
* @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] Positions array.
* @param {Number[]} [cfg.normals] Vertex normal vectors array.
* @param {Number[]} [cfg.uv] UVs array.
* @param {Number[]} [cfg.colors] Vertex colors.
* @param {Number[]} [cfg.indices] Indices array.
* @param {Number} [cfg.edgeThreshold=10] When autogenerating edges for supporting {@link Drawable#edges}, this indicates the threshold angle (in degrees) between the face normals of adjacent triangles below which the edge is discarded.
*/
constructor(owner, cfg = {}) {
super(owner, cfg);
this._state = new RenderState({ // Arrays for emphasis effects are got from xeokit.GeometryLite friend methods
compressGeometry: true,
primitive: null, // WebGL enum
primitiveName: null, // String
positionsDecodeMatrix: null, // Set when compressGeometry == true
uvDecodeMatrix: null, // Set when compressGeometry == true
positionsBuf: null,
normalsBuf: null,
colorsbuf: null,
uvBuf: null,
indicesBuf: null,
hash: ""
});
this._numTriangles = 0;
this._edgeThreshold = cfg.edgeThreshold || 10.0;
this._aabb = null;
this._obb = math.OBB3();
const state = this._state;
const gl = this.scene.canvas.gl;
cfg.primitive = cfg.primitive || "triangles";
switch (cfg.primitive) {
case "points":
state.primitive = gl.POINTS;
state.primitiveName = cfg.primitive;
break;
case "lines":
state.primitive = gl.LINES;
state.primitiveName = cfg.primitive;
break;
case "line-loop":
state.primitive = gl.LINE_LOOP;
state.primitiveName = cfg.primitive;
break;
case "line-strip":
state.primitive = gl.LINE_STRIP;
state.primitiveName = cfg.primitive;
break;
case "triangles":
state.primitive = gl.TRIANGLES;
state.primitiveName = cfg.primitive;
break;
case "triangle-strip":
state.primitive = gl.TRIANGLE_STRIP;
state.primitiveName = cfg.primitive;
break;
case "triangle-fan":
state.primitive = gl.TRIANGLE_FAN;
state.primitiveName = cfg.primitive;
break;
default:
this.error("Unsupported value for 'primitive': '" + cfg.primitive +
"' - supported values are 'points', 'lines', 'line-loop', 'line-strip', 'triangles', " +
"'triangle-strip' and 'triangle-fan'. Defaulting to 'triangles'.");
state.primitive = gl.TRIANGLES;
state.primitiveName = cfg.primitive;
}
if (!cfg.positions) {
this.error("Config expected: positions");
return; // TODO: Recover?
}
if (!cfg.indices) {
this.error("Config expected: indices");
return; // TODO: Recover?
}
var positions;
{
const positionsDecodeMatrix = cfg.positionsDecodeMatrix;
if (positionsDecodeMatrix) {
// Compressed positions
} else {
// Uncompressed positions
const bounds = geometryCompressionUtils.getPositionsBounds(cfg.positions);
const result = geometryCompressionUtils.compressPositions(cfg.positions, bounds.min, bounds.max);
positions = result.quantized;
state.positionsDecodeMatrix = result.decodeMatrix;
state.positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, positions, positions.length, 3, gl.STATIC_DRAW);
memoryStats.positions += state.positionsBuf.numItems;
math.positions3ToAABB3(cfg.positions, this._aabb);
math.positions3ToAABB3(positions, tempAABB, state.positionsDecodeMatrix);
math.AABB3ToOBB3(tempAABB, this._obb);
}
}
if (cfg.colors) {
const colors = cfg.colors.constructor === Float32Array ? cfg.colors : new Float32Array(cfg.colors);
state.colorsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, colors, colors.length, 4, gl.STATIC_DRAW);
memoryStats.colors += state.colorsBuf.numItems;
}
if (cfg.uv) {
const bounds = geometryCompressionUtils.getUVBounds(cfg.uv);
const result = geometryCompressionUtils.compressUVs(cfg.uv, bounds.min, bounds.max);
const uv = result.quantized;
state.uvDecodeMatrix = result.decodeMatrix;
state.uvBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, uv, uv.length, 2, gl.STATIC_DRAW);
memoryStats.uvs += state.uvBuf.numItems;
}
if (cfg.normals) {
const normals = geometryCompressionUtils.compressNormals(cfg.normals);
let normalized = state.compressGeometry;
state.normalsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, normals, normals.length, 3, gl.STATIC_DRAW, normalized);
memoryStats.normals += state.normalsBuf.numItems;
}
{
const indices = (cfg.indices.constructor === Uint32Array || cfg.indices.constructor === Uint16Array) ? cfg.indices : new Uint32Array(cfg.indices);
state.indicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, indices, indices.length, 1, gl.STATIC_DRAW);
memoryStats.indices += state.indicesBuf.numItems;
const edgeIndices = buildEdgeIndices(positions, indices, state.positionsDecodeMatrix, this._edgeThreshold);
this._edgeIndicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, edgeIndices, edgeIndices.length, 1, gl.STATIC_DRAW);
if (this._state.primitiveName === "triangles") {
this._numTriangles = (cfg.indices.length / 3);
}
}
this._buildHash();
memoryStats.meshes++;
}
_buildHash() {
const state = this._state;
const hash = ["/g"];
hash.push("/" + state.primitive + ";");
if (state.positionsBuf) {
hash.push("p");
}
if (state.colorsBuf) {
hash.push("c");
}
if (state.normalsBuf || state.autoVertexNormals) {
hash.push("n");
}
if (state.uvBuf) {
hash.push("u");
}
hash.push("cp"); // Always compressed
hash.push(";");
state.hash = hash.join("");
}
_getEdgeIndices() {
return this._edgeIndicesBuf;
}
/**
* Gets the primitive type.
*
* Possible types are: 'points', 'lines', 'line-loop', 'line-strip', 'triangles', 'triangle-strip' and 'triangle-fan'.
*
* @type {String}
*/
get primitive() {
return this._state.primitiveName;
}
/**
* Gets the local-space axis-aligned 3D boundary (AABB).
*
* The AABB is 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() {
return this._aabb;
}
/**
* Gets the local-space oriented 3D boundary (OBB).
*
* The OBB is represented by a 32-element Float64Array containing the eight vertices of the box, where each vertex is a homogeneous coordinate having [x,y,z,w] elements.
*
* @type {Number[]}
*/
get obb() {
return this._obb;
}
/**
* Approximate number of triangles in this VBOGeometry.
*
* Will be zero if {@link VBOGeometry#primitive} is not 'triangles', 'triangle-strip' or 'triangle-fan'.
*
* @type {Number}
*/
get numTriangles() {
return this._numTriangles;
}
/** @private */
_getState() {
return this._state;
}
/**
* Destroys this component.
*/
destroy() {
super.destroy();
const state = this._state;
if (state.indicesBuf) {
state.indicesBuf.destroy();
}
if (state.positionsBuf) {
state.positionsBuf.destroy();
}
if (state.normalsBuf) {
state.normalsBuf.destroy();
}
if (state.uvBuf) {
state.uvBuf.destroy();
}
if (state.colorsBuf) {
state.colorsBuf.destroy();
}
if (this._edgeIndicesBuf) {
this._edgeIndicesBuf.destroy();
}
state.destroy();
memoryStats.meshes--;
}
}
export {VBOGeometry};