@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
1,167 lines (1,165 loc) • 59.6 kB
JavaScript
import { Vector3 } from "../Maths/math.vector.js";
import { Color4 } from "../Maths/math.color.js";
import { VertexData } from "../Meshes/mesh.vertexData.js";
import { VertexBuffer } from "../Buffers/buffer.js";
import { SubMesh } from "../Meshes/subMesh.js";
import { SceneLoaderFlags } from "../Loading/sceneLoaderFlags.js";
import { BoundingInfo } from "../Culling/boundingInfo.js";
import { Tools } from "../Misc/tools.js";
import { Tags } from "../Misc/tags.js";
import { extractMinAndMax } from "../Maths/math.functions.js";
import { EngineStore } from "../Engines/engineStore.js";
import { useOpenGLOrientationForUV } from "../Compat/compatibilityOptions.js";
import { CopyFloatData } from "../Buffers/bufferUtils.js";
/**
* Class used to store geometry data (vertex buffers + index buffer)
*/
export class Geometry {
/**
* Gets or sets the Bias Vector to apply on the bounding elements (box/sphere), the max extend is computed as v += v * bias.x + bias.y, the min is computed as v -= v * bias.x + bias.y
*/
get boundingBias() {
return this._boundingBias;
}
/**
* Gets or sets the Bias Vector to apply on the bounding elements (box/sphere), the max extend is computed as v += v * bias.x + bias.y, the min is computed as v -= v * bias.x + bias.y
*/
set boundingBias(value) {
if (this._boundingBias) {
this._boundingBias.copyFrom(value);
}
else {
this._boundingBias = value.clone();
}
this._updateBoundingInfo(true, null);
}
/**
* Static function used to attach a new empty geometry to a mesh
* @param mesh defines the mesh to attach the geometry to
* @returns the new Geometry
*/
static CreateGeometryForMesh(mesh) {
const geometry = new Geometry(Geometry.RandomId(), mesh.getScene());
geometry.applyToMesh(mesh);
return geometry;
}
/** Get the list of meshes using this geometry */
get meshes() {
return this._meshes;
}
/**
* Creates a new geometry
* @param id defines the unique ID
* @param scene defines the hosting scene
* @param vertexData defines the VertexData used to get geometry data
* @param updatable defines if geometry must be updatable (false by default)
* @param mesh defines the mesh that will be associated with the geometry
*/
constructor(id, scene, vertexData, updatable = false, mesh = null) {
/**
* Gets the delay loading state of the geometry (none by default which means not delayed)
*/
this.delayLoadState = 0;
this._totalVertices = 0;
this._isDisposed = false;
this._extend = {
minimum: new Vector3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE),
maximum: new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE),
};
this._indexBufferIsUpdatable = false;
this._positionsCache = [];
/** @internal */
this._parentContainer = null;
/**
* If set to true (false by default), the bounding info applied to the meshes sharing this geometry will be the bounding info defined at the class level
* and won't be computed based on the vertex positions (which is what we get when useBoundingInfoFromGeometry = false)
*/
this.useBoundingInfoFromGeometry = false;
this._scene = scene || EngineStore.LastCreatedScene;
if (!this._scene) {
return;
}
this.id = id;
this.uniqueId = this._scene.getUniqueId();
this._engine = this._scene.getEngine();
this._meshes = [];
//Init vertex buffer cache
this._vertexBuffers = {};
this._indices = [];
this._updatable = updatable;
// vertexData
if (vertexData) {
this.setAllVerticesData(vertexData, updatable);
}
else {
this._totalVertices = 0;
}
if (this._engine.getCaps().vertexArrayObject) {
this._vertexArrayObjects = {};
}
// applyToMesh
if (mesh) {
this.applyToMesh(mesh);
mesh.computeWorldMatrix(true);
}
}
/**
* Gets the current extend of the geometry
*/
get extend() {
return this._extend;
}
/**
* Gets the hosting scene
* @returns the hosting Scene
*/
getScene() {
return this._scene;
}
/**
* Gets the hosting engine
* @returns the hosting Engine
*/
getEngine() {
return this._engine;
}
/**
* Defines if the geometry is ready to use
* @returns true if the geometry is ready to be used
*/
isReady() {
return this.delayLoadState === 1 || this.delayLoadState === 0;
}
/**
* Gets a value indicating that the geometry should not be serialized
*/
get doNotSerialize() {
for (let index = 0; index < this._meshes.length; index++) {
if (!this._meshes[index].doNotSerialize) {
return false;
}
}
return true;
}
/** @internal */
_rebuild() {
if (this._vertexArrayObjects) {
this._vertexArrayObjects = {};
}
// Index buffer
if (this._meshes.length !== 0 && this._indices) {
this._indexBuffer = this._engine.createIndexBuffer(this._indices, this._updatable, "Geometry_" + this.id + "_IndexBuffer");
}
// Vertex buffers
const buffers = new Set();
for (const key in this._vertexBuffers) {
buffers.add(this._vertexBuffers[key].getWrapperBuffer());
}
buffers.forEach((buffer) => {
buffer._rebuild();
});
}
/**
* Affects all geometry data in one call
* @param vertexData defines the geometry data
* @param updatable defines if the geometry must be flagged as updatable (false as default)
*/
setAllVerticesData(vertexData, updatable) {
vertexData.applyToGeometry(this, updatable);
this._notifyUpdate();
}
/**
* Set specific vertex data
* @param kind defines the data kind (Position, normal, etc...)
* @param data defines the vertex data to use
* @param updatable defines if the vertex must be flagged as updatable (false as default)
* @param stride defines the stride to use (0 by default). This value is deduced from the kind value if not specified
*/
setVerticesData(kind, data, updatable = false, stride) {
if (updatable && Array.isArray(data)) {
// to avoid converting to Float32Array at each draw call in engine.updateDynamicVertexBuffer, we make the conversion a single time here
data = new Float32Array(data);
}
const buffer = new VertexBuffer(this._engine, data, kind, {
updatable,
postponeInternalCreation: this._meshes.length === 0,
stride,
label: "Geometry_" + this.id + "_" + kind,
});
this.setVerticesBuffer(buffer);
}
/**
* Removes a specific vertex data
* @param kind defines the data kind (Position, normal, etc...)
*/
removeVerticesData(kind) {
if (this._vertexBuffers[kind]) {
this._vertexBuffers[kind].dispose();
delete this._vertexBuffers[kind];
}
if (this._vertexArrayObjects) {
this._disposeVertexArrayObjects();
}
}
/**
* Affect a vertex buffer to the geometry. the vertexBuffer.getKind() function is used to determine where to store the data
* @param buffer defines the vertex buffer to use
* @param totalVertices defines the total number of vertices for position kind (could be null)
* @param disposeExistingBuffer disposes the existing buffer, if any (default: true)
*/
setVerticesBuffer(buffer, totalVertices = null, disposeExistingBuffer = true) {
const kind = buffer.getKind();
if (this._vertexBuffers[kind] && disposeExistingBuffer) {
this._vertexBuffers[kind].dispose();
}
if (buffer._buffer && buffer._ownsBuffer) {
buffer._buffer._increaseReferences();
}
this._vertexBuffers[kind] = buffer;
const meshes = this._meshes;
const numOfMeshes = meshes.length;
if (kind === VertexBuffer.PositionKind) {
this._totalVertices = totalVertices ?? buffer._maxVerticesCount;
this._updateExtend(buffer.getFloatData(this._totalVertices));
this._resetPointsArrayCache();
// this._extend can be empty if buffer.getFloatData(this._totalVertices) returned null
const minimum = (this._extend && this._extend.minimum) || new Vector3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
const maximum = (this._extend && this._extend.maximum) || new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
for (let index = 0; index < numOfMeshes; index++) {
const mesh = meshes[index];
mesh.buildBoundingInfo(minimum, maximum);
mesh._createGlobalSubMesh(mesh.isUnIndexed);
mesh.computeWorldMatrix(true);
mesh.synchronizeInstances();
}
}
this._notifyUpdate(kind);
}
/**
* Update a specific vertex buffer
* This function will directly update the underlying DataBuffer according to the passed numeric array or Float32Array
* It will do nothing if the buffer is not updatable
* @param kind defines the data kind (Position, normal, etc...)
* @param data defines the data to use
* @param offset defines the offset in the target buffer where to store the data
* @param useBytes set to true if the offset is in bytes
*/
updateVerticesDataDirectly(kind, data, offset, useBytes = false) {
const vertexBuffer = this.getVertexBuffer(kind);
if (!vertexBuffer) {
return;
}
vertexBuffer.updateDirectly(data, offset, useBytes);
this._notifyUpdate(kind);
}
/**
* Update a specific vertex buffer
* This function will create a new buffer if the current one is not updatable
* @param kind defines the data kind (Position, normal, etc...)
* @param data defines the data to use
* @param updateExtends defines if the geometry extends must be recomputed (false by default)
*/
updateVerticesData(kind, data, updateExtends = false) {
const vertexBuffer = this.getVertexBuffer(kind);
if (!vertexBuffer) {
return;
}
vertexBuffer.update(data);
if (kind === VertexBuffer.PositionKind) {
this._updateBoundingInfo(updateExtends, data);
}
this._notifyUpdate(kind);
}
_updateBoundingInfo(updateExtends, data) {
if (updateExtends) {
this._updateExtend(data);
}
this._resetPointsArrayCache();
if (updateExtends) {
const meshes = this._meshes;
for (const mesh of meshes) {
if (mesh.hasBoundingInfo) {
mesh.getBoundingInfo().reConstruct(this._extend.minimum, this._extend.maximum);
}
else {
mesh.buildBoundingInfo(this._extend.minimum, this._extend.maximum);
}
const subMeshes = mesh.subMeshes;
for (const subMesh of subMeshes) {
subMesh.refreshBoundingInfo();
}
}
}
}
/**
* @internal
*/
_bind(effect, indexToBind, overrideVertexBuffers, overrideVertexArrayObjects) {
if (!effect) {
return;
}
if (indexToBind === undefined) {
indexToBind = this._indexBuffer;
}
const vbs = this.getVertexBuffers();
if (!vbs) {
return;
}
if (indexToBind != this._indexBuffer || (!this._vertexArrayObjects && !overrideVertexArrayObjects)) {
this._engine.bindBuffers(vbs, indexToBind, effect, overrideVertexBuffers);
return;
}
const vaos = overrideVertexArrayObjects ? overrideVertexArrayObjects : this._vertexArrayObjects;
const engine = this._engine;
// Using VAO
if (!vaos[effect.key]) {
vaos[effect.key] = engine.recordVertexArrayObject(vbs, indexToBind, effect, overrideVertexBuffers);
}
engine.bindVertexArrayObject(vaos[effect.key], indexToBind);
}
/**
* Gets total number of vertices
* @returns the total number of vertices
*/
getTotalVertices() {
if (!this.isReady()) {
return 0;
}
return this._totalVertices;
}
/**
* Gets a specific vertex data attached to this geometry. Float data is constructed if the vertex buffer data cannot be returned directly.
* @param kind defines the data kind (Position, normal, etc...)
* @param copyWhenShared defines if the returned array must be cloned upon returning it if the current geometry is shared between multiple meshes
* @param forceCopy defines a boolean indicating that the returned array must be cloned upon returning it
* @returns a float array containing vertex data
*/
getVerticesData(kind, copyWhenShared, forceCopy) {
const vertexBuffer = this.getVertexBuffer(kind);
if (!vertexBuffer) {
return null;
}
return vertexBuffer.getFloatData(this._totalVertices, forceCopy || (copyWhenShared && this._meshes.length !== 1));
}
/**
* Copies the requested vertex data kind into the given vertex data map. Float data is constructed if the map doesn't have the data.
* @param kind defines the data kind (Position, normal, etc...)
* @param vertexData defines the map that stores the resulting data
*/
copyVerticesData(kind, vertexData) {
const vertexBuffer = this.getVertexBuffer(kind);
if (!vertexBuffer) {
return;
}
vertexData[kind] || (vertexData[kind] = new Float32Array(this._totalVertices * vertexBuffer.getSize()));
const data = vertexBuffer.getData();
if (data) {
CopyFloatData(data, vertexBuffer.getSize(), vertexBuffer.type, vertexBuffer.byteOffset, vertexBuffer.byteStride, vertexBuffer.normalized, this._totalVertices, vertexData[kind]);
}
}
/**
* Returns a boolean defining if the vertex data for the requested `kind` is updatable
* @param kind defines the data kind (Position, normal, etc...)
* @returns true if the vertex buffer with the specified kind is updatable
*/
isVertexBufferUpdatable(kind) {
const vb = this._vertexBuffers[kind];
if (!vb) {
return false;
}
return vb.isUpdatable();
}
/**
* Gets a specific vertex buffer
* @param kind defines the data kind (Position, normal, etc...)
* @returns a VertexBuffer
*/
getVertexBuffer(kind) {
if (!this.isReady()) {
return null;
}
return this._vertexBuffers[kind];
}
/**
* Returns all vertex buffers
* @returns an object holding all vertex buffers indexed by kind
*/
getVertexBuffers() {
if (!this.isReady()) {
return null;
}
return this._vertexBuffers;
}
/**
* Gets a boolean indicating if specific vertex buffer is present
* @param kind defines the data kind (Position, normal, etc...)
* @returns true if data is present
*/
isVerticesDataPresent(kind) {
if (!this._vertexBuffers) {
if (this._delayInfo) {
return this._delayInfo.indexOf(kind) !== -1;
}
return false;
}
return this._vertexBuffers[kind] !== undefined;
}
/**
* Gets a list of all attached data kinds (Position, normal, etc...)
* @returns a list of string containing all kinds
*/
getVerticesDataKinds() {
const result = [];
let kind;
if (!this._vertexBuffers && this._delayInfo) {
for (kind in this._delayInfo) {
result.push(kind);
}
}
else {
for (kind in this._vertexBuffers) {
result.push(kind);
}
}
return result;
}
/**
* Update index buffer
* @param indices defines the indices to store in the index buffer
* @param offset defines the offset in the target buffer where to store the data
* @param gpuMemoryOnly defines a boolean indicating that only the GPU memory must be updated leaving the CPU version of the indices unchanged (false by default)
*/
updateIndices(indices, offset, gpuMemoryOnly = false) {
if (!this._indexBuffer) {
return;
}
if (!this._indexBufferIsUpdatable) {
this.setIndices(indices, null, true);
}
else {
const needToUpdateSubMeshes = indices.length !== this._indices.length;
if (!gpuMemoryOnly) {
this._indices = indices.slice();
}
this._engine.updateDynamicIndexBuffer(this._indexBuffer, indices, offset);
if (needToUpdateSubMeshes) {
for (const mesh of this._meshes) {
mesh._createGlobalSubMesh(true);
}
}
}
}
/**
* Sets the index buffer for this geometry.
* @param indexBuffer Defines the index buffer to use for this geometry
* @param totalVertices Defines the total number of vertices used by the buffer
* @param totalIndices Defines the total number of indices in the index buffer
* @param is32Bits Defines if the indices are 32 bits. If null (default), the value is guessed from the number of vertices
*/
setIndexBuffer(indexBuffer, totalVertices, totalIndices, is32Bits = null) {
this._indices = [];
this._indexBufferIsUpdatable = false;
this._indexBuffer = indexBuffer;
this._totalVertices = totalVertices;
this._totalIndices = totalIndices;
if (is32Bits === null) {
indexBuffer.is32Bits = totalVertices > 65535;
}
else {
indexBuffer.is32Bits = is32Bits;
}
for (const mesh of this._meshes) {
mesh._createGlobalSubMesh(true);
mesh.synchronizeInstances();
}
this._notifyUpdate();
}
/**
* Creates a new index buffer
* @param indices defines the indices to store in the index buffer
* @param totalVertices defines the total number of vertices (could be null)
* @param updatable defines if the index buffer must be flagged as updatable (false by default)
* @param dontForceSubMeshRecreation defines a boolean indicating that we don't want to force the recreation of sub-meshes if we don't have to (false by default)
*/
setIndices(indices, totalVertices = null, updatable = false, dontForceSubMeshRecreation = false) {
if (this._indexBuffer) {
this._engine._releaseBuffer(this._indexBuffer);
}
this._indices = indices;
this._indexBufferIsUpdatable = updatable;
if (this._meshes.length !== 0 && this._indices) {
this._indexBuffer = this._engine.createIndexBuffer(this._indices, updatable, "Geometry_" + this.id + "_IndexBuffer");
}
if (totalVertices != undefined) {
// including null and undefined
this._totalVertices = totalVertices;
}
for (const mesh of this._meshes) {
mesh._createGlobalSubMesh(!dontForceSubMeshRecreation);
mesh.synchronizeInstances();
}
this._notifyUpdate();
}
/**
* Return the total number of indices
* @returns the total number of indices
*/
getTotalIndices() {
if (!this.isReady()) {
return 0;
}
return this._totalIndices !== undefined ? this._totalIndices : this._indices.length;
}
/**
* Gets the index buffer array
* @param copyWhenShared defines if the returned array must be cloned upon returning it if the current geometry is shared between multiple meshes
* @param forceCopy defines a boolean indicating that the returned array must be cloned upon returning it
* @returns the index buffer array
*/
getIndices(copyWhenShared, forceCopy) {
if (!this.isReady()) {
return null;
}
const orig = this._indices;
if (!forceCopy && (!copyWhenShared || this._meshes.length === 1)) {
return orig;
}
else {
return orig.slice();
}
}
/**
* Gets the index buffer
* @returns the index buffer
*/
getIndexBuffer() {
if (!this.isReady()) {
return null;
}
return this._indexBuffer;
}
/**
* @internal
*/
_releaseVertexArrayObject(effect = null) {
if (!effect || !this._vertexArrayObjects) {
return;
}
if (this._vertexArrayObjects[effect.key]) {
this._engine.releaseVertexArrayObject(this._vertexArrayObjects[effect.key]);
delete this._vertexArrayObjects[effect.key];
}
}
/**
* Release the associated resources for a specific mesh
* @param mesh defines the source mesh
* @param shouldDispose defines if the geometry must be disposed if there is no more mesh pointing to it
*/
releaseForMesh(mesh, shouldDispose) {
const meshes = this._meshes;
const index = meshes.indexOf(mesh);
if (index === -1) {
return;
}
meshes.splice(index, 1);
if (this._vertexArrayObjects) {
mesh._invalidateInstanceVertexArrayObject();
}
mesh._geometry = null;
if (meshes.length === 0 && shouldDispose) {
this.dispose();
}
}
/**
* Apply current geometry to a given mesh
* @param mesh defines the mesh to apply geometry to
*/
applyToMesh(mesh) {
if (mesh._geometry === this) {
return;
}
const previousGeometry = mesh._geometry;
if (previousGeometry) {
previousGeometry.releaseForMesh(mesh);
}
if (this._vertexArrayObjects) {
mesh._invalidateInstanceVertexArrayObject();
}
const meshes = this._meshes;
// must be done before setting vertexBuffers because of mesh._createGlobalSubMesh()
mesh._geometry = this;
mesh._internalAbstractMeshDataInfo._positions = null;
this._scene.pushGeometry(this);
meshes.push(mesh);
if (this.isReady()) {
this._applyToMesh(mesh);
}
else if (this._boundingInfo) {
mesh.setBoundingInfo(this._boundingInfo);
}
}
_updateExtend(data = null) {
if (this.useBoundingInfoFromGeometry && this._boundingInfo) {
this._extend = {
minimum: this._boundingInfo.minimum.clone(),
maximum: this._boundingInfo.maximum.clone(),
};
}
else {
if (!data) {
data = this.getVerticesData(VertexBuffer.PositionKind);
// This can happen if the buffer comes from a Hardware Buffer where
// The data have not been uploaded by Babylon. (ex: Compute Shaders and Storage Buffers)
if (!data) {
return;
}
}
this._extend = extractMinAndMax(data, 0, this._totalVertices, this.boundingBias, 3);
}
}
_applyToMesh(mesh) {
const numOfMeshes = this._meshes.length;
// vertexBuffers
for (const kind in this._vertexBuffers) {
if (numOfMeshes === 1) {
this._vertexBuffers[kind].create();
}
if (kind === VertexBuffer.PositionKind) {
if (!this._extend) {
this._updateExtend();
}
mesh.buildBoundingInfo(this._extend.minimum, this._extend.maximum);
mesh._createGlobalSubMesh(mesh.isUnIndexed);
//bounding info was just created again, world matrix should be applied again.
mesh._updateBoundingInfo();
}
}
// indexBuffer
if (numOfMeshes === 1 && this._indices && this._indices.length > 0) {
this._indexBuffer = this._engine.createIndexBuffer(this._indices, this._updatable, "Geometry_" + this.id + "_IndexBuffer");
}
// morphTargets
mesh._syncGeometryWithMorphTargetManager();
// instances
mesh.synchronizeInstances();
}
_notifyUpdate(kind) {
if (this.onGeometryUpdated) {
this.onGeometryUpdated(this, kind);
}
if (this._vertexArrayObjects) {
this._disposeVertexArrayObjects();
}
for (const mesh of this._meshes) {
mesh._markSubMeshesAsAttributesDirty();
}
}
/**
* Load the geometry if it was flagged as delay loaded
* @param scene defines the hosting scene
* @param onLoaded defines a callback called when the geometry is loaded
*/
load(scene, onLoaded) {
if (this.delayLoadState === 2) {
return;
}
if (this.isReady()) {
if (onLoaded) {
onLoaded();
}
return;
}
this.delayLoadState = 2;
this._queueLoad(scene, onLoaded);
}
_queueLoad(scene, onLoaded) {
if (!this.delayLoadingFile) {
return;
}
scene.addPendingData(this);
scene._loadFile(this.delayLoadingFile, (data) => {
if (!this._delayLoadingFunction) {
return;
}
this._delayLoadingFunction(JSON.parse(data), this);
this.delayLoadState = 1;
this._delayInfo = [];
scene.removePendingData(this);
const meshes = this._meshes;
const numOfMeshes = meshes.length;
for (let index = 0; index < numOfMeshes; index++) {
this._applyToMesh(meshes[index]);
}
if (onLoaded) {
onLoaded();
}
}, undefined, true);
}
/**
* Invert the geometry to move from a right handed system to a left handed one.
*/
toLeftHanded() {
// Flip faces
const tIndices = this.getIndices(false);
if (tIndices != null && tIndices.length > 0) {
for (let i = 0; i < tIndices.length; i += 3) {
const tTemp = tIndices[i + 0];
tIndices[i + 0] = tIndices[i + 2];
tIndices[i + 2] = tTemp;
}
this.setIndices(tIndices);
}
// Negate position.z
const tPositions = this.getVerticesData(VertexBuffer.PositionKind, false);
if (tPositions != null && tPositions.length > 0) {
for (let i = 0; i < tPositions.length; i += 3) {
tPositions[i + 2] = -tPositions[i + 2];
}
this.setVerticesData(VertexBuffer.PositionKind, tPositions, false);
}
// Negate normal.z
const tNormals = this.getVerticesData(VertexBuffer.NormalKind, false);
if (tNormals != null && tNormals.length > 0) {
for (let i = 0; i < tNormals.length; i += 3) {
tNormals[i + 2] = -tNormals[i + 2];
}
this.setVerticesData(VertexBuffer.NormalKind, tNormals, false);
}
}
// Cache
/** @internal */
_resetPointsArrayCache() {
this._positions = null;
}
/** @internal */
_generatePointsArray() {
if (this._positions) {
return true;
}
const data = this.getVerticesData(VertexBuffer.PositionKind);
if (!data || data.length === 0) {
return false;
}
for (let index = this._positionsCache.length * 3, arrayIdx = this._positionsCache.length; index < data.length; index += 3, ++arrayIdx) {
this._positionsCache[arrayIdx] = Vector3.FromArray(data, index);
}
for (let index = 0, arrayIdx = 0; index < data.length; index += 3, ++arrayIdx) {
this._positionsCache[arrayIdx].set(data[0 + index], data[1 + index], data[2 + index]);
}
// just in case the number of positions was reduced, splice the array
this._positionsCache.length = data.length / 3;
this._positions = this._positionsCache;
return true;
}
/**
* Gets a value indicating if the geometry is disposed
* @returns true if the geometry was disposed
*/
isDisposed() {
return this._isDisposed;
}
_disposeVertexArrayObjects() {
if (this._vertexArrayObjects) {
for (const kind in this._vertexArrayObjects) {
this._engine.releaseVertexArrayObject(this._vertexArrayObjects[kind]);
}
this._vertexArrayObjects = {}; // Will trigger a rebuild of the VAO if supported
const meshes = this._meshes;
const numOfMeshes = meshes.length;
for (let index = 0; index < numOfMeshes; index++) {
meshes[index]._invalidateInstanceVertexArrayObject();
}
}
}
/**
* Free all associated resources
*/
dispose() {
const meshes = this._meshes;
const numOfMeshes = meshes.length;
let index;
for (index = 0; index < numOfMeshes; index++) {
this.releaseForMesh(meshes[index]);
}
this._meshes.length = 0;
this._disposeVertexArrayObjects();
for (const kind in this._vertexBuffers) {
this._vertexBuffers[kind].dispose();
}
this._vertexBuffers = {};
this._totalVertices = 0;
if (this._indexBuffer) {
this._engine._releaseBuffer(this._indexBuffer);
}
this._indexBuffer = null;
this._indices = [];
this.delayLoadState = 0;
this.delayLoadingFile = null;
this._delayLoadingFunction = null;
this._delayInfo = [];
this._boundingInfo = null;
this._scene.removeGeometry(this);
if (this._parentContainer) {
const index = this._parentContainer.geometries.indexOf(this);
if (index > -1) {
this._parentContainer.geometries.splice(index, 1);
}
this._parentContainer = null;
}
this._isDisposed = true;
}
/**
* Clone the current geometry into a new geometry
* @param id defines the unique ID of the new geometry
* @returns a new geometry object
*/
copy(id) {
const vertexData = new VertexData();
vertexData.indices = [];
const indices = this.getIndices();
if (indices) {
for (let index = 0; index < indices.length; index++) {
vertexData.indices.push(indices[index]);
}
}
let updatable = false;
let stopChecking = false;
let kind;
for (kind in this._vertexBuffers) {
const data = this.getVerticesData(kind);
if (data) {
if (data instanceof Float32Array) {
vertexData.set(new Float32Array(data), kind);
}
else {
vertexData.set(data.slice(0), kind);
}
if (!stopChecking) {
const vb = this.getVertexBuffer(kind);
if (vb) {
updatable = vb.isUpdatable();
stopChecking = !updatable;
}
}
}
}
const geometry = new Geometry(id, this._scene, vertexData, updatable);
geometry.delayLoadState = this.delayLoadState;
geometry.delayLoadingFile = this.delayLoadingFile;
geometry._delayLoadingFunction = this._delayLoadingFunction;
for (kind in this._delayInfo) {
geometry._delayInfo = geometry._delayInfo || [];
geometry._delayInfo.push(kind);
}
// Bounding info
geometry._boundingInfo = new BoundingInfo(this._extend.minimum, this._extend.maximum);
return geometry;
}
/**
* Serialize the current geometry info (and not the vertices data) into a JSON object
* @returns a JSON representation of the current geometry data (without the vertices data)
*/
serialize() {
const serializationObject = {};
serializationObject.id = this.id;
serializationObject.uniqueId = this.uniqueId;
serializationObject.updatable = this._updatable;
if (Tags && Tags.HasTags(this)) {
serializationObject.tags = Tags.GetTags(this);
}
return serializationObject;
}
_toNumberArray(origin) {
if (Array.isArray(origin)) {
return origin;
}
else {
return Array.prototype.slice.call(origin);
}
}
/**
* Release any memory retained by the cached data on the Geometry.
*
* Call this function to reduce memory footprint of the mesh.
* Vertex buffers will not store CPU data anymore (this will prevent picking, collisions or physics to work correctly)
*/
clearCachedData() {
this._indices = [];
this._resetPointsArrayCache();
for (const vbName in this._vertexBuffers) {
if (!Object.prototype.hasOwnProperty.call(this._vertexBuffers, vbName)) {
continue;
}
this._vertexBuffers[vbName]._buffer._data = null;
}
}
/**
* Serialize all vertices data into a JSON object
* @returns a JSON representation of the current geometry data
*/
serializeVerticeData() {
const serializationObject = this.serialize();
if (this.isVerticesDataPresent(VertexBuffer.PositionKind)) {
serializationObject.positions = this._toNumberArray(this.getVerticesData(VertexBuffer.PositionKind));
if (this.isVertexBufferUpdatable(VertexBuffer.PositionKind)) {
serializationObject.positionsUpdatable = true;
}
}
if (this.isVerticesDataPresent(VertexBuffer.NormalKind)) {
serializationObject.normals = this._toNumberArray(this.getVerticesData(VertexBuffer.NormalKind));
if (this.isVertexBufferUpdatable(VertexBuffer.NormalKind)) {
serializationObject.normalsUpdatable = true;
}
}
if (this.isVerticesDataPresent(VertexBuffer.TangentKind)) {
serializationObject.tangents = this._toNumberArray(this.getVerticesData(VertexBuffer.TangentKind));
if (this.isVertexBufferUpdatable(VertexBuffer.TangentKind)) {
serializationObject.tangentsUpdatable = true;
}
}
if (this.isVerticesDataPresent(VertexBuffer.UVKind)) {
serializationObject.uvs = this._toNumberArray(this.getVerticesData(VertexBuffer.UVKind));
if (this.isVertexBufferUpdatable(VertexBuffer.UVKind)) {
serializationObject.uvsUpdatable = true;
}
}
if (this.isVerticesDataPresent(VertexBuffer.UV2Kind)) {
serializationObject.uvs2 = this._toNumberArray(this.getVerticesData(VertexBuffer.UV2Kind));
if (this.isVertexBufferUpdatable(VertexBuffer.UV2Kind)) {
serializationObject.uvs2Updatable = true;
}
}
if (this.isVerticesDataPresent(VertexBuffer.UV3Kind)) {
serializationObject.uvs3 = this._toNumberArray(this.getVerticesData(VertexBuffer.UV3Kind));
if (this.isVertexBufferUpdatable(VertexBuffer.UV3Kind)) {
serializationObject.uvs3Updatable = true;
}
}
if (this.isVerticesDataPresent(VertexBuffer.UV4Kind)) {
serializationObject.uvs4 = this._toNumberArray(this.getVerticesData(VertexBuffer.UV4Kind));
if (this.isVertexBufferUpdatable(VertexBuffer.UV4Kind)) {
serializationObject.uvs4Updatable = true;
}
}
if (this.isVerticesDataPresent(VertexBuffer.UV5Kind)) {
serializationObject.uvs5 = this._toNumberArray(this.getVerticesData(VertexBuffer.UV5Kind));
if (this.isVertexBufferUpdatable(VertexBuffer.UV5Kind)) {
serializationObject.uvs5Updatable = true;
}
}
if (this.isVerticesDataPresent(VertexBuffer.UV6Kind)) {
serializationObject.uvs6 = this._toNumberArray(this.getVerticesData(VertexBuffer.UV6Kind));
if (this.isVertexBufferUpdatable(VertexBuffer.UV6Kind)) {
serializationObject.uvs6Updatable = true;
}
}
if (this.isVerticesDataPresent(VertexBuffer.ColorKind)) {
serializationObject.colors = this._toNumberArray(this.getVerticesData(VertexBuffer.ColorKind));
if (this.isVertexBufferUpdatable(VertexBuffer.ColorKind)) {
serializationObject.colorsUpdatable = true;
}
}
if (this.isVerticesDataPresent(VertexBuffer.MatricesIndicesKind)) {
serializationObject.matricesIndices = this._toNumberArray(this.getVerticesData(VertexBuffer.MatricesIndicesKind));
serializationObject.matricesIndicesExpanded = true;
if (this.isVertexBufferUpdatable(VertexBuffer.MatricesIndicesKind)) {
serializationObject.matricesIndicesUpdatable = true;
}
}
if (this.isVerticesDataPresent(VertexBuffer.MatricesWeightsKind)) {
serializationObject.matricesWeights = this._toNumberArray(this.getVerticesData(VertexBuffer.MatricesWeightsKind));
if (this.isVertexBufferUpdatable(VertexBuffer.MatricesWeightsKind)) {
serializationObject.matricesWeightsUpdatable = true;
}
}
serializationObject.indices = this._toNumberArray(this.getIndices());
return serializationObject;
}
// Statics
/**
* Extracts a clone of a mesh geometry
* @param mesh defines the source mesh
* @param id defines the unique ID of the new geometry object
* @returns the new geometry object
*/
static ExtractFromMesh(mesh, id) {
const geometry = mesh._geometry;
if (!geometry) {
return null;
}
return geometry.copy(id);
}
/**
* You should now use Tools.RandomId(), this method is still here for legacy reasons.
* Implementation from http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#answer-2117523
* Be aware Math.random() could cause collisions, but:
* "All but 6 of the 128 bits of the ID are randomly generated, which means that for any two ids, there's a 1 in 2^^122 (or 5.3x10^^36) chance they'll collide"
* @returns a string containing a new GUID
*/
static RandomId() {
return Tools.RandomId();
}
static _GetGeometryByLoadedUniqueId(uniqueId, scene) {
for (let index = 0; index < scene.geometries.length; index++) {
if (scene.geometries[index]._loadedUniqueId === uniqueId) {
return scene.geometries[index];
}
}
return null;
}
/**
* @internal
*/
static _ImportGeometry(parsedGeometry, mesh) {
const scene = mesh.getScene();
// Geometry
const geometryUniqueId = parsedGeometry.geometryUniqueId;
const geometryId = parsedGeometry.geometryId;
if (geometryUniqueId || geometryId) {
const geometry = geometryUniqueId ? this._GetGeometryByLoadedUniqueId(geometryUniqueId, scene) : scene.getGeometryById(geometryId);
if (geometry) {
geometry.applyToMesh(mesh);
}
}
else if (parsedGeometry instanceof ArrayBuffer) {
const binaryInfo = mesh._binaryInfo;
if (binaryInfo.positionsAttrDesc && binaryInfo.positionsAttrDesc.count > 0) {
const positionsData = new Float32Array(parsedGeometry, binaryInfo.positionsAttrDesc.offset, binaryInfo.positionsAttrDesc.count);
mesh.setVerticesData(VertexBuffer.PositionKind, positionsData, false);
}
if (binaryInfo.normalsAttrDesc && binaryInfo.normalsAttrDesc.count > 0) {
const normalsData = new Float32Array(parsedGeometry, binaryInfo.normalsAttrDesc.offset, binaryInfo.normalsAttrDesc.count);
mesh.setVerticesData(VertexBuffer.NormalKind, normalsData, false);
}
if (binaryInfo.tangetsAttrDesc && binaryInfo.tangetsAttrDesc.count > 0) {
const tangentsData = new Float32Array(parsedGeometry, binaryInfo.tangetsAttrDesc.offset, binaryInfo.tangetsAttrDesc.count);
mesh.setVerticesData(VertexBuffer.TangentKind, tangentsData, false);
}
if (binaryInfo.uvsAttrDesc && binaryInfo.uvsAttrDesc.count > 0) {
const uvsData = new Float32Array(parsedGeometry, binaryInfo.uvsAttrDesc.offset, binaryInfo.uvsAttrDesc.count);
if (useOpenGLOrientationForUV) {
for (let index = 1; index < uvsData.length; index += 2) {
uvsData[index] = 1 - uvsData[index];
}
}
mesh.setVerticesData(VertexBuffer.UVKind, uvsData, false);
}
if (binaryInfo.uvs2AttrDesc && binaryInfo.uvs2AttrDesc.count > 0) {
const uvs2Data = new Float32Array(parsedGeometry, binaryInfo.uvs2AttrDesc.offset, binaryInfo.uvs2AttrDesc.count);
if (useOpenGLOrientationForUV) {
for (let index = 1; index < uvs2Data.length; index += 2) {
uvs2Data[index] = 1 - uvs2Data[index];
}
}
mesh.setVerticesData(VertexBuffer.UV2Kind, uvs2Data, false);
}
if (binaryInfo.uvs3AttrDesc && binaryInfo.uvs3AttrDesc.count > 0) {
const uvs3Data = new Float32Array(parsedGeometry, binaryInfo.uvs3AttrDesc.offset, binaryInfo.uvs3AttrDesc.count);
if (useOpenGLOrientationForUV) {
for (let index = 1; index < uvs3Data.length; index += 2) {
uvs3Data[index] = 1 - uvs3Data[index];
}
}
mesh.setVerticesData(VertexBuffer.UV3Kind, uvs3Data, false);
}
if (binaryInfo.uvs4AttrDesc && binaryInfo.uvs4AttrDesc.count > 0) {
const uvs4Data = new Float32Array(parsedGeometry, binaryInfo.uvs4AttrDesc.offset, binaryInfo.uvs4AttrDesc.count);
if (useOpenGLOrientationForUV) {
for (let index = 1; index < uvs4Data.length; index += 2) {
uvs4Data[index] = 1 - uvs4Data[index];
}
}
mesh.setVerticesData(VertexBuffer.UV4Kind, uvs4Data, false);
}
if (binaryInfo.uvs5AttrDesc && binaryInfo.uvs5AttrDesc.count > 0) {
const uvs5Data = new Float32Array(parsedGeometry, binaryInfo.uvs5AttrDesc.offset, binaryInfo.uvs5AttrDesc.count);
if (useOpenGLOrientationForUV) {
for (let index = 1; index < uvs5Data.length; index += 2) {
uvs5Data[index] = 1 - uvs5Data[index];
}
}
mesh.setVerticesData(VertexBuffer.UV5Kind, uvs5Data, false);
}
if (binaryInfo.uvs6AttrDesc && binaryInfo.uvs6AttrDesc.count > 0) {
const uvs6Data = new Float32Array(parsedGeometry, binaryInfo.uvs6AttrDesc.offset, binaryInfo.uvs6AttrDesc.count);
if (useOpenGLOrientationForUV) {
for (let index = 1; index < uvs6Data.length; index += 2) {
uvs6Data[index] = 1 - uvs6Data[index];
}
}
mesh.setVerticesData(VertexBuffer.UV6Kind, uvs6Data, false);
}
if (binaryInfo.colorsAttrDesc && binaryInfo.colorsAttrDesc.count > 0) {
const colorsData = new Float32Array(parsedGeometry, binaryInfo.colorsAttrDesc.offset, binaryInfo.colorsAttrDesc.count);
mesh.setVerticesData(VertexBuffer.ColorKind, colorsData, false, binaryInfo.colorsAttrDesc.stride);
}
if (binaryInfo.matricesIndicesAttrDesc && binaryInfo.matricesIndicesAttrDesc.count > 0) {
const matricesIndicesData = new Int32Array(parsedGeometry, binaryInfo.matricesIndicesAttrDesc.offset, binaryInfo.matricesIndicesAttrDesc.count);
const floatIndices = [];
for (let i = 0; i < matricesIndicesData.length; i++) {
const index = matricesIndicesData[i];
floatIndices.push(index & 0x000000ff);
floatIndices.push((index & 0x0000ff00) >> 8);
floatIndices.push((index & 0x00ff0000) >> 16);
floatIndices.push((index >> 24) & 0xff); // & 0xFF to convert to v + 256 if v < 0
}
mesh.setVerticesData(VertexBuffer.MatricesIndicesKind, floatIndices, false);
}
if (binaryInfo.matricesIndicesExtraAttrDesc && binaryInfo.matricesIndicesExtraAttrDesc.count > 0) {
const matricesIndicesData = new Int32Array(parsedGeometry, binaryInfo.matricesIndicesExtraAttrDesc.offset, binaryInfo.matricesIndicesExtraAttrDesc.count);
const floatIndices = [];
for (let i = 0; i < matricesIndicesData.length; i++) {
const index = matricesIndicesData[i];
floatIndices.push(index & 0x000000ff);
floatIndices.push((index & 0x0000ff00) >> 8);
floatIndices.push((index & 0x00ff0000) >> 16);
floatIndices.push((index >> 24) & 0xff); // & 0xFF to convert to v + 256 if v < 0
}
mesh.setVerticesData(VertexBuffer.MatricesIndicesExtraKind, floatIndices, false);
}
if (binaryInfo.matricesWeightsAttrDesc && binaryInfo.matricesWeightsAttrDesc.count > 0) {
const matricesWeightsData = new Float32Array(parsedGeometry, binaryInfo.matricesWeightsAttrDesc.offset, binaryInfo.matricesWeightsAttrDesc.count);
mesh.setVerticesData(VertexBuffer.MatricesWeightsKind, matricesWeightsData, false);
}
if (binaryInfo.indicesAttrDesc && binaryInfo.indicesAttrDesc.count > 0) {
const indicesData = new Int32Array(parsedGeometry, binaryInfo.indicesAttrDesc.offset, binaryInfo.indicesAttrDesc.count);
mesh.setIndices(indicesData, null);
}
if (binaryInfo.subMeshesAttrDesc && binaryInfo.subMeshesAttrDesc.count > 0) {
const subMeshesData = new Int32Array(parsedGeometry, binaryInfo.subMeshesAttrDesc.offset, binaryInfo.subMeshesAttrDesc.count * 5);
mesh.subMeshes = [];
for (let i = 0; i < binaryInfo.subMeshesAttrDesc.count; i++) {
const materialIndex = subMeshesData[i * 5 + 0];
const verticesStart = subMeshesData[i * 5 + 1];
const verticesCount = subMeshesData[i * 5 + 2];
const indexStart = subMeshesData[i * 5 + 3];
const indexCount = subMeshesData[i * 5 + 4];
SubMesh.AddToMesh(materialIndex, verticesStart, verticesCount, indexStart, indexCount, mesh);
}
}
}
else if (parsedGeometry.positions && parsedGeometry.normals && parsedGeometry.indices) {
mesh.setVerticesData(VertexBuffer.PositionKind, parsedGeometry.positions, parsedGeometry.positions._updatable || parsedGeometry.positionsUpdatable);
mesh.setVerticesData(VertexBuffer.NormalKind, parsedGeometry.normals, parsedGeometry.normals._updatable || parsedGeometry.normalsUpdatable);
if (parsedGeometry.tangents) {
mesh.setVerticesData(VertexBuffer.TangentKind, parsedGeometry.tangents, parsedGeometry.tangents._updatable || parsedGeometry.tangentsUpdatable);
}
if (parsedGeometry.uvs) {
mesh.setVerticesData(VertexBuffer.UVKind, parsedGeometry.uvs, parsedGeometry.uvs._updatable || parsedGeometry.uvsUpdatable);
}
if (parsedGeometry.uvs2) {
mesh.setVerticesData(VertexBuffer.UV2Kind, parsedGeometry.uvs2, parsedGeometry.uvs2._updatable || parsedGeometry.uvs2Updatable);
}
if (parsedGeometry.uvs3) {
mesh.setVerticesData(VertexBuffer.UV3Kind, parsedGeometry.uvs3, parsedGeometry.uvs3._updatable || parsedGeometry.uvs3Updatable);
}
if (parsedGeometry.uvs4) {
mesh.setVerticesData(VertexBuffer.UV4Kind, parsedGeometry.uvs4, parsedGeometry.uvs4._updatable || parsedGeometry.uvs4Updatable);
}
if (parsedGeometry.uvs5) {
mesh.setVerticesData(VertexBuffer.UV5Kind, parsedGeometry.uvs5, parsedGeometry.uvs5._updatable || parsedGeometry.uvs5Updatable);
}
if (parsedGeometry.uvs6) {
mesh.setVerticesData(VertexBuffer.UV6Kind, parsedGeometry.uvs6, parsedGeometry.uvs6._updatable || parsedGeometry.uvs6Updatable);
}
if (parsedGeometry.colors) {