Skip to content

Commit

Permalink
InstancedMeshBVH: optional get box from boundingSphere (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
agargaro authored Sep 29, 2024
1 parent c74617a commit 676e7f3
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 26 deletions.
16 changes: 7 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 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.1",
"version": "0.2.2",
"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 All @@ -21,7 +21,8 @@
"performance",
"BVH",
"acceleration",
"raycasting"
"raycasting",
"LOD"
],
"scripts": {
"start": "vite",
Expand All @@ -36,7 +37,7 @@
"devDependencies": {
"@eslint/js": "^9.10.0",
"@three.ez/main": "^0.5.7",
"@types/three": "^0.168.0",
"@types/three": "^0.169.0",
"eslint": "^9.9.0",
"typescript": "^5.1.3",
"typescript-eslint": "^8.1.0",
Expand Down
12 changes: 6 additions & 6 deletions src/objects/InstancedMesh2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class InstancedMesh2<
> extends Mesh<TGeometry, TMaterial, TEventMap> {

public override type = 'InstancedMesh2';
public isInstancedMesh2 = true;
public readonly isInstancedMesh2 = true;
public instances: Entity<TCustomData>[];
public instanceIndex: GLInstancedBufferAttribute;
public matricesTexture: DataTexture;
Expand All @@ -65,9 +65,9 @@ export class InstancedMesh2<
public override customDistanceMaterial = new MeshDistanceMaterial();

// HACK TO MAKE IT WORK WITHOUT UPDATE CORE
private isInstancedMesh = true; // must be set to use instancing rendering
private instanceMatrix = new InstancedBufferAttribute(new Float32Array(0), 16); // must be init to avoid exception
private instanceColor = null; // must be null to avoid exception
private readonly isInstancedMesh = true; // must be set to use instancing rendering
private readonly instanceMatrix = new InstancedBufferAttribute(new Float32Array(0), 16); // must be init to avoid exception
private readonly instanceColor = null; // must be null to avoid exception

public get count() { return this._count }
public get maxCount() { return this._maxCount }
Expand Down Expand Up @@ -160,7 +160,7 @@ export class InstancedMesh2<

material.onBeforeCompile = (shader: WebGLProgramParametersWithUniforms, renderer) => {
if (onBeforeCompile) onBeforeCompile(shader, renderer);

if (!shader.instancing) return;

shader.instancing = false;
Expand Down Expand Up @@ -521,7 +521,7 @@ export class InstancedMesh2<
for (let i = 0; i < instancesCount; i++) {
if (!this.getVisibilityAt(i)) continue;

const matrix = this.getMatrixAt(i);
const matrix = this.getMatrixAt(i); // we can optimize this a little avoiding copy? what about using instances if available?
if (geometryCentered) _sphere.center.copy(_position.setFromMatrixPosition(matrix));
else _sphere.center.copy(center).applyMatrix4(matrix);
_sphere.radius = radius * matrix.getMaxScaleOnAxis();
Expand Down
38 changes: 31 additions & 7 deletions src/objects/InstancedMeshBVH.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { box3ToArray, BVH, BVHNode, FloatArray, HybridBuilder, onFrustumIntersectionCallback, onFrustumIntersectionLODCallback, onIntersectionCallback, onIntersectionRayCallback, vec3ToArray, WebGLCoordinateSystem } from 'bvh.js';
import { Box3, Matrix4, Raycaster, Vector3 } from 'three';
import { Box3, Matrix4, Raycaster, Sphere, Vector3 } from 'three';
import { InstancedMesh2 } from './InstancedMesh2.js';
import { InstancedMeshLOD, LODLevel } from './InstancedMeshLOD.js';
import { getSphereFromMatrix, SphereTarget } from '../utils/matrixUtils.js';

export class InstancedMeshBVH {
public target: InstancedMesh2 | InstancedMeshLOD;
Expand All @@ -15,23 +16,33 @@ export class InstancedMeshBVH {
protected _boxArray: FloatArray;
protected _cameraPos: FloatArray;
protected _levels: FloatArray; // TODO improve this
protected _getBoxFromSphere: boolean; // works only if geometry is centered for now
protected _geoBoundingSphere: Sphere = null;
protected _sphereTarget: SphereTarget = null;

constructor(target: InstancedMesh2 | InstancedMeshLOD, margin = 0, highPrecision = false) {
constructor(target: InstancedMesh2 | InstancedMeshLOD, margin = 0, highPrecision = false, getBoxFromSphere = false) {
this._margin = margin;
this.target = target;

const geometry = (target as InstancedMesh2).isInstancedMesh2 ?
(target as InstancedMesh2).geometry :
(target as InstancedMeshLOD).levels[(target as InstancedMeshLOD).levels.length - 1].object.geometry; // TODO improve this
if (!geometry.boundingBox) geometry.computeBoundingBox();

if (!geometry.boundingBox) geometry.computeBoundingBox();
this.geoBoundingBox = geometry.boundingBox;

if (getBoxFromSphere) {
if (!geometry.boundingSphere) geometry.computeBoundingSphere();
this._geoBoundingSphere = geometry.boundingSphere;
this._sphereTarget = { centerX: 0, centerY: 0, centerZ: 0, maxScale: 0 };
}

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);
this._cameraPos = new this._arrayType(3);
this._getBoxFromSphere = getBoxFromSphere;
}

public create(): void {
Expand Down Expand Up @@ -108,7 +119,7 @@ export class InstancedMeshBVH {
if (this._levels?.length !== levels.length) { // TODO improve
this._levels = new this._arrayType(levels.length);
}

const levelsArray = this._levels;
for (let i = 0; i < levels.length; i++) {
levelsArray[i] = levels[i].distance;
Expand Down Expand Up @@ -153,8 +164,21 @@ export class InstancedMeshBVH {
}

protected getBox(id: number, array: FloatArray): FloatArray {
_box3.copy(this.geoBoundingBox).applyMatrix4(this.target.getMatrixAt(id));
box3ToArray(_box3, array);
// TODO add check if geometry is centered. adjust ref using this._matrixArray insteaad of this.target._matrixArray
if (this._getBoxFromSphere) {
const { centerX, centerY, centerZ, maxScale } = getSphereFromMatrix(id, this.target._matrixArray, this._sphereTarget);
const radius = this._geoBoundingSphere.radius * maxScale;
array[0] = centerX - radius;
array[1] = centerX + radius;
array[2] = centerY - radius;
array[3] = centerY + radius;
array[4] = centerZ - radius;
array[5] = centerZ + radius;
} else {
_box3.copy(this.geoBoundingBox).applyMatrix4(this.target.getMatrixAt(id));
box3ToArray(_box3, array);
}

return array;
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/utils/createTexture.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DataTexture, FloatType, RedFormat, RGBAFormat, RGFormat } from "three";
import { DataTexture, FloatType, RedFormat, RedIntegerFormat, RGBAFormat, RGFormat, UnsignedIntType } from "three";

export function createTexture_float(count: number): DataTexture {
const size = Math.ceil(Math.sqrt(count));
Expand All @@ -8,6 +8,14 @@ export function createTexture_float(count: number): DataTexture {
return texture;
}

export function createTexture_uint(count: number): DataTexture {
const size = Math.ceil(Math.sqrt(count));
const array = new Uint32Array(size * size);
const texture = new DataTexture(array, size, size, RedIntegerFormat, UnsignedIntType);
texture.needsUpdate = true;
return texture;
}

export function createTexture_vec2(count: number): DataTexture {
const size = Math.ceil(Math.sqrt(count));
const array = new Float32Array(size * size * 2);
Expand Down
37 changes: 37 additions & 0 deletions src/utils/matrixUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { FloatArray } from "bvh.js";

export interface SphereTarget {
centerX: number;
centerY: number;
centerZ: number;
maxScale: number;
}

// this method works if geometry is centered
export function getSphereFromMatrix(id: number, array: FloatArray, target: SphereTarget): SphereTarget {
const offset = id * 16;

// get max scale from matrix
const m0 = array[offset + 0];
const m1 = array[offset + 1];
const m2 = array[offset + 2];
const m4 = array[offset + 4];
const m5 = array[offset + 5];
const m6 = array[offset + 6];
const m8 = array[offset + 8];
const m9 = array[offset + 9];
const m10 = array[offset + 10];

const scaleXSq = m0 * m0 + m1 * m1 + m2 * m2;
const scaleYSq = m4 * m4 + m5 * m5 + m6 * m6;
const scaleZSq = m8 * m8 + m9 * m9 + m10 * m10;

target.maxScale = Math.sqrt(Math.max(scaleXSq, scaleYSq, scaleZSq));

// get position from matrix
target.centerX = array[offset + 12];
target.centerY = array[offset + 13];
target.centerZ = array[offset + 14];

return target;
}

0 comments on commit 676e7f3

Please sign in to comment.