UNPKG

@three.ez/batched-mesh-extensions

Version:
368 lines (367 loc) 15.5 kB
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 O, Frustum as et, Vector3 as D, Sphere as k, Mesh as nt, Ray as ot, BatchedMesh as g } 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(e, t, 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 = e, this.accurateCulling = n, this._margin = o, this.bvh = new J(new K(), t === 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 e = this.target.instanceCount, t = this.target._instanceInfo.length, o = this.target._instanceInfo, n = new Array(e), r = new Uint32Array(e); let i = 0; this.clear(); for (let c = 0; c < t; 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(e) { const t = this.bvh.insert(e, this.getBox(e, new Float32Array(6)), this._margin); this.nodesMap.set(e, t); } /** * Inserts a range of instances into the BVH. * @param ids An array of ids to insert. */ insertRange(e) { const t = e.length, o = new Array(t); for (let n = 0; n < t; n++) o[n] = this.getBox(e[n], new Float32Array(6)); this.bvh.insertRange(e, 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(e) { const t = this.nodesMap.get(e); t && (this.getBox(e, t.box), this.bvh.move(t, this._margin)); } /** * Deletes an instance from the BVH. * @param id The id of the instance to delete. */ delete(e) { const t = this.nodesMap.get(e); t && (this.bvh.delete(t), this.nodesMap.delete(e)); } /** * 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(e, t) { this._margin > 0 && this.accurateCulling ? this.bvh.frustumCulling(e.elements, (o, n, r) => { n.isIntersectedMargin(o.box, r, this._margin) && t(o); }) : this.bvh.frustumCulling(e.elements, t); } /** * 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(e, t) { const o = e.ray, n = this._origin, r = this._dir; W(o.origin, n), W(o.direction, r), this.bvh.rayIntersections(r, n, t, e.near, e.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(e, t) { const o = this._boxArray; return q(e, o), this.bvh.intersectsBox(o, t); } getBox(e, t) { const o = this.target, n = o._instanceInfo[e].geometryIndex; return o.getBoundingBoxAt(n, V).applyMatrix4(o.getMatrixAt(e, it)), q(V, t), t; } } const V = new U(), it = new O(); class ct { constructor() { this.array = [], this.pool = []; } push(e, t, 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 = e, a.start = o, a.count = n, a.z = t, i.push(a); } reset() { this.array.length = 0; } } function at(s, e = {}) { this.bvh = new rt(this, s, e.margin, e.accurateCulling), this.bvh.create(); } function Tt(s) { const e = { get: (t) => t.zSort, aux: new Array(s.maxInstanceCount), reversed: null }; return function(o) { e.reversed = s.material.transparent, s.maxInstanceCount > e.aux.length && (e.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, e); }; } function ut(s, e) { return s.z - e.z; } function lt(s, e) { return e.z - s.z; } const H = new et(), w = new ct(), j = new O(), T = new O(), E = new D(), P = new D(), R = new D(), ht = new D(), B = new k(); function dt(s, e, t, o, n, r) { this.frustumCulling(t); } function mt(s, e = s) { if (!this._visibilityChanged && !this.perObjectFrustumCulled && !this.sortObjects) return; this._indirectTexture.needsUpdate = !0, this._visibilityChanged = !1; const t = this.sortObjects, o = this.perObjectFrustumCulled; if (!o && !t) { this.updateIndexArray(); return; } if (T.copy(this.matrixWorld).invert(), P.setFromMatrixPosition(s.matrixWorld).applyMatrix4(T), R.setFromMatrixPosition(e.matrixWorld).applyMatrix4(T), E.set(0, 0, -1).transformDirection(s.matrixWorld).transformDirection(T), o ? (j.multiplyMatrices(s.projectionMatrix, s.matrixWorldInverse).multiply(this.matrixWorld), this.bvh ? this.BVHCulling(s, e) : this.linearCulling(s, e)) : this.updateRenderList(), t) { 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, m = this.customSort; m === null ? w.array.sort(this.material.transparent ? lt : ut) : m(w.array); const l = w.array, h = l.length; for (let u = 0; u < h; u++) { const d = l[u]; i[u] = d.start * r, c[u] = d.count, a[u] = d.index; } w.reset(); } } function gt() { if (!this._visibilityChanged) return; const s = this.geometry.getIndex(), e = s === null ? 1 : s.array.BYTES_PER_ELEMENT, t = this._instanceInfo, o = this._geometryInfo, n = this._multiDrawStarts, r = this._multiDrawCounts, i = this._indirectTexture.image.data; let c = 0; for (let a = 0, m = t.length; a < m; a++) { const l = t[a]; if (l.visible && l.active) { const h = l.geometryIndex, u = o[h]; n[c] = u.start * e, r[c] = u.count, i[c] = a, c++; } } this._multiDrawCount = c; } function ft() { const s = this._instanceInfo, e = this._geometryInfo; for (let t = 0, o = s.length; t < o; t++) { const n = s[t]; if (n.visible && n.active) { const r = n.geometryIndex, i = e[r], c = this.getPositionAt(t).sub(P).dot(E); w.push(t, c, i.start, i.count); } } this._multiDrawCount = w.array.length; } function xt(s, e) { const t = this.geometry.getIndex(), o = t === null ? 1 : t.array.BYTES_PER_ELEMENT, n = this._instanceInfo, r = this._geometryInfo, i = this.sortObjects, c = this._multiDrawStarts, a = this._multiDrawCounts, m = this._indirectTexture.image.data, l = this.onFrustumEnter; let h = 0; this.bvh.frustumCulling(j, (u) => { const d = u.object, f = n[d]; if (!f.visible) return; const v = f.geometryIndex, x = r[v], y = x.LOD; let p, I; if (y) { const C = this.getPositionAt(d).distanceToSquared(R), S = this.getLODIndex(y, C); if (l && !l(d, s, e, S)) return; p = y[S].start, I = y[S].count; } else { if (l && !l(d, s)) return; p = x.start, I = x.count; } if (i) { const C = this.getPositionAt(d).sub(P).dot(E); w.push(d, C, p, I); } else c[h] = p * o, a[h] = I, m[h] = d, h++; }), this._multiDrawCount = i ? w.array.length : h; } function yt(s, e) { const t = this.geometry.getIndex(), o = t === null ? 1 : t.array.BYTES_PER_ELEMENT, n = this._instanceInfo, r = this._geometryInfo, i = this.sortObjects, c = this._multiDrawStarts, a = this._multiDrawCounts, m = this._indirectTexture.image.data, l = this.onFrustumEnter; let h = 0; H.setFromProjectionMatrix(j); for (let u = 0, d = n.length; u < d; u++) { const f = n[u]; if (!f.visible || !f.active) continue; const v = f.geometryIndex, x = r[v], y = x.LOD; let p, I; const C = x.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 (y) { const b = B.center.distanceToSquared(R), M = this.getLODIndex(y, b); if (l && !l(u, s, e, M)) continue; p = y[M].start, I = y[M].count; } else { if (l && !l(u, s)) continue; p = x.start, I = x.count; } if (i) { const b = ht.subVectors(B.center, P).dot(E); w.push(u, b, p, I); } else c[h] = p * o, a[h] = I, m[h] = u, h++; } } this._multiDrawCount = i ? w.array.length : h; } const pt = new D(); function It(s, e = pt) { const t = s * 16, o = this._matricesTexture.image.data; return e.x = o[t + 12], e.y = o[t + 13], e.z = o[t + 14], e; } function _t(s, e) { const t = s * 16, o = this._matricesTexture.image.data, n = o[t + 0], r = o[t + 1], i = o[t + 2], c = n * n + r * r + i * i, a = o[t + 4], m = o[t + 5], l = o[t + 6], h = a * a + m * m + l * l, u = o[t + 8], d = o[t + 9], f = o[t + 10], v = u * u + d * d + f * f; return e.x = o[t + 12], e.y = o[t + 13], e.z = o[t + 14], Math.sqrt(Math.max(c, h, v)); } function bt(s, e, t, o) { const n = s * 16, r = this._matricesTexture.image.data, i = r[n + 0], c = r[n + 1], a = r[n + 2], m = r[n + 3], l = r[n + 4], h = r[n + 5], u = r[n + 6], d = r[n + 7], f = r[n + 8], v = r[n + 9], x = r[n + 10], y = r[n + 11], p = r[n + 12], I = r[n + 13], C = r[n + 14], S = r[n + 15], A = e.center, L = t.x, b = t.y, M = t.z, F = 1 / (m * L + d * b + y * M + S); A.x = (i * L + l * b + f * M + p) * F, A.y = (c * L + h * b + v * M + I) * F, A.z = (a * L + u * b + x * M + C) * F; const Z = i * i + c * c + a * a, X = l * l + h * h + u * u, $ = f * f + v * v + x * x; e.radius = o * Math.sqrt(Math.max(Z, X, $)); } function wt(s, e, t, o = 0) { const n = this._geometryInfo[s]; t = t ** 2, n.LOD ?? (n.LOD = [{ start: n.start, count: n.count, distance: 0, hysteresis: 0 }]); const r = n.LOD, i = r[r.length - 1], c = i.start + i.count, a = e.index.count; if (c - n.start + a > n.reservedIndexCount) throw new Error("BatchedMesh LOD: Reserved space request exceeds the maximum buffer size."); r.push({ start: c, count: a, distance: t, hysteresis: o }); const m = e.getIndex().array, l = this.geometry.getIndex(), h = l.array, u = n.vertexStart; for (let d = 0; d < a; d++) h[c + d] = m[d] + u; l.needsUpdate = !0; } function vt(s, e) { for (let t = s.length - 1; t > 0; t--) { const o = s[t], n = o.distance - o.distance * o.hysteresis; if (e >= n) return t; } return 0; } const z = [], _ = new nt(), Ct = new ot(), Y = new D(), G = new D(), N = new O(); function St(s, e) { var i, c; if (!this.material || this.instanceCount === 0) return; _.geometry = this.geometry, _.material = this.material, (i = _.geometry).boundingBox ?? (i.boundingBox = new U()), (c = _.geometry).boundingSphere ?? (c.boundingSphere = new k()); const t = s.ray, o = s.near, n = s.far; N.copy(this.matrixWorld).invert(), G.setFromMatrixScale(this.matrixWorld), Y.copy(s.ray.direction).multiply(G); const r = Y.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, e)); else if (this.boundingSphere === null && this.computeBoundingSphere(), s.ray.intersectsSphere(this.boundingSphere)) for (let a = 0, m = this._instanceInfo.length; a < m; a++) this.checkInstanceIntersection(s, a, e); s.ray = t, s.near = o, s.far = n; } function At(s, e, t) { const o = this._instanceInfo[e]; if (!o.active || !o.visible) return; const n = o.geometryIndex, r = this._geometryInfo[n]; this.getMatrixAt(e, _.matrixWorld), _.geometry.boundsTree = this.boundsTrees ? this.boundsTrees[n] : void 0, _.geometry.boundsTree || (this.getBoundingBoxAt(n, _.geometry.boundingBox), this.getBoundingSphereAt(n, _.geometry.boundingSphere), _.geometry.setDrawRange(r.start, r.count)), _.raycast(s, z); for (const i of z) i.batchId = e, i.object = this, t.push(i); z.length = 0; } function Mt() { g.prototype.computeBVH = at, g.prototype.onBeforeRender = dt, g.prototype.frustumCulling = mt, g.prototype.updateIndexArray = gt, g.prototype.updateRenderList = ft, g.prototype.BVHCulling = xt, g.prototype.linearCulling = yt, g.prototype.getPositionAt = It, g.prototype.getPositionAndMaxScaleOnAxisAt = _t, g.prototype.applyMatrixAtToSphere = bt, g.prototype.addGeometryLOD = wt, g.prototype.getLODIndex = vt, g.prototype.raycast = St, g.prototype.checkInstanceIntersection = At; } function Ot() { Mt(); } function Et(s) { const e = s.material, t = e.onBeforeCompile.bind(e); e.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 );"); } t(o, n); }; } function Pt(s) { let e = 0, t = 0; for (const o of s) e += o.attributes.position.count, t += o.index.count; return { vertexCount: e, indexCount: t }; } function Ft(s) { const e = []; let t = 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, t += i.attributes.position.count; } e.push(r); } return { vertexCount: t, indexCount: o, LODIndexCount: e }; } export { xt as BVHCulling, rt as BatchedMeshBVH, ct as MultiDrawRenderList, wt as addGeometryLOD, bt as applyMatrixAtToSphere, At as checkInstanceIntersection, at as computeBVH, Tt as createRadixSort, Ot as extendBatchedMeshPrototype, mt as frustumCulling, Pt as getBatchedMeshCount, Ft as getBatchedMeshLODCount, vt as getLODIndex, _t as getPositionAndMaxScaleOnAxisAt, It as getPositionAt, yt as linearCulling, dt as onBeforeRender, Et as patchBatchedMeshMaterial, St as raycast, ut as sortOpaque, lt as sortTransparent, gt as updateIndexArray, ft as updateRenderList }; //# sourceMappingURL=webgpu.js.map