UNPKG

@threepipe/plugin-path-tracing

Version:

Path tracing plugin interfaces for Threepipe

1,481 lines 343 kB
/** * @license * @threepipe/plugin-path-tracing v0.1.2 * Copyright 2022-2025 repalash <palash@shaders.app> * Apache-2.0 License * See ./dependencies.txt for any bundled third-party dependencies and licenses. */ import { BufferAttribute, Vector3, Plane, Line3, Vector2, Triangle, Sphere, Matrix4, Box3, BackSide, DoubleSide, REVISION, FrontSide, UnsignedIntType, DataTexture, NearestFilter, IntType, FloatType, UnsignedByteType, UnsignedShortType, ByteType, ShortType, RGBAFormat, RGBAIntegerFormat, RGIntegerFormat, RedIntegerFormat, RGFormat, RedFormat, BufferGeometry, Matrix3, Vector4, MeshBasicMaterial, Mesh, ShaderMaterial, NoBlending, WebGLRenderTarget, FullScreenQuad, PerspectiveCamera, DataUtils, HalfFloatType, LinearFilter, RepeatWrapping, ClampToEdgeWrapping, Source, Quaternion, DataArrayTexture, WebGLArrayRenderTarget, Color, NoToneMapping, NormalBlending, EquirectangularReflectionMapping, Spherical, LinearMipMapLinearFilter, Clock, Scene, AdditiveBlending, uiNumber, uiVector, uiToggle, serialize, uiSlider, uiButton, uiFolderContainer, Euler, onChange, uiConfig, AViewerPluginSync, ProgressivePlugin, NoColorSpace } from "threepipe"; const CENTER = 0; const AVERAGE = 1; const SAH = 2; const CONTAINED = 2; const TRIANGLE_INTERSECT_COST = 1.25; const TRAVERSAL_COST = 1; const BYTES_PER_NODE = 6 * 4 + 4 + 4; const IS_LEAFNODE_FLAG = 65535; const FLOAT32_EPSILON = Math.pow(2, -24); const SKIP_GENERATION = Symbol("SKIP_GENERATION"); function getVertexCount(geo) { return geo.index ? geo.index.count : geo.attributes.position.count; } function getTriCount(geo) { return getVertexCount(geo) / 3; } function getIndexArray(vertexCount, BufferConstructor = ArrayBuffer) { if (vertexCount > 65535) { return new Uint32Array(new BufferConstructor(4 * vertexCount)); } else { return new Uint16Array(new BufferConstructor(2 * vertexCount)); } } function ensureIndex(geo, options) { if (!geo.index) { const vertexCount = geo.attributes.position.count; const BufferConstructor = options.useSharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer; const index = getIndexArray(vertexCount, BufferConstructor); geo.setIndex(new BufferAttribute(index, 1)); for (let i = 0; i < vertexCount; i++) { index[i] = i; } } } function getFullGeometryRange(geo, range) { const triCount = getTriCount(geo); const drawRange = range ? range : geo.drawRange; const start = drawRange.start / 3; const end = (drawRange.start + drawRange.count) / 3; const offset = Math.max(0, start); const count = Math.min(triCount, end) - offset; return [{ offset: Math.floor(offset), count: Math.floor(count) }]; } function getRootIndexRanges(geo, range) { if (!geo.groups || !geo.groups.length) { return getFullGeometryRange(geo, range); } const ranges = []; const rangeBoundaries = /* @__PURE__ */ new Set(); const drawRange = range ? range : geo.drawRange; const drawRangeStart = drawRange.start / 3; const drawRangeEnd = (drawRange.start + drawRange.count) / 3; for (const group of geo.groups) { const groupStart = group.start / 3; const groupEnd = (group.start + group.count) / 3; rangeBoundaries.add(Math.max(drawRangeStart, groupStart)); rangeBoundaries.add(Math.min(drawRangeEnd, groupEnd)); } const sortedBoundaries = Array.from(rangeBoundaries.values()).sort((a, b) => a - b); for (let i = 0; i < sortedBoundaries.length - 1; i++) { const start = sortedBoundaries[i]; const end = sortedBoundaries[i + 1]; ranges.push({ offset: Math.floor(start), count: Math.floor(end - start) }); } return ranges; } function hasGroupGaps(geometry, range) { const vertexCount = getTriCount(geometry); const groups = getRootIndexRanges(geometry, range).sort((a, b) => a.offset - b.offset); const finalGroup = groups[groups.length - 1]; finalGroup.count = Math.min(vertexCount - finalGroup.offset, finalGroup.count); let total = 0; groups.forEach(({ count }) => total += count); return vertexCount !== total; } function getBounds(triangleBounds, offset, count, target2, centroidTarget) { let minx = Infinity; let miny = Infinity; let minz = Infinity; let maxx = -Infinity; let maxy = -Infinity; let maxz = -Infinity; let cminx = Infinity; let cminy = Infinity; let cminz = Infinity; let cmaxx = -Infinity; let cmaxy = -Infinity; let cmaxz = -Infinity; for (let i = offset * 6, end = (offset + count) * 6; i < end; i += 6) { const cx = triangleBounds[i + 0]; const hx = triangleBounds[i + 1]; const lx = cx - hx; const rx = cx + hx; if (lx < minx) minx = lx; if (rx > maxx) maxx = rx; if (cx < cminx) cminx = cx; if (cx > cmaxx) cmaxx = cx; const cy = triangleBounds[i + 2]; const hy = triangleBounds[i + 3]; const ly = cy - hy; const ry = cy + hy; if (ly < miny) miny = ly; if (ry > maxy) maxy = ry; if (cy < cminy) cminy = cy; if (cy > cmaxy) cmaxy = cy; const cz = triangleBounds[i + 4]; const hz = triangleBounds[i + 5]; const lz = cz - hz; const rz = cz + hz; if (lz < minz) minz = lz; if (rz > maxz) maxz = rz; if (cz < cminz) cminz = cz; if (cz > cmaxz) cmaxz = cz; } target2[0] = minx; target2[1] = miny; target2[2] = minz; target2[3] = maxx; target2[4] = maxy; target2[5] = maxz; centroidTarget[0] = cminx; centroidTarget[1] = cminy; centroidTarget[2] = cminz; centroidTarget[3] = cmaxx; centroidTarget[4] = cmaxy; centroidTarget[5] = cmaxz; } function computeTriangleBounds(geo, target2 = null, offset = null, count = null) { const posAttr = geo.attributes.position; const index = geo.index ? geo.index.array : null; const triCount = getTriCount(geo); const normalized = posAttr.normalized; let triangleBounds; if (target2 === null) { triangleBounds = new Float32Array(triCount * 6); offset = 0; count = triCount; } else { triangleBounds = target2; offset = offset || 0; count = count || triCount; } const posArr = posAttr.array; const bufferOffset = posAttr.offset || 0; let stride = 3; if (posAttr.isInterleavedBufferAttribute) { stride = posAttr.data.stride; } const getters = ["getX", "getY", "getZ"]; for (let tri = offset; tri < offset + count; tri++) { const tri3 = tri * 3; const tri6 = tri * 6; let ai = tri3 + 0; let bi = tri3 + 1; let ci = tri3 + 2; if (index) { ai = index[ai]; bi = index[bi]; ci = index[ci]; } if (!normalized) { ai = ai * stride + bufferOffset; bi = bi * stride + bufferOffset; ci = ci * stride + bufferOffset; } for (let el = 0; el < 3; el++) { let a, b, c; if (normalized) { a = posAttr[getters[el]](ai); b = posAttr[getters[el]](bi); c = posAttr[getters[el]](ci); } else { a = posArr[ai + el]; b = posArr[bi + el]; c = posArr[ci + el]; } let min = a; if (b < min) min = b; if (c < min) min = c; let max = a; if (b > max) max = b; if (c > max) max = c; const halfExtents = (max - min) / 2; const el2 = el * 2; triangleBounds[tri6 + el2 + 0] = min + halfExtents; triangleBounds[tri6 + el2 + 1] = halfExtents + (Math.abs(min) + halfExtents) * FLOAT32_EPSILON; } } return triangleBounds; } function arrayToBox(nodeIndex32, array, target2) { target2.min.x = array[nodeIndex32]; target2.min.y = array[nodeIndex32 + 1]; target2.min.z = array[nodeIndex32 + 2]; target2.max.x = array[nodeIndex32 + 3]; target2.max.y = array[nodeIndex32 + 4]; target2.max.z = array[nodeIndex32 + 5]; return target2; } function getLongestEdgeIndex(bounds) { let splitDimIdx = -1; let splitDist = -Infinity; for (let i = 0; i < 3; i++) { const dist = bounds[i + 3] - bounds[i]; if (dist > splitDist) { splitDist = dist; splitDimIdx = i; } } return splitDimIdx; } function copyBounds(source, target2) { target2.set(source); } function unionBounds(a, b, target2) { let aVal, bVal; for (let d = 0; d < 3; d++) { const d3 = d + 3; aVal = a[d]; bVal = b[d]; target2[d] = aVal < bVal ? aVal : bVal; aVal = a[d3]; bVal = b[d3]; target2[d3] = aVal > bVal ? aVal : bVal; } } function expandByTriangleBounds(startIndex, triangleBounds, bounds) { for (let d = 0; d < 3; d++) { const tCenter = triangleBounds[startIndex + 2 * d]; const tHalf = triangleBounds[startIndex + 2 * d + 1]; const tMin = tCenter - tHalf; const tMax = tCenter + tHalf; if (tMin < bounds[d]) { bounds[d] = tMin; } if (tMax > bounds[d + 3]) { bounds[d + 3] = tMax; } } } function computeSurfaceArea(bounds) { const d0 = bounds[3] - bounds[0]; const d1 = bounds[4] - bounds[1]; const d2 = bounds[5] - bounds[2]; return 2 * (d0 * d1 + d1 * d2 + d2 * d0); } const BIN_COUNT = 32; const binsSort = (a, b) => a.candidate - b.candidate; const sahBins = new Array(BIN_COUNT).fill().map(() => { return { count: 0, bounds: new Float32Array(6), rightCacheBounds: new Float32Array(6), leftCacheBounds: new Float32Array(6), candidate: 0 }; }); const leftBounds = new Float32Array(6); function getOptimalSplit(nodeBoundingData, centroidBoundingData, triangleBounds, offset, count, strategy) { let axis = -1; let pos = 0; if (strategy === CENTER) { axis = getLongestEdgeIndex(centroidBoundingData); if (axis !== -1) { pos = (centroidBoundingData[axis] + centroidBoundingData[axis + 3]) / 2; } } else if (strategy === AVERAGE) { axis = getLongestEdgeIndex(nodeBoundingData); if (axis !== -1) { pos = getAverage(triangleBounds, offset, count, axis); } } else if (strategy === SAH) { const rootSurfaceArea = computeSurfaceArea(nodeBoundingData); let bestCost = TRIANGLE_INTERSECT_COST * count; const cStart = offset * 6; const cEnd = (offset + count) * 6; for (let a = 0; a < 3; a++) { const axisLeft = centroidBoundingData[a]; const axisRight = centroidBoundingData[a + 3]; const axisLength = axisRight - axisLeft; const binWidth = axisLength / BIN_COUNT; if (count < BIN_COUNT / 4) { const truncatedBins = [...sahBins]; truncatedBins.length = count; let b = 0; for (let c = cStart; c < cEnd; c += 6, b++) { const bin = truncatedBins[b]; bin.candidate = triangleBounds[c + 2 * a]; bin.count = 0; const { bounds, leftCacheBounds, rightCacheBounds } = bin; for (let d = 0; d < 3; d++) { rightCacheBounds[d] = Infinity; rightCacheBounds[d + 3] = -Infinity; leftCacheBounds[d] = Infinity; leftCacheBounds[d + 3] = -Infinity; bounds[d] = Infinity; bounds[d + 3] = -Infinity; } expandByTriangleBounds(c, triangleBounds, bounds); } truncatedBins.sort(binsSort); let splitCount = count; for (let bi = 0; bi < splitCount; bi++) { const bin = truncatedBins[bi]; while (bi + 1 < splitCount && truncatedBins[bi + 1].candidate === bin.candidate) { truncatedBins.splice(bi + 1, 1); splitCount--; } } for (let c = cStart; c < cEnd; c += 6) { const center = triangleBounds[c + 2 * a]; for (let bi = 0; bi < splitCount; bi++) { const bin = truncatedBins[bi]; if (center >= bin.candidate) { expandByTriangleBounds(c, triangleBounds, bin.rightCacheBounds); } else { expandByTriangleBounds(c, triangleBounds, bin.leftCacheBounds); bin.count++; } } } for (let bi = 0; bi < splitCount; bi++) { const bin = truncatedBins[bi]; const leftCount = bin.count; const rightCount = count - bin.count; const leftBounds2 = bin.leftCacheBounds; const rightBounds = bin.rightCacheBounds; let leftProb = 0; if (leftCount !== 0) { leftProb = computeSurfaceArea(leftBounds2) / rootSurfaceArea; } let rightProb = 0; if (rightCount !== 0) { rightProb = computeSurfaceArea(rightBounds) / rootSurfaceArea; } const cost = TRAVERSAL_COST + TRIANGLE_INTERSECT_COST * (leftProb * leftCount + rightProb * rightCount); if (cost < bestCost) { axis = a; bestCost = cost; pos = bin.candidate; } } } else { for (let i = 0; i < BIN_COUNT; i++) { const bin = sahBins[i]; bin.count = 0; bin.candidate = axisLeft + binWidth + i * binWidth; const bounds = bin.bounds; for (let d = 0; d < 3; d++) { bounds[d] = Infinity; bounds[d + 3] = -Infinity; } } for (let c = cStart; c < cEnd; c += 6) { const triCenter = triangleBounds[c + 2 * a]; const relativeCenter = triCenter - axisLeft; let binIndex = ~~(relativeCenter / binWidth); if (binIndex >= BIN_COUNT) binIndex = BIN_COUNT - 1; const bin = sahBins[binIndex]; bin.count++; expandByTriangleBounds(c, triangleBounds, bin.bounds); } const lastBin = sahBins[BIN_COUNT - 1]; copyBounds(lastBin.bounds, lastBin.rightCacheBounds); for (let i = BIN_COUNT - 2; i >= 0; i--) { const bin = sahBins[i]; const nextBin = sahBins[i + 1]; unionBounds(bin.bounds, nextBin.rightCacheBounds, bin.rightCacheBounds); } let leftCount = 0; for (let i = 0; i < BIN_COUNT - 1; i++) { const bin = sahBins[i]; const binCount = bin.count; const bounds = bin.bounds; const nextBin = sahBins[i + 1]; const rightBounds = nextBin.rightCacheBounds; if (binCount !== 0) { if (leftCount === 0) { copyBounds(bounds, leftBounds); } else { unionBounds(bounds, leftBounds, leftBounds); } } leftCount += binCount; let leftProb = 0; let rightProb = 0; if (leftCount !== 0) { leftProb = computeSurfaceArea(leftBounds) / rootSurfaceArea; } const rightCount = count - leftCount; if (rightCount !== 0) { rightProb = computeSurfaceArea(rightBounds) / rootSurfaceArea; } const cost = TRAVERSAL_COST + TRIANGLE_INTERSECT_COST * (leftProb * leftCount + rightProb * rightCount); if (cost < bestCost) { axis = a; bestCost = cost; pos = bin.candidate; } } } } } else { console.warn(`MeshBVH: Invalid build strategy value ${strategy} used.`); } return { axis, pos }; } function getAverage(triangleBounds, offset, count, axis) { let avg = 0; for (let i = offset, end = offset + count; i < end; i++) { avg += triangleBounds[i * 6 + axis * 2]; } return avg / count; } class MeshBVHNode { constructor() { this.boundingData = new Float32Array(6); } } function partition(indirectBuffer, index, triangleBounds, offset, count, split) { let left = offset; let right = offset + count - 1; const pos = split.pos; const axisOffset = split.axis * 2; while (true) { while (left <= right && triangleBounds[left * 6 + axisOffset] < pos) { left++; } while (left <= right && triangleBounds[right * 6 + axisOffset] >= pos) { right--; } if (left < right) { for (let i = 0; i < 3; i++) { let t0 = index[left * 3 + i]; index[left * 3 + i] = index[right * 3 + i]; index[right * 3 + i] = t0; } for (let i = 0; i < 6; i++) { let tb = triangleBounds[left * 6 + i]; triangleBounds[left * 6 + i] = triangleBounds[right * 6 + i]; triangleBounds[right * 6 + i] = tb; } left++; right--; } else { return left; } } } function partition_indirect(indirectBuffer, index, triangleBounds, offset, count, split) { let left = offset; let right = offset + count - 1; const pos = split.pos; const axisOffset = split.axis * 2; while (true) { while (left <= right && triangleBounds[left * 6 + axisOffset] < pos) { left++; } while (left <= right && triangleBounds[right * 6 + axisOffset] >= pos) { right--; } if (left < right) { let t = indirectBuffer[left]; indirectBuffer[left] = indirectBuffer[right]; indirectBuffer[right] = t; for (let i = 0; i < 6; i++) { let tb = triangleBounds[left * 6 + i]; triangleBounds[left * 6 + i] = triangleBounds[right * 6 + i]; triangleBounds[right * 6 + i] = tb; } left++; right--; } else { return left; } } } function IS_LEAF(n16, uint16Array2) { return uint16Array2[n16 + 15] === 65535; } function OFFSET(n32, uint32Array2) { return uint32Array2[n32 + 6]; } function COUNT(n16, uint16Array2) { return uint16Array2[n16 + 14]; } function LEFT_NODE(n32) { return n32 + 8; } function RIGHT_NODE(n32, uint32Array2) { return uint32Array2[n32 + 6]; } function SPLIT_AXIS(n32, uint32Array2) { return uint32Array2[n32 + 7]; } function BOUNDING_DATA_INDEX(n32) { return n32; } let float32Array, uint32Array, uint16Array, uint8Array; const MAX_POINTER = Math.pow(2, 32); function countNodes(node) { if ("count" in node) { return 1; } else { return 1 + countNodes(node.left) + countNodes(node.right); } } function populateBuffer(byteOffset, node, buffer) { float32Array = new Float32Array(buffer); uint32Array = new Uint32Array(buffer); uint16Array = new Uint16Array(buffer); uint8Array = new Uint8Array(buffer); return _populateBuffer(byteOffset, node); } function _populateBuffer(byteOffset, node) { const stride4Offset = byteOffset / 4; const stride2Offset = byteOffset / 2; const isLeaf = "count" in node; const boundingData = node.boundingData; for (let i = 0; i < 6; i++) { float32Array[stride4Offset + i] = boundingData[i]; } if (isLeaf) { if (node.buffer) { const buffer = node.buffer; uint8Array.set(new Uint8Array(buffer), byteOffset); for (let offset = byteOffset, l = byteOffset + buffer.byteLength; offset < l; offset += BYTES_PER_NODE) { const offset2 = offset / 2; if (!IS_LEAF(offset2, uint16Array)) { uint32Array[offset / 4 + 6] += stride4Offset; } } return byteOffset + buffer.byteLength; } else { const offset = node.offset; const count = node.count; uint32Array[stride4Offset + 6] = offset; uint16Array[stride2Offset + 14] = count; uint16Array[stride2Offset + 15] = IS_LEAFNODE_FLAG; return byteOffset + BYTES_PER_NODE; } } else { const left = node.left; const right = node.right; const splitAxis = node.splitAxis; let nextUnusedPointer; nextUnusedPointer = _populateBuffer(byteOffset + BYTES_PER_NODE, left); if (nextUnusedPointer / 4 > MAX_POINTER) { throw new Error("MeshBVH: Cannot store child pointer greater than 32 bits."); } uint32Array[stride4Offset + 6] = nextUnusedPointer / 4; nextUnusedPointer = _populateBuffer(nextUnusedPointer, right); uint32Array[stride4Offset + 7] = splitAxis; return nextUnusedPointer; } } function generateIndirectBuffer(geometry, useSharedArrayBuffer) { const triCount = (geometry.index ? geometry.index.count : geometry.attributes.position.count) / 3; const useUint32 = triCount > 2 ** 16; const byteCount = useUint32 ? 4 : 2; const buffer = useSharedArrayBuffer ? new SharedArrayBuffer(triCount * byteCount) : new ArrayBuffer(triCount * byteCount); const indirectBuffer = useUint32 ? new Uint32Array(buffer) : new Uint16Array(buffer); for (let i = 0, l = indirectBuffer.length; i < l; i++) { indirectBuffer[i] = i; } return indirectBuffer; } function buildTree(bvh, triangleBounds, offset, count, options) { const { maxDepth, verbose, maxLeafTris, strategy, onProgress, indirect } = options; const indirectBuffer = bvh._indirectBuffer; const geometry = bvh.geometry; const indexArray = geometry.index ? geometry.index.array : null; const partionFunc = indirect ? partition_indirect : partition; const totalTriangles = getTriCount(geometry); const cacheCentroidBoundingData = new Float32Array(6); let reachedMaxDepth = false; const root = new MeshBVHNode(); getBounds(triangleBounds, offset, count, root.boundingData, cacheCentroidBoundingData); splitNode(root, offset, count, cacheCentroidBoundingData); return root; function triggerProgress(trianglesProcessed) { if (onProgress) { onProgress(trianglesProcessed / totalTriangles); } } function splitNode(node, offset2, count2, centroidBoundingData = null, depth = 0) { if (!reachedMaxDepth && depth >= maxDepth) { reachedMaxDepth = true; if (verbose) { console.warn(`MeshBVH: Max depth of ${maxDepth} reached when generating BVH. Consider increasing maxDepth.`); console.warn(geometry); } } if (count2 <= maxLeafTris || depth >= maxDepth) { triggerProgress(offset2 + count2); node.offset = offset2; node.count = count2; return node; } const split = getOptimalSplit(node.boundingData, centroidBoundingData, triangleBounds, offset2, count2, strategy); if (split.axis === -1) { triggerProgress(offset2 + count2); node.offset = offset2; node.count = count2; return node; } const splitOffset = partionFunc(indirectBuffer, indexArray, triangleBounds, offset2, count2, split); if (splitOffset === offset2 || splitOffset === offset2 + count2) { triggerProgress(offset2 + count2); node.offset = offset2; node.count = count2; } else { node.splitAxis = split.axis; const left = new MeshBVHNode(); const lstart = offset2; const lcount = splitOffset - offset2; node.left = left; getBounds(triangleBounds, lstart, lcount, left.boundingData, cacheCentroidBoundingData); splitNode(left, lstart, lcount, cacheCentroidBoundingData, depth + 1); const right = new MeshBVHNode(); const rstart = splitOffset; const rcount = count2 - lcount; node.right = right; getBounds(triangleBounds, rstart, rcount, right.boundingData, cacheCentroidBoundingData); splitNode(right, rstart, rcount, cacheCentroidBoundingData, depth + 1); } return node; } } function buildPackedTree(bvh, options) { const geometry = bvh.geometry; if (options.indirect) { bvh._indirectBuffer = generateIndirectBuffer(geometry, options.useSharedArrayBuffer); if (hasGroupGaps(geometry, options.range) && !options.verbose) { console.warn( 'MeshBVH: Provided geometry contains groups or a range that do not fully span the vertex contents while using the "indirect" option. BVH may incorrectly report intersections on unrendered portions of the geometry.' ); } } if (!bvh._indirectBuffer) { ensureIndex(geometry, options); } const BufferConstructor = options.useSharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer; const triangleBounds = computeTriangleBounds(geometry); const geometryRanges = options.indirect ? getFullGeometryRange(geometry, options.range) : getRootIndexRanges(geometry, options.range); bvh._roots = geometryRanges.map((range) => { const root = buildTree(bvh, triangleBounds, range.offset, range.count, options); const nodeCount = countNodes(root); const buffer = new BufferConstructor(BYTES_PER_NODE * nodeCount); populateBuffer(0, root, buffer); return buffer; }); } class SeparatingAxisBounds { constructor() { this.min = Infinity; this.max = -Infinity; } setFromPointsField(points, field) { let min = Infinity; let max = -Infinity; for (let i = 0, l = points.length; i < l; i++) { const p = points[i]; const val = p[field]; min = val < min ? val : min; max = val > max ? val : max; } this.min = min; this.max = max; } setFromPoints(axis, points) { let min = Infinity; let max = -Infinity; for (let i = 0, l = points.length; i < l; i++) { const p = points[i]; const val = axis.dot(p); min = val < min ? val : min; max = val > max ? val : max; } this.min = min; this.max = max; } isSeparated(other) { return this.min > other.max || other.min > this.max; } } SeparatingAxisBounds.prototype.setFromBox = function() { const p = new Vector3(); return function setFromBox(axis, box) { const boxMin = box.min; const boxMax = box.max; let min = Infinity; let max = -Infinity; for (let x = 0; x <= 1; x++) { for (let y = 0; y <= 1; y++) { for (let z = 0; z <= 1; z++) { p.x = boxMin.x * x + boxMax.x * (1 - x); p.y = boxMin.y * y + boxMax.y * (1 - y); p.z = boxMin.z * z + boxMax.z * (1 - z); const val = axis.dot(p); min = Math.min(val, min); max = Math.max(val, max); } } } this.min = min; this.max = max; }; }(); const closestPointLineToLine = function() { const dir1 = new Vector3(); const dir2 = new Vector3(); const v02 = new Vector3(); return function closestPointLineToLine2(l1, l2, result) { const v0 = l1.start; const v10 = dir1; const v2 = l2.start; const v32 = dir2; v02.subVectors(v0, v2); dir1.subVectors(l1.end, l1.start); dir2.subVectors(l2.end, l2.start); const d0232 = v02.dot(v32); const d3210 = v32.dot(v10); const d3232 = v32.dot(v32); const d0210 = v02.dot(v10); const d1010 = v10.dot(v10); const denom = d1010 * d3232 - d3210 * d3210; let d, d2; if (denom !== 0) { d = (d0232 * d3210 - d0210 * d3232) / denom; } else { d = 0; } d2 = (d0232 + d * d3210) / d3232; result.x = d; result.y = d2; }; }(); const closestPointsSegmentToSegment = function() { const paramResult = new Vector2(); const temp12 = new Vector3(); const temp22 = new Vector3(); return function closestPointsSegmentToSegment2(l1, l2, target1, target2) { closestPointLineToLine(l1, l2, paramResult); let d = paramResult.x; let d2 = paramResult.y; if (d >= 0 && d <= 1 && d2 >= 0 && d2 <= 1) { l1.at(d, target1); l2.at(d2, target2); return; } else if (d >= 0 && d <= 1) { if (d2 < 0) { l2.at(0, target2); } else { l2.at(1, target2); } l1.closestPointToPoint(target2, true, target1); return; } else if (d2 >= 0 && d2 <= 1) { if (d < 0) { l1.at(0, target1); } else { l1.at(1, target1); } l2.closestPointToPoint(target1, true, target2); return; } else { let p; if (d < 0) { p = l1.start; } else { p = l1.end; } let p2; if (d2 < 0) { p2 = l2.start; } else { p2 = l2.end; } const closestPoint = temp12; const closestPoint2 = temp22; l1.closestPointToPoint(p2, true, temp12); l2.closestPointToPoint(p, true, temp22); if (closestPoint.distanceToSquared(p2) <= closestPoint2.distanceToSquared(p)) { target1.copy(closestPoint); target2.copy(p2); return; } else { target1.copy(p); target2.copy(closestPoint2); return; } } }; }(); const sphereIntersectTriangle = function() { const closestPointTemp = new Vector3(); const projectedPointTemp = new Vector3(); const planeTemp = new Plane(); const lineTemp = new Line3(); return function sphereIntersectTriangle2(sphere, triangle3) { const { radius, center } = sphere; const { a, b, c } = triangle3; lineTemp.start = a; lineTemp.end = b; const closestPoint1 = lineTemp.closestPointToPoint(center, true, closestPointTemp); if (closestPoint1.distanceTo(center) <= radius) return true; lineTemp.start = a; lineTemp.end = c; const closestPoint2 = lineTemp.closestPointToPoint(center, true, closestPointTemp); if (closestPoint2.distanceTo(center) <= radius) return true; lineTemp.start = b; lineTemp.end = c; const closestPoint3 = lineTemp.closestPointToPoint(center, true, closestPointTemp); if (closestPoint3.distanceTo(center) <= radius) return true; const plane = triangle3.getPlane(planeTemp); const dp = Math.abs(plane.distanceToPoint(center)); if (dp <= radius) { const pp = plane.projectPoint(center, projectedPointTemp); const cp = triangle3.containsPoint(pp); if (cp) return true; } return false; }; }(); const ZERO_EPSILON = 1e-15; function isNearZero(value) { return Math.abs(value) < ZERO_EPSILON; } class ExtendedTriangle extends Triangle { constructor(...args) { super(...args); this.isExtendedTriangle = true; this.satAxes = new Array(4).fill().map(() => new Vector3()); this.satBounds = new Array(4).fill().map(() => new SeparatingAxisBounds()); this.points = [this.a, this.b, this.c]; this.sphere = new Sphere(); this.plane = new Plane(); this.needsUpdate = true; } intersectsSphere(sphere) { return sphereIntersectTriangle(sphere, this); } update() { const a = this.a; const b = this.b; const c = this.c; const points = this.points; const satAxes = this.satAxes; const satBounds = this.satBounds; const axis0 = satAxes[0]; const sab0 = satBounds[0]; this.getNormal(axis0); sab0.setFromPoints(axis0, points); const axis1 = satAxes[1]; const sab1 = satBounds[1]; axis1.subVectors(a, b); sab1.setFromPoints(axis1, points); const axis2 = satAxes[2]; const sab2 = satBounds[2]; axis2.subVectors(b, c); sab2.setFromPoints(axis2, points); const axis3 = satAxes[3]; const sab3 = satBounds[3]; axis3.subVectors(c, a); sab3.setFromPoints(axis3, points); this.sphere.setFromPoints(this.points); this.plane.setFromNormalAndCoplanarPoint(axis0, a); this.needsUpdate = false; } } ExtendedTriangle.prototype.closestPointToSegment = function() { const point1 = new Vector3(); const point2 = new Vector3(); const edge = new Line3(); return function distanceToSegment(segment, target1 = null, target2 = null) { const { start, end } = segment; const points = this.points; let distSq; let closestDistanceSq = Infinity; for (let i = 0; i < 3; i++) { const nexti = (i + 1) % 3; edge.start.copy(points[i]); edge.end.copy(points[nexti]); closestPointsSegmentToSegment(edge, segment, point1, point2); distSq = point1.distanceToSquared(point2); if (distSq < closestDistanceSq) { closestDistanceSq = distSq; if (target1) target1.copy(point1); if (target2) target2.copy(point2); } } this.closestPointToPoint(start, point1); distSq = start.distanceToSquared(point1); if (distSq < closestDistanceSq) { closestDistanceSq = distSq; if (target1) target1.copy(point1); if (target2) target2.copy(start); } this.closestPointToPoint(end, point1); distSq = end.distanceToSquared(point1); if (distSq < closestDistanceSq) { closestDistanceSq = distSq; if (target1) target1.copy(point1); if (target2) target2.copy(end); } return Math.sqrt(closestDistanceSq); }; }(); ExtendedTriangle.prototype.intersectsTriangle = function() { const saTri2 = new ExtendedTriangle(); const arr1 = new Array(3); const arr2 = new Array(3); const cachedSatBounds = new SeparatingAxisBounds(); const cachedSatBounds2 = new SeparatingAxisBounds(); const cachedAxis = new Vector3(); const dir = new Vector3(); const dir1 = new Vector3(); const dir2 = new Vector3(); const tempDir = new Vector3(); const edge = new Line3(); const edge1 = new Line3(); const edge2 = new Line3(); const tempPoint = new Vector3(); function triIntersectPlane(tri, plane, targetEdge) { const points = tri.points; let count = 0; let startPointIntersection = -1; for (let i = 0; i < 3; i++) { const { start, end } = edge; start.copy(points[i]); end.copy(points[(i + 1) % 3]); edge.delta(dir); const startIntersects = isNearZero(plane.distanceToPoint(start)); if (isNearZero(plane.normal.dot(dir)) && startIntersects) { targetEdge.copy(edge); count = 2; break; } const doesIntersect = plane.intersectLine(edge, tempPoint); if (!doesIntersect && startIntersects) { tempPoint.copy(start); } if ((doesIntersect || startIntersects) && !isNearZero(tempPoint.distanceTo(end))) { if (count <= 1) { const point = count === 1 ? targetEdge.start : targetEdge.end; point.copy(tempPoint); if (startIntersects) { startPointIntersection = count; } } else if (count >= 2) { const point = startPointIntersection === 1 ? targetEdge.start : targetEdge.end; point.copy(tempPoint); count = 2; break; } count++; if (count === 2 && startPointIntersection === -1) { break; } } } return count; } return function intersectsTriangle(other, target2 = null, suppressLog = false) { if (this.needsUpdate) { this.update(); } if (!other.isExtendedTriangle) { saTri2.copy(other); saTri2.update(); other = saTri2; } else if (other.needsUpdate) { other.update(); } const plane1 = this.plane; const plane2 = other.plane; if (Math.abs(plane1.normal.dot(plane2.normal)) > 1 - 1e-10) { const satBounds1 = this.satBounds; const satAxes1 = this.satAxes; arr2[0] = other.a; arr2[1] = other.b; arr2[2] = other.c; for (let i = 0; i < 4; i++) { const sb = satBounds1[i]; const sa = satAxes1[i]; cachedSatBounds.setFromPoints(sa, arr2); if (sb.isSeparated(cachedSatBounds)) return false; } const satBounds2 = other.satBounds; const satAxes2 = other.satAxes; arr1[0] = this.a; arr1[1] = this.b; arr1[2] = this.c; for (let i = 0; i < 4; i++) { const sb = satBounds2[i]; const sa = satAxes2[i]; cachedSatBounds.setFromPoints(sa, arr1); if (sb.isSeparated(cachedSatBounds)) return false; } for (let i = 0; i < 4; i++) { const sa1 = satAxes1[i]; for (let i2 = 0; i2 < 4; i2++) { const sa2 = satAxes2[i2]; cachedAxis.crossVectors(sa1, sa2); cachedSatBounds.setFromPoints(cachedAxis, arr1); cachedSatBounds2.setFromPoints(cachedAxis, arr2); if (cachedSatBounds.isSeparated(cachedSatBounds2)) return false; } } if (target2) { if (!suppressLog) { console.warn("ExtendedTriangle.intersectsTriangle: Triangles are coplanar which does not support an output edge. Setting edge to 0, 0, 0."); } target2.start.set(0, 0, 0); target2.end.set(0, 0, 0); } return true; } else { const count1 = triIntersectPlane(this, plane2, edge1); if (count1 === 1 && other.containsPoint(edge1.end)) { if (target2) { target2.start.copy(edge1.end); target2.end.copy(edge1.end); } return true; } else if (count1 !== 2) { return false; } const count2 = triIntersectPlane(other, plane1, edge2); if (count2 === 1 && this.containsPoint(edge2.end)) { if (target2) { target2.start.copy(edge2.end); target2.end.copy(edge2.end); } return true; } else if (count2 !== 2) { return false; } edge1.delta(dir1); edge2.delta(dir2); if (dir1.dot(dir2) < 0) { let tmp = edge2.start; edge2.start = edge2.end; edge2.end = tmp; } const s1 = edge1.start.dot(dir1); const e1 = edge1.end.dot(dir1); const s2 = edge2.start.dot(dir1); const e2 = edge2.end.dot(dir1); const separated1 = e1 < s2; const separated2 = s1 < e2; if (s1 !== e2 && s2 !== e1 && separated1 === separated2) { return false; } if (target2) { tempDir.subVectors(edge1.start, edge2.start); if (tempDir.dot(dir1) > 0) { target2.start.copy(edge1.start); } else { target2.start.copy(edge2.start); } tempDir.subVectors(edge1.end, edge2.end); if (tempDir.dot(dir1) < 0) { target2.end.copy(edge1.end); } else { target2.end.copy(edge2.end); } } return true; } }; }(); ExtendedTriangle.prototype.distanceToPoint = function() { const target2 = new Vector3(); return function distanceToPoint(point) { this.closestPointToPoint(point, target2); return point.distanceTo(target2); }; }(); ExtendedTriangle.prototype.distanceToTriangle = function() { const point = new Vector3(); const point2 = new Vector3(); const cornerFields = ["a", "b", "c"]; const line1 = new Line3(); const line2 = new Line3(); return function distanceToTriangle(other, target1 = null, target2 = null) { const lineTarget = target1 || target2 ? line1 : null; if (this.intersectsTriangle(other, lineTarget)) { if (target1 || target2) { if (target1) lineTarget.getCenter(target1); if (target2) lineTarget.getCenter(target2); } return 0; } let closestDistanceSq = Infinity; for (let i = 0; i < 3; i++) { let dist; const field = cornerFields[i]; const otherVec = other[field]; this.closestPointToPoint(otherVec, point); dist = otherVec.distanceToSquared(point); if (dist < closestDistanceSq) { closestDistanceSq = dist; if (target1) target1.copy(point); if (target2) target2.copy(otherVec); } const thisVec = this[field]; other.closestPointToPoint(thisVec, point); dist = thisVec.distanceToSquared(point); if (dist < closestDistanceSq) { closestDistanceSq = dist; if (target1) target1.copy(thisVec); if (target2) target2.copy(point); } } for (let i = 0; i < 3; i++) { const f11 = cornerFields[i]; const f12 = cornerFields[(i + 1) % 3]; line1.set(this[f11], this[f12]); for (let i2 = 0; i2 < 3; i2++) { const f21 = cornerFields[i2]; const f22 = cornerFields[(i2 + 1) % 3]; line2.set(other[f21], other[f22]); closestPointsSegmentToSegment(line1, line2, point, point2); const dist = point.distanceToSquared(point2); if (dist < closestDistanceSq) { closestDistanceSq = dist; if (target1) target1.copy(point); if (target2) target2.copy(point2); } } } return Math.sqrt(closestDistanceSq); }; }(); class OrientedBox { constructor(min, max, matrix) { this.isOrientedBox = true; this.min = new Vector3(); this.max = new Vector3(); this.matrix = new Matrix4(); this.invMatrix = new Matrix4(); this.points = new Array(8).fill().map(() => new Vector3()); this.satAxes = new Array(3).fill().map(() => new Vector3()); this.satBounds = new Array(3).fill().map(() => new SeparatingAxisBounds()); this.alignedSatBounds = new Array(3).fill().map(() => new SeparatingAxisBounds()); this.needsUpdate = false; if (min) this.min.copy(min); if (max) this.max.copy(max); if (matrix) this.matrix.copy(matrix); } set(min, max, matrix) { this.min.copy(min); this.max.copy(max); this.matrix.copy(matrix); this.needsUpdate = true; } copy(other) { this.min.copy(other.min); this.max.copy(other.max); this.matrix.copy(other.matrix); this.needsUpdate = true; } } OrientedBox.prototype.update = /* @__PURE__ */ function() { return function update() { const matrix = this.matrix; const min = this.min; const max = this.max; const points = this.points; for (let x = 0; x <= 1; x++) { for (let y = 0; y <= 1; y++) { for (let z = 0; z <= 1; z++) { const i = (1 << 0) * x | (1 << 1) * y | (1 << 2) * z; const v2 = points[i]; v2.x = x ? max.x : min.x; v2.y = y ? max.y : min.y; v2.z = z ? max.z : min.z; v2.applyMatrix4(matrix); } } } const satBounds = this.satBounds; const satAxes = this.satAxes; const minVec = points[0]; for (let i = 0; i < 3; i++) { const axis = satAxes[i]; const sb = satBounds[i]; const index = 1 << i; const pi = points[index]; axis.subVectors(minVec, pi); sb.setFromPoints(axis, points); } const alignedSatBounds = this.alignedSatBounds; alignedSatBounds[0].setFromPointsField(points, "x"); alignedSatBounds[1].setFromPointsField(points, "y"); alignedSatBounds[2].setFromPointsField(points, "z"); this.invMatrix.copy(this.matrix).invert(); this.needsUpdate = false; }; }(); OrientedBox.prototype.intersectsBox = function() { const aabbBounds = new SeparatingAxisBounds(); return function intersectsBox(box) { if (this.needsUpdate) { this.update(); } const min = box.min; const max = box.max; const satBounds = this.satBounds; const satAxes = this.satAxes; const alignedSatBounds = this.alignedSatBounds; aabbBounds.min = min.x; aabbBounds.max = max.x; if (alignedSatBounds[0].isSeparated(aabbBounds)) return false; aabbBounds.min = min.y; aabbBounds.max = max.y; if (alignedSatBounds[1].isSeparated(aabbBounds)) return false; aabbBounds.min = min.z; aabbBounds.max = max.z; if (alignedSatBounds[2].isSeparated(aabbBounds)) return false; for (let i = 0; i < 3; i++) { const axis = satAxes[i]; const sb = satBounds[i]; aabbBounds.setFromBox(axis, box); if (sb.isSeparated(aabbBounds)) return false; } return true; }; }(); OrientedBox.prototype.intersectsTriangle = function() { const saTri = new ExtendedTriangle(); const pointsArr = new Array(3); const cachedSatBounds = new SeparatingAxisBounds(); const cachedSatBounds2 = new SeparatingAxisBounds(); const cachedAxis = new Vector3(); return function intersectsTriangle(triangle3) { if (this.needsUpdate) { this.update(); } if (!triangle3.isExtendedTriangle) { saTri.copy(triangle3); saTri.update(); triangle3 = saTri; } else if (triangle3.needsUpdate) { triangle3.update(); } const satBounds = this.satBounds; const satAxes = this.satAxes; pointsArr[0] = triangle3.a; pointsArr[1] = triangle3.b; pointsArr[2] = triangle3.c; for (let i = 0; i < 3; i++) { const sb = satBounds[i]; const sa = satAxes[i]; cachedSatBounds.setFromPoints(sa, pointsArr); if (sb.isSeparated(cachedSatBounds)) return false; } const triSatBounds = triangle3.satBounds; const triSatAxes = triangle3.satAxes; const points = this.points; for (let i = 0; i < 3; i++) { const sb = triSatBounds[i]; const sa = triSatAxes[i]; cachedSatBounds.setFromPoints(sa, points); if (sb.isSeparated(cachedSatBounds)) return false; } for (let i = 0; i < 3; i++) { const sa1 = satAxes[i]; for (let i2 = 0; i2 < 4; i2++) { const sa2 = triSatAxes[i2]; cachedAxis.crossVectors(sa1, sa2); cachedSatBounds.setFromPoints(cachedAxis, pointsArr); cachedSatBounds2.setFromPoints(cachedAxis, points); if (cachedSatBounds.isSeparated(cachedSatBounds2)) return false; } } return true; }; }(); OrientedBox.prototype.closestPointToPoint = /* @__PURE__ */ function() { return function closestPointToPoint2(point, target1) { if (this.needsUpdate) { this.update(); } target1.copy(point).applyMatrix4(this.invMatrix).clamp(this.min, this.max).applyMatrix4(this.matrix); return target1; }; }(); OrientedBox.prototype.distanceToPoint = function() { const target2 = new Vector3(); return function distanceToPoint(point) { this.closestPointToPoint(point, target2); return point.distanceTo(target2); }; }(); OrientedBox.prototype.distanceToBox = function() { const xyzFields = ["x", "y", "z"]; const segments1 = new Array(12).fill().map(() => new Line3()); const segments2 = new Array(12).fill().map(() => new Line3()); const point1 = new Vector3(); const point2 = new Vector3(); return function distanceToBox(box, threshold = 0, target1 = null, target2 = null) { if (this.needsUpdate) { this.update(); } if (this.intersectsBox(box)) { if (target1 || target2) { box.getCenter(point2); this.closestPointToPoint(point2, point1); box.closestPointToPoint(point1, point2); if (target1) target1.copy(point1); if (target2) target2.copy(point2); } return 0; } const threshold2 = threshold * threshold; const min = box.min; const max = box.max; const points = this.points; let closestDistanceSq = Infinity; for (let i = 0; i < 8; i++) { const p = points[i]; point2.copy(p).clamp(min, max); const dist = p.distanceToSquared(point2); if (dist < closestDistanceSq) { closestDistanceSq = dist; if (target1) target1.copy(p); if (target2) target2.copy(point2); if (dist < threshold2) return Math.sqrt(dist); } } let count = 0; for (let i = 0; i < 3; i++) { for (let i1 = 0; i1 <= 1; i1++) { for (let i2 = 0; i2 <= 1; i2++) { const nextIndex = (i + 1) % 3; const nextIndex2 = (i + 2) % 3; const index = i1 << nextIndex | i2 << nextIndex2; const index2 = 1 << i | i1 << nextIndex | i2 << nextIndex2; const p1 = points[index]; const p2 = points[index2]; const line1 = segments1[count]; line1.set(p1, p2); const f1 = xyzFields[i]; const f2 = xyzFields[nextIndex]; const f3 = xyzFields[nextIndex2]; const line2 = segments2[count]; const start = line2.start; const end = line2.end; start[f1] = min[f1]; start[f2] = i1 ? min[f2] : max[f2]; start[f3] = i2 ? min[f3] : max[f2]; end[f1] = max[f1]; end[f2] = i1 ? min[f2] : max[f2]; end[f3] = i2 ? min[f3] : max[f2]; count++; } } } for (let x = 0; x <= 1; x++) { for (let y = 0; y <= 1; y++) { for (let z = 0; z <= 1; z++) { point2.x = x ? max.x : min.x; point2.y = y ? max.y : min.y; point2.z = z ? max.z : min.z; this.closestPointToPoint(point2, point1); const dist = point2.distanceToSquared(point1); if (dist < closestDistanceSq) { closestDistanceSq = dist; if (target1) target1.copy(point1); if (target2) target2.copy(point2); if (dist < threshold2) return Math.sqrt(dist); } } } } for (let i = 0; i < 12; i++) { const l1 = segments1[i]; for (let i2 = 0; i2 < 12; i2++) { const l2 = segments2[i2]; closestPointsSegmentToSegment(l1, l2, point1, point2); const dist = point1.distanceToSquared(point2); if (dist < closestDistanceSq) { closestDistanceSq = dist; if (target1) target1.copy(point1); if (target2) target2.copy(point2); if (dist < threshold2) return Math.sqrt(dist); } } } return Math.sqrt(closestDistanceSq); }; }(); class PrimitivePool { constructor(getNewPrimitive) { this._getNewPrimitive = getNewPrimitive; this._primitives = []; } getPrimitive() { const primitives = this._primitives; if (primitives.length === 0) { return this._getNewPrimitive(); } else { return primitives.pop(); } } releasePrimitive(primitive) { this._primitives.push(primitive); } } class ExtendedTrianglePoolBase extends PrimitivePool { constructor() { super(() => new ExtendedTriangle()); } } const ExtendedTrianglePool = /* @__PURE__ */ new ExtendedTrianglePoolBase(); class _BufferStack { constructor() { this.float32Array = null; this.uint16Array = null; this.uint32Array = null; const stack = []; let prevBuffer = null; this.setBuffer = (buffer) => { if (prevBuffer) { stack.push(prevBuffer); } prevBuffer = buffer; this.float32Array = new Float32Array(buffer); this.uint16Array = new Uint16Array(buffer); this.uint32Array = new Uint32Array(buffer); }; this.clearBuffer = () => { prevBuffer = null; this.float32Array = null; this.uint16Array = null; this.uint32Array = null; if (stack.length !== 0) { this.setBuffer(stack.pop()); } }; } } const BufferStack = new _BufferStack(); let _box1, _box2; const boxStack = []; const boxPool = /* @__PURE__ */ new PrimitivePool(() => new Box3()); function shapecast(bvh, root, intersectsBounds, intersectsRange, boundsTraverseOrder, byteOffset) { _box1 = boxPool.getPrimitive(); _box2 = boxPool.getPrimitive(); boxStack.push(_box1, _box2); BufferStack.setBuffer(bvh._roots[root]); const result = shapecastTraverse(0, bvh.geometry, intersectsBounds, intersectsRange, boundsTraverseOrder, byteOffset); BufferStack.cle