3862 lines
125 KiB
Plaintext
3862 lines
125 KiB
Plaintext
import {
|
|
BackSide,
|
|
Box3,
|
|
BufferAttribute,
|
|
BufferGeometry,
|
|
ByteType,
|
|
DataTexture,
|
|
DoubleSide,
|
|
FloatType,
|
|
FrontSide,
|
|
Group,
|
|
IntType,
|
|
Line3,
|
|
LineBasicMaterial,
|
|
Matrix3,
|
|
Matrix4,
|
|
Mesh,
|
|
MeshBasicMaterial,
|
|
NearestFilter,
|
|
Object3D,
|
|
Plane,
|
|
RGBAFormat,
|
|
RGBAIntegerFormat,
|
|
RGFormat,
|
|
RGIntegerFormat,
|
|
Ray,
|
|
RedFormat,
|
|
RedIntegerFormat,
|
|
ShortType,
|
|
Sphere,
|
|
Triangle,
|
|
UnsignedByteType,
|
|
UnsignedIntType,
|
|
UnsignedShortType,
|
|
Vector2,
|
|
Vector3,
|
|
Vector4
|
|
} from "/_nuxt/node_modules/.cache/vite/client/deps/chunk-KHL3VXVA.js?v=e4f18c29";
|
|
import "/_nuxt/node_modules/.cache/vite/client/deps/chunk-V4OQ3NZ2.js?v=e4f18c29";
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/core/Constants.js
|
|
var CENTER = 0;
|
|
var AVERAGE = 1;
|
|
var SAH = 2;
|
|
var NOT_INTERSECTED = 0;
|
|
var INTERSECTED = 1;
|
|
var CONTAINED = 2;
|
|
var TRIANGLE_INTERSECT_COST = 1.25;
|
|
var TRAVERSAL_COST = 1;
|
|
var BYTES_PER_NODE = 6 * 4 + 4 + 4;
|
|
var IS_LEAFNODE_FLAG = 65535;
|
|
var FLOAT32_EPSILON = Math.pow(2, -24);
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/core/MeshBVHNode.js
|
|
var MeshBVHNode = class {
|
|
constructor() {
|
|
}
|
|
};
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/utils/ArrayBoxUtilities.js
|
|
function arrayToBox(nodeIndex32, array, target) {
|
|
target.min.x = array[nodeIndex32];
|
|
target.min.y = array[nodeIndex32 + 1];
|
|
target.min.z = array[nodeIndex32 + 2];
|
|
target.max.x = array[nodeIndex32 + 3];
|
|
target.max.y = array[nodeIndex32 + 4];
|
|
target.max.z = array[nodeIndex32 + 5];
|
|
return target;
|
|
}
|
|
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, target) {
|
|
target.set(source);
|
|
}
|
|
function unionBounds(a, b, target) {
|
|
let aVal, bVal;
|
|
for (let d = 0; d < 3; d++) {
|
|
const d3 = d + 3;
|
|
aVal = a[d];
|
|
bVal = b[d];
|
|
target[d] = aVal < bVal ? aVal : bVal;
|
|
aVal = a[d3];
|
|
bVal = b[d3];
|
|
target[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);
|
|
}
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/core/buildFunctions.js
|
|
function ensureIndex(geo, options) {
|
|
if (!geo.index) {
|
|
const vertexCount = geo.attributes.position.count;
|
|
const BufferConstructor = options.useSharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer;
|
|
let index;
|
|
if (vertexCount > 65535) {
|
|
index = new Uint32Array(new BufferConstructor(4 * vertexCount));
|
|
} else {
|
|
index = new Uint16Array(new BufferConstructor(2 * vertexCount));
|
|
}
|
|
geo.setIndex(new BufferAttribute(index, 1));
|
|
for (let i = 0; i < vertexCount; i++) {
|
|
index[i] = i;
|
|
}
|
|
}
|
|
}
|
|
function getRootIndexRanges(geo) {
|
|
if (!geo.groups || !geo.groups.length) {
|
|
return [{ offset: 0, count: geo.index.count / 3 }];
|
|
}
|
|
const ranges = [];
|
|
const rangeBoundaries = /* @__PURE__ */ new Set();
|
|
for (const group of geo.groups) {
|
|
rangeBoundaries.add(group.start);
|
|
rangeBoundaries.add(group.start + group.count);
|
|
}
|
|
const sortedBoundaries = Array.from(rangeBoundaries.values()).sort((a, b) => a - b);
|
|
for (let i = 0; i < sortedBoundaries.length - 1; i++) {
|
|
const start = sortedBoundaries[i], end = sortedBoundaries[i + 1];
|
|
ranges.push({ offset: start / 3, count: (end - start) / 3 });
|
|
}
|
|
return ranges;
|
|
}
|
|
function getBounds(triangleBounds, offset, count, target, centroidTarget = null) {
|
|
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;
|
|
const includeCentroid = centroidTarget !== null;
|
|
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 (includeCentroid && cx < cminx) cminx = cx;
|
|
if (includeCentroid && 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 (includeCentroid && cy < cminy) cminy = cy;
|
|
if (includeCentroid && 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 (includeCentroid && cz < cminz) cminz = cz;
|
|
if (includeCentroid && cz > cmaxz) cmaxz = cz;
|
|
}
|
|
target[0] = minx;
|
|
target[1] = miny;
|
|
target[2] = minz;
|
|
target[3] = maxx;
|
|
target[4] = maxy;
|
|
target[5] = maxz;
|
|
if (includeCentroid) {
|
|
centroidTarget[0] = cminx;
|
|
centroidTarget[1] = cminy;
|
|
centroidTarget[2] = cminz;
|
|
centroidTarget[3] = cmaxx;
|
|
centroidTarget[4] = cmaxy;
|
|
centroidTarget[5] = cmaxz;
|
|
}
|
|
}
|
|
function getCentroidBounds(triangleBounds, offset, count, centroidTarget) {
|
|
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];
|
|
if (cx < cminx) cminx = cx;
|
|
if (cx > cmaxx) cmaxx = cx;
|
|
const cy = triangleBounds[i + 2];
|
|
if (cy < cminy) cminy = cy;
|
|
if (cy > cmaxy) cmaxy = cy;
|
|
const cz = triangleBounds[i + 4];
|
|
if (cz < cminz) cminz = cz;
|
|
if (cz > cmaxz) cmaxz = cz;
|
|
}
|
|
centroidTarget[0] = cminx;
|
|
centroidTarget[1] = cminy;
|
|
centroidTarget[2] = cminz;
|
|
centroidTarget[3] = cmaxx;
|
|
centroidTarget[4] = cmaxy;
|
|
centroidTarget[5] = cmaxz;
|
|
}
|
|
function partition(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;
|
|
let t1 = triangleBounds[left * 6 + i * 2 + 0];
|
|
triangleBounds[left * 6 + i * 2 + 0] = triangleBounds[right * 6 + i * 2 + 0];
|
|
triangleBounds[right * 6 + i * 2 + 0] = t1;
|
|
let t2 = triangleBounds[left * 6 + i * 2 + 1];
|
|
triangleBounds[left * 6 + i * 2 + 1] = triangleBounds[right * 6 + i * 2 + 1];
|
|
triangleBounds[right * 6 + i * 2 + 1] = t2;
|
|
}
|
|
left++;
|
|
right--;
|
|
} else {
|
|
return left;
|
|
}
|
|
}
|
|
}
|
|
var BIN_COUNT = 32;
|
|
var binsSort = (a, b) => a.candidate - b.candidate;
|
|
var sahBins = new Array(BIN_COUNT).fill().map(() => {
|
|
return {
|
|
count: 0,
|
|
bounds: new Float32Array(6),
|
|
rightCacheBounds: new Float32Array(6),
|
|
leftCacheBounds: new Float32Array(6),
|
|
candidate: 0
|
|
};
|
|
});
|
|
var 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;
|
|
}
|
|
function computeTriangleBounds(geo, fullBounds) {
|
|
const posAttr = geo.attributes.position;
|
|
const index = geo.index.array;
|
|
const triCount = index.length / 3;
|
|
const triangleBounds = new Float32Array(triCount * 6);
|
|
const normalized = posAttr.normalized;
|
|
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 = 0; tri < triCount; tri++) {
|
|
const tri3 = tri * 3;
|
|
const tri6 = tri * 6;
|
|
let ai, bi, ci;
|
|
if (normalized) {
|
|
ai = index[tri3 + 0];
|
|
bi = index[tri3 + 1];
|
|
ci = index[tri3 + 2];
|
|
} else {
|
|
ai = index[tri3 + 0] * stride + bufferOffset;
|
|
bi = index[tri3 + 1] * stride + bufferOffset;
|
|
ci = index[tri3 + 2] * 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;
|
|
if (min < fullBounds[el]) fullBounds[el] = min;
|
|
if (max > fullBounds[el + 3]) fullBounds[el + 3] = max;
|
|
}
|
|
}
|
|
return triangleBounds;
|
|
}
|
|
function buildTree(geo, options) {
|
|
function triggerProgress(trianglesProcessed) {
|
|
if (onProgress) {
|
|
onProgress(trianglesProcessed / totalTriangles);
|
|
}
|
|
}
|
|
function splitNode(node, offset, count, 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(geo);
|
|
}
|
|
}
|
|
if (count <= maxLeafTris || depth >= maxDepth) {
|
|
triggerProgress(offset + count);
|
|
node.offset = offset;
|
|
node.count = count;
|
|
return node;
|
|
}
|
|
const split = getOptimalSplit(node.boundingData, centroidBoundingData, triangleBounds, offset, count, strategy);
|
|
if (split.axis === -1) {
|
|
triggerProgress(offset + count);
|
|
node.offset = offset;
|
|
node.count = count;
|
|
return node;
|
|
}
|
|
const splitOffset = partition(indexArray, triangleBounds, offset, count, split);
|
|
if (splitOffset === offset || splitOffset === offset + count) {
|
|
triggerProgress(offset + count);
|
|
node.offset = offset;
|
|
node.count = count;
|
|
} else {
|
|
node.splitAxis = split.axis;
|
|
const left = new MeshBVHNode();
|
|
const lstart = offset;
|
|
const lcount = splitOffset - offset;
|
|
node.left = left;
|
|
left.boundingData = new Float32Array(6);
|
|
getBounds(triangleBounds, lstart, lcount, left.boundingData, cacheCentroidBoundingData);
|
|
splitNode(left, lstart, lcount, cacheCentroidBoundingData, depth + 1);
|
|
const right = new MeshBVHNode();
|
|
const rstart = splitOffset;
|
|
const rcount = count - lcount;
|
|
node.right = right;
|
|
right.boundingData = new Float32Array(6);
|
|
getBounds(triangleBounds, rstart, rcount, right.boundingData, cacheCentroidBoundingData);
|
|
splitNode(right, rstart, rcount, cacheCentroidBoundingData, depth + 1);
|
|
}
|
|
return node;
|
|
}
|
|
ensureIndex(geo, options);
|
|
const fullBounds = new Float32Array(6);
|
|
const cacheCentroidBoundingData = new Float32Array(6);
|
|
const triangleBounds = computeTriangleBounds(geo, fullBounds);
|
|
const indexArray = geo.index.array;
|
|
const maxDepth = options.maxDepth;
|
|
const verbose = options.verbose;
|
|
const maxLeafTris = options.maxLeafTris;
|
|
const strategy = options.strategy;
|
|
const onProgress = options.onProgress;
|
|
const totalTriangles = geo.index.count / 3;
|
|
let reachedMaxDepth = false;
|
|
const roots = [];
|
|
const ranges = getRootIndexRanges(geo);
|
|
if (ranges.length === 1) {
|
|
const range = ranges[0];
|
|
const root = new MeshBVHNode();
|
|
root.boundingData = fullBounds;
|
|
getCentroidBounds(triangleBounds, range.offset, range.count, cacheCentroidBoundingData);
|
|
splitNode(root, range.offset, range.count, cacheCentroidBoundingData);
|
|
roots.push(root);
|
|
} else {
|
|
for (let range of ranges) {
|
|
const root = new MeshBVHNode();
|
|
root.boundingData = new Float32Array(6);
|
|
getBounds(triangleBounds, range.offset, range.count, root.boundingData, cacheCentroidBoundingData);
|
|
splitNode(root, range.offset, range.count, cacheCentroidBoundingData);
|
|
roots.push(root);
|
|
}
|
|
}
|
|
return roots;
|
|
}
|
|
function buildPackedTree(geo, options) {
|
|
const roots = buildTree(geo, options);
|
|
let float32Array;
|
|
let uint32Array;
|
|
let uint16Array;
|
|
const packedRoots = [];
|
|
const BufferConstructor = options.useSharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer;
|
|
for (let i = 0; i < roots.length; i++) {
|
|
const root = roots[i];
|
|
let nodeCount = countNodes(root);
|
|
const buffer = new BufferConstructor(BYTES_PER_NODE * nodeCount);
|
|
float32Array = new Float32Array(buffer);
|
|
uint32Array = new Uint32Array(buffer);
|
|
uint16Array = new Uint16Array(buffer);
|
|
populateBuffer(0, root);
|
|
packedRoots.push(buffer);
|
|
}
|
|
return packedRoots;
|
|
function countNodes(node) {
|
|
if (node.count) {
|
|
return 1;
|
|
} else {
|
|
return 1 + countNodes(node.left) + countNodes(node.right);
|
|
}
|
|
}
|
|
function populateBuffer(byteOffset, node) {
|
|
const stride4Offset = byteOffset / 4;
|
|
const stride2Offset = byteOffset / 2;
|
|
const isLeaf = !!node.count;
|
|
const boundingData = node.boundingData;
|
|
for (let i = 0; i < 6; i++) {
|
|
float32Array[stride4Offset + i] = boundingData[i];
|
|
}
|
|
if (isLeaf) {
|
|
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 > Math.pow(2, 32)) {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/math/SeparatingAxisBounds.js
|
|
var SeparatingAxisBounds = class {
|
|
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;
|
|
};
|
|
}();
|
|
var areIntersecting = function() {
|
|
const cacheSatBounds = new SeparatingAxisBounds();
|
|
return function areIntersecting2(shape1, shape2) {
|
|
const points1 = shape1.points;
|
|
const satAxes1 = shape1.satAxes;
|
|
const satBounds1 = shape1.satBounds;
|
|
const points2 = shape2.points;
|
|
const satAxes2 = shape2.satAxes;
|
|
const satBounds2 = shape2.satBounds;
|
|
for (let i = 0; i < 3; i++) {
|
|
const sb = satBounds1[i];
|
|
const sa = satAxes1[i];
|
|
cacheSatBounds.setFromPoints(sa, points2);
|
|
if (sb.isSeparated(cacheSatBounds)) return false;
|
|
}
|
|
for (let i = 0; i < 3; i++) {
|
|
const sb = satBounds2[i];
|
|
const sa = satAxes2[i];
|
|
cacheSatBounds.setFromPoints(sa, points1);
|
|
if (sb.isSeparated(cacheSatBounds)) return false;
|
|
}
|
|
};
|
|
}();
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/math/MathUtilities.js
|
|
var 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;
|
|
};
|
|
}();
|
|
var 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;
|
|
}
|
|
}
|
|
};
|
|
}();
|
|
var sphereIntersectTriangle = function() {
|
|
const closestPointTemp = new Vector3();
|
|
const projectedPointTemp = new Vector3();
|
|
const planeTemp = new Plane();
|
|
const lineTemp = new Line3();
|
|
return function sphereIntersectTriangle2(sphere, triangle) {
|
|
const { radius, center } = sphere;
|
|
const { a, b, c } = triangle;
|
|
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 = triangle.getPlane(planeTemp);
|
|
const dp = Math.abs(plane.distanceToPoint(center));
|
|
if (dp <= radius) {
|
|
const pp = plane.projectPoint(center, projectedPointTemp);
|
|
const cp = triangle.containsPoint(pp);
|
|
if (cp) return true;
|
|
}
|
|
return false;
|
|
};
|
|
}();
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/math/ExtendedTriangle.js
|
|
var DIST_EPSILON = 1e-15;
|
|
function isNearZero(value) {
|
|
return Math.abs(value) < DIST_EPSILON;
|
|
}
|
|
var ExtendedTriangle = class 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 dir1 = new Vector3();
|
|
const dir2 = new Vector3();
|
|
const tempDir = new Vector3();
|
|
const edge = new Line3();
|
|
const edge1 = new Line3();
|
|
const edge2 = new Line3();
|
|
return function intersectsTriangle(other, target = null) {
|
|
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 (target) {
|
|
console.warn("ExtendedTriangle.intersectsTriangle: Triangles are coplanar which does not support an output edge. Setting edge to 0, 0, 0.");
|
|
target.start.set(0, 0, 0);
|
|
target.end.set(0, 0, 0);
|
|
}
|
|
return true;
|
|
} else {
|
|
const points1 = this.points;
|
|
let found1 = false;
|
|
let count1 = 0;
|
|
for (let i = 0; i < 3; i++) {
|
|
const p = points1[i];
|
|
const pNext = points1[(i + 1) % 3];
|
|
edge.start.copy(p);
|
|
edge.end.copy(pNext);
|
|
edge.delta(dir1);
|
|
const targetPoint = found1 ? edge1.start : edge1.end;
|
|
const startIntersects = isNearZero(plane2.distanceToPoint(p));
|
|
if (isNearZero(plane2.normal.dot(dir1)) && startIntersects) {
|
|
edge1.copy(edge);
|
|
count1 = 2;
|
|
break;
|
|
}
|
|
const doesIntersect = plane2.intersectLine(edge, targetPoint) || startIntersects;
|
|
if (doesIntersect && !isNearZero(targetPoint.distanceTo(pNext))) {
|
|
count1++;
|
|
if (found1) {
|
|
break;
|
|
}
|
|
found1 = true;
|
|
}
|
|
}
|
|
if (count1 === 1 && this.containsPoint(edge1.end)) {
|
|
if (target) {
|
|
target.start.copy(edge1.end);
|
|
target.end.copy(edge1.end);
|
|
}
|
|
return true;
|
|
} else if (count1 !== 2) {
|
|
return false;
|
|
}
|
|
const points2 = other.points;
|
|
let found2 = false;
|
|
let count2 = 0;
|
|
for (let i = 0; i < 3; i++) {
|
|
const p = points2[i];
|
|
const pNext = points2[(i + 1) % 3];
|
|
edge.start.copy(p);
|
|
edge.end.copy(pNext);
|
|
edge.delta(dir2);
|
|
const targetPoint = found2 ? edge2.start : edge2.end;
|
|
const startIntersects = isNearZero(plane1.distanceToPoint(p));
|
|
if (isNearZero(plane1.normal.dot(dir2)) && startIntersects) {
|
|
edge2.copy(edge);
|
|
count2 = 2;
|
|
break;
|
|
}
|
|
const doesIntersect = plane1.intersectLine(edge, targetPoint) || startIntersects;
|
|
if (doesIntersect && !isNearZero(targetPoint.distanceTo(pNext))) {
|
|
count2++;
|
|
if (found2) {
|
|
break;
|
|
}
|
|
found2 = true;
|
|
}
|
|
}
|
|
if (count2 === 1 && this.containsPoint(edge2.end)) {
|
|
if (target) {
|
|
target.start.copy(edge2.end);
|
|
target.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 (target) {
|
|
tempDir.subVectors(edge1.start, edge2.start);
|
|
if (tempDir.dot(dir1) > 0) {
|
|
target.start.copy(edge1.start);
|
|
} else {
|
|
target.start.copy(edge2.start);
|
|
}
|
|
tempDir.subVectors(edge1.end, edge2.end);
|
|
if (tempDir.dot(dir1) < 0) {
|
|
target.end.copy(edge1.end);
|
|
} else {
|
|
target.end.copy(edge2.end);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
}();
|
|
ExtendedTriangle.prototype.distanceToPoint = function() {
|
|
const target = new Vector3();
|
|
return function distanceToPoint(point) {
|
|
this.closestPointToPoint(point, target);
|
|
return point.distanceTo(target);
|
|
};
|
|
}();
|
|
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);
|
|
};
|
|
}();
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/math/OrientedBox.js
|
|
var OrientedBox = class {
|
|
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 v = points[i];
|
|
v.x = x ? max.x : min.x;
|
|
v.y = y ? max.y : min.y;
|
|
v.z = z ? max.z : min.z;
|
|
v.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(triangle) {
|
|
if (this.needsUpdate) {
|
|
this.update();
|
|
}
|
|
if (!triangle.isExtendedTriangle) {
|
|
saTri.copy(triangle);
|
|
saTri.update();
|
|
triangle = saTri;
|
|
} else if (triangle.needsUpdate) {
|
|
triangle.update();
|
|
}
|
|
const satBounds = this.satBounds;
|
|
const satAxes = this.satAxes;
|
|
pointsArr[0] = triangle.a;
|
|
pointsArr[1] = triangle.b;
|
|
pointsArr[2] = triangle.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 = triangle.satBounds;
|
|
const triSatAxes = triangle.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 closestPointToPoint(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 target = new Vector3();
|
|
return function distanceToPoint(point) {
|
|
this.closestPointToPoint(point, target);
|
|
return point.distanceTo(target);
|
|
};
|
|
}();
|
|
OrientedBox.prototype.distanceToBox = function() {
|
|
const xyzFields2 = ["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 = xyzFields2[i];
|
|
const f2 = xyzFields2[nextIndex];
|
|
const f3 = xyzFields2[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);
|
|
};
|
|
}();
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/utils/ThreeRayIntersectUtilities.js
|
|
var vA = new Vector3();
|
|
var vB = new Vector3();
|
|
var vC = new Vector3();
|
|
var uvA = new Vector2();
|
|
var uvB = new Vector2();
|
|
var uvC = new Vector2();
|
|
var intersectionPoint = new Vector3();
|
|
function checkIntersection(ray2, pA, pB, pC, point, side) {
|
|
let intersect;
|
|
if (side === BackSide) {
|
|
intersect = ray2.intersectTriangle(pC, pB, pA, true, point);
|
|
} else {
|
|
intersect = ray2.intersectTriangle(pA, pB, pC, side !== DoubleSide, point);
|
|
}
|
|
if (intersect === null) return null;
|
|
const distance = ray2.origin.distanceTo(point);
|
|
return {
|
|
distance,
|
|
point: point.clone()
|
|
};
|
|
}
|
|
function checkBufferGeometryIntersection(ray2, position, uv, a, b, c, side) {
|
|
vA.fromBufferAttribute(position, a);
|
|
vB.fromBufferAttribute(position, b);
|
|
vC.fromBufferAttribute(position, c);
|
|
const intersection = checkIntersection(ray2, vA, vB, vC, intersectionPoint, side);
|
|
if (intersection) {
|
|
if (uv) {
|
|
uvA.fromBufferAttribute(uv, a);
|
|
uvB.fromBufferAttribute(uv, b);
|
|
uvC.fromBufferAttribute(uv, c);
|
|
intersection.uv = Triangle.getUV(intersectionPoint, vA, vB, vC, uvA, uvB, uvC, new Vector2());
|
|
}
|
|
const face = {
|
|
a,
|
|
b,
|
|
c,
|
|
normal: new Vector3(),
|
|
materialIndex: 0
|
|
};
|
|
Triangle.getNormal(vA, vB, vC, face.normal);
|
|
intersection.face = face;
|
|
intersection.faceIndex = a;
|
|
}
|
|
return intersection;
|
|
}
|
|
function intersectTri(geo, side, ray2, tri, intersections) {
|
|
const triOffset = tri * 3;
|
|
const a = geo.index.getX(triOffset);
|
|
const b = geo.index.getX(triOffset + 1);
|
|
const c = geo.index.getX(triOffset + 2);
|
|
const intersection = checkBufferGeometryIntersection(ray2, geo.attributes.position, geo.attributes.uv, a, b, c, side);
|
|
if (intersection) {
|
|
intersection.faceIndex = tri;
|
|
if (intersections) intersections.push(intersection);
|
|
return intersection;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/utils/GeometryRayIntersectUtilities.js
|
|
function intersectTris(geo, side, ray2, offset, count, intersections) {
|
|
for (let i = offset, end = offset + count; i < end; i++) {
|
|
intersectTri(geo, side, ray2, i, intersections);
|
|
}
|
|
}
|
|
function intersectClosestTri(geo, side, ray2, offset, count) {
|
|
let dist = Infinity;
|
|
let res = null;
|
|
for (let i = offset, end = offset + count; i < end; i++) {
|
|
const intersection = intersectTri(geo, side, ray2, i);
|
|
if (intersection && intersection.distance < dist) {
|
|
res = intersection;
|
|
dist = intersection.distance;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
function convertRaycastIntersect(hit, object, raycaster) {
|
|
if (hit === null) {
|
|
return null;
|
|
}
|
|
hit.point.applyMatrix4(object.matrixWorld);
|
|
hit.distance = hit.point.distanceTo(raycaster.ray.origin);
|
|
hit.object = object;
|
|
if (hit.distance < raycaster.near || hit.distance > raycaster.far) {
|
|
return null;
|
|
} else {
|
|
return hit;
|
|
}
|
|
}
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/utils/TriangleUtilities.js
|
|
function setTriangle(tri, i, index, pos) {
|
|
const ta = tri.a;
|
|
const tb = tri.b;
|
|
const tc = tri.c;
|
|
let i0 = i;
|
|
let i1 = i + 1;
|
|
let i2 = i + 2;
|
|
if (index) {
|
|
i0 = index.getX(i);
|
|
i1 = index.getX(i + 1);
|
|
i2 = index.getX(i + 2);
|
|
}
|
|
ta.x = pos.getX(i0);
|
|
ta.y = pos.getY(i0);
|
|
ta.z = pos.getZ(i0);
|
|
tb.x = pos.getX(i1);
|
|
tb.y = pos.getY(i1);
|
|
tb.z = pos.getZ(i1);
|
|
tc.x = pos.getX(i2);
|
|
tc.y = pos.getY(i2);
|
|
tc.z = pos.getZ(i2);
|
|
}
|
|
function iterateOverTriangles(offset, count, geometry, intersectsTriangleFunc, contained, depth, triangle) {
|
|
const index = geometry.index;
|
|
const pos = geometry.attributes.position;
|
|
for (let i = offset, l = count + offset; i < l; i++) {
|
|
setTriangle(triangle, i * 3, index, pos);
|
|
triangle.needsUpdate = true;
|
|
if (intersectsTriangleFunc(triangle, i, contained, depth)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
var tempV1 = new Vector3();
|
|
var tempV2 = new Vector3();
|
|
var tempV3 = new Vector3();
|
|
var tempUV1 = new Vector2();
|
|
var tempUV2 = new Vector2();
|
|
var tempUV3 = new Vector2();
|
|
function getTriangleHitPointInfo(point, geometry, triangleIndex, target) {
|
|
const indices = geometry.getIndex().array;
|
|
const positions = geometry.getAttribute("position");
|
|
const uvs = geometry.getAttribute("uv");
|
|
const a = indices[triangleIndex * 3];
|
|
const b = indices[triangleIndex * 3 + 1];
|
|
const c = indices[triangleIndex * 3 + 2];
|
|
tempV1.fromBufferAttribute(positions, a);
|
|
tempV2.fromBufferAttribute(positions, b);
|
|
tempV3.fromBufferAttribute(positions, c);
|
|
let materialIndex = 0;
|
|
const groups = geometry.groups;
|
|
const firstVertexIndex = triangleIndex * 3;
|
|
for (let i = 0, l = groups.length; i < l; i++) {
|
|
const group = groups[i];
|
|
const { start, count } = group;
|
|
if (firstVertexIndex >= start && firstVertexIndex < start + count) {
|
|
materialIndex = group.materialIndex;
|
|
break;
|
|
}
|
|
}
|
|
let uv = null;
|
|
if (uvs) {
|
|
tempUV1.fromBufferAttribute(uvs, a);
|
|
tempUV2.fromBufferAttribute(uvs, b);
|
|
tempUV3.fromBufferAttribute(uvs, c);
|
|
if (target && target.uv) uv = target.uv;
|
|
else uv = new Vector2();
|
|
Triangle.getUV(point, tempV1, tempV2, tempV3, tempUV1, tempUV2, tempUV3, uv);
|
|
}
|
|
if (target) {
|
|
if (!target.face) target.face = {};
|
|
target.face.a = a;
|
|
target.face.b = b;
|
|
target.face.c = c;
|
|
target.face.materialIndex = materialIndex;
|
|
if (!target.face.normal) target.face.normal = new Vector3();
|
|
Triangle.getNormal(tempV1, tempV2, tempV3, target.face.normal);
|
|
if (!target.uv) target.uv = new Vector2();
|
|
target.uv.copy(uv);
|
|
return target;
|
|
} else {
|
|
return {
|
|
face: {
|
|
a,
|
|
b,
|
|
c,
|
|
materialIndex,
|
|
normal: Triangle.getNormal(tempV1, tempV2, tempV3, new Vector3())
|
|
},
|
|
uv
|
|
};
|
|
}
|
|
}
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/utils/PrimitivePool.js
|
|
var PrimitivePool = class {
|
|
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);
|
|
}
|
|
};
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/core/nodeBufferFunctions.js
|
|
function IS_LEAF(n16, uint16Array) {
|
|
return uint16Array[n16 + 15] === 65535;
|
|
}
|
|
function OFFSET(n32, uint32Array) {
|
|
return uint32Array[n32 + 6];
|
|
}
|
|
function COUNT(n16, uint16Array) {
|
|
return uint16Array[n16 + 14];
|
|
}
|
|
function LEFT_NODE(n32) {
|
|
return n32 + 8;
|
|
}
|
|
function RIGHT_NODE(n32, uint32Array) {
|
|
return uint32Array[n32 + 6];
|
|
}
|
|
function SPLIT_AXIS(n32, uint32Array) {
|
|
return uint32Array[n32 + 7];
|
|
}
|
|
function BOUNDING_DATA_INDEX(n32) {
|
|
return n32;
|
|
}
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/core/castFunctions.js
|
|
var boundingBox = new Box3();
|
|
var boxIntersection = new Vector3();
|
|
var xyzFields = ["x", "y", "z"];
|
|
function raycast(nodeIndex32, geometry, side, ray2, intersects) {
|
|
let nodeIndex16 = nodeIndex32 * 2, float32Array = _float32Array, uint16Array = _uint16Array, uint32Array = _uint32Array;
|
|
const isLeaf = IS_LEAF(nodeIndex16, uint16Array);
|
|
if (isLeaf) {
|
|
const offset = OFFSET(nodeIndex32, uint32Array);
|
|
const count = COUNT(nodeIndex16, uint16Array);
|
|
intersectTris(geometry, side, ray2, offset, count, intersects);
|
|
} else {
|
|
const leftIndex = LEFT_NODE(nodeIndex32);
|
|
if (intersectRay(leftIndex, float32Array, ray2, boxIntersection)) {
|
|
raycast(leftIndex, geometry, side, ray2, intersects);
|
|
}
|
|
const rightIndex = RIGHT_NODE(nodeIndex32, uint32Array);
|
|
if (intersectRay(rightIndex, float32Array, ray2, boxIntersection)) {
|
|
raycast(rightIndex, geometry, side, ray2, intersects);
|
|
}
|
|
}
|
|
}
|
|
function raycastFirst(nodeIndex32, geometry, side, ray2) {
|
|
let nodeIndex16 = nodeIndex32 * 2, float32Array = _float32Array, uint16Array = _uint16Array, uint32Array = _uint32Array;
|
|
const isLeaf = IS_LEAF(nodeIndex16, uint16Array);
|
|
if (isLeaf) {
|
|
const offset = OFFSET(nodeIndex32, uint32Array);
|
|
const count = COUNT(nodeIndex16, uint16Array);
|
|
return intersectClosestTri(geometry, side, ray2, offset, count);
|
|
} else {
|
|
const splitAxis = SPLIT_AXIS(nodeIndex32, uint32Array);
|
|
const xyzAxis = xyzFields[splitAxis];
|
|
const rayDir = ray2.direction[xyzAxis];
|
|
const leftToRight = rayDir >= 0;
|
|
let c1, c2;
|
|
if (leftToRight) {
|
|
c1 = LEFT_NODE(nodeIndex32);
|
|
c2 = RIGHT_NODE(nodeIndex32, uint32Array);
|
|
} else {
|
|
c1 = RIGHT_NODE(nodeIndex32, uint32Array);
|
|
c2 = LEFT_NODE(nodeIndex32);
|
|
}
|
|
const c1Intersection = intersectRay(c1, float32Array, ray2, boxIntersection);
|
|
const c1Result = c1Intersection ? raycastFirst(c1, geometry, side, ray2) : null;
|
|
if (c1Result) {
|
|
const point = c1Result.point[xyzAxis];
|
|
const isOutside = leftToRight ? point <= float32Array[c2 + splitAxis] : (
|
|
// min bounding data
|
|
point >= float32Array[c2 + splitAxis + 3]
|
|
);
|
|
if (isOutside) {
|
|
return c1Result;
|
|
}
|
|
}
|
|
const c2Intersection = intersectRay(c2, float32Array, ray2, boxIntersection);
|
|
const c2Result = c2Intersection ? raycastFirst(c2, geometry, side, ray2) : null;
|
|
if (c1Result && c2Result) {
|
|
return c1Result.distance <= c2Result.distance ? c1Result : c2Result;
|
|
} else {
|
|
return c1Result || c2Result || null;
|
|
}
|
|
}
|
|
}
|
|
var shapecast = function() {
|
|
let _box12, _box22;
|
|
const boxStack = [];
|
|
const boxPool = new PrimitivePool(() => new Box3());
|
|
return function shapecast2(...args) {
|
|
_box12 = boxPool.getPrimitive();
|
|
_box22 = boxPool.getPrimitive();
|
|
boxStack.push(_box12, _box22);
|
|
const result = shapecastTraverse(...args);
|
|
boxPool.releasePrimitive(_box12);
|
|
boxPool.releasePrimitive(_box22);
|
|
boxStack.pop();
|
|
boxStack.pop();
|
|
const length = boxStack.length;
|
|
if (length > 0) {
|
|
_box22 = boxStack[length - 1];
|
|
_box12 = boxStack[length - 2];
|
|
}
|
|
return result;
|
|
};
|
|
function shapecastTraverse(nodeIndex32, geometry, intersectsBoundsFunc, intersectsRangeFunc, nodeScoreFunc = null, nodeIndexByteOffset = 0, depth = 0) {
|
|
function getLeftOffset(nodeIndex322) {
|
|
let nodeIndex162 = nodeIndex322 * 2, uint16Array2 = _uint16Array, uint32Array2 = _uint32Array;
|
|
while (!IS_LEAF(nodeIndex162, uint16Array2)) {
|
|
nodeIndex322 = LEFT_NODE(nodeIndex322);
|
|
nodeIndex162 = nodeIndex322 * 2;
|
|
}
|
|
return OFFSET(nodeIndex322, uint32Array2);
|
|
}
|
|
function getRightEndOffset(nodeIndex322) {
|
|
let nodeIndex162 = nodeIndex322 * 2, uint16Array2 = _uint16Array, uint32Array2 = _uint32Array;
|
|
while (!IS_LEAF(nodeIndex162, uint16Array2)) {
|
|
nodeIndex322 = RIGHT_NODE(nodeIndex322, uint32Array2);
|
|
nodeIndex162 = nodeIndex322 * 2;
|
|
}
|
|
return OFFSET(nodeIndex322, uint32Array2) + COUNT(nodeIndex162, uint16Array2);
|
|
}
|
|
let nodeIndex16 = nodeIndex32 * 2, float32Array = _float32Array, uint16Array = _uint16Array, uint32Array = _uint32Array;
|
|
const isLeaf = IS_LEAF(nodeIndex16, uint16Array);
|
|
if (isLeaf) {
|
|
const offset = OFFSET(nodeIndex32, uint32Array);
|
|
const count = COUNT(nodeIndex16, uint16Array);
|
|
arrayToBox(BOUNDING_DATA_INDEX(nodeIndex32), float32Array, _box12);
|
|
return intersectsRangeFunc(offset, count, false, depth, nodeIndexByteOffset + nodeIndex32, _box12);
|
|
} else {
|
|
const left = LEFT_NODE(nodeIndex32);
|
|
const right = RIGHT_NODE(nodeIndex32, uint32Array);
|
|
let c1 = left;
|
|
let c2 = right;
|
|
let score1, score2;
|
|
let box1, box2;
|
|
if (nodeScoreFunc) {
|
|
box1 = _box12;
|
|
box2 = _box22;
|
|
arrayToBox(BOUNDING_DATA_INDEX(c1), float32Array, box1);
|
|
arrayToBox(BOUNDING_DATA_INDEX(c2), float32Array, box2);
|
|
score1 = nodeScoreFunc(box1);
|
|
score2 = nodeScoreFunc(box2);
|
|
if (score2 < score1) {
|
|
c1 = right;
|
|
c2 = left;
|
|
const temp5 = score1;
|
|
score1 = score2;
|
|
score2 = temp5;
|
|
box1 = box2;
|
|
}
|
|
}
|
|
if (!box1) {
|
|
box1 = _box12;
|
|
arrayToBox(BOUNDING_DATA_INDEX(c1), float32Array, box1);
|
|
}
|
|
const isC1Leaf = IS_LEAF(c1 * 2, uint16Array);
|
|
const c1Intersection = intersectsBoundsFunc(box1, isC1Leaf, score1, depth + 1, nodeIndexByteOffset + c1);
|
|
let c1StopTraversal;
|
|
if (c1Intersection === CONTAINED) {
|
|
const offset = getLeftOffset(c1);
|
|
const end = getRightEndOffset(c1);
|
|
const count = end - offset;
|
|
c1StopTraversal = intersectsRangeFunc(offset, count, true, depth + 1, nodeIndexByteOffset + c1, box1);
|
|
} else {
|
|
c1StopTraversal = c1Intersection && shapecastTraverse(
|
|
c1,
|
|
geometry,
|
|
intersectsBoundsFunc,
|
|
intersectsRangeFunc,
|
|
nodeScoreFunc,
|
|
nodeIndexByteOffset,
|
|
depth + 1
|
|
);
|
|
}
|
|
if (c1StopTraversal) return true;
|
|
box2 = _box22;
|
|
arrayToBox(BOUNDING_DATA_INDEX(c2), float32Array, box2);
|
|
const isC2Leaf = IS_LEAF(c2 * 2, uint16Array);
|
|
const c2Intersection = intersectsBoundsFunc(box2, isC2Leaf, score2, depth + 1, nodeIndexByteOffset + c2);
|
|
let c2StopTraversal;
|
|
if (c2Intersection === CONTAINED) {
|
|
const offset = getLeftOffset(c2);
|
|
const end = getRightEndOffset(c2);
|
|
const count = end - offset;
|
|
c2StopTraversal = intersectsRangeFunc(offset, count, true, depth + 1, nodeIndexByteOffset + c2, box2);
|
|
} else {
|
|
c2StopTraversal = c2Intersection && shapecastTraverse(
|
|
c2,
|
|
geometry,
|
|
intersectsBoundsFunc,
|
|
intersectsRangeFunc,
|
|
nodeScoreFunc,
|
|
nodeIndexByteOffset,
|
|
depth + 1
|
|
);
|
|
}
|
|
if (c2StopTraversal) return true;
|
|
return false;
|
|
}
|
|
}
|
|
}();
|
|
var intersectsGeometry = function() {
|
|
const triangle = new ExtendedTriangle();
|
|
const triangle2 = new ExtendedTriangle();
|
|
const invertedMat = new Matrix4();
|
|
const obb3 = new OrientedBox();
|
|
const obb22 = new OrientedBox();
|
|
return function intersectsGeometry2(nodeIndex32, geometry, otherGeometry, geometryToBvh, cachedObb = null) {
|
|
let nodeIndex16 = nodeIndex32 * 2, float32Array = _float32Array, uint16Array = _uint16Array, uint32Array = _uint32Array;
|
|
if (cachedObb === null) {
|
|
if (!otherGeometry.boundingBox) {
|
|
otherGeometry.computeBoundingBox();
|
|
}
|
|
obb3.set(otherGeometry.boundingBox.min, otherGeometry.boundingBox.max, geometryToBvh);
|
|
cachedObb = obb3;
|
|
}
|
|
const isLeaf = IS_LEAF(nodeIndex16, uint16Array);
|
|
if (isLeaf) {
|
|
const thisGeometry = geometry;
|
|
const thisIndex = thisGeometry.index;
|
|
const thisPos = thisGeometry.attributes.position;
|
|
const index = otherGeometry.index;
|
|
const pos = otherGeometry.attributes.position;
|
|
const offset = OFFSET(nodeIndex32, uint32Array);
|
|
const count = COUNT(nodeIndex16, uint16Array);
|
|
invertedMat.copy(geometryToBvh).invert();
|
|
if (otherGeometry.boundsTree) {
|
|
arrayToBox(BOUNDING_DATA_INDEX(nodeIndex32), float32Array, obb22);
|
|
obb22.matrix.copy(invertedMat);
|
|
obb22.needsUpdate = true;
|
|
const res = otherGeometry.boundsTree.shapecast({
|
|
intersectsBounds: (box) => obb22.intersectsBox(box),
|
|
intersectsTriangle: (tri) => {
|
|
tri.a.applyMatrix4(geometryToBvh);
|
|
tri.b.applyMatrix4(geometryToBvh);
|
|
tri.c.applyMatrix4(geometryToBvh);
|
|
tri.needsUpdate = true;
|
|
for (let i = offset * 3, l = (count + offset) * 3; i < l; i += 3) {
|
|
setTriangle(triangle2, i, thisIndex, thisPos);
|
|
triangle2.needsUpdate = true;
|
|
if (tri.intersectsTriangle(triangle2)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
});
|
|
return res;
|
|
} else {
|
|
for (let i = offset * 3, l = count + offset * 3; i < l; i += 3) {
|
|
setTriangle(triangle, i, thisIndex, thisPos);
|
|
triangle.a.applyMatrix4(invertedMat);
|
|
triangle.b.applyMatrix4(invertedMat);
|
|
triangle.c.applyMatrix4(invertedMat);
|
|
triangle.needsUpdate = true;
|
|
for (let i2 = 0, l2 = index.count; i2 < l2; i2 += 3) {
|
|
setTriangle(triangle2, i2, index, pos);
|
|
triangle2.needsUpdate = true;
|
|
if (triangle.intersectsTriangle(triangle2)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
const left = nodeIndex32 + 8;
|
|
const right = uint32Array[nodeIndex32 + 6];
|
|
arrayToBox(BOUNDING_DATA_INDEX(left), float32Array, boundingBox);
|
|
const leftIntersection = cachedObb.intersectsBox(boundingBox) && intersectsGeometry2(left, geometry, otherGeometry, geometryToBvh, cachedObb);
|
|
if (leftIntersection) return true;
|
|
arrayToBox(BOUNDING_DATA_INDEX(right), float32Array, boundingBox);
|
|
const rightIntersection = cachedObb.intersectsBox(boundingBox) && intersectsGeometry2(right, geometry, otherGeometry, geometryToBvh, cachedObb);
|
|
if (rightIntersection) return true;
|
|
return false;
|
|
}
|
|
};
|
|
}();
|
|
function intersectRay(nodeIndex32, array, ray2, target) {
|
|
arrayToBox(nodeIndex32, array, boundingBox);
|
|
return ray2.intersectBox(boundingBox, target);
|
|
}
|
|
var bufferStack = [];
|
|
var _prevBuffer;
|
|
var _float32Array;
|
|
var _uint16Array;
|
|
var _uint32Array;
|
|
function setBuffer(buffer) {
|
|
if (_prevBuffer) {
|
|
bufferStack.push(_prevBuffer);
|
|
}
|
|
_prevBuffer = buffer;
|
|
_float32Array = new Float32Array(buffer);
|
|
_uint16Array = new Uint16Array(buffer);
|
|
_uint32Array = new Uint32Array(buffer);
|
|
}
|
|
function clearBuffer() {
|
|
_prevBuffer = null;
|
|
_float32Array = null;
|
|
_uint16Array = null;
|
|
_uint32Array = null;
|
|
if (bufferStack.length) {
|
|
setBuffer(bufferStack.pop());
|
|
}
|
|
}
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/core/MeshBVH.js
|
|
var SKIP_GENERATION = Symbol("skip tree generation");
|
|
var aabb = new Box3();
|
|
var aabb2 = new Box3();
|
|
var tempMatrix = new Matrix4();
|
|
var obb = new OrientedBox();
|
|
var obb2 = new OrientedBox();
|
|
var temp = new Vector3();
|
|
var temp1 = new Vector3();
|
|
var temp2 = new Vector3();
|
|
var temp3 = new Vector3();
|
|
var temp4 = new Vector3();
|
|
var tempBox = new Box3();
|
|
var trianglePool = new PrimitivePool(() => new ExtendedTriangle());
|
|
var MeshBVH = class _MeshBVH {
|
|
static serialize(bvh, options = {}) {
|
|
if (options.isBufferGeometry) {
|
|
console.warn("MeshBVH.serialize: The arguments for the function have changed. See documentation for new signature.");
|
|
return _MeshBVH.serialize(
|
|
arguments[0],
|
|
{
|
|
cloneBuffers: arguments[2] === void 0 ? true : arguments[2]
|
|
}
|
|
);
|
|
}
|
|
options = {
|
|
cloneBuffers: true,
|
|
...options
|
|
};
|
|
const geometry = bvh.geometry;
|
|
const rootData = bvh._roots;
|
|
const indexAttribute = geometry.getIndex();
|
|
let result;
|
|
if (options.cloneBuffers) {
|
|
result = {
|
|
roots: rootData.map((root) => root.slice()),
|
|
index: indexAttribute.array.slice()
|
|
};
|
|
} else {
|
|
result = {
|
|
roots: rootData,
|
|
index: indexAttribute.array
|
|
};
|
|
}
|
|
return result;
|
|
}
|
|
static deserialize(data, geometry, options = {}) {
|
|
if (typeof options === "boolean") {
|
|
console.warn("MeshBVH.deserialize: The arguments for the function have changed. See documentation for new signature.");
|
|
return _MeshBVH.deserialize(
|
|
arguments[0],
|
|
arguments[1],
|
|
{
|
|
setIndex: arguments[2] === void 0 ? true : arguments[2]
|
|
}
|
|
);
|
|
}
|
|
options = {
|
|
setIndex: true,
|
|
...options
|
|
};
|
|
const { index, roots } = data;
|
|
const bvh = new _MeshBVH(geometry, { ...options, [SKIP_GENERATION]: true });
|
|
bvh._roots = roots;
|
|
if (options.setIndex) {
|
|
const indexAttribute = geometry.getIndex();
|
|
if (indexAttribute === null) {
|
|
const newIndex = new BufferAttribute(data.index, 1, false);
|
|
geometry.setIndex(newIndex);
|
|
} else if (indexAttribute.array !== index) {
|
|
indexAttribute.array.set(index);
|
|
indexAttribute.needsUpdate = true;
|
|
}
|
|
}
|
|
return bvh;
|
|
}
|
|
constructor(geometry, options = {}) {
|
|
if (!geometry.isBufferGeometry) {
|
|
throw new Error("MeshBVH: Only BufferGeometries are supported.");
|
|
} else if (geometry.index && geometry.index.isInterleavedBufferAttribute) {
|
|
throw new Error("MeshBVH: InterleavedBufferAttribute is not supported for the index attribute.");
|
|
}
|
|
options = Object.assign({
|
|
strategy: CENTER,
|
|
maxDepth: 40,
|
|
maxLeafTris: 10,
|
|
verbose: true,
|
|
useSharedArrayBuffer: false,
|
|
setBoundingBox: true,
|
|
onProgress: null,
|
|
// undocumented options
|
|
// Whether to skip generating the tree. Used for deserialization.
|
|
[SKIP_GENERATION]: false
|
|
}, options);
|
|
if (options.useSharedArrayBuffer && typeof SharedArrayBuffer === "undefined") {
|
|
throw new Error("MeshBVH: SharedArrayBuffer is not available.");
|
|
}
|
|
this._roots = null;
|
|
if (!options[SKIP_GENERATION]) {
|
|
this._roots = buildPackedTree(geometry, options);
|
|
if (!geometry.boundingBox && options.setBoundingBox) {
|
|
geometry.boundingBox = this.getBoundingBox(new Box3());
|
|
}
|
|
}
|
|
this.geometry = geometry;
|
|
}
|
|
refit(nodeIndices = null) {
|
|
if (nodeIndices && Array.isArray(nodeIndices)) {
|
|
nodeIndices = new Set(nodeIndices);
|
|
}
|
|
const geometry = this.geometry;
|
|
const indexArr = geometry.index.array;
|
|
const posAttr = geometry.attributes.position;
|
|
let buffer, uint32Array, uint16Array, float32Array;
|
|
let byteOffset = 0;
|
|
const roots = this._roots;
|
|
for (let i = 0, l = roots.length; i < l; i++) {
|
|
buffer = roots[i];
|
|
uint32Array = new Uint32Array(buffer);
|
|
uint16Array = new Uint16Array(buffer);
|
|
float32Array = new Float32Array(buffer);
|
|
_traverse(0, byteOffset);
|
|
byteOffset += buffer.byteLength;
|
|
}
|
|
function _traverse(node32Index, byteOffset2, force = false) {
|
|
const node16Index = node32Index * 2;
|
|
const isLeaf = uint16Array[node16Index + 15] === IS_LEAFNODE_FLAG;
|
|
if (isLeaf) {
|
|
const offset = uint32Array[node32Index + 6];
|
|
const count = uint16Array[node16Index + 14];
|
|
let minx = Infinity;
|
|
let miny = Infinity;
|
|
let minz = Infinity;
|
|
let maxx = -Infinity;
|
|
let maxy = -Infinity;
|
|
let maxz = -Infinity;
|
|
for (let i = 3 * offset, l = 3 * (offset + count); i < l; i++) {
|
|
const index = indexArr[i];
|
|
const x = posAttr.getX(index);
|
|
const y = posAttr.getY(index);
|
|
const z = posAttr.getZ(index);
|
|
if (x < minx) minx = x;
|
|
if (x > maxx) maxx = x;
|
|
if (y < miny) miny = y;
|
|
if (y > maxy) maxy = y;
|
|
if (z < minz) minz = z;
|
|
if (z > maxz) maxz = z;
|
|
}
|
|
if (float32Array[node32Index + 0] !== minx || float32Array[node32Index + 1] !== miny || float32Array[node32Index + 2] !== minz || float32Array[node32Index + 3] !== maxx || float32Array[node32Index + 4] !== maxy || float32Array[node32Index + 5] !== maxz) {
|
|
float32Array[node32Index + 0] = minx;
|
|
float32Array[node32Index + 1] = miny;
|
|
float32Array[node32Index + 2] = minz;
|
|
float32Array[node32Index + 3] = maxx;
|
|
float32Array[node32Index + 4] = maxy;
|
|
float32Array[node32Index + 5] = maxz;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
const left = node32Index + 8;
|
|
const right = uint32Array[node32Index + 6];
|
|
const offsetLeft = left + byteOffset2;
|
|
const offsetRight = right + byteOffset2;
|
|
let forceChildren = force;
|
|
let includesLeft = false;
|
|
let includesRight = false;
|
|
if (nodeIndices) {
|
|
if (!forceChildren) {
|
|
includesLeft = nodeIndices.has(offsetLeft);
|
|
includesRight = nodeIndices.has(offsetRight);
|
|
forceChildren = !includesLeft && !includesRight;
|
|
}
|
|
} else {
|
|
includesLeft = true;
|
|
includesRight = true;
|
|
}
|
|
const traverseLeft = forceChildren || includesLeft;
|
|
const traverseRight = forceChildren || includesRight;
|
|
let leftChange = false;
|
|
if (traverseLeft) {
|
|
leftChange = _traverse(left, byteOffset2, forceChildren);
|
|
}
|
|
let rightChange = false;
|
|
if (traverseRight) {
|
|
rightChange = _traverse(right, byteOffset2, forceChildren);
|
|
}
|
|
const didChange = leftChange || rightChange;
|
|
if (didChange) {
|
|
for (let i = 0; i < 3; i++) {
|
|
const lefti = left + i;
|
|
const righti = right + i;
|
|
const minLeftValue = float32Array[lefti];
|
|
const maxLeftValue = float32Array[lefti + 3];
|
|
const minRightValue = float32Array[righti];
|
|
const maxRightValue = float32Array[righti + 3];
|
|
float32Array[node32Index + i] = minLeftValue < minRightValue ? minLeftValue : minRightValue;
|
|
float32Array[node32Index + i + 3] = maxLeftValue > maxRightValue ? maxLeftValue : maxRightValue;
|
|
}
|
|
}
|
|
return didChange;
|
|
}
|
|
}
|
|
}
|
|
traverse(callback, rootIndex = 0) {
|
|
const buffer = this._roots[rootIndex];
|
|
const uint32Array = new Uint32Array(buffer);
|
|
const uint16Array = new Uint16Array(buffer);
|
|
_traverse(0);
|
|
function _traverse(node32Index, depth = 0) {
|
|
const node16Index = node32Index * 2;
|
|
const isLeaf = uint16Array[node16Index + 15] === IS_LEAFNODE_FLAG;
|
|
if (isLeaf) {
|
|
const offset = uint32Array[node32Index + 6];
|
|
const count = uint16Array[node16Index + 14];
|
|
callback(depth, isLeaf, new Float32Array(buffer, node32Index * 4, 6), offset, count);
|
|
} else {
|
|
const left = node32Index + BYTES_PER_NODE / 4;
|
|
const right = uint32Array[node32Index + 6];
|
|
const splitAxis = uint32Array[node32Index + 7];
|
|
const stopTraversal = callback(depth, isLeaf, new Float32Array(buffer, node32Index * 4, 6), splitAxis);
|
|
if (!stopTraversal) {
|
|
_traverse(left, depth + 1);
|
|
_traverse(right, depth + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* Core Cast Functions */
|
|
raycast(ray2, materialOrSide = FrontSide) {
|
|
const roots = this._roots;
|
|
const geometry = this.geometry;
|
|
const intersects = [];
|
|
const isMaterial = materialOrSide.isMaterial;
|
|
const isArrayMaterial = Array.isArray(materialOrSide);
|
|
const groups = geometry.groups;
|
|
const side = isMaterial ? materialOrSide.side : materialOrSide;
|
|
for (let i = 0, l = roots.length; i < l; i++) {
|
|
const materialSide = isArrayMaterial ? materialOrSide[groups[i].materialIndex].side : side;
|
|
const startCount = intersects.length;
|
|
setBuffer(roots[i]);
|
|
raycast(0, geometry, materialSide, ray2, intersects);
|
|
clearBuffer();
|
|
if (isArrayMaterial) {
|
|
const materialIndex = groups[i].materialIndex;
|
|
for (let j = startCount, jl = intersects.length; j < jl; j++) {
|
|
intersects[j].face.materialIndex = materialIndex;
|
|
}
|
|
}
|
|
}
|
|
return intersects;
|
|
}
|
|
raycastFirst(ray2, materialOrSide = FrontSide) {
|
|
const roots = this._roots;
|
|
const geometry = this.geometry;
|
|
const isMaterial = materialOrSide.isMaterial;
|
|
const isArrayMaterial = Array.isArray(materialOrSide);
|
|
let closestResult = null;
|
|
const groups = geometry.groups;
|
|
const side = isMaterial ? materialOrSide.side : materialOrSide;
|
|
for (let i = 0, l = roots.length; i < l; i++) {
|
|
const materialSide = isArrayMaterial ? materialOrSide[groups[i].materialIndex].side : side;
|
|
setBuffer(roots[i]);
|
|
const result = raycastFirst(0, geometry, materialSide, ray2);
|
|
clearBuffer();
|
|
if (result != null && (closestResult == null || result.distance < closestResult.distance)) {
|
|
closestResult = result;
|
|
if (isArrayMaterial) {
|
|
result.face.materialIndex = groups[i].materialIndex;
|
|
}
|
|
}
|
|
}
|
|
return closestResult;
|
|
}
|
|
intersectsGeometry(otherGeometry, geomToMesh) {
|
|
const geometry = this.geometry;
|
|
let result = false;
|
|
for (const root of this._roots) {
|
|
setBuffer(root);
|
|
result = intersectsGeometry(0, geometry, otherGeometry, geomToMesh);
|
|
clearBuffer();
|
|
if (result) {
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
shapecast(callbacks, _intersectsTriangleFunc, _orderNodesFunc) {
|
|
const geometry = this.geometry;
|
|
if (callbacks instanceof Function) {
|
|
if (_intersectsTriangleFunc) {
|
|
const originalTriangleFunc = _intersectsTriangleFunc;
|
|
_intersectsTriangleFunc = (tri, index, contained, depth) => {
|
|
const i3 = index * 3;
|
|
return originalTriangleFunc(tri, i3, i3 + 1, i3 + 2, contained, depth);
|
|
};
|
|
}
|
|
callbacks = {
|
|
boundsTraverseOrder: _orderNodesFunc,
|
|
intersectsBounds: callbacks,
|
|
intersectsTriangle: _intersectsTriangleFunc,
|
|
intersectsRange: null
|
|
};
|
|
console.warn("MeshBVH: Shapecast function signature has changed and now takes an object of callbacks as a second argument. See docs for new signature.");
|
|
}
|
|
const triangle = trianglePool.getPrimitive();
|
|
let {
|
|
boundsTraverseOrder,
|
|
intersectsBounds,
|
|
intersectsRange,
|
|
intersectsTriangle
|
|
} = callbacks;
|
|
if (intersectsRange && intersectsTriangle) {
|
|
const originalIntersectsRange = intersectsRange;
|
|
intersectsRange = (offset, count, contained, depth, nodeIndex) => {
|
|
if (!originalIntersectsRange(offset, count, contained, depth, nodeIndex)) {
|
|
return iterateOverTriangles(offset, count, geometry, intersectsTriangle, contained, depth, triangle);
|
|
}
|
|
return true;
|
|
};
|
|
} else if (!intersectsRange) {
|
|
if (intersectsTriangle) {
|
|
intersectsRange = (offset, count, contained, depth) => {
|
|
return iterateOverTriangles(offset, count, geometry, intersectsTriangle, contained, depth, triangle);
|
|
};
|
|
} else {
|
|
intersectsRange = (offset, count, contained) => {
|
|
return contained;
|
|
};
|
|
}
|
|
}
|
|
let result = false;
|
|
let byteOffset = 0;
|
|
for (const root of this._roots) {
|
|
setBuffer(root);
|
|
result = shapecast(0, geometry, intersectsBounds, intersectsRange, boundsTraverseOrder, byteOffset);
|
|
clearBuffer();
|
|
if (result) {
|
|
break;
|
|
}
|
|
byteOffset += root.byteLength;
|
|
}
|
|
trianglePool.releasePrimitive(triangle);
|
|
return result;
|
|
}
|
|
bvhcast(otherBvh, matrixToLocal, callbacks) {
|
|
let {
|
|
intersectsRanges,
|
|
intersectsTriangles
|
|
} = callbacks;
|
|
const indexAttr = this.geometry.index;
|
|
const positionAttr = this.geometry.attributes.position;
|
|
const otherIndexAttr = otherBvh.geometry.index;
|
|
const otherPositionAttr = otherBvh.geometry.attributes.position;
|
|
tempMatrix.copy(matrixToLocal).invert();
|
|
const triangle = trianglePool.getPrimitive();
|
|
const triangle2 = trianglePool.getPrimitive();
|
|
if (intersectsTriangles) {
|
|
let iterateOverDoubleTriangles = function(offset1, count1, offset2, count2, depth1, index1, depth2, index2) {
|
|
for (let i2 = offset2, l2 = offset2 + count2; i2 < l2; i2++) {
|
|
setTriangle(triangle2, i2 * 3, otherIndexAttr, otherPositionAttr);
|
|
triangle2.a.applyMatrix4(matrixToLocal);
|
|
triangle2.b.applyMatrix4(matrixToLocal);
|
|
triangle2.c.applyMatrix4(matrixToLocal);
|
|
triangle2.needsUpdate = true;
|
|
for (let i1 = offset1, l1 = offset1 + count1; i1 < l1; i1++) {
|
|
setTriangle(triangle, i1 * 3, indexAttr, positionAttr);
|
|
triangle.needsUpdate = true;
|
|
if (intersectsTriangles(triangle, triangle2, i1, i2, depth1, index1, depth2, index2)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
if (intersectsRanges) {
|
|
const originalIntersectsRanges = intersectsRanges;
|
|
intersectsRanges = function(offset1, count1, offset2, count2, depth1, index1, depth2, index2) {
|
|
if (!originalIntersectsRanges(offset1, count1, offset2, count2, depth1, index1, depth2, index2)) {
|
|
return iterateOverDoubleTriangles(offset1, count1, offset2, count2, depth1, index1, depth2, index2);
|
|
}
|
|
return true;
|
|
};
|
|
} else {
|
|
intersectsRanges = iterateOverDoubleTriangles;
|
|
}
|
|
}
|
|
otherBvh.getBoundingBox(aabb2);
|
|
aabb2.applyMatrix4(matrixToLocal);
|
|
const result = this.shapecast({
|
|
intersectsBounds: (box) => aabb2.intersectsBox(box),
|
|
intersectsRange: (offset1, count1, contained, depth1, nodeIndex1, box) => {
|
|
aabb.copy(box);
|
|
aabb.applyMatrix4(tempMatrix);
|
|
return otherBvh.shapecast({
|
|
intersectsBounds: (box2) => aabb.intersectsBox(box2),
|
|
intersectsRange: (offset2, count2, contained2, depth2, nodeIndex2) => {
|
|
return intersectsRanges(offset1, count1, offset2, count2, depth1, nodeIndex1, depth2, nodeIndex2);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
trianglePool.releasePrimitive(triangle);
|
|
trianglePool.releasePrimitive(triangle2);
|
|
return result;
|
|
}
|
|
/* Derived Cast Functions */
|
|
intersectsBox(box, boxToMesh) {
|
|
obb.set(box.min, box.max, boxToMesh);
|
|
obb.needsUpdate = true;
|
|
return this.shapecast(
|
|
{
|
|
intersectsBounds: (box2) => obb.intersectsBox(box2),
|
|
intersectsTriangle: (tri) => obb.intersectsTriangle(tri)
|
|
}
|
|
);
|
|
}
|
|
intersectsSphere(sphere) {
|
|
return this.shapecast(
|
|
{
|
|
intersectsBounds: (box) => sphere.intersectsBox(box),
|
|
intersectsTriangle: (tri) => tri.intersectsSphere(sphere)
|
|
}
|
|
);
|
|
}
|
|
closestPointToGeometry(otherGeometry, geometryToBvh, target1 = {}, target2 = {}, minThreshold = 0, maxThreshold = Infinity) {
|
|
if (!otherGeometry.boundingBox) {
|
|
otherGeometry.computeBoundingBox();
|
|
}
|
|
obb.set(otherGeometry.boundingBox.min, otherGeometry.boundingBox.max, geometryToBvh);
|
|
obb.needsUpdate = true;
|
|
const geometry = this.geometry;
|
|
const pos = geometry.attributes.position;
|
|
const index = geometry.index;
|
|
const otherPos = otherGeometry.attributes.position;
|
|
const otherIndex = otherGeometry.index;
|
|
const triangle = trianglePool.getPrimitive();
|
|
const triangle2 = trianglePool.getPrimitive();
|
|
let tempTarget1 = temp1;
|
|
let tempTargetDest1 = temp2;
|
|
let tempTarget2 = null;
|
|
let tempTargetDest2 = null;
|
|
if (target2) {
|
|
tempTarget2 = temp3;
|
|
tempTargetDest2 = temp4;
|
|
}
|
|
let closestDistance = Infinity;
|
|
let closestDistanceTriIndex = null;
|
|
let closestDistanceOtherTriIndex = null;
|
|
tempMatrix.copy(geometryToBvh).invert();
|
|
obb2.matrix.copy(tempMatrix);
|
|
this.shapecast(
|
|
{
|
|
boundsTraverseOrder: (box) => {
|
|
return obb.distanceToBox(box);
|
|
},
|
|
intersectsBounds: (box, isLeaf, score) => {
|
|
if (score < closestDistance && score < maxThreshold) {
|
|
if (isLeaf) {
|
|
obb2.min.copy(box.min);
|
|
obb2.max.copy(box.max);
|
|
obb2.needsUpdate = true;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
intersectsRange: (offset, count) => {
|
|
if (otherGeometry.boundsTree) {
|
|
return otherGeometry.boundsTree.shapecast({
|
|
boundsTraverseOrder: (box) => {
|
|
return obb2.distanceToBox(box);
|
|
},
|
|
intersectsBounds: (box, isLeaf, score) => {
|
|
return score < closestDistance && score < maxThreshold;
|
|
},
|
|
intersectsRange: (otherOffset, otherCount) => {
|
|
for (let i2 = otherOffset * 3, l2 = (otherOffset + otherCount) * 3; i2 < l2; i2 += 3) {
|
|
setTriangle(triangle2, i2, otherIndex, otherPos);
|
|
triangle2.a.applyMatrix4(geometryToBvh);
|
|
triangle2.b.applyMatrix4(geometryToBvh);
|
|
triangle2.c.applyMatrix4(geometryToBvh);
|
|
triangle2.needsUpdate = true;
|
|
for (let i = offset * 3, l = (offset + count) * 3; i < l; i += 3) {
|
|
setTriangle(triangle, i, index, pos);
|
|
triangle.needsUpdate = true;
|
|
const dist = triangle.distanceToTriangle(triangle2, tempTarget1, tempTarget2);
|
|
if (dist < closestDistance) {
|
|
tempTargetDest1.copy(tempTarget1);
|
|
if (tempTargetDest2) {
|
|
tempTargetDest2.copy(tempTarget2);
|
|
}
|
|
closestDistance = dist;
|
|
closestDistanceTriIndex = i / 3;
|
|
closestDistanceOtherTriIndex = i2 / 3;
|
|
}
|
|
if (dist < minThreshold) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
const triCount = otherIndex ? otherIndex.count : otherPos.count;
|
|
for (let i2 = 0, l2 = triCount; i2 < l2; i2 += 3) {
|
|
setTriangle(triangle2, i2, otherIndex, otherPos);
|
|
triangle2.a.applyMatrix4(geometryToBvh);
|
|
triangle2.b.applyMatrix4(geometryToBvh);
|
|
triangle2.c.applyMatrix4(geometryToBvh);
|
|
triangle2.needsUpdate = true;
|
|
for (let i = offset * 3, l = (offset + count) * 3; i < l; i += 3) {
|
|
setTriangle(triangle, i, index, pos);
|
|
triangle.needsUpdate = true;
|
|
const dist = triangle.distanceToTriangle(triangle2, tempTarget1, tempTarget2);
|
|
if (dist < closestDistance) {
|
|
tempTargetDest1.copy(tempTarget1);
|
|
if (tempTargetDest2) {
|
|
tempTargetDest2.copy(tempTarget2);
|
|
}
|
|
closestDistance = dist;
|
|
closestDistanceTriIndex = i / 3;
|
|
closestDistanceOtherTriIndex = i2 / 3;
|
|
}
|
|
if (dist < minThreshold) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
);
|
|
trianglePool.releasePrimitive(triangle);
|
|
trianglePool.releasePrimitive(triangle2);
|
|
if (closestDistance === Infinity) return null;
|
|
if (!target1.point) target1.point = tempTargetDest1.clone();
|
|
else target1.point.copy(tempTargetDest1);
|
|
target1.distance = closestDistance, target1.faceIndex = closestDistanceTriIndex;
|
|
if (target2) {
|
|
if (!target2.point) target2.point = tempTargetDest2.clone();
|
|
else target2.point.copy(tempTargetDest2);
|
|
target2.point.applyMatrix4(tempMatrix);
|
|
tempTargetDest1.applyMatrix4(tempMatrix);
|
|
target2.distance = tempTargetDest1.sub(target2.point).length();
|
|
target2.faceIndex = closestDistanceOtherTriIndex;
|
|
}
|
|
return target1;
|
|
}
|
|
closestPointToPoint(point, target = {}, minThreshold = 0, maxThreshold = Infinity) {
|
|
const minThresholdSq = minThreshold * minThreshold;
|
|
const maxThresholdSq = maxThreshold * maxThreshold;
|
|
let closestDistanceSq = Infinity;
|
|
let closestDistanceTriIndex = null;
|
|
this.shapecast(
|
|
{
|
|
boundsTraverseOrder: (box) => {
|
|
temp.copy(point).clamp(box.min, box.max);
|
|
return temp.distanceToSquared(point);
|
|
},
|
|
intersectsBounds: (box, isLeaf, score) => {
|
|
return score < closestDistanceSq && score < maxThresholdSq;
|
|
},
|
|
intersectsTriangle: (tri, triIndex) => {
|
|
tri.closestPointToPoint(point, temp);
|
|
const distSq = point.distanceToSquared(temp);
|
|
if (distSq < closestDistanceSq) {
|
|
temp1.copy(temp);
|
|
closestDistanceSq = distSq;
|
|
closestDistanceTriIndex = triIndex;
|
|
}
|
|
if (distSq < minThresholdSq) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
);
|
|
if (closestDistanceSq === Infinity) return null;
|
|
const closestDistance = Math.sqrt(closestDistanceSq);
|
|
if (!target.point) target.point = temp1.clone();
|
|
else target.point.copy(temp1);
|
|
target.distance = closestDistance, target.faceIndex = closestDistanceTriIndex;
|
|
return target;
|
|
}
|
|
getBoundingBox(target) {
|
|
target.makeEmpty();
|
|
const roots = this._roots;
|
|
roots.forEach((buffer) => {
|
|
arrayToBox(0, new Float32Array(buffer), tempBox);
|
|
target.union(tempBox);
|
|
});
|
|
return target;
|
|
}
|
|
};
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/objects/MeshBVHVisualizer.js
|
|
var boundingBox2 = new Box3();
|
|
var MeshBVHRootVisualizer = class extends Object3D {
|
|
get isMesh() {
|
|
return !this.displayEdges;
|
|
}
|
|
get isLineSegments() {
|
|
return this.displayEdges;
|
|
}
|
|
get isLine() {
|
|
return this.displayEdges;
|
|
}
|
|
constructor(mesh, material, depth = 10, group = 0) {
|
|
super();
|
|
this.material = material;
|
|
this.geometry = new BufferGeometry();
|
|
this.name = "MeshBVHRootVisualizer";
|
|
this.depth = depth;
|
|
this.displayParents = false;
|
|
this.mesh = mesh;
|
|
this.displayEdges = true;
|
|
this._group = group;
|
|
}
|
|
raycast() {
|
|
}
|
|
update() {
|
|
const geometry = this.geometry;
|
|
const boundsTree = this.mesh.geometry.boundsTree;
|
|
const group = this._group;
|
|
geometry.dispose();
|
|
this.visible = false;
|
|
if (boundsTree) {
|
|
const targetDepth = this.depth - 1;
|
|
const displayParents = this.displayParents;
|
|
let boundsCount = 0;
|
|
boundsTree.traverse((depth, isLeaf) => {
|
|
if (depth === targetDepth || isLeaf) {
|
|
boundsCount++;
|
|
return true;
|
|
} else if (displayParents) {
|
|
boundsCount++;
|
|
}
|
|
}, group);
|
|
let posIndex = 0;
|
|
const positionArray = new Float32Array(8 * 3 * boundsCount);
|
|
boundsTree.traverse((depth, isLeaf, boundingData) => {
|
|
const terminate = depth === targetDepth || isLeaf;
|
|
if (terminate || displayParents) {
|
|
arrayToBox(0, boundingData, boundingBox2);
|
|
const { min, max } = boundingBox2;
|
|
for (let x = -1; x <= 1; x += 2) {
|
|
const xVal = x < 0 ? min.x : max.x;
|
|
for (let y = -1; y <= 1; y += 2) {
|
|
const yVal = y < 0 ? min.y : max.y;
|
|
for (let z = -1; z <= 1; z += 2) {
|
|
const zVal = z < 0 ? min.z : max.z;
|
|
positionArray[posIndex + 0] = xVal;
|
|
positionArray[posIndex + 1] = yVal;
|
|
positionArray[posIndex + 2] = zVal;
|
|
posIndex += 3;
|
|
}
|
|
}
|
|
}
|
|
return terminate;
|
|
}
|
|
}, group);
|
|
let indexArray;
|
|
let indices;
|
|
if (this.displayEdges) {
|
|
indices = new Uint8Array([
|
|
// x axis
|
|
0,
|
|
4,
|
|
1,
|
|
5,
|
|
2,
|
|
6,
|
|
3,
|
|
7,
|
|
// y axis
|
|
0,
|
|
2,
|
|
1,
|
|
3,
|
|
4,
|
|
6,
|
|
5,
|
|
7,
|
|
// z axis
|
|
0,
|
|
1,
|
|
2,
|
|
3,
|
|
4,
|
|
5,
|
|
6,
|
|
7
|
|
]);
|
|
} else {
|
|
indices = new Uint8Array([
|
|
// X-, X+
|
|
0,
|
|
1,
|
|
2,
|
|
2,
|
|
1,
|
|
3,
|
|
4,
|
|
6,
|
|
5,
|
|
6,
|
|
7,
|
|
5,
|
|
// Y-, Y+
|
|
1,
|
|
4,
|
|
5,
|
|
0,
|
|
4,
|
|
1,
|
|
2,
|
|
3,
|
|
6,
|
|
3,
|
|
7,
|
|
6,
|
|
// Z-, Z+
|
|
0,
|
|
2,
|
|
4,
|
|
2,
|
|
6,
|
|
4,
|
|
1,
|
|
5,
|
|
3,
|
|
3,
|
|
5,
|
|
7
|
|
]);
|
|
}
|
|
if (positionArray.length > 65535) {
|
|
indexArray = new Uint32Array(indices.length * boundsCount);
|
|
} else {
|
|
indexArray = new Uint16Array(indices.length * boundsCount);
|
|
}
|
|
const indexLength = indices.length;
|
|
for (let i = 0; i < boundsCount; i++) {
|
|
const posOffset = i * 8;
|
|
const indexOffset = i * indexLength;
|
|
for (let j = 0; j < indexLength; j++) {
|
|
indexArray[indexOffset + j] = posOffset + indices[j];
|
|
}
|
|
}
|
|
geometry.setIndex(
|
|
new BufferAttribute(indexArray, 1, false)
|
|
);
|
|
geometry.setAttribute(
|
|
"position",
|
|
new BufferAttribute(positionArray, 3, false)
|
|
);
|
|
this.visible = true;
|
|
}
|
|
}
|
|
};
|
|
var MeshBVHVisualizer = class _MeshBVHVisualizer extends Group {
|
|
get color() {
|
|
return this.edgeMaterial.color;
|
|
}
|
|
get opacity() {
|
|
return this.edgeMaterial.opacity;
|
|
}
|
|
set opacity(v) {
|
|
this.edgeMaterial.opacity = v;
|
|
this.meshMaterial.opacity = v;
|
|
}
|
|
constructor(mesh, depth = 10) {
|
|
super();
|
|
this.name = "MeshBVHVisualizer";
|
|
this.depth = depth;
|
|
this.mesh = mesh;
|
|
this.displayParents = false;
|
|
this.displayEdges = true;
|
|
this._roots = [];
|
|
const edgeMaterial = new LineBasicMaterial({
|
|
color: 65416,
|
|
transparent: true,
|
|
opacity: 0.3,
|
|
depthWrite: false
|
|
});
|
|
const meshMaterial = new MeshBasicMaterial({
|
|
color: 65416,
|
|
transparent: true,
|
|
opacity: 0.3,
|
|
depthWrite: false
|
|
});
|
|
meshMaterial.color = edgeMaterial.color;
|
|
this.edgeMaterial = edgeMaterial;
|
|
this.meshMaterial = meshMaterial;
|
|
this.update();
|
|
}
|
|
update() {
|
|
const bvh = this.mesh.geometry.boundsTree;
|
|
const totalRoots = bvh ? bvh._roots.length : 0;
|
|
while (this._roots.length > totalRoots) {
|
|
const root = this._roots.pop();
|
|
root.geometry.dispose();
|
|
this.remove(root);
|
|
}
|
|
for (let i = 0; i < totalRoots; i++) {
|
|
if (i >= this._roots.length) {
|
|
const root2 = new MeshBVHRootVisualizer(this.mesh, this.edgeMaterial, this.depth, i);
|
|
this.add(root2);
|
|
this._roots.push(root2);
|
|
}
|
|
const root = this._roots[i];
|
|
root.depth = this.depth;
|
|
root.mesh = this.mesh;
|
|
root.displayParents = this.displayParents;
|
|
root.displayEdges = this.displayEdges;
|
|
root.material = this.displayEdges ? this.edgeMaterial : this.meshMaterial;
|
|
root.update();
|
|
}
|
|
}
|
|
updateMatrixWorld(...args) {
|
|
this.position.copy(this.mesh.position);
|
|
this.rotation.copy(this.mesh.rotation);
|
|
this.scale.copy(this.mesh.scale);
|
|
super.updateMatrixWorld(...args);
|
|
}
|
|
copy(source) {
|
|
this.depth = source.depth;
|
|
this.mesh = source.mesh;
|
|
}
|
|
clone() {
|
|
return new _MeshBVHVisualizer(this.mesh, this.depth);
|
|
}
|
|
dispose() {
|
|
this.edgeMaterial.dispose();
|
|
this.meshMaterial.dispose();
|
|
const children = this.children;
|
|
for (let i = 0, l = children.length; i < l; i++) {
|
|
children[i].geometry.dispose();
|
|
}
|
|
}
|
|
};
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/debug/Debug.js
|
|
var _box1 = new Box3();
|
|
var _box2 = new Box3();
|
|
var _vec = new Vector3();
|
|
function getPrimitiveSize(el) {
|
|
switch (typeof el) {
|
|
case "number":
|
|
return 8;
|
|
case "string":
|
|
return el.length * 2;
|
|
case "boolean":
|
|
return 4;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
function isTypedArray(arr) {
|
|
const regex = /(Uint|Int|Float)(8|16|32)Array/;
|
|
return regex.test(arr.constructor.name);
|
|
}
|
|
function getRootExtremes(bvh, group) {
|
|
const result = {
|
|
nodeCount: 0,
|
|
leafNodeCount: 0,
|
|
depth: {
|
|
min: Infinity,
|
|
max: -Infinity
|
|
},
|
|
tris: {
|
|
min: Infinity,
|
|
max: -Infinity
|
|
},
|
|
splits: [0, 0, 0],
|
|
surfaceAreaScore: 0
|
|
};
|
|
bvh.traverse((depth, isLeaf, boundingData, offsetOrSplit, count) => {
|
|
const l0 = boundingData[0 + 3] - boundingData[0];
|
|
const l1 = boundingData[1 + 3] - boundingData[1];
|
|
const l2 = boundingData[2 + 3] - boundingData[2];
|
|
const surfaceArea = 2 * (l0 * l1 + l1 * l2 + l2 * l0);
|
|
result.nodeCount++;
|
|
if (isLeaf) {
|
|
result.leafNodeCount++;
|
|
result.depth.min = Math.min(depth, result.depth.min);
|
|
result.depth.max = Math.max(depth, result.depth.max);
|
|
result.tris.min = Math.min(count, result.tris.min);
|
|
result.tris.max = Math.max(count, result.tris.max);
|
|
result.surfaceAreaScore += surfaceArea * TRIANGLE_INTERSECT_COST * count;
|
|
} else {
|
|
result.splits[offsetOrSplit]++;
|
|
result.surfaceAreaScore += surfaceArea * TRAVERSAL_COST;
|
|
}
|
|
}, group);
|
|
if (result.tris.min === Infinity) {
|
|
result.tris.min = 0;
|
|
result.tris.max = 0;
|
|
}
|
|
if (result.depth.min === Infinity) {
|
|
result.depth.min = 0;
|
|
result.depth.max = 0;
|
|
}
|
|
return result;
|
|
}
|
|
function getBVHExtremes(bvh) {
|
|
return bvh._roots.map((root, i) => getRootExtremes(bvh, i));
|
|
}
|
|
function estimateMemoryInBytes(obj) {
|
|
const traversed = /* @__PURE__ */ new Set();
|
|
const stack = [obj];
|
|
let bytes = 0;
|
|
while (stack.length) {
|
|
const curr = stack.pop();
|
|
if (traversed.has(curr)) {
|
|
continue;
|
|
}
|
|
traversed.add(curr);
|
|
for (let key in curr) {
|
|
if (!curr.hasOwnProperty(key)) {
|
|
continue;
|
|
}
|
|
bytes += getPrimitiveSize(key);
|
|
const value = curr[key];
|
|
if (value && (typeof value === "object" || typeof value === "function")) {
|
|
if (isTypedArray(value)) {
|
|
bytes += value.byteLength;
|
|
} else if (value instanceof ArrayBuffer) {
|
|
bytes += value.byteLength;
|
|
} else {
|
|
stack.push(value);
|
|
}
|
|
} else {
|
|
bytes += getPrimitiveSize(value);
|
|
}
|
|
}
|
|
}
|
|
return bytes;
|
|
}
|
|
function validateBounds(bvh) {
|
|
const geometry = bvh.geometry;
|
|
const depthStack = [];
|
|
const index = geometry.index;
|
|
const position = geometry.getAttribute("position");
|
|
let passes = true;
|
|
bvh.traverse((depth, isLeaf, boundingData, offset, count) => {
|
|
const info = {
|
|
depth,
|
|
isLeaf,
|
|
boundingData,
|
|
offset,
|
|
count
|
|
};
|
|
depthStack[depth] = info;
|
|
arrayToBox(0, boundingData, _box1);
|
|
const parent = depthStack[depth - 1];
|
|
if (isLeaf) {
|
|
for (let i = offset * 3, l = (offset + count) * 3; i < l; i += 3) {
|
|
const i0 = index.getX(i);
|
|
const i1 = index.getX(i + 1);
|
|
const i2 = index.getX(i + 2);
|
|
let isContained;
|
|
_vec.fromBufferAttribute(position, i0);
|
|
isContained = _box1.containsPoint(_vec);
|
|
_vec.fromBufferAttribute(position, i1);
|
|
isContained = isContained && _box1.containsPoint(_vec);
|
|
_vec.fromBufferAttribute(position, i2);
|
|
isContained = isContained && _box1.containsPoint(_vec);
|
|
console.assert(isContained, "Leaf bounds does not fully contain triangle.");
|
|
passes = passes && isContained;
|
|
}
|
|
}
|
|
if (parent) {
|
|
arrayToBox(0, boundingData, _box2);
|
|
const isContained = _box2.containsBox(_box1);
|
|
console.assert(isContained, "Parent bounds does not fully contain child.");
|
|
passes = passes && isContained;
|
|
}
|
|
});
|
|
return passes;
|
|
}
|
|
function getJSONStructure(bvh) {
|
|
const depthStack = [];
|
|
bvh.traverse((depth, isLeaf, boundingData, offset, count) => {
|
|
const info = {
|
|
bounds: arrayToBox(0, boundingData, new Box3())
|
|
};
|
|
if (isLeaf) {
|
|
info.count = count;
|
|
info.offset = offset;
|
|
} else {
|
|
info.left = null;
|
|
info.right = null;
|
|
}
|
|
depthStack[depth] = info;
|
|
const parent = depthStack[depth - 1];
|
|
if (parent) {
|
|
if (parent.left === null) {
|
|
parent.left = info;
|
|
} else {
|
|
parent.right = info;
|
|
}
|
|
}
|
|
});
|
|
return depthStack[0];
|
|
}
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/utils/ExtensionUtilities.js
|
|
var ray = new Ray();
|
|
var tmpInverseMatrix = new Matrix4();
|
|
var origMeshRaycastFunc = Mesh.prototype.raycast;
|
|
function acceleratedRaycast(raycaster, intersects) {
|
|
if (this.geometry.boundsTree) {
|
|
if (this.material === void 0) return;
|
|
tmpInverseMatrix.copy(this.matrixWorld).invert();
|
|
ray.copy(raycaster.ray).applyMatrix4(tmpInverseMatrix);
|
|
const bvh = this.geometry.boundsTree;
|
|
if (raycaster.firstHitOnly === true) {
|
|
const hit = convertRaycastIntersect(bvh.raycastFirst(ray, this.material), this, raycaster);
|
|
if (hit) {
|
|
intersects.push(hit);
|
|
}
|
|
} else {
|
|
const hits = bvh.raycast(ray, this.material);
|
|
for (let i = 0, l = hits.length; i < l; i++) {
|
|
const hit = convertRaycastIntersect(hits[i], this, raycaster);
|
|
if (hit) {
|
|
intersects.push(hit);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
origMeshRaycastFunc.call(this, raycaster, intersects);
|
|
}
|
|
}
|
|
function computeBoundsTree(options) {
|
|
this.boundsTree = new MeshBVH(this, options);
|
|
return this.boundsTree;
|
|
}
|
|
function disposeBoundsTree() {
|
|
this.boundsTree = null;
|
|
}
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/gpu/VertexAttributeTexture.js
|
|
function countToStringFormat(count) {
|
|
switch (count) {
|
|
case 1:
|
|
return "R";
|
|
case 2:
|
|
return "RG";
|
|
case 3:
|
|
return "RGBA";
|
|
case 4:
|
|
return "RGBA";
|
|
}
|
|
throw new Error();
|
|
}
|
|
function countToFormat(count) {
|
|
switch (count) {
|
|
case 1:
|
|
return RedFormat;
|
|
case 2:
|
|
return RGFormat;
|
|
case 3:
|
|
return RGBAFormat;
|
|
case 4:
|
|
return RGBAFormat;
|
|
}
|
|
}
|
|
function countToIntFormat(count) {
|
|
switch (count) {
|
|
case 1:
|
|
return RedIntegerFormat;
|
|
case 2:
|
|
return RGIntegerFormat;
|
|
case 3:
|
|
return RGBAIntegerFormat;
|
|
case 4:
|
|
return RGBAIntegerFormat;
|
|
}
|
|
}
|
|
var VertexAttributeTexture = class extends DataTexture {
|
|
constructor() {
|
|
super();
|
|
this.minFilter = NearestFilter;
|
|
this.magFilter = NearestFilter;
|
|
this.generateMipmaps = false;
|
|
this.overrideItemSize = null;
|
|
this._forcedType = null;
|
|
}
|
|
updateFrom(attr) {
|
|
const overrideItemSize = this.overrideItemSize;
|
|
const originalItemSize = attr.itemSize;
|
|
const originalCount = attr.count;
|
|
if (overrideItemSize !== null) {
|
|
if (originalItemSize * originalCount % overrideItemSize !== 0) {
|
|
throw new Error("VertexAttributeTexture: overrideItemSize must divide evenly into buffer length.");
|
|
}
|
|
attr.itemSize = overrideItemSize;
|
|
attr.count = originalCount * originalItemSize / overrideItemSize;
|
|
}
|
|
const itemSize = attr.itemSize;
|
|
const count = attr.count;
|
|
const normalized = attr.normalized;
|
|
const originalBufferCons = attr.array.constructor;
|
|
const byteCount = originalBufferCons.BYTES_PER_ELEMENT;
|
|
let targetType = this._forcedType;
|
|
let finalStride = itemSize;
|
|
if (targetType === null) {
|
|
switch (originalBufferCons) {
|
|
case Float32Array:
|
|
targetType = FloatType;
|
|
break;
|
|
case Uint8Array:
|
|
case Uint16Array:
|
|
case Uint32Array:
|
|
targetType = UnsignedIntType;
|
|
break;
|
|
case Int8Array:
|
|
case Int16Array:
|
|
case Int32Array:
|
|
targetType = IntType;
|
|
break;
|
|
}
|
|
}
|
|
let type, format, normalizeValue, targetBufferCons;
|
|
let internalFormat = countToStringFormat(itemSize);
|
|
switch (targetType) {
|
|
case FloatType:
|
|
normalizeValue = 1;
|
|
format = countToFormat(itemSize);
|
|
if (normalized && byteCount === 1) {
|
|
targetBufferCons = originalBufferCons;
|
|
internalFormat += "8";
|
|
if (originalBufferCons === Uint8Array) {
|
|
type = UnsignedByteType;
|
|
} else {
|
|
type = ByteType;
|
|
internalFormat += "_SNORM";
|
|
}
|
|
} else {
|
|
targetBufferCons = Float32Array;
|
|
internalFormat += "32F";
|
|
type = FloatType;
|
|
}
|
|
break;
|
|
case IntType:
|
|
internalFormat += byteCount * 8 + "I";
|
|
normalizeValue = normalized ? Math.pow(2, originalBufferCons.BYTES_PER_ELEMENT * 8 - 1) : 1;
|
|
format = countToIntFormat(itemSize);
|
|
if (byteCount === 1) {
|
|
targetBufferCons = Int8Array;
|
|
type = ByteType;
|
|
} else if (byteCount === 2) {
|
|
targetBufferCons = Int16Array;
|
|
type = ShortType;
|
|
} else {
|
|
targetBufferCons = Int32Array;
|
|
type = IntType;
|
|
}
|
|
break;
|
|
case UnsignedIntType:
|
|
internalFormat += byteCount * 8 + "UI";
|
|
normalizeValue = normalized ? Math.pow(2, originalBufferCons.BYTES_PER_ELEMENT * 8 - 1) : 1;
|
|
format = countToIntFormat(itemSize);
|
|
if (byteCount === 1) {
|
|
targetBufferCons = Uint8Array;
|
|
type = UnsignedByteType;
|
|
} else if (byteCount === 2) {
|
|
targetBufferCons = Uint16Array;
|
|
type = UnsignedShortType;
|
|
} else {
|
|
targetBufferCons = Uint32Array;
|
|
type = UnsignedIntType;
|
|
}
|
|
break;
|
|
}
|
|
if (finalStride === 3 && (format === RGBAFormat || format === RGBAIntegerFormat)) {
|
|
finalStride = 4;
|
|
}
|
|
const dimension = Math.ceil(Math.sqrt(count));
|
|
const length = finalStride * dimension * dimension;
|
|
const dataArray = new targetBufferCons(length);
|
|
const originalNormalized = attr.normalized;
|
|
attr.normalized = false;
|
|
for (let i = 0; i < count; i++) {
|
|
const ii = finalStride * i;
|
|
dataArray[ii] = attr.getX(i) / normalizeValue;
|
|
if (itemSize >= 2) {
|
|
dataArray[ii + 1] = attr.getY(i) / normalizeValue;
|
|
}
|
|
if (itemSize >= 3) {
|
|
dataArray[ii + 2] = attr.getZ(i) / normalizeValue;
|
|
if (finalStride === 4) {
|
|
dataArray[ii + 3] = 1;
|
|
}
|
|
}
|
|
if (itemSize >= 4) {
|
|
dataArray[ii + 3] = attr.getW(i) / normalizeValue;
|
|
}
|
|
}
|
|
attr.normalized = originalNormalized;
|
|
this.internalFormat = internalFormat;
|
|
this.format = format;
|
|
this.type = type;
|
|
this.image.width = dimension;
|
|
this.image.height = dimension;
|
|
this.image.data = dataArray;
|
|
this.needsUpdate = true;
|
|
this.dispose();
|
|
attr.itemSize = originalItemSize;
|
|
attr.count = originalCount;
|
|
}
|
|
};
|
|
var UIntVertexAttributeTexture = class extends VertexAttributeTexture {
|
|
constructor() {
|
|
super();
|
|
this._forcedType = UnsignedIntType;
|
|
}
|
|
};
|
|
var IntVertexAttributeTexture = class extends VertexAttributeTexture {
|
|
constructor() {
|
|
super();
|
|
this._forcedType = IntType;
|
|
}
|
|
};
|
|
var FloatVertexAttributeTexture = class extends VertexAttributeTexture {
|
|
constructor() {
|
|
super();
|
|
this._forcedType = FloatType;
|
|
}
|
|
};
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/gpu/MeshBVHUniformStruct.js
|
|
function bvhToTextures(bvh, boundsTexture, contentsTexture) {
|
|
const roots = bvh._roots;
|
|
if (roots.length !== 1) {
|
|
throw new Error("MeshBVHUniformStruct: Multi-root BVHs not supported.");
|
|
}
|
|
const root = roots[0];
|
|
const uint16Array = new Uint16Array(root);
|
|
const uint32Array = new Uint32Array(root);
|
|
const float32Array = new Float32Array(root);
|
|
const nodeCount = root.byteLength / BYTES_PER_NODE;
|
|
const boundsDimension = 2 * Math.ceil(Math.sqrt(nodeCount / 2));
|
|
const boundsArray = new Float32Array(4 * boundsDimension * boundsDimension);
|
|
const contentsDimension = Math.ceil(Math.sqrt(nodeCount));
|
|
const contentsArray = new Uint32Array(2 * contentsDimension * contentsDimension);
|
|
for (let i = 0; i < nodeCount; i++) {
|
|
const nodeIndex32 = i * BYTES_PER_NODE / 4;
|
|
const nodeIndex16 = nodeIndex32 * 2;
|
|
const boundsIndex = BOUNDING_DATA_INDEX(nodeIndex32);
|
|
for (let b = 0; b < 3; b++) {
|
|
boundsArray[8 * i + 0 + b] = float32Array[boundsIndex + 0 + b];
|
|
boundsArray[8 * i + 4 + b] = float32Array[boundsIndex + 3 + b];
|
|
}
|
|
if (IS_LEAF(nodeIndex16, uint16Array)) {
|
|
const count = COUNT(nodeIndex16, uint16Array);
|
|
const offset = OFFSET(nodeIndex32, uint32Array);
|
|
const mergedLeafCount = 4294901760 | count;
|
|
contentsArray[i * 2 + 0] = mergedLeafCount;
|
|
contentsArray[i * 2 + 1] = offset;
|
|
} else {
|
|
const rightIndex = 4 * RIGHT_NODE(nodeIndex32, uint32Array) / BYTES_PER_NODE;
|
|
const splitAxis = SPLIT_AXIS(nodeIndex32, uint32Array);
|
|
contentsArray[i * 2 + 0] = splitAxis;
|
|
contentsArray[i * 2 + 1] = rightIndex;
|
|
}
|
|
}
|
|
boundsTexture.image.data = boundsArray;
|
|
boundsTexture.image.width = boundsDimension;
|
|
boundsTexture.image.height = boundsDimension;
|
|
boundsTexture.format = RGBAFormat;
|
|
boundsTexture.type = FloatType;
|
|
boundsTexture.internalFormat = "RGBA32F";
|
|
boundsTexture.minFilter = NearestFilter;
|
|
boundsTexture.magFilter = NearestFilter;
|
|
boundsTexture.generateMipmaps = false;
|
|
boundsTexture.needsUpdate = true;
|
|
boundsTexture.dispose();
|
|
contentsTexture.image.data = contentsArray;
|
|
contentsTexture.image.width = contentsDimension;
|
|
contentsTexture.image.height = contentsDimension;
|
|
contentsTexture.format = RGIntegerFormat;
|
|
contentsTexture.type = UnsignedIntType;
|
|
contentsTexture.internalFormat = "RG32UI";
|
|
contentsTexture.minFilter = NearestFilter;
|
|
contentsTexture.magFilter = NearestFilter;
|
|
contentsTexture.generateMipmaps = false;
|
|
contentsTexture.needsUpdate = true;
|
|
contentsTexture.dispose();
|
|
}
|
|
var MeshBVHUniformStruct = class {
|
|
constructor() {
|
|
this.autoDispose = true;
|
|
this.index = new UIntVertexAttributeTexture();
|
|
this.position = new FloatVertexAttributeTexture();
|
|
this.bvhBounds = new DataTexture();
|
|
this.bvhContents = new DataTexture();
|
|
this.index.overrideItemSize = 3;
|
|
}
|
|
updateFrom(bvh) {
|
|
const { geometry } = bvh;
|
|
bvhToTextures(bvh, this.bvhBounds, this.bvhContents);
|
|
this.index.updateFrom(geometry.index);
|
|
this.position.updateFrom(geometry.attributes.position);
|
|
}
|
|
dispose() {
|
|
const { index, position, bvhBounds, bvhContents } = this;
|
|
if (index) index.dispose();
|
|
if (position) position.dispose();
|
|
if (bvhBounds) bvhBounds.dispose();
|
|
if (bvhContents) bvhContents.dispose();
|
|
}
|
|
};
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/gpu/shaderFunctions.js
|
|
var shaderStructs = (
|
|
/* glsl */
|
|
`
|
|
#ifndef TRI_INTERSECT_EPSILON
|
|
#define TRI_INTERSECT_EPSILON 1e-5
|
|
#endif
|
|
|
|
#ifndef INFINITY
|
|
#define INFINITY 1e20
|
|
#endif
|
|
|
|
struct BVH {
|
|
|
|
usampler2D index;
|
|
sampler2D position;
|
|
|
|
sampler2D bvhBounds;
|
|
usampler2D bvhContents;
|
|
|
|
};
|
|
|
|
// Note that a struct cannot be used for the hit record including faceIndices, faceNormal, barycoord,
|
|
// side, and dist because on some mobile GPUS (such as Adreno) numbers are afforded less precision specifically
|
|
// when in a struct leading to inaccurate hit results. See KhronosGroup/WebGL#3351 for more details.
|
|
`
|
|
);
|
|
var shaderIntersectFunction = (
|
|
/* glsl */
|
|
`
|
|
|
|
uvec4 uTexelFetch1D( usampler2D tex, uint index ) {
|
|
|
|
uint width = uint( textureSize( tex, 0 ).x );
|
|
uvec2 uv;
|
|
uv.x = index % width;
|
|
uv.y = index / width;
|
|
|
|
return texelFetch( tex, ivec2( uv ), 0 );
|
|
|
|
}
|
|
|
|
ivec4 iTexelFetch1D( isampler2D tex, uint index ) {
|
|
|
|
uint width = uint( textureSize( tex, 0 ).x );
|
|
uvec2 uv;
|
|
uv.x = index % width;
|
|
uv.y = index / width;
|
|
|
|
return texelFetch( tex, ivec2( uv ), 0 );
|
|
|
|
}
|
|
|
|
vec4 texelFetch1D( sampler2D tex, uint index ) {
|
|
|
|
uint width = uint( textureSize( tex, 0 ).x );
|
|
uvec2 uv;
|
|
uv.x = index % width;
|
|
uv.y = index / width;
|
|
|
|
return texelFetch( tex, ivec2( uv ), 0 );
|
|
|
|
}
|
|
|
|
vec4 textureSampleBarycoord( sampler2D tex, vec3 barycoord, uvec3 faceIndices ) {
|
|
|
|
return
|
|
barycoord.x * texelFetch1D( tex, faceIndices.x ) +
|
|
barycoord.y * texelFetch1D( tex, faceIndices.y ) +
|
|
barycoord.z * texelFetch1D( tex, faceIndices.z );
|
|
|
|
}
|
|
|
|
void ndcToCameraRay(
|
|
vec2 coord, mat4 cameraWorld, mat4 invProjectionMatrix,
|
|
out vec3 rayOrigin, out vec3 rayDirection
|
|
) {
|
|
|
|
// get camera look direction and near plane for camera clipping
|
|
vec4 lookDirection = cameraWorld * vec4( 0.0, 0.0, - 1.0, 0.0 );
|
|
vec4 nearVector = invProjectionMatrix * vec4( 0.0, 0.0, - 1.0, 1.0 );
|
|
float near = abs( nearVector.z / nearVector.w );
|
|
|
|
// get the camera direction and position from camera matrices
|
|
vec4 origin = cameraWorld * vec4( 0.0, 0.0, 0.0, 1.0 );
|
|
vec4 direction = invProjectionMatrix * vec4( coord, 0.5, 1.0 );
|
|
direction /= direction.w;
|
|
direction = cameraWorld * direction - origin;
|
|
|
|
// slide the origin along the ray until it sits at the near clip plane position
|
|
origin.xyz += direction.xyz * near / dot( direction, lookDirection );
|
|
|
|
rayOrigin = origin.xyz;
|
|
rayDirection = direction.xyz;
|
|
|
|
}
|
|
|
|
float intersectsBounds( vec3 rayOrigin, vec3 rayDirection, vec3 boundsMin, vec3 boundsMax ) {
|
|
|
|
// https://www.reddit.com/r/opengl/comments/8ntzz5/fast_glsl_ray_box_intersection/
|
|
// https://tavianator.com/2011/ray_box.html
|
|
vec3 invDir = 1.0 / rayDirection;
|
|
|
|
// find intersection distances for each plane
|
|
vec3 tMinPlane = invDir * ( boundsMin - rayOrigin );
|
|
vec3 tMaxPlane = invDir * ( boundsMax - rayOrigin );
|
|
|
|
// get the min and max distances from each intersection
|
|
vec3 tMinHit = min( tMaxPlane, tMinPlane );
|
|
vec3 tMaxHit = max( tMaxPlane, tMinPlane );
|
|
|
|
// get the furthest hit distance
|
|
vec2 t = max( tMinHit.xx, tMinHit.yz );
|
|
float t0 = max( t.x, t.y );
|
|
|
|
// get the minimum hit distance
|
|
t = min( tMaxHit.xx, tMaxHit.yz );
|
|
float t1 = min( t.x, t.y );
|
|
|
|
// set distance to 0.0 if the ray starts inside the box
|
|
float dist = max( t0, 0.0 );
|
|
|
|
return t1 >= dist ? dist : INFINITY;
|
|
|
|
}
|
|
|
|
bool intersectsTriangle(
|
|
vec3 rayOrigin, vec3 rayDirection, vec3 a, vec3 b, vec3 c,
|
|
out vec3 barycoord, out vec3 norm, out float dist, out float side
|
|
) {
|
|
|
|
// https://stackoverflow.com/questions/42740765/intersection-between-line-and-triangle-in-3d
|
|
vec3 edge1 = b - a;
|
|
vec3 edge2 = c - a;
|
|
norm = cross( edge1, edge2 );
|
|
|
|
float det = - dot( rayDirection, norm );
|
|
float invdet = 1.0 / det;
|
|
|
|
vec3 AO = rayOrigin - a;
|
|
vec3 DAO = cross( AO, rayDirection );
|
|
|
|
vec4 uvt;
|
|
uvt.x = dot( edge2, DAO ) * invdet;
|
|
uvt.y = - dot( edge1, DAO ) * invdet;
|
|
uvt.z = dot( AO, norm ) * invdet;
|
|
uvt.w = 1.0 - uvt.x - uvt.y;
|
|
|
|
// set the hit information
|
|
barycoord = uvt.wxy; // arranged in A, B, C order
|
|
dist = uvt.z;
|
|
side = sign( det );
|
|
norm = side * normalize( norm );
|
|
|
|
// add an epsilon to avoid misses between triangles
|
|
uvt += vec4( TRI_INTERSECT_EPSILON );
|
|
|
|
return all( greaterThanEqual( uvt, vec4( 0.0 ) ) );
|
|
|
|
}
|
|
|
|
bool intersectTriangles(
|
|
BVH bvh, vec3 rayOrigin, vec3 rayDirection, uint offset, uint count,
|
|
inout float minDistance,
|
|
|
|
// output variables
|
|
out uvec4 faceIndices, out vec3 faceNormal, out vec3 barycoord,
|
|
out float side, out float dist
|
|
) {
|
|
|
|
bool found = false;
|
|
vec3 localBarycoord, localNormal;
|
|
float localDist, localSide;
|
|
for ( uint i = offset, l = offset + count; i < l; i ++ ) {
|
|
|
|
uvec3 indices = uTexelFetch1D( bvh.index, i ).xyz;
|
|
vec3 a = texelFetch1D( bvh.position, indices.x ).rgb;
|
|
vec3 b = texelFetch1D( bvh.position, indices.y ).rgb;
|
|
vec3 c = texelFetch1D( bvh.position, indices.z ).rgb;
|
|
|
|
if (
|
|
intersectsTriangle( rayOrigin, rayDirection, a, b, c, localBarycoord, localNormal, localDist, localSide )
|
|
&& localDist < minDistance
|
|
) {
|
|
|
|
found = true;
|
|
minDistance = localDist;
|
|
|
|
faceIndices = uvec4( indices.xyz, i );
|
|
faceNormal = localNormal;
|
|
|
|
side = localSide;
|
|
barycoord = localBarycoord;
|
|
dist = localDist;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return found;
|
|
|
|
}
|
|
|
|
float intersectsBVHNodeBounds( vec3 rayOrigin, vec3 rayDirection, BVH bvh, uint currNodeIndex ) {
|
|
|
|
vec3 boundsMin = texelFetch1D( bvh.bvhBounds, currNodeIndex * 2u + 0u ).xyz;
|
|
vec3 boundsMax = texelFetch1D( bvh.bvhBounds, currNodeIndex * 2u + 1u ).xyz;
|
|
return intersectsBounds( rayOrigin, rayDirection, boundsMin, boundsMax );
|
|
|
|
}
|
|
|
|
bool bvhIntersectFirstHit(
|
|
BVH bvh, vec3 rayOrigin, vec3 rayDirection,
|
|
|
|
// output variables
|
|
out uvec4 faceIndices, out vec3 faceNormal, out vec3 barycoord,
|
|
out float side, out float dist
|
|
) {
|
|
|
|
// stack needs to be twice as long as the deepest tree we expect because
|
|
// we push both the left and right child onto the stack every traversal
|
|
int ptr = 0;
|
|
uint stack[ 60 ];
|
|
stack[ 0 ] = 0u;
|
|
|
|
float triangleDistance = 1e20;
|
|
bool found = false;
|
|
while ( ptr > - 1 && ptr < 60 ) {
|
|
|
|
uint currNodeIndex = stack[ ptr ];
|
|
ptr --;
|
|
|
|
// check if we intersect the current bounds
|
|
float boundsHitDistance = intersectsBVHNodeBounds( rayOrigin, rayDirection, bvh, currNodeIndex );
|
|
if ( boundsHitDistance == INFINITY || boundsHitDistance > triangleDistance ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
uvec2 boundsInfo = uTexelFetch1D( bvh.bvhContents, currNodeIndex ).xy;
|
|
bool isLeaf = bool( boundsInfo.x & 0xffff0000u );
|
|
|
|
if ( isLeaf ) {
|
|
|
|
uint count = boundsInfo.x & 0x0000ffffu;
|
|
uint offset = boundsInfo.y;
|
|
|
|
found = intersectTriangles(
|
|
bvh, rayOrigin, rayDirection, offset, count, triangleDistance,
|
|
faceIndices, faceNormal, barycoord, side, dist
|
|
) || found;
|
|
|
|
} else {
|
|
|
|
uint leftIndex = currNodeIndex + 1u;
|
|
uint splitAxis = boundsInfo.x & 0x0000ffffu;
|
|
uint rightIndex = boundsInfo.y;
|
|
|
|
bool leftToRight = rayDirection[ splitAxis ] >= 0.0;
|
|
uint c1 = leftToRight ? leftIndex : rightIndex;
|
|
uint c2 = leftToRight ? rightIndex : leftIndex;
|
|
|
|
// set c2 in the stack so we traverse it later. We need to keep track of a pointer in
|
|
// the stack while we traverse. The second pointer added is the one that will be
|
|
// traversed first
|
|
ptr ++;
|
|
stack[ ptr ] = c2;
|
|
|
|
ptr ++;
|
|
stack[ ptr ] = c1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return found;
|
|
|
|
}
|
|
|
|
`
|
|
);
|
|
|
|
// ../../node_modules/three-mesh-bvh/src/utils/StaticGeometryGenerator.js
|
|
var _positionVector = new Vector3();
|
|
var _normalVector = new Vector3();
|
|
var _tangentVector = new Vector3();
|
|
var _tangentVector4 = new Vector4();
|
|
var _morphVector = new Vector3();
|
|
var _temp = new Vector3();
|
|
var _skinIndex = new Vector4();
|
|
var _skinWeight = new Vector4();
|
|
var _matrix = new Matrix4();
|
|
var _boneMatrix = new Matrix4();
|
|
function validateAttributes(attr1, attr2) {
|
|
if (!attr1 && !attr2) {
|
|
return;
|
|
}
|
|
const sameCount = attr1.count === attr2.count;
|
|
const sameNormalized = attr1.normalized === attr2.normalized;
|
|
const sameType = attr1.array.constructor === attr2.array.constructor;
|
|
const sameItemSize = attr1.itemSize === attr2.itemSize;
|
|
if (!sameCount || !sameNormalized || !sameType || !sameItemSize) {
|
|
throw new Error();
|
|
}
|
|
}
|
|
function createAttributeClone(attr, countOverride = null) {
|
|
const cons = attr.array.constructor;
|
|
const normalized = attr.normalized;
|
|
const itemSize = attr.itemSize;
|
|
const count = countOverride === null ? attr.count : countOverride;
|
|
return new BufferAttribute(new cons(itemSize * count), itemSize, normalized);
|
|
}
|
|
function copyAttributeContents(attr, target, targetOffset = 0) {
|
|
if (attr.isInterleavedBufferAttribute) {
|
|
const itemSize = attr.itemSize;
|
|
for (let i = 0, l = attr.count; i < l; i++) {
|
|
const io = i + targetOffset;
|
|
target.setX(io, attr.getX(i));
|
|
if (itemSize >= 2) target.setY(io, attr.getY(i));
|
|
if (itemSize >= 3) target.setZ(io, attr.getZ(i));
|
|
if (itemSize >= 4) target.setW(io, attr.getW(i));
|
|
}
|
|
} else {
|
|
const array = target.array;
|
|
const cons = array.constructor;
|
|
const byteOffset = array.BYTES_PER_ELEMENT * attr.itemSize * targetOffset;
|
|
const temp5 = new cons(array.buffer, byteOffset, attr.array.length);
|
|
temp5.set(attr.array);
|
|
}
|
|
}
|
|
function addScaledMatrix(target, matrix, scale) {
|
|
const targetArray = target.elements;
|
|
const matrixArray = matrix.elements;
|
|
for (let i = 0, l = matrixArray.length; i < l; i++) {
|
|
targetArray[i] += matrixArray[i] * scale;
|
|
}
|
|
}
|
|
function boneNormalTransform(mesh, index, target) {
|
|
const skeleton = mesh.skeleton;
|
|
const geometry = mesh.geometry;
|
|
const bones = skeleton.bones;
|
|
const boneInverses = skeleton.boneInverses;
|
|
_skinIndex.fromBufferAttribute(geometry.attributes.skinIndex, index);
|
|
_skinWeight.fromBufferAttribute(geometry.attributes.skinWeight, index);
|
|
_matrix.elements.fill(0);
|
|
for (let i = 0; i < 4; i++) {
|
|
const weight = _skinWeight.getComponent(i);
|
|
if (weight !== 0) {
|
|
const boneIndex = _skinIndex.getComponent(i);
|
|
_boneMatrix.multiplyMatrices(bones[boneIndex].matrixWorld, boneInverses[boneIndex]);
|
|
addScaledMatrix(_matrix, _boneMatrix, weight);
|
|
}
|
|
}
|
|
_matrix.multiply(mesh.bindMatrix).premultiply(mesh.bindMatrixInverse);
|
|
target.transformDirection(_matrix);
|
|
return target;
|
|
}
|
|
function applyMorphTarget(morphData, morphInfluences, morphTargetsRelative, i, target) {
|
|
_morphVector.set(0, 0, 0);
|
|
for (let j = 0, jl = morphData.length; j < jl; j++) {
|
|
const influence = morphInfluences[j];
|
|
const morphAttribute = morphData[j];
|
|
if (influence === 0) continue;
|
|
_temp.fromBufferAttribute(morphAttribute, i);
|
|
if (morphTargetsRelative) {
|
|
_morphVector.addScaledVector(_temp, influence);
|
|
} else {
|
|
_morphVector.addScaledVector(_temp.sub(target), influence);
|
|
}
|
|
}
|
|
target.add(_morphVector);
|
|
}
|
|
function mergeBufferGeometries(geometries, options = { useGroups: false, updateIndex: false }, targetGeometry = new BufferGeometry()) {
|
|
const isIndexed = geometries[0].index !== null;
|
|
const { useGroups, updateIndex } = options;
|
|
const attributesUsed = new Set(Object.keys(geometries[0].attributes));
|
|
const attributes = {};
|
|
let offset = 0;
|
|
for (let i = 0; i < geometries.length; ++i) {
|
|
const geometry = geometries[i];
|
|
let attributesCount = 0;
|
|
if (isIndexed !== (geometry.index !== null)) {
|
|
throw new Error("StaticGeometryGenerator: All geometries must have compatible attributes; make sure index attribute exists among all geometries, or in none of them.");
|
|
}
|
|
for (const name in geometry.attributes) {
|
|
if (!attributesUsed.has(name)) {
|
|
throw new Error('StaticGeometryGenerator: All geometries must have compatible attributes; make sure "' + name + '" attribute exists among all geometries, or in none of them.');
|
|
}
|
|
if (attributes[name] === void 0) {
|
|
attributes[name] = [];
|
|
}
|
|
attributes[name].push(geometry.attributes[name]);
|
|
attributesCount++;
|
|
}
|
|
if (attributesCount !== attributesUsed.size) {
|
|
throw new Error("StaticGeometryGenerator: Make sure all geometries have the same number of attributes.");
|
|
}
|
|
if (useGroups) {
|
|
let count;
|
|
if (isIndexed) {
|
|
count = geometry.index.count;
|
|
} else if (geometry.attributes.position !== void 0) {
|
|
count = geometry.attributes.position.count;
|
|
} else {
|
|
throw new Error("StaticGeometryGenerator: The geometry must have either an index or a position attribute");
|
|
}
|
|
targetGeometry.addGroup(offset, count, i);
|
|
offset += count;
|
|
}
|
|
}
|
|
if (isIndexed) {
|
|
let forceUpateIndex = false;
|
|
if (!targetGeometry.index) {
|
|
let indexCount = 0;
|
|
for (let i = 0; i < geometries.length; ++i) {
|
|
indexCount += geometries[i].index.count;
|
|
}
|
|
targetGeometry.setIndex(new BufferAttribute(new Uint32Array(indexCount), 1, false));
|
|
forceUpateIndex = true;
|
|
}
|
|
if (updateIndex || forceUpateIndex) {
|
|
const targetIndex = targetGeometry.index;
|
|
let targetOffset = 0;
|
|
let indexOffset = 0;
|
|
for (let i = 0; i < geometries.length; ++i) {
|
|
const geometry = geometries[i];
|
|
const index = geometry.index;
|
|
for (let j = 0; j < index.count; ++j) {
|
|
targetIndex.setX(targetOffset, index.getX(j) + indexOffset);
|
|
targetOffset++;
|
|
}
|
|
indexOffset += geometry.attributes.position.count;
|
|
}
|
|
}
|
|
}
|
|
for (const name in attributes) {
|
|
const attrList = attributes[name];
|
|
if (!(name in targetGeometry.attributes)) {
|
|
let count = 0;
|
|
for (const key in attrList) {
|
|
count += attrList[key].count;
|
|
}
|
|
targetGeometry.setAttribute(name, createAttributeClone(attributes[name][0], count));
|
|
}
|
|
const targetAttribute = targetGeometry.attributes[name];
|
|
let offset2 = 0;
|
|
for (const key in attrList) {
|
|
const attr = attrList[key];
|
|
copyAttributeContents(attr, targetAttribute, offset2);
|
|
offset2 += attr.count;
|
|
}
|
|
}
|
|
return targetGeometry;
|
|
}
|
|
var StaticGeometryGenerator = class {
|
|
constructor(meshes) {
|
|
if (!Array.isArray(meshes)) {
|
|
meshes = [meshes];
|
|
}
|
|
const finalMeshes = [];
|
|
meshes.forEach((object) => {
|
|
object.traverse((c) => {
|
|
if (c.isMesh) {
|
|
finalMeshes.push(c);
|
|
}
|
|
});
|
|
});
|
|
this.meshes = finalMeshes;
|
|
this.useGroups = true;
|
|
this.applyWorldTransforms = true;
|
|
this.attributes = ["position", "normal", "tangent", "uv", "uv2"];
|
|
this._intermediateGeometry = new Array(finalMeshes.length).fill().map(() => new BufferGeometry());
|
|
}
|
|
getMaterials() {
|
|
const materials = [];
|
|
this.meshes.forEach((mesh) => {
|
|
if (Array.isArray(mesh.material)) {
|
|
materials.push(...mesh.material);
|
|
} else {
|
|
materials.push(mesh.material);
|
|
}
|
|
});
|
|
return materials;
|
|
}
|
|
generate(targetGeometry = new BufferGeometry()) {
|
|
const { meshes, useGroups, _intermediateGeometry } = this;
|
|
for (let i = 0, l = meshes.length; i < l; i++) {
|
|
const mesh = meshes[i];
|
|
const geom = _intermediateGeometry[i];
|
|
this._convertToStaticGeometry(mesh, geom);
|
|
}
|
|
mergeBufferGeometries(_intermediateGeometry, { useGroups }, targetGeometry);
|
|
for (const key in targetGeometry.attributes) {
|
|
targetGeometry.attributes[key].needsUpdate = true;
|
|
}
|
|
return targetGeometry;
|
|
}
|
|
_convertToStaticGeometry(mesh, targetGeometry = new BufferGeometry()) {
|
|
const geometry = mesh.geometry;
|
|
const applyWorldTransforms = this.applyWorldTransforms;
|
|
const includeNormal = this.attributes.includes("normal");
|
|
const includeTangent = this.attributes.includes("tangent");
|
|
const attributes = geometry.attributes;
|
|
const targetAttributes = targetGeometry.attributes;
|
|
if (!targetGeometry.index) {
|
|
targetGeometry.index = geometry.index;
|
|
}
|
|
if (!targetAttributes.position) {
|
|
targetGeometry.setAttribute("position", createAttributeClone(attributes.position));
|
|
}
|
|
if (includeNormal && !targetAttributes.normal && attributes.normal) {
|
|
targetGeometry.setAttribute("normal", createAttributeClone(attributes.normal));
|
|
}
|
|
if (includeTangent && !targetAttributes.tangent && attributes.tangent) {
|
|
targetGeometry.setAttribute("tangent", createAttributeClone(attributes.tangent));
|
|
}
|
|
validateAttributes(geometry.index, targetGeometry.index);
|
|
validateAttributes(attributes.position, targetAttributes.position);
|
|
if (includeNormal) {
|
|
validateAttributes(attributes.normal, targetAttributes.normal);
|
|
}
|
|
if (includeTangent) {
|
|
validateAttributes(attributes.tangent, targetAttributes.tangent);
|
|
}
|
|
const position = attributes.position;
|
|
const normal = includeNormal ? attributes.normal : null;
|
|
const tangent = includeTangent ? attributes.tangent : null;
|
|
const morphPosition = geometry.morphAttributes.position;
|
|
const morphNormal = geometry.morphAttributes.normal;
|
|
const morphTangent = geometry.morphAttributes.tangent;
|
|
const morphTargetsRelative = geometry.morphTargetsRelative;
|
|
const morphInfluences = mesh.morphTargetInfluences;
|
|
const normalMatrix = new Matrix3();
|
|
normalMatrix.getNormalMatrix(mesh.matrixWorld);
|
|
for (let i = 0, l = attributes.position.count; i < l; i++) {
|
|
_positionVector.fromBufferAttribute(position, i);
|
|
if (normal) {
|
|
_normalVector.fromBufferAttribute(normal, i);
|
|
}
|
|
if (tangent) {
|
|
_tangentVector4.fromBufferAttribute(tangent, i);
|
|
_tangentVector.fromBufferAttribute(tangent, i);
|
|
}
|
|
if (morphInfluences) {
|
|
if (morphPosition) {
|
|
applyMorphTarget(morphPosition, morphInfluences, morphTargetsRelative, i, _positionVector);
|
|
}
|
|
if (morphNormal) {
|
|
applyMorphTarget(morphNormal, morphInfluences, morphTargetsRelative, i, _normalVector);
|
|
}
|
|
if (morphTangent) {
|
|
applyMorphTarget(morphTangent, morphInfluences, morphTargetsRelative, i, _tangentVector);
|
|
}
|
|
}
|
|
if (mesh.isSkinnedMesh) {
|
|
mesh.boneTransform(i, _positionVector);
|
|
if (normal) {
|
|
boneNormalTransform(mesh, i, _normalVector);
|
|
}
|
|
if (tangent) {
|
|
boneNormalTransform(mesh, i, _tangentVector);
|
|
}
|
|
}
|
|
if (applyWorldTransforms) {
|
|
_positionVector.applyMatrix4(mesh.matrixWorld);
|
|
}
|
|
targetAttributes.position.setXYZ(i, _positionVector.x, _positionVector.y, _positionVector.z);
|
|
if (normal) {
|
|
if (applyWorldTransforms) {
|
|
_normalVector.applyNormalMatrix(normalMatrix);
|
|
}
|
|
targetAttributes.normal.setXYZ(i, _normalVector.x, _normalVector.y, _normalVector.z);
|
|
}
|
|
if (tangent) {
|
|
if (applyWorldTransforms) {
|
|
_tangentVector.transformDirection(mesh.matrixWorld);
|
|
}
|
|
targetAttributes.tangent.setXYZW(i, _tangentVector.x, _tangentVector.y, _tangentVector.z, _tangentVector4.w);
|
|
}
|
|
}
|
|
for (const i in this.attributes) {
|
|
const key = this.attributes[i];
|
|
if (key === "position" || key === "tangent" || key === "normal" || !(key in attributes)) {
|
|
continue;
|
|
}
|
|
if (!targetAttributes[key]) {
|
|
targetGeometry.setAttribute(key, createAttributeClone(attributes[key]));
|
|
}
|
|
validateAttributes(attributes[key], targetAttributes[key]);
|
|
copyAttributeContents(attributes[key], targetAttributes[key]);
|
|
}
|
|
return targetGeometry;
|
|
}
|
|
};
|
|
export {
|
|
AVERAGE,
|
|
CENTER,
|
|
CONTAINED,
|
|
ExtendedTriangle,
|
|
FloatVertexAttributeTexture,
|
|
INTERSECTED,
|
|
IntVertexAttributeTexture,
|
|
MeshBVH,
|
|
MeshBVHUniformStruct,
|
|
MeshBVHVisualizer,
|
|
NOT_INTERSECTED,
|
|
OrientedBox,
|
|
SAH,
|
|
StaticGeometryGenerator,
|
|
UIntVertexAttributeTexture,
|
|
VertexAttributeTexture,
|
|
acceleratedRaycast,
|
|
computeBoundsTree,
|
|
disposeBoundsTree,
|
|
estimateMemoryInBytes,
|
|
getBVHExtremes,
|
|
getJSONStructure,
|
|
getTriangleHitPointInfo,
|
|
shaderIntersectFunction,
|
|
shaderStructs,
|
|
validateBounds
|
|
};
|
|
//# sourceMappingURL=three-mesh-bvh.js.map
|