Browse Source

Take model on regular instance implementation + add new methods + docs

Popov72 5 years ago
parent
commit
4074753bbb
2 changed files with 267 additions and 104 deletions
  1. 17 2
      src/Meshes/mesh.ts
  2. 250 102
      src/Meshes/thinInstanceMesh.ts

+ 17 - 2
src/Meshes/mesh.ts

@@ -91,6 +91,18 @@ export class _InstancesBatch {
 /**
  * @hidden
  **/
+class _ThinInstanceDataStorage {
+    public instancesCount: number = 0;
+    public nonUniformScaling: boolean = false;
+    public matrixBuffer: Nullable<Buffer> = null;
+    public matrixBufferSize = 32 * 16; // let's start with a maximum of 32 thin instances
+    public matrixData: Nullable<Float32Array>;
+    public boundingVectors: Array<Vector3> = [];
+}
+
+/**
+ * @hidden
+ **/
 class _InternalMeshDataInfo {
     // Events
     public _onBeforeRenderObservable: Nullable<Observable<Mesh>>;
@@ -276,7 +288,7 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
     }
 
     public get hasThinInstances(): boolean {
-        return (this._thinInstanceStorage?.instancesCount ?? 0) > 0;
+        return (this._thinInstanceDataStorage?.instancesCount ?? 0) > 0;
     }
 
     // Members
@@ -339,6 +351,9 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
     /** @hidden */
     public _instanceDataStorage = new _InstanceDataStorage();
 
+    /** @hidden */
+    public _thinInstanceDataStorage = new _ThinInstanceDataStorage();
+
     private _effectiveMaterial: Nullable<Material> = null;
 
     /** @hidden */
@@ -1632,7 +1647,7 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
     /** @hidden */
     public _renderWithThinInstances(subMesh: SubMesh, fillMode: number, effect: Effect, engine: Engine) {
         // Stats
-        const instancesCount = this._thinInstanceStorage?.instancesCount ?? 0;
+        const instancesCount = this._thinInstanceDataStorage?.instancesCount ?? 0;
 
         this.getScene()._activeIndices.addCount(subMesh.indexCount * instancesCount, false);
 

+ 250 - 102
src/Meshes/thinInstanceMesh.ts

@@ -1,9 +1,6 @@
-import { Nullable } from "../types";
+import { Nullable, DeepImmutableObject } from "../types";
 import { Mesh, _InstancesBatch } from "../Meshes/mesh";
 import { VertexBuffer, Buffer } from './buffer';
-//import { SubMesh } from "./subMesh";
-//import { Effect } from "../Materials/effect";
-//import { Engine } from "../Engines/engine";
 import { Matrix, Vector3 } from '../Maths/math.vector';
 
 const tmpMatrix = Matrix.Identity();
@@ -11,97 +8,207 @@ const tmpV1 = new Vector3();
 const tmpV2 = new Vector3();
 const tmpV3 = new Vector3();
 
+const isNonUniform = (buffer: DeepImmutableObject<Float32Array>, i: number) => {
+    tmpV2.copyFromFloats(buffer[i * 16 + 0], buffer[i * 16 + 1], buffer[i * 16 + 2]);
+    tmpV1.x = tmpV2.lengthSquared(); // scale x squared
+    tmpV2.copyFromFloats(buffer[i * 16 + 4], buffer[i * 16 + 5], buffer[i * 16 + 6]);
+    tmpV1.y = tmpV2.lengthSquared(); // scale y squared
+    tmpV2.copyFromFloats(buffer[i * 16 + 8], buffer[i * 16 + 9], buffer[i * 16 + 10]);
+    tmpV1.z = tmpV2.lengthSquared(); // scale z squared
+    return tmpV1.isNonUniformWithinEpsilon(0.0001);
+};
+
 declare module "./mesh" {
     export interface Mesh {
+        /**
+         * Creates a new thin instance
+         * @param matrix the matrix or array of matrices (position, rotation, scale) of the thin instance(s) to create
+         * @param refresh true to refresh the underlying gpu buffer (default: true). If you do multiple calls to this method in a row, set refresh to true only for the last call to save performance
+         * @returns the thin instance index number. If you pass an array of matrices, other instance indexes are index+1, index+2, etc
+         */
+        thinInstanceAdd(matrix: DeepImmutableObject<Matrix> | Array<DeepImmutableObject<Matrix>>, refresh: boolean): number;
+
+        /**
+         * Adds the transformation (matrix) of the current mesh as a thin instance
+         * @param refresh true to refresh the underlying gpu buffer (default: true). If you do multiple calls to this method in a row, set refresh to true only for the last call to save performance
+         * @returns the thin instance index number
+         */
+        thinInstanceAddSelf(refresh: boolean): number;
+
+        /**
+         * Registers a custom attribute to be used with thin instances
+         * @param kind name of the attribute
+         * @param stride size in floats of the attribute
+         */
+        thinInstanceRegisterAttribute(kind: string, stride: number): void;
+
+        /**
+         * Sets the matrix of a thin instance
+         * @param index index of the thin instance
+         * @param matrix matrix to set
+         * @param refresh true to refresh the underlying gpu buffer (default: true). If you do multiple calls to this method in a row, set refresh to true only for the last call to save performance
+         */
+        thinInstanceSetMatrixAt(index: number, matrix: DeepImmutableObject<Matrix>, refresh: boolean): void;
+
+        /**
+         * Sets the value of a custom attribute for a thin instance
+         * @param kind name of the attribute
+         * @param index index of the thin instance
+         * @param value value to set
+         * @param refresh true to refresh the underlying gpu buffer (default: true). If you do multiple calls to this method in a row, set refresh to true only for the last call to save performance
+         */
+        thinInstanceSetAttributeAt(kind: string, index: number, value: Array<number>, refresh: boolean): void;
+
+        /**
+         * Sets a buffer to be used with thin instances. This method is a faster way to setup multiple instances than calling thinInstanceAdd repeatedly
+         * @param kind name of the attribute. Use "matrix" to setup the buffer of matrices
+         * @param buffer buffer to set
+         * @param stride size in floats of each value of the buffer
+         */
         thinInstanceSetBuffer(kind: string, buffer: Nullable<Float32Array>,  stride: number): void;
 
+        /**
+         * Synchronize the gpu buffers with a thin instance buffer. Call this method if you update later on the buffers passed to thinInstanceSetBuffer
+         * @param kind name of the attribute to update. Use "matrix" to update the buffer of matrices
+         */
         thinInstanceBufferUpdated(kind: string): void;
 
-        thinInstanceGetMatrixAt(index: number, matrix: Matrix): boolean;
+        /**
+         * Refreshes the bounding info, taking into account all the thin instances defined
+         */
+        thinInstanceRefreshBoundingInfo(): void;
 
-        thinInstanceSetMatrixAt(index: number, matrix: Matrix, refresh: boolean): void;
+        /** @hidden */
+        _thinInstanceInitializeUserStorage(): void;
 
-        thinInstanceRefreshBoundingInfo(): void;
+        /** @hidden */
+        _thinInstanceUpdateBufferSize(kind: string, numInstances: number): void;
 
         /** @hidden */
-        _thinInstanceStorage: Nullable<{
-            instancesCount: number,
-            nonUniformScaling: boolean,
-            matrixBuffer: Nullable<Buffer>,
+        _userThinInstanceBuffersStorage: {
             data: {[key: string]: Float32Array},
             sizes: {[key: string]: number},
             vertexBuffers: {[key: string]: Nullable<VertexBuffer>},
-            strides: {[key: string]: number},
-            boundingVectors: Array<Vector3>,
-        }>;
+            strides: {[key: string]: number}
+        };
+
     }
 }
 
-Mesh.prototype.thinInstanceSetBuffer = function(kind: string, buffer: Nullable<Float32Array>, stride: number = 0): void {
-    if (kind === "matrix") {
-        stride = 16;
+Mesh.prototype.thinInstanceAdd = function(matrix: DeepImmutableObject<Matrix> | Array<DeepImmutableObject<Matrix>>, refresh: boolean = true): number {
+    this._thinInstanceUpdateBufferSize("matrix", Array.isArray(matrix) ? matrix.length : 1);
+
+    const index = this._thinInstanceDataStorage.instancesCount;
+
+    if (Array.isArray(matrix)) {
+        for (let i = 0; i < matrix.length; ++i) {
+            this.thinInstanceSetMatrixAt(this._thinInstanceDataStorage.instancesCount++, matrix[i], (i === matrix.length - 1) && refresh);
+        }
+    } else {
+        this.thinInstanceSetMatrixAt(this._thinInstanceDataStorage.instancesCount++, matrix, refresh);
     }
 
-    if (!this._thinInstanceStorage) {
-        this._thinInstanceStorage = {
-            instancesCount: 0,
-            nonUniformScaling: false,
-            matrixBuffer: null,
-            data: {},
-            vertexBuffers: {},
-            strides: {},
-            sizes: {},
-            boundingVectors: [],
-        };
+    return index;
+};
+
+Mesh.prototype.thinInstanceAddSelf = function(refresh: boolean = true): number {
+    return this.thinInstanceAdd(Matrix.IdentityReadOnly, refresh);
+};
+
+Mesh.prototype.thinInstanceRegisterAttribute = function(kind: string, stride: number): void {
+    this.removeVerticesData(kind);
+
+    this._thinInstanceInitializeUserStorage();
+
+    this._userThinInstanceBuffersStorage.strides[kind] = stride;
+    this._userThinInstanceBuffersStorage.sizes[kind] = stride * Math.max(32, this._thinInstanceDataStorage.instancesCount); // Initial size
+    this._userThinInstanceBuffersStorage.data[kind] = new Float32Array(this._userThinInstanceBuffersStorage.sizes[kind]);
+    this._userThinInstanceBuffersStorage.vertexBuffers[kind] = new VertexBuffer(this.getEngine(), this._userThinInstanceBuffersStorage.data[kind], kind, true, false, stride, true);
+
+    this.setVerticesBuffer(this._userThinInstanceBuffersStorage.vertexBuffers[kind]!);
+};
+
+Mesh.prototype.thinInstanceSetMatrixAt = function(index: number, matrix: DeepImmutableObject<Matrix>, refresh: boolean = true): boolean {
+    if (!this._thinInstanceDataStorage.matrixData || index >= this._thinInstanceDataStorage.instancesCount) {
+        return false;
     }
 
-    if (buffer === null) {
-        delete this._thinInstanceStorage.data[kind];
-    } else {
-        this._thinInstanceStorage.data[kind] = buffer;
+    const matrixData = this._thinInstanceDataStorage.matrixData;
+
+    matrix.copyToArray(matrixData, index * 16);
+
+    if (!this.nonUniformScaling) {
+        this._thinInstanceDataStorage.nonUniformScaling = isNonUniform(matrix.m, 0);
+        if (this._thinInstanceDataStorage.nonUniformScaling) {
+            this._updateNonUniformScalingState(true);
+        }
     }
 
-    if (kind === "matrix") {
-        if (this._thinInstanceStorage.matrixBuffer) {
-            this._thinInstanceStorage.matrixBuffer.dispose();
-            this._thinInstanceStorage.matrixBuffer = null;
+    if (refresh) {
+        this.thinInstanceBufferUpdated("matrix");
+
+        if (!this.doNotSyncBoundingInfo) {
+            this.thinInstanceRefreshBoundingInfo();
         }
+    }
+
+    return true;
+};
+
+Mesh.prototype.thinInstanceSetAttributeAt = function(kind: string, index: number, value: Array<number>, refresh: boolean = true): boolean {
+    if (!this._userThinInstanceBuffersStorage || !this._userThinInstanceBuffersStorage.data[kind] || index >= this._thinInstanceDataStorage.instancesCount) {
+        return false;
+    }
+
+    this._thinInstanceUpdateBufferSize(kind, 0); // make sur the buffer for the kind attribute is big enough
+
+    this._userThinInstanceBuffersStorage.data[kind].set(value, index * this._userThinInstanceBuffersStorage.strides[kind]);
+
+    if (refresh) {
+        this.thinInstanceBufferUpdated(kind);
+    }
+
+    return true;
+};
+
+Mesh.prototype.thinInstanceSetBuffer = function(kind: string, buffer: Nullable<Float32Array>, stride: number = 0): void {
+    stride = stride || 16;
+
+    if (kind === "matrix") {
+        this._thinInstanceDataStorage.matrixBuffer?.dispose();
+        this._thinInstanceDataStorage.matrixBuffer = null;
+        this._thinInstanceDataStorage.matrixBufferSize = buffer ? buffer.length : 32 * stride;
+        this._thinInstanceDataStorage.matrixData = buffer;
 
         if (buffer !== null) {
-            this._thinInstanceStorage.instancesCount = buffer.length / stride;
+            this._thinInstanceDataStorage.instancesCount = buffer.length / stride;
 
             const matrixBuffer = new Buffer(this.getEngine(), buffer, true, stride, false, true);
 
-            this._thinInstanceStorage.matrixBuffer = matrixBuffer;
+            this._thinInstanceDataStorage.matrixBuffer = matrixBuffer;
 
             this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world0", 0, 4));
             this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world1", 4, 4));
             this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world2", 8, 4));
             this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world3", 12, 4));
 
-            this._thinInstanceStorage.nonUniformScaling = false;
-
-            /*if (!this.ignoreNonUniformScaling) {
-                for (let i = 0; i < this._thinInstanceStorage.instancesCount && !this._thinInstanceStorage.nonUniformScaling; ++i) {
-                    tmpV2.copyFromFloats(buffer[i * 16 + 0], buffer[i * 16 + 1], buffer[i * 16 + 2]);
-                    tmpV1.x = tmpV2.lengthSquared(); // scale x squared
-                    tmpV2.copyFromFloats(buffer[i * 16 + 4], buffer[i * 16 + 5], buffer[i * 16 + 6]);
-                    tmpV1.y = tmpV2.lengthSquared(); // scale y squared
-                    tmpV2.copyFromFloats(buffer[i * 16 + 8], buffer[i * 16 + 9], buffer[i * 16 + 10]);
-                    tmpV1.z = tmpV2.lengthSquared(); // scale z squared
-                    this._thinInstanceStorage.nonUniformScaling = tmpV1.isNonUniformWithinEpsilon(0.0001);
+            this._thinInstanceDataStorage.nonUniformScaling = false;
+
+            if (!this.ignoreNonUniformScaling) {
+                for (let i = 0; i < this._thinInstanceDataStorage.instancesCount && !this._thinInstanceDataStorage.nonUniformScaling; ++i) {
+                    this._thinInstanceDataStorage.nonUniformScaling = isNonUniform(buffer, i);
                 }
 
-                if (this._thinInstanceStorage.nonUniformScaling && !this.nonUniformScaling) {
+                if (this._thinInstanceDataStorage.nonUniformScaling && !this.nonUniformScaling) {
                     this._updateNonUniformScalingState(true);
                 }
-            }*/
+            }
 
             if (!this.doNotSyncBoundingInfo) {
                 this.thinInstanceRefreshBoundingInfo();
             }
         } else {
-            this._thinInstanceStorage.instancesCount = 0;
+            this._thinInstanceDataStorage.instancesCount = 0;
             if (!this.doNotSyncBoundingInfo) {
                 // mesh has no more thin instances, so need to recompute the bounding box because it's the regular mesh that will now be displayed
                 this.refreshBoundingInfo(true);
@@ -109,66 +216,45 @@ Mesh.prototype.thinInstanceSetBuffer = function(kind: string, buffer: Nullable<F
         }
     } else {
         if (buffer === null) {
-            this.removeVerticesData(kind);
-            delete this._thinInstanceStorage.vertexBuffers[kind];
+            if (this._userThinInstanceBuffersStorage?.data[kind]) {
+                this.removeVerticesData(kind);
+                delete this._userThinInstanceBuffersStorage.data[kind];
+                delete this._userThinInstanceBuffersStorage.strides[kind];
+                delete this._userThinInstanceBuffersStorage.sizes[kind];
+                delete this._userThinInstanceBuffersStorage.vertexBuffers[kind];
+            }
         } else {
-            this._thinInstanceStorage.vertexBuffers[kind] = new VertexBuffer(this.getEngine(), buffer, kind, true, false, stride, true);
-            this.setVerticesBuffer(this._thinInstanceStorage.vertexBuffers[kind]!);
-        }
-    }
-};
+            this._thinInstanceInitializeUserStorage();
 
-Mesh.prototype.thinInstanceBufferUpdated = function(kind: string): void {
-    if (this._thinInstanceStorage) {
-        if (kind === "matrix") {
-            this._thinInstanceStorage.matrixBuffer!.updateDirectly(this._thinInstanceStorage.data["matrix"], 0, this._thinInstanceStorage.instancesCount);
-        } else if (this._thinInstanceStorage.vertexBuffers[kind]) {
-            this._thinInstanceStorage.vertexBuffers[kind]!.updateDirectly(this._thinInstanceStorage.data[kind], 0);
-        }
-    }
-};
+            this._userThinInstanceBuffersStorage.data[kind] = buffer;
+            this._userThinInstanceBuffersStorage.strides[kind] = stride;
+            this._userThinInstanceBuffersStorage.sizes[kind] = buffer.length;
+            this._userThinInstanceBuffersStorage.vertexBuffers[kind] = new VertexBuffer(this.getEngine(), buffer, kind, true, false, stride, true);
 
-Mesh.prototype.thinInstanceGetMatrixAt = function(index: number, matrix: Matrix): boolean {
-    if (!this._thinInstanceStorage || index >= this._thinInstanceStorage.instancesCount) {
-        return false;
+            this.setVerticesBuffer(this._userThinInstanceBuffersStorage.vertexBuffers[kind]!);
+        }
     }
-
-    const matrixData = this._thinInstanceStorage.data["matrix"];
-
-    Matrix.FromArrayToRef(matrixData, index * 16, matrix);
-
-    return true;
 };
 
-Mesh.prototype.thinInstanceSetMatrixAt = function(index: number, matrix: Matrix, refresh: boolean = true): boolean {
-    if (!this._thinInstanceStorage || index >= this._thinInstanceStorage.instancesCount) {
-        return false;
-    }
-
-    const matrixData = this._thinInstanceStorage.data["matrix"];
-
-    matrix.copyToArray(matrixData, index * 16);
-
-    if (refresh) {
-        this.thinInstanceBufferUpdated("matrix");
-
-        if (!this.doNotSyncBoundingInfo) {
-            this.thinInstanceRefreshBoundingInfo();
+Mesh.prototype.thinInstanceBufferUpdated = function(kind: string): void {
+    if (kind === "matrix") {
+        if (this._thinInstanceDataStorage.matrixBuffer) {
+            this._thinInstanceDataStorage.matrixBuffer!.updateDirectly(this._thinInstanceDataStorage.matrixData!, 0, this._thinInstanceDataStorage.instancesCount);
         }
+    } else if (this._userThinInstanceBuffersStorage?.vertexBuffers[kind]) {
+        this._userThinInstanceBuffersStorage.vertexBuffers[kind]!.updateDirectly(this._userThinInstanceBuffersStorage.data[kind], 0);
     }
-
-    return true;
 };
 
 Mesh.prototype.thinInstanceRefreshBoundingInfo = function() {
-    if (!this._thinInstanceStorage || !this._thinInstanceStorage.matrixBuffer) {
+    if (!this._thinInstanceDataStorage.matrixData || !this._thinInstanceDataStorage.matrixBuffer) {
         return;
     }
 
     const boundingInfo = this.getBoundingInfo();
-    const matrixData = this._thinInstanceStorage.data["matrix"];
+    const matrixData = this._thinInstanceDataStorage.matrixData;
 
-    const vectors = this._thinInstanceStorage.boundingVectors;
+    const vectors = this._thinInstanceDataStorage.boundingVectors;
 
     if (vectors.length === 0) {
         for (let v = 0; v < boundingInfo.boundingBox.vectors.length; ++v) {
@@ -179,7 +265,7 @@ Mesh.prototype.thinInstanceRefreshBoundingInfo = function() {
     tmpV1.setAll(Number.MAX_VALUE); // min
     tmpV2.setAll(Number.MIN_VALUE); // max
 
-    for (let i = 0; i < this._thinInstanceStorage.instancesCount; ++i) {
+    for (let i = 0; i < this._thinInstanceDataStorage.instancesCount; ++i) {
         Matrix.FromArrayToRef(matrixData, i * 16, tmpMatrix);
 
         for (let v = 0; v < vectors.length; ++v) {
@@ -192,11 +278,73 @@ Mesh.prototype.thinInstanceRefreshBoundingInfo = function() {
     boundingInfo.reConstruct(tmpV1, tmpV2);
 };
 
-Mesh.prototype._disposeThinInstanceSpecificData = function() {
-    if (this._thinInstanceStorage?.matrixBuffer) {
-        this._thinInstanceStorage.matrixBuffer.dispose();
-        this._thinInstanceStorage.matrixBuffer = null;
+Mesh.prototype._thinInstanceUpdateBufferSize = function(kind: string, numInstances: number = 1) {
+    const kindIsMatrix = kind === "matrix";
+
+    if (!kindIsMatrix && (!this._userThinInstanceBuffersStorage || !this._userThinInstanceBuffersStorage.strides[kind])) {
+        return;
+    }
+
+    const stride = kindIsMatrix ? 16 : this._userThinInstanceBuffersStorage.strides[kind];
+    const currentSize = kindIsMatrix ? this._thinInstanceDataStorage.matrixBufferSize : this._userThinInstanceBuffersStorage.sizes[kind];
+    let data = kindIsMatrix ? this._thinInstanceDataStorage.matrixData : this._userThinInstanceBuffersStorage.data[kind];
+
+    const bufferSize = (this._thinInstanceDataStorage.instancesCount + numInstances) * stride;
+
+    let newSize = currentSize;
+
+    while (newSize < bufferSize) {
+        newSize *= 2;
     }
 
-    this._thinInstanceStorage = null;
+    if (!data || currentSize != newSize) {
+        if (!data) {
+            data = new Float32Array(newSize);
+        } else {
+            const newData = new Float32Array(newSize);
+            newData.set(data, 0);
+            data = newData;
+        }
+
+        if (kindIsMatrix) {
+            this._thinInstanceDataStorage.matrixBuffer?.dispose();
+
+            const matrixBuffer = new Buffer(this.getEngine(), data, true, stride, false, true);
+
+            this._thinInstanceDataStorage.matrixBuffer = matrixBuffer;
+            this._thinInstanceDataStorage.matrixData = data;
+            this._thinInstanceDataStorage.matrixBufferSize = newSize;
+
+            this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world0", 0, 4));
+            this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world1", 4, 4));
+            this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world2", 8, 4));
+            this.setVerticesBuffer(matrixBuffer.createVertexBuffer("world3", 12, 4));
+        } else {
+            this._userThinInstanceBuffersStorage.vertexBuffers[kind]?.dispose();
+
+            this._userThinInstanceBuffersStorage.data[kind] = data;
+            this._userThinInstanceBuffersStorage.sizes[kind] = newSize;
+            this._userThinInstanceBuffersStorage.vertexBuffers[kind] = new VertexBuffer(this.getEngine(), data, kind, true, false, stride, true);
+
+            this.setVerticesBuffer(this._userThinInstanceBuffersStorage.vertexBuffers[kind]!);
+        }
+    }
+};
+
+Mesh.prototype._thinInstanceInitializeUserStorage = function() {
+    if (!this._userThinInstanceBuffersStorage) {
+        this._userThinInstanceBuffersStorage = {
+            data: {},
+            sizes: {},
+            vertexBuffers: {},
+            strides: {},
+        };
+    }
+};
+
+Mesh.prototype._disposeThinInstanceSpecificData = function() {
+    if (this._thinInstanceDataStorage?.matrixBuffer) {
+        this._thinInstanceDataStorage.matrixBuffer.dispose();
+        this._thinInstanceDataStorage.matrixBuffer = null;
+    }
 };