Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove instances #94

Merged
merged 5 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/core/InstancedEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
*/
Expand Down
65 changes: 54 additions & 11 deletions src/core/InstancedMesh2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -172,6 +173,8 @@ export class InstancedMesh2<
protected _propertiesGetBase: (obj: unknown) => unknown = null;
protected _propertiesGetMap = new WeakMap<Material, (obj: unknown) => unknown>();
protected _properties = new WeakMap<Material, unknown>();
protected _freeIds: number[] = [];
protected _createEntities: boolean;

// HACK TO MAKE IT WORK WITHOUT UPDATE CORE
/** @internal */ isInstancedMesh = true; // must be set to use instancing rendering
Expand All @@ -190,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.
Expand Down Expand Up @@ -269,12 +270,11 @@ 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);
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 {
Expand Down Expand Up @@ -618,7 +618,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;
}

Expand All @@ -628,7 +628,50 @@ 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;
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;
}

/**
Expand Down Expand Up @@ -740,7 +783,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);
}
Expand Down
28 changes: 18 additions & 10 deletions src/core/feature/Capacity.ts
Original file line number Diff line number Diff line change
@@ -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 {
/**
Expand All @@ -10,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;
}
}

Expand All @@ -41,10 +39,7 @@ InstancedMesh2.prototype.resizeBuffers = function (capacity: number): InstancedM
}
}

this.visibilityArray.length = capacity;
if (capacity > oldCapacity) {
this.visibilityArray.fill(true, oldCapacity);
}
this.availabilityArray.length = capacity * 2;

this.matricesTexture.resize(capacity);

Expand All @@ -69,6 +64,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) {
Expand All @@ -78,6 +85,7 @@ InstancedMesh2.prototype.setInstancesCount = function (count: number): void {
this.resizeBuffers(newCapacity);
}

const start = this._instancesCount;
this._instancesCount = count;
if (this.instances) this.createEntities();
if (this._createEntities) this.createEntities(start);
};
28 changes: 16 additions & 12 deletions src/core/feature/FrustumCulling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,20 @@ const _position = new Vector3();
const _sphere = new Sphere();

InstancedMesh2.prototype.performFrustumCulling = function (camera: Camera, cameraLOD = camera) {
const info = this.LODinfo;
if (!this._parentLOD && this._instancesCount === 0) {
this._count = 0;
return;
}

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;
}
}

Expand Down Expand Up @@ -129,7 +132,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;
}
}
Expand All @@ -142,7 +145,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);
}
Expand All @@ -159,6 +162,7 @@ InstancedMesh2.prototype.BVHCulling = function (camera: Camera) {
this.bvh.frustumCulling(_projScreenMatrix, (node: BVHNode<{}, number>) => {
const index = node.object;

// 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);
Expand Down Expand Up @@ -187,7 +191,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);
Expand Down Expand Up @@ -260,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];
}
};
Expand All @@ -273,6 +276,7 @@ InstancedMesh2.prototype.BVHCullingLOD = function (LODrenderList: LODRenderList,
if (sortObjects) {
this.bvh.frustumCulling(_projScreenMatrix, (node: BVHNode<{}, number>) => {
const index = node.object;
// 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);
Expand Down Expand Up @@ -308,7 +312,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);
Expand Down
Loading
Loading