playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
877 lines (876 loc) • 35.2 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import { Debug } from "../core/debug.js";
import { RefCountedObject } from "../core/ref-counted-object.js";
import { Vec3 } from "../core/math/vec3.js";
import { BoundingBox } from "../core/shape/bounding-box.js";
import {
BUFFER_DYNAMIC,
BUFFER_STATIC,
INDEXFORMAT_UINT16,
INDEXFORMAT_UINT32,
PRIMITIVE_LINES,
PRIMITIVE_TRIANGLES,
PRIMITIVE_POINTS,
SEMANTIC_BLENDINDICES,
SEMANTIC_BLENDWEIGHT,
SEMANTIC_COLOR,
SEMANTIC_NORMAL,
SEMANTIC_POSITION,
SEMANTIC_TEXCOORD,
TYPE_FLOAT32,
TYPE_UINT8,
TYPE_INT8,
TYPE_INT16,
TYPE_UINT16,
typedArrayIndexFormats,
SEMANTIC_TANGENT
} from "../platform/graphics/constants.js";
import { IndexBuffer } from "../platform/graphics/index-buffer.js";
import { VertexBuffer } from "../platform/graphics/vertex-buffer.js";
import { VertexFormat } from "../platform/graphics/vertex-format.js";
import { VertexIterator } from "../platform/graphics/vertex-iterator.js";
import { RENDERSTYLE_SOLID, RENDERSTYLE_WIREFRAME, RENDERSTYLE_POINTS } from "./constants.js";
let id = 0;
class GeometryData {
constructor() {
this.initDefaults();
}
initDefaults() {
this.recreate = false;
this.verticesUsage = BUFFER_STATIC;
this.indicesUsage = BUFFER_STATIC;
this.maxVertices = 0;
this.maxIndices = 0;
this.vertexCount = 0;
this.indexCount = 0;
this.vertexStreamsUpdated = false;
this.indexStreamUpdated = false;
this.vertexStreamDictionary = {};
this.indices = null;
}
// function called when vertex stream is requested to be updated, and validates / updates currently used vertex count
_changeVertexCount(count, semantic) {
if (!this.vertexCount) {
this.vertexCount = count;
} else {
Debug.assert(this.vertexCount === count, `Vertex stream ${semantic} has ${count} vertices, which does not match already set streams with ${this.vertexCount} vertices.`);
}
}
}
// default counts for vertex components
__publicField(GeometryData, "DEFAULT_COMPONENTS_POSITION", 3);
__publicField(GeometryData, "DEFAULT_COMPONENTS_NORMAL", 3);
__publicField(GeometryData, "DEFAULT_COMPONENTS_UV", 2);
__publicField(GeometryData, "DEFAULT_COMPONENTS_COLORS", 4);
class GeometryVertexStream {
constructor(data, componentCount, dataType, dataTypeNormalize, asInt) {
this.data = data;
this.componentCount = componentCount;
this.dataType = dataType;
this.dataTypeNormalize = dataTypeNormalize;
this.asInt = asInt;
}
}
class Mesh extends RefCountedObject {
/**
* Create a new Mesh instance.
*
* @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this mesh.
* @param {object} [options] - Object for passing optional arguments.
* @param {boolean} [options.storageVertex] - Defines if the vertex buffer can be used as
* a storage buffer by a compute shader. Defaults to false. Only supported on WebGPU.
* @param {boolean} [options.storageIndex] - Defines if the index buffer can be used as
* a storage buffer by a compute shader. Defaults to false. Only supported on WebGPU.
*/
constructor(graphicsDevice, options) {
super();
/**
* An array of index buffers. For unindexed meshes, this array can be empty. The first index
* buffer in the array is used by {@link MeshInstance}s with a `renderStyle` property set to
* {@link RENDERSTYLE_SOLID}. The second index buffer in the array is used if `renderStyle` is
* set to {@link RENDERSTYLE_WIREFRAME}.
*
* @type {IndexBuffer[]}
*/
__publicField(this, "indexBuffer", [null]);
/**
* The vertex buffer holding the vertex data of the mesh.
*
* @type {VertexBuffer}
*/
__publicField(this, "vertexBuffer", null);
/**
* Array of primitive objects defining how vertex (and index) data in the mesh should be
* interpreted by the graphics device.
*
* - `type` is the type of primitive to render. Can be:
*
* - {@link PRIMITIVE_POINTS}
* - {@link PRIMITIVE_LINES}
* - {@link PRIMITIVE_LINELOOP}
* - {@link PRIMITIVE_LINESTRIP}
* - {@link PRIMITIVE_TRIANGLES}
* - {@link PRIMITIVE_TRISTRIP}
* - {@link PRIMITIVE_TRIFAN}
*
* - `base` is the offset of the first index or vertex to dispatch in the draw call.
* - `baseVertex` is the number added to each index value before indexing into the vertex buffers. (supported only in WebGPU, ignored in WebGL2)
* - `count` is the number of indices or vertices to dispatch in the draw call.
* - `indexed` specifies whether to interpret the primitive as indexed, thereby using the
* currently set index buffer.
*
* @type {{type: number, base: number, baseVertex: number, count: number, indexed?: boolean}[]}
*/
__publicField(this, "primitive", [{
type: 0,
base: 0,
baseVertex: 0,
count: 0
}]);
/**
* The skin data (if any) that drives skinned mesh animations for this mesh.
*
* @type {Skin|null}
*/
__publicField(this, "skin", null);
/**
* Array of object space AABBs of vertices affected by each bone.
*
* @type {BoundingBox[]|null}
* @ignore
*/
__publicField(this, "boneAabb", null);
/**
* Internal version of AABB, incremented when local AABB changes.
*
* @ignore
*/
__publicField(this, "_aabbVer", 0);
/**
* AABB representing object space bounds of the mesh.
*
* @private
*/
__publicField(this, "_aabb", new BoundingBox());
/**
* @type {GeometryData|null}
* @private
*/
__publicField(this, "_geometryData", null);
/**
* @type {Morph|null}
* @private
*/
__publicField(this, "_morph", null);
/**
* True if the created index buffer should be accessible as a storage buffer in compute shader.
*
* @private
*/
__publicField(this, "_storageIndex", false);
/**
* True if the created vertex buffer should be accessible as a storage buffer in compute shader.
*
* @private
*/
__publicField(this, "_storageVertex", false);
this.id = id++;
Debug.assert(graphicsDevice, "Mesh constructor takes a GraphicsDevice as a parameter, and it was not provided.");
this.device = graphicsDevice;
this._storageIndex = options?.storageIndex || false;
this._storageVertex = options?.storageVertex || false;
}
/**
* Create a new Mesh instance from {@link Geometry} object.
*
* @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this mesh.
* @param {Geometry} geometry - The geometry object to create the mesh from.
* @param {object} [options] - An object that specifies optional inputs for the function as follows:
* @param {boolean} [options.storageVertex] - Defines if the vertex buffer of the mesh can be used as
* a storage buffer by a compute shader. Defaults to false. Only supported on WebGPU.
* @param {boolean} [options.storageIndex] - Defines if the index buffer of the mesh can be used as
* a storage buffer by a compute shader. Defaults to false. Only supported on WebGPU.
* @returns {Mesh} A new mesh.
*/
static fromGeometry(graphicsDevice, geometry, options = {}) {
const mesh = new Mesh(graphicsDevice, options);
const { positions, normals, tangents, colors, uvs, uvs1, blendIndices, blendWeights, indices } = geometry;
if (positions) {
mesh.setPositions(positions);
}
if (normals) {
mesh.setNormals(normals);
}
if (tangents) {
mesh.setVertexStream(SEMANTIC_TANGENT, tangents, 4);
}
if (colors) {
mesh.setColors32(colors);
}
if (uvs) {
mesh.setUvs(0, uvs);
}
if (uvs1) {
mesh.setUvs(1, uvs1);
}
if (blendIndices) {
mesh.setVertexStream(SEMANTIC_BLENDINDICES, blendIndices, 4, blendIndices.length / 4, TYPE_UINT8);
}
if (blendWeights) {
mesh.setVertexStream(SEMANTIC_BLENDWEIGHT, blendWeights, 4);
}
if (indices) {
mesh.setIndices(indices);
}
mesh.update();
return mesh;
}
/**
* Sets the morph data that drives morph target animations for this mesh. Set to null if
* morphing is not used.
*
* @type {Morph|null}
*/
set morph(morph) {
if (morph !== this._morph) {
if (this._morph) {
this._morph.decRefCount();
}
this._morph = morph;
if (morph) {
morph.incRefCount();
}
}
}
/**
* Gets the morph data that drives morph target animations for this mesh.
*
* @type {Morph|null}
*/
get morph() {
return this._morph;
}
/**
* Sets the axis-aligned bounding box for the object space vertices of this mesh.
*
* @type {BoundingBox}
*/
set aabb(aabb) {
this._aabb = aabb;
this._aabbVer++;
}
/**
* Gets the axis-aligned bounding box for the object space vertices of this mesh.
*
* @type {BoundingBox}
*/
get aabb() {
return this._aabb;
}
/**
* Destroys the {@link VertexBuffer} and {@link IndexBuffer}s associated with the mesh. This is
* normally called by {@link Model#destroy} and does not need to be called manually.
*/
destroy() {
const morph = this.morph;
if (morph) {
this.morph = null;
if (morph.refCount < 1) {
morph.destroy();
}
}
if (this.vertexBuffer) {
this.vertexBuffer.destroy();
this.vertexBuffer = null;
}
for (let j = 0; j < this.indexBuffer.length; j++) {
this._destroyIndexBuffer(j);
}
this.indexBuffer.length = 0;
this._geometryData = null;
}
_destroyIndexBuffer(index) {
if (this.indexBuffer[index]) {
this.indexBuffer[index].destroy();
this.indexBuffer[index] = null;
}
}
// initializes local bounding boxes for each bone based on vertices affected by the bone
// if morph targets are provided, it also adjusts local bone bounding boxes by maximum morph displacement
_initBoneAabbs(morphTargets) {
this.boneAabb = [];
this.boneUsed = [];
let x, y, z;
let bMax, bMin;
const boneMin = [];
const boneMax = [];
const boneUsed = this.boneUsed;
const numBones = this.skin.boneNames.length;
let maxMorphX, maxMorphY, maxMorphZ;
for (let i = 0; i < numBones; i++) {
boneMin[i] = new Vec3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
boneMax[i] = new Vec3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
}
const iterator = new VertexIterator(this.vertexBuffer);
const posElement = iterator.element[SEMANTIC_POSITION];
const weightsElement = iterator.element[SEMANTIC_BLENDWEIGHT];
const indicesElement = iterator.element[SEMANTIC_BLENDINDICES];
const numVerts = this.vertexBuffer.numVertices;
for (let j = 0; j < numVerts; j++) {
for (let k = 0; k < 4; k++) {
const boneWeight = weightsElement.array[weightsElement.index + k];
if (boneWeight > 0) {
const boneIndex = indicesElement.array[indicesElement.index + k];
boneUsed[boneIndex] = true;
x = posElement.array[posElement.index];
y = posElement.array[posElement.index + 1];
z = posElement.array[posElement.index + 2];
bMax = boneMax[boneIndex];
bMin = boneMin[boneIndex];
if (bMin.x > x) bMin.x = x;
if (bMin.y > y) bMin.y = y;
if (bMin.z > z) bMin.z = z;
if (bMax.x < x) bMax.x = x;
if (bMax.y < y) bMax.y = y;
if (bMax.z < z) bMax.z = z;
if (morphTargets) {
let minMorphX = maxMorphX = x;
let minMorphY = maxMorphY = y;
let minMorphZ = maxMorphZ = z;
for (let l = 0; l < morphTargets.length; l++) {
const target = morphTargets[l];
const dx = target.deltaPositions[j * 3];
const dy = target.deltaPositions[j * 3 + 1];
const dz = target.deltaPositions[j * 3 + 2];
if (dx < 0) {
minMorphX += dx;
} else {
maxMorphX += dx;
}
if (dy < 0) {
minMorphY += dy;
} else {
maxMorphY += dy;
}
if (dz < 0) {
minMorphZ += dz;
} else {
maxMorphZ += dz;
}
}
if (bMin.x > minMorphX) bMin.x = minMorphX;
if (bMin.y > minMorphY) bMin.y = minMorphY;
if (bMin.z > minMorphZ) bMin.z = minMorphZ;
if (bMax.x < maxMorphX) bMax.x = maxMorphX;
if (bMax.y < maxMorphY) bMax.y = maxMorphY;
if (bMax.z < maxMorphZ) bMax.z = maxMorphZ;
}
}
}
iterator.next();
}
const positionElement = this.vertexBuffer.getFormat().elements.find((e) => e.name === SEMANTIC_POSITION);
if (positionElement && positionElement.normalize) {
const func = (() => {
switch (positionElement.dataType) {
case TYPE_INT8:
return (x2) => Math.max(x2 / 127, -1);
case TYPE_UINT8:
return (x2) => x2 / 255;
case TYPE_INT16:
return (x2) => Math.max(x2 / 32767, -1);
case TYPE_UINT16:
return (x2) => x2 / 65535;
default:
return (x2) => x2;
}
})();
for (let i = 0; i < numBones; i++) {
if (boneUsed[i]) {
const min = boneMin[i];
const max = boneMax[i];
min.set(func(min.x), func(min.y), func(min.z));
max.set(func(max.x), func(max.y), func(max.z));
}
}
}
for (let i = 0; i < numBones; i++) {
const aabb = new BoundingBox();
aabb.setMinMax(boneMin[i], boneMax[i]);
this.boneAabb.push(aabb);
}
}
// when mesh API to modify vertex / index data are used, this allocates structure to store the data
_initGeometryData() {
if (!this._geometryData) {
this._geometryData = new GeometryData();
if (this.vertexBuffer) {
this._geometryData.vertexCount = this.vertexBuffer.numVertices;
this._geometryData.maxVertices = this.vertexBuffer.numVertices;
}
if (this.indexBuffer.length > 0 && this.indexBuffer[0]) {
this._geometryData.indexCount = this.indexBuffer[0].numIndices;
this._geometryData.maxIndices = this.indexBuffer[0].numIndices;
}
}
}
/**
* Clears the mesh of existing vertices and indices and resets the {@link VertexFormat}
* associated with the mesh. This call is typically followed by calls to methods such as
* {@link setPositions}, {@link setVertexStream} or {@link setIndices} and finally
* {@link update} to rebuild the mesh, allowing different {@link VertexFormat}.
*
* @param {boolean} [verticesDynamic] - Indicates the {@link VertexBuffer} should be created
* with {@link BUFFER_DYNAMIC} usage. If not specified, {@link BUFFER_STATIC} is used.
* @param {boolean} [indicesDynamic] - Indicates the {@link IndexBuffer} should be created with
* {@link BUFFER_DYNAMIC} usage. If not specified, {@link BUFFER_STATIC} is used.
* @param {number} [maxVertices] - A {@link VertexBuffer} will be allocated with at least
* maxVertices, allowing additional vertices to be added to it without the allocation. If no
* value is provided, a size to fit the provided vertices will be allocated.
* @param {number} [maxIndices] - An {@link IndexBuffer} will be allocated with at least
* maxIndices, allowing additional indices to be added to it without the allocation. If no
* value is provided, a size to fit the provided indices will be allocated.
*/
clear(verticesDynamic, indicesDynamic, maxVertices = 0, maxIndices = 0) {
this._initGeometryData();
this._geometryData.initDefaults();
this._geometryData.recreate = true;
this._geometryData.maxVertices = maxVertices;
this._geometryData.maxIndices = maxIndices;
this._geometryData.verticesUsage = verticesDynamic ? BUFFER_STATIC : BUFFER_DYNAMIC;
this._geometryData.indicesUsage = indicesDynamic ? BUFFER_STATIC : BUFFER_DYNAMIC;
}
/**
* Sets the vertex data for any supported semantic.
*
* @param {string} semantic - The meaning of the vertex element. For supported semantics, see
* SEMANTIC_* in {@link VertexFormat}.
* @param {number[]|ArrayBufferView} data - Vertex data for the specified semantic.
* @param {number} componentCount - The number of values that form a single Vertex element. For
* example when setting a 3D position represented by 3 numbers per vertex, number 3 should be
* specified.
* @param {number} [numVertices] - The number of vertices to be used from data array. If not
* provided, the whole data array is used. This allows to use only part of the data array.
* @param {number} [dataType] - The format of data when stored in the {@link VertexBuffer}, see
* TYPE_* in {@link VertexFormat}. When not specified, {@link TYPE_FLOAT32} is used.
* @param {boolean} [dataTypeNormalize] - If true, vertex attribute data will be mapped from a
* 0 to 255 range down to 0 to 1 when fed to a shader. If false, vertex attribute data is left
* unchanged. If this property is unspecified, false is assumed.
* @param {boolean} [asInt] - If true, vertex attribute data will be accessible as integer
* numbers in shader code. Defaults to false, which means that vertex attribute data will be
* accessible as floating point numbers. Can be only used with INT and UINT data types.
*/
setVertexStream(semantic, data, componentCount, numVertices, dataType = TYPE_FLOAT32, dataTypeNormalize = false, asInt = false) {
this._initGeometryData();
const vertexCount = numVertices || data.length / componentCount;
this._geometryData._changeVertexCount(vertexCount, semantic);
this._geometryData.vertexStreamsUpdated = true;
this._geometryData.vertexStreamDictionary[semantic] = new GeometryVertexStream(
data,
componentCount,
dataType,
dataTypeNormalize,
asInt
);
}
/**
* Gets the vertex data corresponding to a semantic.
*
* @param {string} semantic - The semantic of the vertex element to get. For supported
* semantics, see SEMANTIC_* in {@link VertexFormat}.
* @param {number[]|ArrayBufferView} data - An array to populate with the vertex data. When
* typed array is supplied, enough space needs to be reserved, otherwise only partial data is
* copied.
* @returns {number} Returns the number of vertices populated.
*/
getVertexStream(semantic, data) {
let count = 0;
let done = false;
if (this._geometryData) {
const stream = this._geometryData.vertexStreamDictionary[semantic];
if (stream) {
done = true;
count = this._geometryData.vertexCount;
if (ArrayBuffer.isView(data)) {
data.set(stream.data);
} else {
data.length = 0;
data.push(stream.data);
}
}
}
if (!done) {
if (this.vertexBuffer) {
const iterator = new VertexIterator(this.vertexBuffer);
count = iterator.readData(semantic, data);
}
}
return count;
}
/**
* Sets the vertex positions array. Vertices are stored using {@link TYPE_FLOAT32} format.
*
* @param {number[]|ArrayBufferView} positions - Vertex data containing positions.
* @param {number} [componentCount] - The number of values that form a single position element.
* Defaults to 3 if not specified, corresponding to x, y and z coordinates.
* @param {number} [numVertices] - The number of vertices to be used from data array. If not
* provided, the whole data array is used. This allows to use only part of the data array.
*/
setPositions(positions, componentCount = GeometryData.DEFAULT_COMPONENTS_POSITION, numVertices) {
this.setVertexStream(SEMANTIC_POSITION, positions, componentCount, numVertices, TYPE_FLOAT32, false);
}
/**
* Sets the vertex normals array. Normals are stored using {@link TYPE_FLOAT32} format.
*
* @param {number[]|ArrayBufferView} normals - Vertex data containing normals.
* @param {number} [componentCount] - The number of values that form a single normal element.
* Defaults to 3 if not specified, corresponding to x, y and z direction.
* @param {number} [numVertices] - The number of vertices to be used from data array. If not
* provided, the whole data array is used. This allows to use only part of the data array.
*/
setNormals(normals, componentCount = GeometryData.DEFAULT_COMPONENTS_NORMAL, numVertices) {
this.setVertexStream(SEMANTIC_NORMAL, normals, componentCount, numVertices, TYPE_FLOAT32, false);
}
/**
* Sets the vertex uv array. Uvs are stored using {@link TYPE_FLOAT32} format.
*
* @param {number} channel - The uv channel in [0..7] range.
* @param {number[]|ArrayBufferView} uvs - Vertex data containing uv-coordinates.
* @param {number} [componentCount] - The number of values that form a single uv element.
* Defaults to 2 if not specified, corresponding to u and v coordinates.
* @param {number} [numVertices] - The number of vertices to be used from data array. If not
* provided, the whole data array is used. This allows to use only part of the data array.
*/
setUvs(channel, uvs, componentCount = GeometryData.DEFAULT_COMPONENTS_UV, numVertices) {
this.setVertexStream(SEMANTIC_TEXCOORD + channel, uvs, componentCount, numVertices, TYPE_FLOAT32, false);
}
/**
* Sets the vertex color array. Colors are stored using {@link TYPE_FLOAT32} format, which is
* useful for HDR colors.
*
* @param {number[]|ArrayBufferView} colors - Vertex data containing colors.
* @param {number} [componentCount] - The number of values that form a single color element.
* Defaults to 4 if not specified, corresponding to r, g, b and a.
* @param {number} [numVertices] - The number of vertices to be used from data array. If not
* provided, the whole data array is used. This allows to use only part of the data array.
*/
setColors(colors, componentCount = GeometryData.DEFAULT_COMPONENTS_COLORS, numVertices) {
this.setVertexStream(SEMANTIC_COLOR, colors, componentCount, numVertices, TYPE_FLOAT32, false);
}
/**
* Sets the vertex color array. Colors are stored using {@link TYPE_UINT8} format, which is
* useful for LDR colors. Values in the array are expected in [0..255] range, and are mapped to
* [0..1] range in the shader.
*
* @param {number[]|ArrayBufferView} colors - Vertex data containing colors. The array is
* expected to contain 4 components per vertex, corresponding to r, g, b and a.
* @param {number} [numVertices] - The number of vertices to be used from data array. If not
* provided, the whole data array is used. This allows to use only part of the data array.
*/
setColors32(colors, numVertices) {
this.setVertexStream(SEMANTIC_COLOR, colors, GeometryData.DEFAULT_COMPONENTS_COLORS, numVertices, TYPE_UINT8, true);
}
/**
* Sets the index array. Indices are stored using 16-bit format by default, unless more than
* 65535 vertices are specified, in which case 32-bit format is used.
*
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} indices - The array of indices that
* define primitives (lines, triangles, etc.).
* @param {number} [numIndices] - The number of indices to be used from data array. If not
* provided, the whole data array is used. This allows to use only part of the data array.
*/
setIndices(indices, numIndices) {
this._initGeometryData();
this._geometryData.indexStreamUpdated = true;
this._geometryData.indices = indices;
this._geometryData.indexCount = numIndices || indices.length;
}
/**
* Gets the vertex positions data.
*
* @param {number[]|ArrayBufferView} positions - An array to populate with the vertex data.
* When typed array is supplied, enough space needs to be reserved, otherwise only partial data
* is copied.
* @returns {number} Returns the number of vertices populated.
*/
getPositions(positions) {
return this.getVertexStream(SEMANTIC_POSITION, positions);
}
/**
* Gets the vertex normals data.
*
* @param {number[]|ArrayBufferView} normals - An array to populate with the vertex data. When
* typed array is supplied, enough space needs to be reserved, otherwise only partial data is
* copied.
* @returns {number} Returns the number of vertices populated.
*/
getNormals(normals) {
return this.getVertexStream(SEMANTIC_NORMAL, normals);
}
/**
* Gets the vertex uv data.
*
* @param {number} channel - The uv channel in [0..7] range.
* @param {number[]|ArrayBufferView} uvs - An array to populate with the vertex data. When
* typed array is supplied, enough space needs to be reserved, otherwise only partial data is
* copied.
* @returns {number} Returns the number of vertices populated.
*/
getUvs(channel, uvs) {
return this.getVertexStream(SEMANTIC_TEXCOORD + channel, uvs);
}
/**
* Gets the vertex color data.
*
* @param {number[]|ArrayBufferView} colors - An array to populate with the vertex data. When
* typed array is supplied, enough space needs to be reserved, otherwise only partial data is
* copied.
* @returns {number} Returns the number of vertices populated.
*/
getColors(colors) {
return this.getVertexStream(SEMANTIC_COLOR, colors);
}
/**
* Gets the index data.
*
* @param {number[]|Uint8Array|Uint16Array|Uint32Array} indices - An array to populate with the
* index data. When a typed array is supplied, enough space needs to be reserved, otherwise
* only partial data is copied.
* @returns {number} Returns the number of indices populated.
*/
getIndices(indices) {
let count = 0;
if (this._geometryData && this._geometryData.indices) {
const streamIndices = this._geometryData.indices;
count = this._geometryData.indexCount;
if (ArrayBuffer.isView(indices)) {
indices.set(streamIndices);
} else {
indices.length = 0;
for (let i = 0, il = streamIndices.length; i < il; i++) {
indices.push(streamIndices[i]);
}
}
} else {
if (this.indexBuffer.length > 0 && this.indexBuffer[0]) {
const indexBuffer = this.indexBuffer[0];
count = indexBuffer.readData(indices);
}
}
return count;
}
/**
* Applies any changes to vertex stream and indices to mesh. This allocates or reallocates
* {@link vertexBuffer} or {@link indexBuffer} to fit all provided vertices and indices, and
* fills them with data.
*
* @param {number} [primitiveType] - The type of primitive to render. Can be:
*
* - {@link PRIMITIVE_POINTS}
* - {@link PRIMITIVE_LINES}
* - {@link PRIMITIVE_LINELOOP}
* - {@link PRIMITIVE_LINESTRIP}
* - {@link PRIMITIVE_TRIANGLES}
* - {@link PRIMITIVE_TRISTRIP}
* - {@link PRIMITIVE_TRIFAN}
*
* Defaults to {@link PRIMITIVE_TRIANGLES} if not specified.
* @param {boolean} [updateBoundingBox] - True to update bounding box. Bounding box is updated
* only if positions were set since last time update was called, and `componentCount` for
* position was 3, otherwise bounding box is not updated. See {@link setPositions}. Defaults to
* true if not specified. Set this to false to avoid update of the bounding box and use aabb
* property to set it instead.
*/
update(primitiveType = PRIMITIVE_TRIANGLES, updateBoundingBox = true) {
if (this._geometryData) {
if (updateBoundingBox) {
const stream = this._geometryData.vertexStreamDictionary[SEMANTIC_POSITION];
if (stream) {
if (stream.componentCount === 3) {
this._aabb.compute(stream.data, this._geometryData.vertexCount);
this._aabbVer++;
}
}
}
let destroyVB = this._geometryData.recreate;
if (this._geometryData.vertexCount > this._geometryData.maxVertices) {
destroyVB = true;
this._geometryData.maxVertices = this._geometryData.vertexCount;
}
if (destroyVB) {
if (this.vertexBuffer) {
this.vertexBuffer.destroy();
this.vertexBuffer = null;
}
}
let destroyIB = this._geometryData.recreate;
if (this._geometryData.indexCount > this._geometryData.maxIndices) {
destroyIB = true;
this._geometryData.maxIndices = this._geometryData.indexCount;
}
if (destroyIB) {
if (this.indexBuffer.length > 0 && this.indexBuffer[0]) {
this.indexBuffer[0].destroy();
this.indexBuffer[0] = null;
}
}
if (this._geometryData.vertexStreamsUpdated) {
this._updateVertexBuffer();
}
if (this._geometryData.indexStreamUpdated) {
this._updateIndexBuffer();
}
this.primitive[0].type = primitiveType;
if (this.indexBuffer.length > 0 && this.indexBuffer[0]) {
if (this._geometryData.indexStreamUpdated) {
this.primitive[0].count = this._geometryData.indexCount;
this.primitive[0].indexed = true;
}
} else {
if (this._geometryData.vertexStreamsUpdated) {
this.primitive[0].count = this._geometryData.vertexCount;
this.primitive[0].indexed = false;
}
}
this._geometryData.vertexCount = 0;
this._geometryData.indexCount = 0;
this._geometryData.vertexStreamsUpdated = false;
this._geometryData.indexStreamUpdated = false;
this._geometryData.recreate = false;
this.updateRenderStates();
}
}
// builds vertex format based on attached vertex streams
_buildVertexFormat(vertexCount) {
const vertexDesc = [];
for (const semantic in this._geometryData.vertexStreamDictionary) {
const stream = this._geometryData.vertexStreamDictionary[semantic];
vertexDesc.push({
semantic,
components: stream.componentCount,
type: stream.dataType,
normalize: stream.dataTypeNormalize,
asInt: stream.asInt
});
}
return new VertexFormat(this.device, vertexDesc, vertexCount);
}
// copy attached data into vertex buffer
_updateVertexBuffer() {
if (!this.vertexBuffer) {
const allocateVertexCount = this._geometryData.maxVertices;
const format = this._buildVertexFormat(allocateVertexCount);
this.vertexBuffer = new VertexBuffer(this.device, format, allocateVertexCount, {
usage: this._geometryData.verticesUsage,
storage: this._storageVertex
});
}
const iterator = new VertexIterator(this.vertexBuffer);
const numVertices = this._geometryData.vertexCount;
for (const semantic in this._geometryData.vertexStreamDictionary) {
const stream = this._geometryData.vertexStreamDictionary[semantic];
iterator.writeData(semantic, stream.data, numVertices);
delete this._geometryData.vertexStreamDictionary[semantic];
}
iterator.end();
}
// copy attached data into index buffer
_updateIndexBuffer() {
if (this.indexBuffer.length <= 0 || !this.indexBuffer[0]) {
const maxVertices = this._geometryData.maxVertices;
const createFormat = maxVertices > 65535 || maxVertices === 0 ? INDEXFORMAT_UINT32 : INDEXFORMAT_UINT16;
const options = this._storageIndex ? { storage: true } : void 0;
this.indexBuffer[0] = new IndexBuffer(this.device, createFormat, this._geometryData.maxIndices, this._geometryData.indicesUsage, void 0, options);
}
const srcIndices = this._geometryData.indices;
if (srcIndices) {
const indexBuffer = this.indexBuffer[0];
indexBuffer.writeData(srcIndices, this._geometryData.indexCount);
this._geometryData.indices = null;
}
}
// prepares the mesh to be rendered with specific render style
prepareRenderState(renderStyle) {
if (renderStyle === RENDERSTYLE_WIREFRAME) {
this.generateWireframe();
} else if (renderStyle === RENDERSTYLE_POINTS) {
this.primitive[RENDERSTYLE_POINTS] = {
type: PRIMITIVE_POINTS,
base: 0,
baseVertex: 0,
count: this.vertexBuffer ? this.vertexBuffer.numVertices : 0,
indexed: false
};
}
}
// updates existing render states with changes to solid render state
updateRenderStates() {
if (this.primitive[RENDERSTYLE_POINTS]) {
this.prepareRenderState(RENDERSTYLE_POINTS);
}
if (this.primitive[RENDERSTYLE_WIREFRAME]) {
this.prepareRenderState(RENDERSTYLE_WIREFRAME);
}
}
generateWireframe() {
this._destroyIndexBuffer(RENDERSTYLE_WIREFRAME);
const numVertices = this.vertexBuffer.numVertices;
let lines;
let format;
if (this.indexBuffer.length > 0 && this.indexBuffer[0]) {
const offsets = [[0, 1], [1, 2], [2, 0]];
const base = this.primitive[RENDERSTYLE_SOLID].base;
const count = this.primitive[RENDERSTYLE_SOLID].count;
const baseVertex = this.primitive[RENDERSTYLE_SOLID].baseVertex || 0;
const indexBuffer = this.indexBuffer[RENDERSTYLE_SOLID];
const indicesArrayType = typedArrayIndexFormats[indexBuffer.format];
const srcIndices = new indicesArrayType(indexBuffer.storage);
const tmpIndices = new indicesArrayType(count * 2);
const seen = /* @__PURE__ */ new Set();
let len = 0;
for (let j = base; j < base + count; j += 3) {
for (let k = 0; k < 3; k++) {
const i1 = srcIndices[j + offsets[k][0]] + baseVertex;
const i2 = srcIndices[j + offsets[k][1]] + baseVertex;
const hash = i1 > i2 ? i2 * numVertices + i1 : i1 * numVertices + i2;
if (!seen.has(hash)) {
seen.add(hash);
tmpIndices[len++] = i1;
tmpIndices[len++] = i2;
}
}
}
seen.clear();
format = indexBuffer.format;
lines = tmpIndices.slice(0, len);
} else {
const safeNumVertices = numVertices - numVertices % 3;
const count = safeNumVertices / 3 * 6;
format = count > 65535 ? INDEXFORMAT_UINT32 : INDEXFORMAT_UINT16;
lines = count > 65535 ? new Uint32Array(count) : new Uint16Array(count);
let idx = 0;
for (let i = 0; i < safeNumVertices; i += 3) {
lines[idx++] = i;
lines[idx++] = i + 1;
lines[idx++] = i + 1;
lines[idx++] = i + 2;
lines[idx++] = i + 2;
lines[idx++] = i;
}
}
const wireBuffer = new IndexBuffer(this.vertexBuffer.device, format, lines.length, BUFFER_STATIC, lines.buffer);
this.primitive[RENDERSTYLE_WIREFRAME] = {
type: PRIMITIVE_LINES,
base: 0,
baseVertex: 0,
count: lines.length,
indexed: true
};
this.indexBuffer[RENDERSTYLE_WIREFRAME] = wireBuffer;
}
}
export {
Mesh
};