From 6fb1cef490e980b49a25677e63583094eaeab523 Mon Sep 17 00:00:00 2001 From: ANDREA GARGARO Date: Mon, 13 Jan 2025 22:50:32 +0100 Subject: [PATCH 1/5] Introduce active state --- src/core/InstancedEntity.ts | 8 +++++- src/core/InstancedMesh2.ts | 40 ++++++++++++++++++++++++++---- src/core/feature/Capacity.ts | 6 ++--- src/core/feature/FrustumCulling.ts | 14 +++++------ src/core/feature/Instances.ts | 12 +++++++-- src/core/feature/Raycasting.ts | 2 +- 6 files changed, 63 insertions(+), 19 deletions(-) diff --git a/src/core/InstancedEntity.ts b/src/core/InstancedEntity.ts index da6963f..bf5c921 100644 --- a/src/core/InstancedEntity.ts +++ b/src/core/InstancedEntity.ts @@ -41,11 +41,17 @@ export class InstancedEntity { public rotation: Euler; /** - * The visibility state set and got from `owner.visibilityArray`. + * The visibility state set and got from `owner.availabilityArray`. */ public get visible(): boolean { return this.owner.getVisibilityAt(this.id); } public set visible(value: boolean) { this.owner.setVisibilityAt(this.id, value); } + /** + * The availability set and got from `owner.availabilityArray`. + */ + public get active(): boolean { return this.owner.getActiveAt(this.id); } + public set active(value: boolean) { this.owner.setActiveAt(this.id, value); } + /** * Color set and got from `owner.colorsTexture`. */ diff --git a/src/core/InstancedMesh2.ts b/src/core/InstancedMesh2.ts index 554a99b..58d6612 100644 --- a/src/core/InstancedMesh2.ts +++ b/src/core/InstancedMesh2.ts @@ -121,9 +121,10 @@ export class InstancedMesh2< */ public raycastOnlyFrustum = false; /** - * Array storing visibility for instances. + * Array storing visibility and availability for instances. + * [visible0, active0, visible1, active1, ...] */ - public visibilityArray: boolean[]; + public readonly availabilityArray: boolean[]; /** * Contains data for managing LOD, allowing different levels of detail for rendering and shadow casting. */ @@ -269,7 +270,7 @@ export class InstancedMesh2< this.material = material; this._allowsEuler = allowsEuler ?? false; this._tempInstance = new InstancedEntity(this, -1, allowsEuler); - this.visibilityArray = LOD?.visibilityArray ?? new Array(capacity).fill(true); + this.availabilityArray = LOD?.availabilityArray ?? new Array(capacity * 2).fill(true); this.initIndexAttribute(); this.initMatricesTexture(); @@ -618,7 +619,7 @@ export class InstancedMesh2< * @param visible Whether the instance should be visible. */ public setVisibilityAt(id: number, visible: boolean): void { - this.visibilityArray[id] = visible; + this.availabilityArray[id * 2] = visible; this._indexArrayNeedsUpdate = true; } @@ -628,7 +629,36 @@ export class InstancedMesh2< * @returns Whether the instance is visible. */ public getVisibilityAt(id: number): boolean { - return this.visibilityArray[id]; + return this.availabilityArray[id * 2]; + } + + /** + * Sets the availability of a specific instance. + * @param id The index of the instance. + * @param active Whether the instance is active (not deleted). + */ + public setActiveAt(id: number, active: boolean): void { + this.availabilityArray[id * 2 + 1] = active; + this._indexArrayNeedsUpdate = true; + } + + /** + * Gets the availability of a specific instance. + * @param id The index of the instance. + * @returns Whether the instance is active (not deleted). + */ + public getActiveAt(id: number): boolean { + return this.availabilityArray[id * 2 + 1]; + } + + /** + * Indicates if a specific instance is visible and active. + * @param id The index of the instance. + * @returns Whether the instance is visible and active. + */ + public getActiveAndVisibilityAt(id: number): boolean { + const offset = id * 2; + return this.availabilityArray[offset] && this.availabilityArray[offset + 1]; } /** diff --git a/src/core/feature/Capacity.ts b/src/core/feature/Capacity.ts index e4e8a09..02586be 100644 --- a/src/core/feature/Capacity.ts +++ b/src/core/feature/Capacity.ts @@ -14,7 +14,7 @@ declare module '../InstancedMesh2.js' { * Sets the number of instances to render and resizes buffers if necessary. * @param count The desired number of instances. */ - setInstancesCount(count: number): void; + setInstancesCount(count: number): void; // TODO reduceBuffer: boolean } } @@ -41,9 +41,9 @@ InstancedMesh2.prototype.resizeBuffers = function (capacity: number): InstancedM } } - this.visibilityArray.length = capacity; + this.availabilityArray.length = capacity * 2; if (capacity > oldCapacity) { - this.visibilityArray.fill(true, oldCapacity); + this.availabilityArray.fill(true, oldCapacity * 2); } this.matricesTexture.resize(capacity); diff --git a/src/core/feature/FrustumCulling.ts b/src/core/feature/FrustumCulling.ts index 7b36f33..841e0ab 100644 --- a/src/core/feature/FrustumCulling.ts +++ b/src/core/feature/FrustumCulling.ts @@ -129,7 +129,7 @@ InstancedMesh2.prototype.updateIndexArray = function () { let count = 0; for (let i = 0; i < instancesCount; i++) { - if (this.getVisibilityAt(i)) { + if (this.getActiveAndVisibilityAt(i)) { array[count++] = i; } } @@ -142,7 +142,7 @@ InstancedMesh2.prototype.updateRenderList = function () { const instancesCount = this._instancesCount; for (let i = 0; i < instancesCount; i++) { - if (this.getVisibilityAt(i)) { + if (this.getActiveAndVisibilityAt(i)) { const depth = this.getPositionAt(i).sub(_cameraPos).dot(_forward); _renderList.push(depth, i); } @@ -159,7 +159,7 @@ InstancedMesh2.prototype.BVHCulling = function (camera: Camera) { this.bvh.frustumCulling(_projScreenMatrix, (node: BVHNode<{}, number>) => { const index = node.object; - if (index < instancesCount && this.getVisibilityAt(index) && (!onFrustumEnter || onFrustumEnter(index, camera))) { + if (index < instancesCount && this.getActiveAndVisibilityAt(index) && (!onFrustumEnter || onFrustumEnter(index, camera))) { if (sortObjects) { const depth = this.getPositionAt(index).sub(_cameraPos).dot(_forward); _renderList.push(depth, index); @@ -187,7 +187,7 @@ InstancedMesh2.prototype.linearCulling = function (camera: Camera) { _frustum.setFromProjectionMatrix(_projScreenMatrix); for (let i = 0; i < instancesCount; i++) { - if (!this.getVisibilityAt(i)) continue; + if (!this.getActiveAndVisibilityAt(i)) continue; if (geometryCentered) { const maxScale = this.getPositionAndMaxScaleOnAxisAt(i, _sphere.center); @@ -273,7 +273,7 @@ InstancedMesh2.prototype.BVHCullingLOD = function (LODrenderList: LODRenderList, if (sortObjects) { this.bvh.frustumCulling(_projScreenMatrix, (node: BVHNode<{}, number>) => { const index = node.object; - if (index < instancesCount && this.getVisibilityAt(index) && (!onFrustumEnter || onFrustumEnter(index, camera, cameraLOD))) { + if (index < instancesCount && this.getActiveAndVisibilityAt(index) && (!onFrustumEnter || onFrustumEnter(index, camera, cameraLOD))) { const distance = this.getPositionAt(index).distanceToSquared(_cameraLODPos); _renderList.push(distance, index); } @@ -281,7 +281,7 @@ InstancedMesh2.prototype.BVHCullingLOD = function (LODrenderList: LODRenderList, } else { this.bvh.frustumCullingLOD(_projScreenMatrix, _cameraLODPos, levels, (node: BVHNode<{}, number>, level: number) => { const index = node.object; - if (index < instancesCount && this.getVisibilityAt(index)) { + if (index < instancesCount && this.getActiveAndVisibilityAt(index)) { if (level === null) { const distance = this.getPositionAt(index).distanceToSquared(_cameraLODPos); // distance can be get by BVH, but is not the distance from center level = this.getObjectLODIndexForDistance(levels, distance); @@ -308,7 +308,7 @@ InstancedMesh2.prototype.linearCullingLOD = function (LODrenderList: LODRenderLi _frustum.setFromProjectionMatrix(_projScreenMatrix); for (let i = 0; i < instancesCount; i++) { - if (!this.getVisibilityAt(i)) continue; + if (!this.getActiveAndVisibilityAt(i)) continue; if (geometryCentered) { const maxScale = this.getPositionAndMaxScaleOnAxisAt(i, _sphere.center); diff --git a/src/core/feature/Instances.ts b/src/core/feature/Instances.ts index e29c6cf..2a2726c 100644 --- a/src/core/feature/Instances.ts +++ b/src/core/feature/Instances.ts @@ -1,8 +1,6 @@ import { InstancedEntity } from '../InstancedEntity.js'; import { InstancedMesh2 } from '../InstancedMesh2.js'; -// TODO: removeInstances(count: number): void; - /** * Represents an extended entity type with custom data. */ @@ -38,6 +36,16 @@ declare module '../InstancedMesh2.js' { * @returns The current `InstancedMesh2` instance. */ addInstances(count: number, onCreation?: UpdateEntityCallback>): this; + /** + * TODO + * @param ids + * @returns + */ + removeInstances(ids: number[]): number[]; // TODO also addInstances should return indexes? + /** + * TODO + */ + optimize(): void; /** @internal */ clearInstance(instance: InstancedEntity, index: number): InstancedEntity; /** @internal */ createEntities(start?: number, count?: number): this; } diff --git a/src/core/feature/Raycasting.ts b/src/core/feature/Raycasting.ts index 865484a..994c4b9 100644 --- a/src/core/feature/Raycasting.ts +++ b/src/core/feature/Raycasting.ts @@ -61,7 +61,7 @@ InstancedMesh2.prototype.raycast = function (raycaster: Raycaster, result: Inter }; InstancedMesh2.prototype.checkObjectIntersection = function (raycaster: Raycaster, objectIndex: number, result: Intersection[]): void { - if (objectIndex > this._instancesCount || !this.getVisibilityAt(objectIndex)) return; + if (objectIndex > this._instancesCount || !this.getActiveAndVisibilityAt(objectIndex)) return; this.getMatrixAt(objectIndex, _mesh.matrixWorld); From 9aad7444959fa40ce09efecdd9f8a22af7595511 Mon Sep 17 00:00:00 2001 From: ANDREA GARGARO Date: Tue, 14 Jan 2025 22:44:31 +0100 Subject: [PATCH 2/5] wip --- src/core/InstancedMesh2.ts | 1 + src/core/feature/Capacity.ts | 2 +- src/core/feature/FrustumCulling.ts | 8 +++--- src/core/feature/Instances.ts | 41 ++++++++++++++++++++++-------- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/core/InstancedMesh2.ts b/src/core/InstancedMesh2.ts index 58d6612..b99c376 100644 --- a/src/core/InstancedMesh2.ts +++ b/src/core/InstancedMesh2.ts @@ -173,6 +173,7 @@ export class InstancedMesh2< protected _propertiesGetBase: (obj: unknown) => unknown = null; protected _propertiesGetMap = new WeakMap unknown>(); protected _properties = new WeakMap(); + protected _freeIds: number[] = []; // HACK TO MAKE IT WORK WITHOUT UPDATE CORE /** @internal */ isInstancedMesh = true; // must be set to use instancing rendering diff --git a/src/core/feature/Capacity.ts b/src/core/feature/Capacity.ts index 02586be..4d33713 100644 --- a/src/core/feature/Capacity.ts +++ b/src/core/feature/Capacity.ts @@ -79,5 +79,5 @@ InstancedMesh2.prototype.setInstancesCount = function (count: number): void { } this._instancesCount = count; - if (this.instances) this.createEntities(); + if (this.instances) this.createEntities(); // TODO check se è giusto, perchè sembra partire da 0 }; diff --git a/src/core/feature/FrustumCulling.ts b/src/core/feature/FrustumCulling.ts index 841e0ab..d7fdf22 100644 --- a/src/core/feature/FrustumCulling.ts +++ b/src/core/feature/FrustumCulling.ts @@ -159,7 +159,8 @@ InstancedMesh2.prototype.BVHCulling = function (camera: Camera) { this.bvh.frustumCulling(_projScreenMatrix, (node: BVHNode<{}, number>) => { const index = node.object; - if (index < instancesCount && this.getActiveAndVisibilityAt(index) && (!onFrustumEnter || onFrustumEnter(index, camera))) { + // we don't check if active because we remove inactive instances from BVH + if (index < instancesCount && this.getVisibilityAt(index) && (!onFrustumEnter || onFrustumEnter(index, camera))) { if (sortObjects) { const depth = this.getPositionAt(index).sub(_cameraPos).dot(_forward); _renderList.push(depth, index); @@ -273,7 +274,8 @@ InstancedMesh2.prototype.BVHCullingLOD = function (LODrenderList: LODRenderList, if (sortObjects) { this.bvh.frustumCulling(_projScreenMatrix, (node: BVHNode<{}, number>) => { const index = node.object; - if (index < instancesCount && this.getActiveAndVisibilityAt(index) && (!onFrustumEnter || onFrustumEnter(index, camera, cameraLOD))) { + // we don't check if active because we remove inactive instances from BVH + if (index < instancesCount && this.getVisibilityAt(index) && (!onFrustumEnter || onFrustumEnter(index, camera, cameraLOD))) { const distance = this.getPositionAt(index).distanceToSquared(_cameraLODPos); _renderList.push(distance, index); } @@ -281,7 +283,7 @@ InstancedMesh2.prototype.BVHCullingLOD = function (LODrenderList: LODRenderList, } else { this.bvh.frustumCullingLOD(_projScreenMatrix, _cameraLODPos, levels, (node: BVHNode<{}, number>, level: number) => { const index = node.object; - if (index < instancesCount && this.getActiveAndVisibilityAt(index)) { + if (index < instancesCount && this.getVisibilityAt(index)) { if (level === null) { const distance = this.getPositionAt(index).distanceToSquared(_cameraLODPos); // distance can be get by BVH, but is not the distance from center level = this.getObjectLODIndexForDistance(levels, distance); diff --git a/src/core/feature/Instances.ts b/src/core/feature/Instances.ts index 2a2726c..a070dc8 100644 --- a/src/core/feature/Instances.ts +++ b/src/core/feature/Instances.ts @@ -1,6 +1,9 @@ import { InstancedEntity } from '../InstancedEntity.js'; import { InstancedMesh2 } from '../InstancedMesh2.js'; +// TODO: optimize method to fill 'holes'. +// TODO: should setInstances delete instances from BVH? if yes, change frustum culling check too + /** * Represents an extended entity type with custom data. */ @@ -13,18 +16,18 @@ export type UpdateEntityCallback = (obj: Entity, index: declare module '../InstancedMesh2.js' { interface InstancedMesh2 { /** - * Updates instances by applying a callback function to each instance. + * Updates instances by applying a callback function to each instance. It calls `updateMatrix` for each instance. * @param onUpdate A callback function to update each entity. - * @param start The starting index of the instances to update. Defaults to 0. + * @param start The starting index of the instances to update. Defaults to `0`. * @param count The number of instances to update. Defaults to the total instance count. * @returns The current `InstancedMesh2` instance. */ updateInstances(onUpdate: UpdateEntityCallback>, start?: number, count?: number): this; /** - * Updates instances position by applying a callback function to each instance. + * Updates instances position by applying a callback function to each instance. It calls `updateMatrixPosition` for each instance. * This method updates only the position attributes of the matrix. * @param onUpdate A callback function to update each entity. - * @param start The starting index of the instances to update. Defaults to 0. + * @param start The starting index of the instances to update. Defaults to `0`. * @param count The number of instances to update. Defaults to the total instance count. * @returns The current `InstancedMesh2` instance. */ @@ -38,14 +41,10 @@ declare module '../InstancedMesh2.js' { addInstances(count: number, onCreation?: UpdateEntityCallback>): this; /** * TODO - * @param ids - * @returns - */ - removeInstances(ids: number[]): number[]; // TODO also addInstances should return indexes? - /** - * TODO + * @param ids TODO + * @returns The current `InstancedMesh2` instance. */ - optimize(): void; + removeInstances(ids: number[]): this; /** @internal */ clearInstance(instance: InstancedEntity, index: number): InstancedEntity; /** @internal */ createEntities(start?: number, count?: number): this; } @@ -104,6 +103,7 @@ InstancedMesh2.prototype.createEntities = function (this: InstancedMesh2, start }; InstancedMesh2.prototype.addInstances = function (count: number, onCreation?: UpdateEntityCallback): InstancedMesh2 { + // TODO handle freeIds const start = this._instancesCount; const end = start + count; const bvh = this.bvh; @@ -121,3 +121,22 @@ InstancedMesh2.prototype.addInstances = function (count: number, onCreation?: Up return this; }; + +InstancedMesh2.prototype.removeInstances = function (ids: number[]): InstancedMesh2 { + const freeIds = this._freeIds; + const bvh = this.bvh; + + for (const id of ids) { + this.setActiveAt(id, false); + freeIds.push(id); + bvh?.delete(id); + } + + for (let i = this._instancesCount - 1; i >= 0; i--) { + if (this.getActiveAt(i)) break; + this._instancesCount--; + // il buffer però avrà ancora l'istanza, attenzione ai conflitti + } + + return this; +}; From 46d8a1a5426fb669422557d1a0fd041b0b7d6850 Mon Sep 17 00:00:00 2001 From: ANDREA GARGARO Date: Wed, 15 Jan 2025 14:02:22 +0100 Subject: [PATCH 3/5] wip --- src/core/InstancedMesh2.ts | 26 +++++++-- src/core/feature/Capacity.ts | 22 ++++++-- src/core/feature/FrustumCulling.ts | 5 ++ src/core/feature/Instances.ts | 88 +++++++++++++++++++++--------- src/core/feature/Raycasting.ts | 2 +- 5 files changed, 104 insertions(+), 39 deletions(-) diff --git a/src/core/InstancedMesh2.ts b/src/core/InstancedMesh2.ts index b99c376..9ea154d 100644 --- a/src/core/InstancedMesh2.ts +++ b/src/core/InstancedMesh2.ts @@ -72,7 +72,7 @@ export class InstancedMesh2< /** * Attribute storing indices of the instances to be rendered. */ - public instanceIndex: GLInstancedBufferAttribute; + public instanceIndex: GLInstancedBufferAttribute = null; /** * Texture storing matrices for instances. */ @@ -174,6 +174,7 @@ export class InstancedMesh2< protected _propertiesGetMap = new WeakMap unknown>(); protected _properties = new WeakMap(); protected _freeIds: number[] = []; + protected _createEntities: boolean; // HACK TO MAKE IT WORK WITHOUT UPDATE CORE /** @internal */ isInstancedMesh = true; // must be set to use instancing rendering @@ -271,12 +272,11 @@ export class InstancedMesh2< this.material = material; this._allowsEuler = allowsEuler ?? false; this._tempInstance = new InstancedEntity(this, -1, allowsEuler); - this.availabilityArray = LOD?.availabilityArray ?? new Array(capacity * 2).fill(true); + this.availabilityArray = LOD?.availabilityArray ?? new Array(capacity * 2); + this._createEntities = createEntities; this.initIndexAttribute(); this.initMatricesTexture(); - - if (createEntities) this.createEntities(); } public override onBeforeShadow(renderer: WebGLRenderer, scene: Scene, camera: Camera, shadowCamera: Camera, geometry: BufferGeometry, depthMaterial: Material, group: any): void { @@ -659,7 +659,21 @@ export class InstancedMesh2< */ public getActiveAndVisibilityAt(id: number): boolean { const offset = id * 2; - return this.availabilityArray[offset] && this.availabilityArray[offset + 1]; + const availabilityArray = this.availabilityArray; + return availabilityArray[offset] && availabilityArray[offset + 1]; + } + + /** + * Set if a specific instance is visible and active. + * @param id The index of the instance. + * @param value Whether the instance is active and active (not deleted). + */ + public setActiveAndVisibilityAt(id: number, value: boolean): void { + const offset = id * 2; + const availabilityArray = this.availabilityArray; + availabilityArray[offset] = value; + availabilityArray[offset + 1] = value; + this._indexArrayNeedsUpdate = true; } /** @@ -771,7 +785,7 @@ export class InstancedMesh2< capacity: this._capacity, renderer: this._renderer, allowsEuler: this._allowsEuler, - createEntities: !!this.instances + createEntities: this._createEntities }; return new (this as any).constructor(this.geometry, this.material, params).copy(this, recursive); } diff --git a/src/core/feature/Capacity.ts b/src/core/feature/Capacity.ts index 4d33713..179dafe 100644 --- a/src/core/feature/Capacity.ts +++ b/src/core/feature/Capacity.ts @@ -1,6 +1,8 @@ import { DataTexture, FloatType, RedFormat } from 'three'; import { InstancedMesh2 } from '../InstancedMesh2.js'; +// TODO: add optimize method to reduce buffer size and remove instances objects + declare module '../InstancedMesh2.js' { interface InstancedMesh2 { /** @@ -14,7 +16,7 @@ declare module '../InstancedMesh2.js' { * Sets the number of instances to render and resizes buffers if necessary. * @param count The desired number of instances. */ - setInstancesCount(count: number): void; // TODO reduceBuffer: boolean + setInstancesCount(count: number): void; } } @@ -42,9 +44,6 @@ InstancedMesh2.prototype.resizeBuffers = function (capacity: number): InstancedM } this.availabilityArray.length = capacity * 2; - if (capacity > oldCapacity) { - this.availabilityArray.fill(true, oldCapacity * 2); - } this.matricesTexture.resize(capacity); @@ -69,6 +68,18 @@ InstancedMesh2.prototype.resizeBuffers = function (capacity: number): InstancedM }; InstancedMesh2.prototype.setInstancesCount = function (count: number): void { + if (count < this._instancesCount) { + const bvh = this.bvh; + if (bvh) { + for (let i = this._instancesCount - 1; i >= count; i--) { + bvh.delete(i); + } + } + + this._instancesCount = count; + return; + } + if (count > this._capacity) { let newCapacity = this._capacity + (this._capacity >> 1) + 512; while (newCapacity < count) { @@ -78,6 +89,7 @@ InstancedMesh2.prototype.setInstancesCount = function (count: number): void { this.resizeBuffers(newCapacity); } + const start = this._instancesCount; this._instancesCount = count; - if (this.instances) this.createEntities(); // TODO check se è giusto, perchè sembra partire da 0 + if (this._createEntities) this.createEntities(start); }; diff --git a/src/core/feature/FrustumCulling.ts b/src/core/feature/FrustumCulling.ts index d7fdf22..e4c719f 100644 --- a/src/core/feature/FrustumCulling.ts +++ b/src/core/feature/FrustumCulling.ts @@ -54,6 +54,11 @@ const _position = new Vector3(); const _sphere = new Sphere(); InstancedMesh2.prototype.performFrustumCulling = function (camera: Camera, cameraLOD = camera) { + if (this._instancesCount === 0) { + this._count = 0; + return; + } + const info = this.LODinfo; const isShadowRendering = camera !== cameraLOD; let LODrenderList: LODRenderList; diff --git a/src/core/feature/Instances.ts b/src/core/feature/Instances.ts index a070dc8..00cc028 100644 --- a/src/core/feature/Instances.ts +++ b/src/core/feature/Instances.ts @@ -38,19 +38,25 @@ declare module '../InstancedMesh2.js' { * @param onCreation A callback function to initialize each new entity. * @returns The current `InstancedMesh2` instance. */ - addInstances(count: number, onCreation?: UpdateEntityCallback>): this; + addInstances(count: number, onCreation: UpdateEntityCallback>): this; /** * TODO * @param ids TODO * @returns The current `InstancedMesh2` instance. */ removeInstances(ids: number[]): this; + /** + * TODO + * @returns The current `InstancedMesh2` instance. + */ + clearInstances(): this; /** @internal */ clearInstance(instance: InstancedEntity, index: number): InstancedEntity; - /** @internal */ createEntities(start?: number, count?: number): this; + /** @internal */ createEntities(start: number): this; + /** @internal */ addInstance(id: number, onCreation: UpdateEntityCallback): void; } } -InstancedMesh2.prototype.clearInstance = function (instance: InstancedEntity, index: number): InstancedEntity { +InstancedMesh2.prototype.clearInstance = function (instance: InstancedEntity, index: number) { (instance as any).id = index; instance.position.set(0, 0, 0); instance.scale.set(1, 1, 1); @@ -59,7 +65,7 @@ InstancedMesh2.prototype.clearInstance = function (instance: InstancedEntity, in return instance; }; -InstancedMesh2.prototype.updateInstances = function (this: InstancedMesh2, onUpdate?: UpdateEntityCallback, start = 0, count = this._instancesCount): InstancedMesh2 { +InstancedMesh2.prototype.updateInstances = function (this: InstancedMesh2, onUpdate?: UpdateEntityCallback, start = 0, count = this._instancesCount) { const end = start + count; const instances = this.instances; const tempInstance = this._tempInstance; @@ -73,7 +79,7 @@ InstancedMesh2.prototype.updateInstances = function (this: InstancedMesh2, onUpd return this; }; -InstancedMesh2.prototype.updateInstancesPosition = function (this: InstancedMesh2, onUpdate?: UpdateEntityCallback, start = 0, count = this._instancesCount): InstancedMesh2 { +InstancedMesh2.prototype.updateInstancesPosition = function (this: InstancedMesh2, onUpdate?: UpdateEntityCallback, start = 0, count = this._instancesCount) { const end = start + count; const instances = this.instances; const tempInstance = this._tempInstance; @@ -87,56 +93,84 @@ InstancedMesh2.prototype.updateInstancesPosition = function (this: InstancedMesh return this; }; -InstancedMesh2.prototype.createEntities = function (this: InstancedMesh2, start = 0, count = this._instancesCount): InstancedMesh2 { - const end = start + count; +InstancedMesh2.prototype.createEntities = function (this: InstancedMesh2, start: number) { + const end = this._instancesCount; - if (!this.instances) this.instances = new Array(count); - else this.instances.length = end; + if (!this.instances) { + this.instances = new Array(end); + } else if (this.instances.length < end) { + this.instances.length = end; + } else { + return this; + } + // we can also revert this for and put 'break' instead of 'continue' but no it's memory consecutive const instances = this.instances; for (let i = start; i < end; i++) { - const instance = new InstancedEntity(this, i, this._allowsEuler); - instances[i] = instance; + if (instances[i]) continue; + instances[i] = new InstancedEntity(this, i, this._allowsEuler); } return this; }; -InstancedMesh2.prototype.addInstances = function (count: number, onCreation?: UpdateEntityCallback): InstancedMesh2 { - // TODO handle freeIds +InstancedMesh2.prototype.addInstances = function (count: number, onCreation: UpdateEntityCallback) { + const freeIds = this._freeIds; + if (freeIds.length > 0) { + const freeIdsUsed = Math.min(freeIds.length, count); + const freeidsEnd = freeIds.length - freeIdsUsed; + + for (let i = freeIds.length - 1; i >= freeidsEnd; i--) { + this.addInstance(i, onCreation); + } + + freeIds.length -= freeIdsUsed; + count -= freeIdsUsed; + if (count === 0) return this; + } + const start = this._instancesCount; const end = start + count; - const bvh = this.bvh; - this.setInstancesCount(this._instancesCount + count); - if (onCreation) { - for (let i = start; i < end; i++) { - const instance = this.instances ? this.instances[i] : this.clearInstance(this._tempInstance, i); - onCreation(instance, i); - instance.updateMatrix(); - bvh?.insert(i); - } + for (let i = start; i < end; i++) { + this.addInstance(i, onCreation); } return this; }; -InstancedMesh2.prototype.removeInstances = function (ids: number[]): InstancedMesh2 { +InstancedMesh2.prototype.addInstance = function (id: number, onCreation: UpdateEntityCallback) { + this.setActiveAndVisibilityAt(id, true); + const instance = this.instances ? this.instances[id] : this.clearInstance(this._tempInstance, id); + onCreation(instance, id); + instance.updateMatrix(); + this.bvh?.insert(id); +}; + +InstancedMesh2.prototype.removeInstances = function (ids: number[]) { const freeIds = this._freeIds; const bvh = this.bvh; for (const id of ids) { - this.setActiveAt(id, false); - freeIds.push(id); - bvh?.delete(id); + if (id < this._instancesCount && this.getActiveAt(id)) { + this.setActiveAt(id, false); + freeIds.push(id); + bvh?.delete(id); + } } for (let i = this._instancesCount - 1; i >= 0; i--) { if (this.getActiveAt(i)) break; this._instancesCount--; - // il buffer però avrà ancora l'istanza, attenzione ai conflitti } return this; }; + +InstancedMesh2.prototype.clearInstances = function () { + this._instancesCount = 0; + this._freeIds.length = 0; + this.bvh?.clear(); + return this; +}; diff --git a/src/core/feature/Raycasting.ts b/src/core/feature/Raycasting.ts index 994c4b9..d5cf237 100644 --- a/src/core/feature/Raycasting.ts +++ b/src/core/feature/Raycasting.ts @@ -17,7 +17,7 @@ const _invMatrixWorld = new Matrix4(); const _sphere = new Sphere(); InstancedMesh2.prototype.raycast = function (raycaster: Raycaster, result: Intersection[]): void { - if (this._parentLOD || this.material === undefined || this.instanceIndex === undefined) return; + if (this._parentLOD || !this.material || this._instancesCount === 0 || !this.instanceIndex) return; const raycastFrustum = this.raycastOnlyFrustum && this._perObjectFrustumCulled && !this.bvh; _mesh.geometry = this._geometry; From 5f23e1a9503c6939c4b9fe2289e2163d378a0434 Mon Sep 17 00:00:00 2001 From: ANDREA GARGARO Date: Wed, 15 Jan 2025 18:44:50 +0100 Subject: [PATCH 4/5] wip --- src/core/InstancedMesh2.ts | 2 -- src/core/feature/Capacity.ts | 6 +----- src/core/feature/FrustumCulling.ts | 15 ++++++--------- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/core/InstancedMesh2.ts b/src/core/InstancedMesh2.ts index 9ea154d..b4ffdd3 100644 --- a/src/core/InstancedMesh2.ts +++ b/src/core/InstancedMesh2.ts @@ -193,10 +193,8 @@ export class InstancedMesh2< /** * The number of active instances. - * If a number greater than the `capacity` is set, the `capacity` will be increased automatically. */ public get instancesCount(): number { return this._instancesCount; } - public set instancesCount(value: number) { this.setInstancesCount(value); } /** * Determines if per-instance frustum culling is enabled. diff --git a/src/core/feature/Capacity.ts b/src/core/feature/Capacity.ts index 179dafe..1daf72c 100644 --- a/src/core/feature/Capacity.ts +++ b/src/core/feature/Capacity.ts @@ -12,11 +12,7 @@ declare module '../InstancedMesh2.js' { * @returns The current `InstancedMesh2` instance. */ resizeBuffers(capacity: number): this; - /** - * Sets the number of instances to render and resizes buffers if necessary. - * @param count The desired number of instances. - */ - setInstancesCount(count: number): void; + /** @internal */ setInstancesCount(count: number): void; } } diff --git a/src/core/feature/FrustumCulling.ts b/src/core/feature/FrustumCulling.ts index e4c719f..af84c05 100644 --- a/src/core/feature/FrustumCulling.ts +++ b/src/core/feature/FrustumCulling.ts @@ -54,22 +54,20 @@ const _position = new Vector3(); const _sphere = new Sphere(); InstancedMesh2.prototype.performFrustumCulling = function (camera: Camera, cameraLOD = camera) { - if (this._instancesCount === 0) { + if (!this._parentLOD && this._instancesCount === 0) { this._count = 0; return; } - const info = this.LODinfo; + const LODinfo = this.LODinfo; const isShadowRendering = camera !== cameraLOD; let LODrenderList: LODRenderList; - if (info) { - LODrenderList = !isShadowRendering ? info.render : (info.shadowRender ?? info.render); + if (LODinfo) { + LODrenderList = !isShadowRendering ? LODinfo.render : (LODinfo.shadowRender ?? LODinfo.render); - // Hide all LODs except this one. They will be shown after frustum culling if at least one instance is visible. - for (const object of info.objects) { - if (object === this) object._count = 0; - else object.visible = false; + for (const object of LODinfo.objects) { + object._count = 0; } } @@ -266,7 +264,6 @@ InstancedMesh2.prototype.frustumCullingLOD = function (LODrenderList: LODRenderL for (let i = 0; i < levels.length; i++) { const object = levels[i].object; - object.visible = object === this || count[i] > 0; object._count = count[i]; } }; From 42e898fb00e1dfd886ba57dc683b3e3425b206de Mon Sep 17 00:00:00 2001 From: ANDREA GARGARO Date: Wed, 15 Jan 2025 19:18:13 +0100 Subject: [PATCH 5/5] fix --- src/core/feature/Instances.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/core/feature/Instances.ts b/src/core/feature/Instances.ts index 00cc028..2869b0d 100644 --- a/src/core/feature/Instances.ts +++ b/src/core/feature/Instances.ts @@ -44,7 +44,7 @@ declare module '../InstancedMesh2.js' { * @param ids TODO * @returns The current `InstancedMesh2` instance. */ - removeInstances(ids: number[]): this; + removeInstances(...ids: number[]): this; /** * TODO * @returns The current `InstancedMesh2` instance. @@ -117,16 +117,19 @@ InstancedMesh2.prototype.createEntities = function (this: InstancedMesh2, start: InstancedMesh2.prototype.addInstances = function (count: number, onCreation: UpdateEntityCallback) { const freeIds = this._freeIds; if (freeIds.length > 0) { + let maxId = -1; const freeIdsUsed = Math.min(freeIds.length, count); const freeidsEnd = freeIds.length - freeIdsUsed; for (let i = freeIds.length - 1; i >= freeidsEnd; i--) { - this.addInstance(i, onCreation); + const id = freeIds[i]; + if (id > maxId) maxId = id; + this.addInstance(id, onCreation); } freeIds.length -= freeIdsUsed; count -= freeIdsUsed; - if (count === 0) return this; + this._instancesCount = Math.max(maxId + 1, this._instancesCount); } const start = this._instancesCount; @@ -148,7 +151,7 @@ InstancedMesh2.prototype.addInstance = function (id: number, onCreation: UpdateE this.bvh?.insert(id); }; -InstancedMesh2.prototype.removeInstances = function (ids: number[]) { +InstancedMesh2.prototype.removeInstances = function (...ids: number[]) { const freeIds = this._freeIds; const bvh = this.bvh;