@three.ez/batched-mesh-extensions
Version:
Utility extension methods for BatchedMesh
368 lines (367 loc) • 15.5 kB
JavaScript
import { BVH as J, HybridBuilder as K, WebGLCoordinateSystem as Q, WebGPUCoordinateSystem as tt, vec3ToArray as W, box3ToArray as q } from "bvh.js";
import { Box3 as U, Matrix4 as E, Frustum as et, Vector3 as D, Sphere as k, Mesh as nt, Ray as ot, BatchedMesh as f } from "three";
import { radixSort as st } from "three/addons/utils/SortUtils.js";
class rt {
/**
* @param target The target `BatchedMesh`.
* @param margin The margin applied for bounding box calculations (default is 0).
* @param accurateCulling Flag to enable accurate frustum culling without considering margin (default is true).
*/
constructor(t, e, o = 0, n = !0) {
this.nodesMap = /* @__PURE__ */ new Map(), this._origin = new Float32Array(3), this._dir = new Float32Array(3), this._cameraPos = new Float32Array(3), this._boxArray = new Float32Array(6), this.target = t, this.accurateCulling = n, this._margin = o, this.bvh = new J(new K(), e === 2e3 ? Q : tt);
}
/**
* Builds the BVH from the target mesh's instances using a top-down construction method.
* This approach is more efficient and accurate compared to incremental methods, which add one instance at a time.
*/
create() {
const t = this.target.instanceCount, e = this.target._instanceInfo.length, o = this.target._instanceInfo, n = new Array(t), r = new Uint32Array(t);
let i = 0;
this.clear();
for (let c = 0; c < e; c++)
o[c].active && (n[i] = this.getBox(c, new Float32Array(6)), r[i] = c, i++);
this.bvh.createFromArray(r, n, (c) => {
this.nodesMap.set(c.object, c);
}, this._margin);
}
/**
* Inserts an instance into the BVH.
* @param id The id of the instance to insert.
*/
insert(t) {
const e = this.bvh.insert(t, this.getBox(t, new Float32Array(6)), this._margin);
this.nodesMap.set(t, e);
}
/**
* Inserts a range of instances into the BVH.
* @param ids An array of ids to insert.
*/
insertRange(t) {
const e = t.length, o = new Array(e);
for (let n = 0; n < e; n++)
o[n] = this.getBox(t[n], new Float32Array(6));
this.bvh.insertRange(t, o, this._margin, (n) => {
this.nodesMap.set(n.object, n);
});
}
/**
* Moves an instance within the BVH.
* @param id The id of the instance to move.
*/
move(t) {
const e = this.nodesMap.get(t);
e && (this.getBox(t, e.box), this.bvh.move(e, this._margin));
}
/**
* Deletes an instance from the BVH.
* @param id The id of the instance to delete.
*/
delete(t) {
const e = this.nodesMap.get(t);
e && (this.bvh.delete(e), this.nodesMap.delete(t));
}
/**
* Clears the BVH.
*/
clear() {
this.bvh.clear(), this.nodesMap.clear();
}
/**
* Performs frustum culling to determine which instances are visible based on the provided projection matrix.
* @param projScreenMatrix The projection screen matrix for frustum culling.
* @param onFrustumIntersection Callback function invoked when an instance intersects the frustum.
*/
frustumCulling(t, e) {
this._margin > 0 && this.accurateCulling ? this.bvh.frustumCulling(t.elements, (o, n, r) => {
n.isIntersectedMargin(o.box, r, this._margin) && e(o);
}) : this.bvh.frustumCulling(t.elements, e);
}
/**
* Performs raycasting to check if a ray intersects any instances.
* @param raycaster The raycaster used for raycasting.
* @param onIntersection Callback function invoked when a ray intersects an instance.
*/
raycast(t, e) {
const o = t.ray, n = this._origin, r = this._dir;
W(o.origin, n), W(o.direction, r), this.bvh.rayIntersections(r, n, e, t.near, t.far);
}
/**
* Checks if a given box intersects with any instance bounding box.
* @param target The target bounding box.
* @param onIntersection Callback function invoked when an intersection occurs.
* @returns `True` if there is an intersection, otherwise `false`.
*/
intersectBox(t, e) {
const o = this._boxArray;
return q(t, o), this.bvh.intersectsBox(o, e);
}
getBox(t, e) {
const o = this.target, n = o._instanceInfo[t].geometryIndex;
return o.getBoundingBoxAt(n, V).applyMatrix4(o.getMatrixAt(t, it)), q(V, e), e;
}
}
const V = new U(), it = new E();
class ct {
constructor() {
this.array = [], this.pool = [];
}
push(t, e, o, n) {
const r = this.pool, i = this.array, c = i.length;
c >= r.length && r.push({ start: null, count: null, z: null, zSort: null, index: null });
const a = r[c];
a.index = t, a.start = o, a.count = n, a.z = e, i.push(a);
}
reset() {
this.array.length = 0;
}
}
function at(s, t = {}) {
this.bvh = new rt(this, s, t.margin, t.accurateCulling), this.bvh.create();
}
function Tt(s) {
const t = {
get: (e) => e.zSort,
aux: new Array(s.maxInstanceCount),
reversed: null
};
return function(o) {
t.reversed = s.material.transparent, s.maxInstanceCount > t.aux.length && (t.aux.length = s.maxInstanceCount);
let n = 1 / 0, r = -1 / 0;
for (const { z: a } of o)
a > r && (r = a), a < n && (n = a);
const i = r - n, c = (2 ** 32 - 1) / i;
for (const a of o)
a.zSort = (a.z - n) * c;
st(o, t);
};
}
function ut(s, t) {
return s.z - t.z;
}
function lt(s, t) {
return t.z - s.z;
}
const H = new et(), w = new ct(), j = new E(), T = new E(), O = new D(), P = new D(), R = new D(), ht = new D(), B = new k();
function mt(s, t, e, o, n, r) {
this.frustumCulling(e);
}
function dt(s, t = s) {
if (!this._visibilityChanged && !this.perObjectFrustumCulled && !this.sortObjects)
return;
this._indirectTexture.needsUpdate = !0, this._visibilityChanged = !1;
const e = this.sortObjects, o = this.perObjectFrustumCulled;
if (!o && !e) {
this.updateIndexArray();
return;
}
if (T.copy(this.matrixWorld).invert(), P.setFromMatrixPosition(s.matrixWorld).applyMatrix4(T), R.setFromMatrixPosition(t.matrixWorld).applyMatrix4(T), O.set(0, 0, -1).transformDirection(s.matrixWorld).transformDirection(T), o ? (j.multiplyMatrices(s.projectionMatrix, s.matrixWorldInverse).multiply(this.matrixWorld), this.bvh ? this.BVHCulling(s, t) : this.linearCulling(s, t)) : this.updateRenderList(), e) {
const n = this.geometry.getIndex(), r = n === null ? 1 : n.array.BYTES_PER_ELEMENT, i = this._multiDrawStarts, c = this._multiDrawCounts, a = this._indirectTexture.image.data, d = this.customSort;
d === null ? w.array.sort(this.material.transparent ? lt : ut) : d(w.array);
const l = w.array, h = l.length;
for (let u = 0; u < h; u++) {
const m = l[u];
i[u] = m.start * r, c[u] = m.count, a[u] = m.index;
}
w.reset();
}
}
function ft() {
if (!this._visibilityChanged) return;
const s = this.geometry.getIndex(), t = s === null ? 1 : s.array.BYTES_PER_ELEMENT, e = this._instanceInfo, o = this._geometryInfo, n = this._multiDrawStarts, r = this._multiDrawCounts, i = this._indirectTexture.image.data;
let c = 0;
for (let a = 0, d = e.length; a < d; a++) {
const l = e[a];
if (l.visible && l.active) {
const h = l.geometryIndex, u = o[h];
n[c] = u.start * t, r[c] = u.count, i[c] = a, c++;
}
}
this._multiDrawCount = c;
}
function gt() {
const s = this._instanceInfo, t = this._geometryInfo;
for (let e = 0, o = s.length; e < o; e++) {
const n = s[e];
if (n.visible && n.active) {
const r = n.geometryIndex, i = t[r], c = this.getPositionAt(e).sub(P).dot(O);
w.push(e, c, i.start, i.count);
}
}
this._multiDrawCount = w.array.length;
}
function yt(s, t) {
const e = this.geometry.getIndex(), o = e === null ? 1 : e.array.BYTES_PER_ELEMENT, n = this._instanceInfo, r = this._geometryInfo, i = this.sortObjects, c = this._multiDrawStarts, a = this._multiDrawCounts, d = this._indirectTexture.image.data, l = this.onFrustumEnter;
let h = 0;
this.bvh.frustumCulling(j, (u) => {
const m = u.object, g = n[m];
if (!g.visible) return;
const v = g.geometryIndex, y = r[v], x = y.LOD;
let p, _;
if (x) {
const C = this.getPositionAt(m).distanceToSquared(R), S = this.getLODIndex(x, C);
if (l && !l(m, s, t, S)) return;
p = x[S].start, _ = x[S].count;
} else {
if (l && !l(m, s)) return;
p = y.start, _ = y.count;
}
if (i) {
const C = this.getPositionAt(m).sub(P).dot(O);
w.push(m, C, p, _);
} else
c[h] = p * o, a[h] = _, d[h] = m, h++;
}), this._multiDrawCount = i ? w.array.length : h;
}
function xt(s, t) {
const e = this.geometry.getIndex(), o = e === null ? 1 : e.array.BYTES_PER_ELEMENT, n = this._instanceInfo, r = this._geometryInfo, i = this.sortObjects, c = this._multiDrawStarts, a = this._multiDrawCounts, d = this._indirectTexture.image.data, l = this.onFrustumEnter;
let h = 0;
H.setFromProjectionMatrix(j);
for (let u = 0, m = n.length; u < m; u++) {
const g = n[u];
if (!g.visible || !g.active) continue;
const v = g.geometryIndex, y = r[v], x = y.LOD;
let p, _;
const C = y.boundingSphere, S = C.radius, A = C.center;
if (A.x === 0 && A.y === 0 && A.z === 0) {
const b = this.getPositionAndMaxScaleOnAxisAt(u, B.center);
B.radius = S * b;
} else
this.applyMatrixAtToSphere(u, B, A, S);
if (H.intersectsSphere(B)) {
if (x) {
const b = B.center.distanceToSquared(R), M = this.getLODIndex(x, b);
if (l && !l(u, s, t, M)) continue;
p = x[M].start, _ = x[M].count;
} else {
if (l && !l(u, s)) continue;
p = y.start, _ = y.count;
}
if (i) {
const b = ht.subVectors(B.center, P).dot(O);
w.push(u, b, p, _);
} else
c[h] = p * o, a[h] = _, d[h] = u, h++;
}
}
this._multiDrawCount = i ? w.array.length : h;
}
const pt = new D();
function _t(s, t = pt) {
const e = s * 16, o = this._matricesTexture.image.data;
return t.x = o[e + 12], t.y = o[e + 13], t.z = o[e + 14], t;
}
function It(s, t) {
const e = s * 16, o = this._matricesTexture.image.data, n = o[e + 0], r = o[e + 1], i = o[e + 2], c = n * n + r * r + i * i, a = o[e + 4], d = o[e + 5], l = o[e + 6], h = a * a + d * d + l * l, u = o[e + 8], m = o[e + 9], g = o[e + 10], v = u * u + m * m + g * g;
return t.x = o[e + 12], t.y = o[e + 13], t.z = o[e + 14], Math.sqrt(Math.max(c, h, v));
}
function bt(s, t, e, o) {
const n = s * 16, r = this._matricesTexture.image.data, i = r[n + 0], c = r[n + 1], a = r[n + 2], d = r[n + 3], l = r[n + 4], h = r[n + 5], u = r[n + 6], m = r[n + 7], g = r[n + 8], v = r[n + 9], y = r[n + 10], x = r[n + 11], p = r[n + 12], _ = r[n + 13], C = r[n + 14], S = r[n + 15], A = t.center, L = e.x, b = e.y, M = e.z, F = 1 / (d * L + m * b + x * M + S);
A.x = (i * L + l * b + g * M + p) * F, A.y = (c * L + h * b + v * M + _) * F, A.z = (a * L + u * b + y * M + C) * F;
const Z = i * i + c * c + a * a, X = l * l + h * h + u * u, $ = g * g + v * v + y * y;
t.radius = o * Math.sqrt(Math.max(Z, X, $));
}
function wt(s, t, e, o = 0) {
const n = this._geometryInfo[s], r = t.isBufferGeometry ? t.index.array : t;
e = e ** 2, n.LOD ?? (n.LOD = [{ start: n.start, count: n.count, distance: 0, hysteresis: 0 }]);
const i = n.LOD, c = i[i.length - 1], a = c.start + c.count, d = r.length;
if (a - n.start + d > n.reservedIndexCount)
throw new Error("BatchedMesh LOD: Reserved space request exceeds the maximum buffer size.");
i.push({ start: a, count: d, distance: e, hysteresis: o });
const l = this.geometry.getIndex(), h = l.array, u = n.vertexStart;
for (let m = 0; m < d; m++)
h[a + m] = r[m] + u;
l.needsUpdate = !0;
}
function vt(s, t) {
for (let e = s.length - 1; e > 0; e--) {
const o = s[e], n = o.distance - o.distance * o.hysteresis;
if (t >= n) return e;
}
return 0;
}
const z = [], I = new nt(), Ct = new ot(), G = new D(), Y = new D(), N = new E();
function St(s, t) {
var i, c;
if (!this.material || this.instanceCount === 0) return;
I.geometry = this.geometry, I.material = this.material, (i = I.geometry).boundingBox ?? (i.boundingBox = new U()), (c = I.geometry).boundingSphere ?? (c.boundingSphere = new k());
const e = s.ray, o = s.near, n = s.far;
N.copy(this.matrixWorld).invert(), Y.setFromMatrixScale(this.matrixWorld), G.copy(s.ray.direction).multiply(Y);
const r = G.length();
if (s.ray = Ct.copy(s.ray).applyMatrix4(N), s.near /= r, s.far /= r, this.bvh)
this.bvh.raycast(s, (a) => this.checkInstanceIntersection(s, a, t));
else if (this.boundingSphere === null && this.computeBoundingSphere(), s.ray.intersectsSphere(this.boundingSphere))
for (let a = 0, d = this._instanceInfo.length; a < d; a++)
this.checkInstanceIntersection(s, a, t);
s.ray = e, s.near = o, s.far = n;
}
function At(s, t, e) {
const o = this._instanceInfo[t];
if (!o.active || !o.visible) return;
const n = o.geometryIndex, r = this._geometryInfo[n];
this.getMatrixAt(t, I.matrixWorld), I.geometry.boundsTree = this.boundsTrees ? this.boundsTrees[n] : void 0, I.geometry.boundsTree || (this.getBoundingBoxAt(n, I.geometry.boundingBox), this.getBoundingSphereAt(n, I.geometry.boundingSphere), I.geometry.setDrawRange(r.start, r.count)), I.raycast(s, z);
for (const i of z)
i.batchId = t, i.object = this, e.push(i);
z.length = 0;
}
function Mt() {
f.prototype.computeBVH = at, f.prototype.onBeforeRender = mt, f.prototype.frustumCulling = dt, f.prototype.updateIndexArray = ft, f.prototype.updateRenderList = gt, f.prototype.BVHCulling = yt, f.prototype.linearCulling = xt, f.prototype.getPositionAt = _t, f.prototype.getPositionAndMaxScaleOnAxisAt = It, f.prototype.applyMatrixAtToSphere = bt, f.prototype.addGeometryLOD = wt, f.prototype.getLODIndex = vt, f.prototype.raycast = St, f.prototype.checkInstanceIntersection = At;
}
function Et() {
Mt();
}
function Ot(s) {
const t = s.material, e = t.onBeforeCompile.bind(t);
t.onBeforeCompile = (o, n) => {
if (s.uniformsTexture) {
o.uniforms.uniformsTexture = { value: s.uniformsTexture };
const { vertex: r, fragment: i } = s.uniformsTexture.getUniformsGLSL("uniformsTexture", "batchIndex", "float");
o.vertexShader = o.vertexShader.replace("void main() {", r), o.fragmentShader = o.fragmentShader.replace("void main() {", i), o.vertexShader = o.vertexShader.replace("void main() {", "void main() { float batchIndex = getIndirectIndex( gl_DrawID );");
}
e(o, n);
};
}
function Pt(s) {
let t = 0, e = 0;
for (const o of s)
t += o.attributes.position.count, e += o.index.count;
return { vertexCount: t, indexCount: e };
}
function Ft(s) {
const t = [];
let e = 0, o = 0;
for (const n of s) {
let r = 0;
for (const i of n) {
const c = i.index.count;
o += c, r += c, e += i.attributes.position.count;
}
t.push(r);
}
return { vertexCount: e, indexCount: o, LODIndexCount: t };
}
export {
yt as BVHCulling,
rt as BatchedMeshBVH,
ct as MultiDrawRenderList,
wt as addGeometryLOD,
bt as applyMatrixAtToSphere,
At as checkInstanceIntersection,
at as computeBVH,
Tt as createRadixSort,
Et as extendBatchedMeshPrototype,
dt as frustumCulling,
Pt as getBatchedMeshCount,
Ft as getBatchedMeshLODCount,
vt as getLODIndex,
It as getPositionAndMaxScaleOnAxisAt,
_t as getPositionAt,
xt as linearCulling,
mt as onBeforeRender,
Ot as patchBatchedMeshMaterial,
St as raycast,
ut as sortOpaque,
lt as sortTransparent,
ft as updateIndexArray,
gt as updateRenderList
};
//# sourceMappingURL=webgpu.js.map