Skip to content

Commit

Permalink
Merge branch 'master' into feature/es-lint-basic-config
Browse files Browse the repository at this point in the history
  • Loading branch information
agargaro committed Sep 15, 2024
2 parents 3898016 + 2456b07 commit 0bf82dd
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 69 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ myInstancedMesh.updateInstances((obj, index) => {
myInstancedMesh.computeBVH();
```

This library has only one dependency: `three.js r159+`.
This library has two dependencies:
- `three.js r159+`
- [`bvh.js`](https://github.com/agargaro/BVH.js)

## Live Examples

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@three.ez/instanced-mesh",
"version": "0.2.0",
"version": "0.2.1",
"description": "Simplified and enhanced InstancedMesh with frustum culling, fast raycasting (using BVH), sorting, visibility management and more.",
"author": "Andrea Gargaro <devgargaro@gmail.com>",
"license": "MIT",
Expand Down Expand Up @@ -49,6 +49,6 @@
"three": ">=0.159.0"
},
"dependencies": {
"bvh.js": "^0.0.7"
"bvh.js": "^0.0.8"
}
}
4 changes: 4 additions & 0 deletions src/objects/InstancedEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export class InstancedEntity {
this.owner.composeMatrixInstance(this);
}

public updateMatrixPosition(): void {
this.owner.setPositionMatrixInstance(this);
}

public setUniform(name: string, value: UniformValue): void {
this.owner.setUniformAt(this.id, name, value);
}
Expand Down
77 changes: 44 additions & 33 deletions src/objects/InstancedMesh2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { GLInstancedBufferAttribute } from "./GLInstancedBufferAttribute.js";
import { InstancedEntity, UniformValue, UniformValueNoNumber } from "./InstancedEntity.js";
import { InstancedMeshBVH } from "./InstancedMeshBVH.js";
import { InstancedRenderItem, InstancedRenderList } from "./InstancedRenderList.js";
import { BVHNode } from "bvh.js";

// TODO: Add expand and count/maxCount when create?
// TODO: autoUpdate (send indexes data to gpu only if changes)
Expand Down Expand Up @@ -90,14 +91,13 @@ export class InstancedMesh2<
/** THIS MATERIAL AND GEOMETRY CANNOT BE SHARED */
constructor(renderer: WebGLRenderer, count: number, geometry: TGeometry, material?: TMaterial) {
if (!renderer) throw new Error("'renderer' is mandatory.");
if (!(count > 0)) throw new Error("'count' must be greater than 0.");
if (!count || count < 0) throw new Error("'count' must be greater than 0.");
if (!geometry) throw new Error("'geometry' is mandatory.");

super(geometry, material);

if (this.geometry.getAttribute("instanceIndex")) throw new Error('Cannot reuse already patched geometry.');

// this.frustumCulled = !this.perObjectFrustumCulled;
this.instancesCount = count;
this._maxCount = count;
this._count = count;
Expand Down Expand Up @@ -219,10 +219,8 @@ export class InstancedMesh2<
}

public computeBVH(config: BVHParams = {}): void {
// TODO reuse same BVH
this.bvh = new InstancedMeshBVH(this, config.margin, config.highPrecision);

// TODO check if instances valorized
if (!this.bvh) this.bvh = new InstancedMeshBVH(this, config.margin, config.highPrecision);
this.bvh.clear();
this.bvh.create();
}

Expand Down Expand Up @@ -366,11 +364,25 @@ export class InstancedMesh2<
this.bvh?.move(id);
}

/** @internal */
public setPositionMatrixInstance(entity: InstancedEntity): void {
const position = entity.position;
const te = this._matrixArray;
const id = entity.id;
const offset = id * 16;

te[offset + 12] = position.x;
te[offset + 13] = position.y;
te[offset + 14] = position.z;

this.matricesTexture.needsUpdate = true; // TODO
this.bvh?.move(id);
}

public override raycast(raycaster: Raycaster, result: Intersection[]): void {
if (this.material === undefined) return;

const raycastFrustum = this.raycastOnlyFrustum && this.perObjectFrustumCulled && !this.bvh;
let instancesToCheck: number[] | Uint32Array; // TODO Uint32Array
_mesh.geometry = this.geometry;
_mesh.material = this.material;

Expand All @@ -390,49 +402,47 @@ export class InstancedMesh2<

if (this.bvh) {

instancesToCheck = _instancesIntersected;
this.bvh.raycast(raycaster, _instancesIntersected);
this.bvh.raycast(raycaster, (instanceIndex) => this.checkObjectIntersection(raycaster, instanceIndex, result));
// TODO test with three-mesh-bvh

} else {

if (this.boundingSphere === null) this.computeBoundingSphere();
_sphere.copy(this.boundingSphere);
if (!raycaster.ray.intersectsSphere(_sphere)) return;

instancesToCheck = this.instanceIndex.array as Uint32Array;

}

const instancesCount = this.instancesCount;
const checkCount = raycastFrustum ? this._count : Math.min(instancesToCheck.length, instancesCount);

for (let i = 0; i < checkCount; i++) {
const objectIndex = instancesToCheck[i];
const instancesToCheck = this.instanceIndex.array;
const checkCount = raycastFrustum ? this._count : this.instancesCount;

if (objectIndex > instancesCount || !this.getVisibilityAt(objectIndex)) continue;

this.getMatrixAt(objectIndex, _mesh.matrixWorld);

_mesh.raycast(raycaster, _intersections);

for (const intersect of _intersections) {
intersect.instanceId = objectIndex;
intersect.object = this;
result.push(intersect);
for (let i = 0; i < checkCount; i++) {
this.checkObjectIntersection(raycaster, instancesToCheck[i], result);
}

_intersections.length = 0;
}

_instancesIntersected.length = 0;

result.sort(ascSortIntersection);

raycaster.ray = originalRay;
raycaster.near = originalNear;
raycaster.far = originalFar;
}

protected checkObjectIntersection(raycaster: Raycaster, objectIndex: number, result: Intersection[]): void {
if (objectIndex > this.instancesCount || !this.getVisibilityAt(objectIndex)) return;

this.getMatrixAt(objectIndex, _mesh.matrixWorld);

_mesh.raycast(raycaster, _intersections);

for (const intersect of _intersections) {
intersect.instanceId = objectIndex;
intersect.object = this;
result.push(intersect);
}

_intersections.length = 0;
}

protected frustumCulling(camera: Camera): void {
const sortObjects = this.sortObjects;
const array = this.instanceIndex.array;
Expand Down Expand Up @@ -473,7 +483,9 @@ export class InstancedMesh2<
const sortObjects = this.sortObjects;
let count = 0;

this.bvh.frustumCulling(_projScreenMatrix, (index: number) => {
this.bvh.frustumCulling(_projScreenMatrix, (node: BVHNode<{}, number>) => {
const index = node.object;

if (index < instancesCount && this.getVisibilityAt(index)) {
if (sortObjects) {
_position.setFromMatrixPosition(this.getMatrixAt(index))
Expand Down Expand Up @@ -608,7 +620,6 @@ const _box3 = new Box3();
const _sphere = new Sphere();
const _frustum = new Frustum();
const _projScreenMatrix = new Matrix4();
const _instancesIntersected: number[] = [];
const _intersections: Intersection[] = [];
const _mesh = new Mesh();
const _ray = new Ray();
Expand Down
67 changes: 34 additions & 33 deletions src/objects/InstancedMeshBVH.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { BVH, BVHNode, FloatArray, HybridBuilder, WebGLCoordinateSystem } from 'bvh.js';
import { box3ToArray, BVH, BVHNode, FloatArray, HybridBuilder, onFrustumIntersectionCallback, onIntersectionCallback, onIntersectionRayCallback, vec3ToArray, WebGLCoordinateSystem } from 'bvh.js';
import { Box3, Matrix4, Raycaster } from 'three';
import { InstancedMesh2 } from './InstancedMesh2.js';

export class InstancedMeshBVH {
public target: InstancedMesh2;
public geoBoundingBox: Box3;
public bvh: BVH<unknown, number>;
public map = new Map<number, BVHNode<unknown, number>>();
public bvh: BVH<{}, number>;
public map = new Map<number, BVHNode<{}, number>>();
protected _arrayType: typeof Float32Array | typeof Float64Array;
protected _margin: number;
protected _origin: FloatArray;
protected _dir: FloatArray;
protected _boxArray: FloatArray;

constructor(target: InstancedMesh2, margin = 0, highPrecision = false) {
this._margin = margin;
Expand All @@ -17,6 +20,8 @@ export class InstancedMeshBVH {
this.geoBoundingBox = target.geometry.boundingBox;
this._arrayType = highPrecision ? Float64Array : Float32Array;
this.bvh = new BVH(new HybridBuilder(highPrecision), WebGLCoordinateSystem);
this._origin = new this._arrayType(3);
this._dir = new this._arrayType(3);
}

public create(): void {
Expand Down Expand Up @@ -73,49 +78,45 @@ export class InstancedMeshBVH {
this.map = new Map();
}

public frustumCulling(projScreenMatrix: Matrix4, onFrustumIntersected: (index: number) => void): void {
this.bvh.frustumCulling(projScreenMatrix.elements, (node, frustum, mask) => {
if (frustum.isIntersected(node.box, mask, this._margin)) {
onFrustumIntersected(node.object);
}
});
}
public frustumCulling(projScreenMatrix: Matrix4, onFrustumIntersection: onFrustumIntersectionCallback<{}, number>): void {
if (this._margin > 0) {

this.bvh.frustumCulling(projScreenMatrix.elements, (node, frustum, mask) => {
if (frustum.isIntersectedMargin(node.box, mask, this._margin)) {
onFrustumIntersection(node);
}
});

} else {

this.bvh.frustumCulling(projScreenMatrix.elements, onFrustumIntersection);

public frustumCullingConservative(): void {
throw new Error("Not implemented yet.");
}
}

public raycast(raycaster: Raycaster, result: number[]): void {
public raycast(raycaster: Raycaster, onIntersection: onIntersectionRayCallback<number>): void {
const ray = raycaster.ray;
const origin = this._origin;
const dir = this._dir;

_origin[0] = ray.origin.x;
_origin[1] = ray.origin.y;
_origin[2] = ray.origin.z;
vec3ToArray(ray.origin, origin);
vec3ToArray(ray.direction, dir);

_dir[0] = ray.direction.x;
_dir[1] = ray.direction.y;
_dir[2] = ray.direction.z;
this.bvh.rayIntersections(dir, origin, onIntersection, raycaster.near, raycaster.far);
}

this.bvh.intersectRay(_dir, _origin, raycaster.near, raycaster.far, result);
public intersectBox(target: Box3, onIntersection: onIntersectionCallback<number>): boolean {
if (!this._boxArray) this._boxArray = new this._arrayType(6);
const array = this._boxArray;
box3ToArray(target, array);
return this.bvh.intersectsBox(array, onIntersection);
}

protected getBox(id: number, array: FloatArray): FloatArray {
_box3.copy(this.geoBoundingBox).applyMatrix4(this.target.getMatrixAt(id));

const min = _box3.min;
const max = _box3.max;

array[0] = min.x;
array[1] = max.x;
array[2] = min.y;
array[3] = max.y;
array[4] = min.z;
array[5] = max.z;

box3ToArray(_box3, array);
return array;
}
}

const _origin = new Float64Array(3);
const _dir = new Float64Array(3);
const _box3 = new Box3();

0 comments on commit 0bf82dd

Please sign in to comment.