@ifc-lite/renderer 1.15.2 → 1.15.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bvh.d.ts +1 -1
- package/dist/bvh.d.ts.map +1 -1
- package/dist/pipeline.js.map +1 -1
- package/dist/scene-geometry.d.ts +31 -0
- package/dist/scene-geometry.d.ts.map +1 -0
- package/dist/scene-geometry.js +137 -0
- package/dist/scene-geometry.js.map +1 -0
- package/dist/scene-raycaster.d.ts +65 -0
- package/dist/scene-raycaster.d.ts.map +1 -0
- package/dist/scene-raycaster.js +234 -0
- package/dist/scene-raycaster.js.map +1 -0
- package/dist/scene.d.ts +9 -39
- package/dist/scene.d.ts.map +1 -1
- package/dist/scene.js +15 -339
- package/dist/scene.js.map +1 -1
- package/dist/snap-detector.d.ts +1 -17
- package/dist/snap-detector.d.ts.map +1 -1
- package/dist/snap-detector.js +16 -213
- package/dist/snap-detector.js.map +1 -1
- package/dist/snap-geometry-cache.d.ts +24 -0
- package/dist/snap-geometry-cache.d.ts.map +1 -0
- package/dist/snap-geometry-cache.js +155 -0
- package/dist/snap-geometry-cache.js.map +1 -0
- package/dist/snap-geometry-utils.d.ts +27 -0
- package/dist/snap-geometry-utils.d.ts.map +1 -0
- package/dist/snap-geometry-utils.js +56 -0
- package/dist/snap-geometry-utils.js.map +1 -0
- package/package.json +2 -2
package/dist/scene.d.ts
CHANGED
|
@@ -4,10 +4,7 @@
|
|
|
4
4
|
import type { Mesh, InstancedMesh, BatchedMesh, Vec3 } from './types.js';
|
|
5
5
|
import type { MeshData } from '@ifc-lite/geometry';
|
|
6
6
|
import type { RenderPipeline } from './pipeline.js';
|
|
7
|
-
|
|
8
|
-
min: Vec3;
|
|
9
|
-
max: Vec3;
|
|
10
|
-
}
|
|
7
|
+
import { type BoundingBox, type RaycastHit } from './scene-raycaster.js';
|
|
11
8
|
export declare class Scene {
|
|
12
9
|
private meshes;
|
|
13
10
|
private instancedMeshes;
|
|
@@ -196,9 +193,8 @@ export declare class Scene {
|
|
|
196
193
|
*/
|
|
197
194
|
private createBatchedMesh;
|
|
198
195
|
/**
|
|
199
|
-
* Merge multiple mesh geometries into single vertex/index buffers
|
|
200
|
-
*
|
|
201
|
-
* OPTIMIZATION: Uses efficient loops and bulk index adjustment
|
|
196
|
+
* Merge multiple mesh geometries into single vertex/index buffers.
|
|
197
|
+
* Delegates to the extracted mergeGeometry() utility.
|
|
202
198
|
*/
|
|
203
199
|
private mergeGeometry;
|
|
204
200
|
/**
|
|
@@ -206,12 +202,8 @@ export declare class Scene {
|
|
|
206
202
|
*/
|
|
207
203
|
private getMaxBufferSize;
|
|
208
204
|
/**
|
|
209
|
-
* Split a meshDataArray into chunks
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* Each mesh is kept intact — we never split a single element's geometry.
|
|
213
|
-
* If a single mesh exceeds the limit on its own it is placed in a solo chunk
|
|
214
|
-
* (WebGPU will clamp or error, but we don't silently drop geometry).
|
|
205
|
+
* Split a meshDataArray into chunks that fit within GPU buffer limits.
|
|
206
|
+
* Delegates to the extracted splitMeshDataForBufferLimit() utility.
|
|
215
207
|
*/
|
|
216
208
|
private splitMeshDataForBufferLimit;
|
|
217
209
|
/**
|
|
@@ -294,32 +286,10 @@ export declare class Scene {
|
|
|
294
286
|
*/
|
|
295
287
|
getEntityBoundingBox(expressId: number): BoundingBox | null;
|
|
296
288
|
/**
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
|
|
300
|
-
private rayIntersectsBox;
|
|
301
|
-
/**
|
|
302
|
-
* Ray-box intersection returning entry distance (tNear).
|
|
303
|
-
* Returns null if no intersection, otherwise the distance along the ray
|
|
304
|
-
* to the entry point (clamped to 0 if the ray originates inside the box).
|
|
305
|
-
* Handles zero ray direction components (axis-aligned rays) safely.
|
|
289
|
+
* CPU raycast against all mesh data.
|
|
290
|
+
* Returns expressId and modelIndex of closest hit, or null.
|
|
291
|
+
* Delegates to extracted raycaster utilities.
|
|
306
292
|
*/
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Möller–Trumbore ray-triangle intersection
|
|
310
|
-
* Returns distance to intersection or null if no hit
|
|
311
|
-
*/
|
|
312
|
-
private rayTriangleIntersect;
|
|
313
|
-
/**
|
|
314
|
-
* CPU raycast against all mesh data
|
|
315
|
-
* Returns expressId and modelIndex of closest hit, or null
|
|
316
|
-
* For multi-model support: tracks which model's geometry was hit
|
|
317
|
-
*/
|
|
318
|
-
raycast(rayOrigin: Vec3, rayDir: Vec3, hiddenIds?: Set<number>, isolatedIds?: Set<number> | null): {
|
|
319
|
-
expressId: number;
|
|
320
|
-
distance: number;
|
|
321
|
-
modelIndex?: number;
|
|
322
|
-
} | null;
|
|
293
|
+
raycast(rayOrigin: Vec3, rayDir: Vec3, hiddenIds?: Set<number>, isolatedIds?: Set<number> | null): RaycastHit | null;
|
|
323
294
|
}
|
|
324
|
-
export {};
|
|
325
295
|
//# sourceMappingURL=scene.d.ts.map
|
package/dist/scene.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene.d.ts","sourceRoot":"","sources":["../src/scene.ts"],"names":[],"mappings":"AAIA;;GAEG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"scene.d.ts","sourceRoot":"","sources":["../src/scene.ts"],"names":[],"mappings":"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"}
|
package/dist/scene.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
-
import { MathUtils } from './math.js';
|
|
5
4
|
import { BATCH_CONSTANTS } from './constants.js';
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import { prepareRayDirInv, raycastBoundingBoxes, raycastTriangles, } from './scene-raycaster.js';
|
|
6
|
+
import { mergeGeometry, splitMeshDataForBufferLimit } from './scene-geometry.js';
|
|
8
7
|
export class Scene {
|
|
9
8
|
meshes = [];
|
|
10
9
|
instancedMeshes = [];
|
|
@@ -1029,89 +1028,11 @@ export class Scene {
|
|
|
1029
1028
|
};
|
|
1030
1029
|
}
|
|
1031
1030
|
/**
|
|
1032
|
-
* Merge multiple mesh geometries into single vertex/index buffers
|
|
1033
|
-
*
|
|
1034
|
-
* OPTIMIZATION: Uses efficient loops and bulk index adjustment
|
|
1031
|
+
* Merge multiple mesh geometries into single vertex/index buffers.
|
|
1032
|
+
* Delegates to the extracted mergeGeometry() utility.
|
|
1035
1033
|
*/
|
|
1036
1034
|
mergeGeometry(meshDataArray) {
|
|
1037
|
-
|
|
1038
|
-
let totalIndices = 0;
|
|
1039
|
-
// Calculate total sizes
|
|
1040
|
-
for (const mesh of meshDataArray) {
|
|
1041
|
-
totalVertices += mesh.positions.length / 3;
|
|
1042
|
-
totalIndices += mesh.indices.length;
|
|
1043
|
-
}
|
|
1044
|
-
// Create merged buffers
|
|
1045
|
-
const vertexBufferRaw = new ArrayBuffer(totalVertices * 7 * 4);
|
|
1046
|
-
const vertexData = new Float32Array(vertexBufferRaw); // position + normal
|
|
1047
|
-
const vertexDataU32 = new Uint32Array(vertexBufferRaw); // entityId lane
|
|
1048
|
-
const indices = new Uint32Array(totalIndices);
|
|
1049
|
-
// Track bounds during merge (avoids a second pass)
|
|
1050
|
-
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
1051
|
-
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
1052
|
-
let indexOffset = 0;
|
|
1053
|
-
let vertexBase = 0;
|
|
1054
|
-
for (const mesh of meshDataArray) {
|
|
1055
|
-
const positions = mesh.positions;
|
|
1056
|
-
const normals = mesh.normals;
|
|
1057
|
-
const vertexCount = positions.length / 3;
|
|
1058
|
-
// Interleave vertex data (position + normal + entityId)
|
|
1059
|
-
// This loop is O(n) per mesh and unavoidable for interleaving
|
|
1060
|
-
let outIdx = vertexBase * 7;
|
|
1061
|
-
const perVertexEntityIds = mesh.entityIds; // color-merged batches
|
|
1062
|
-
let entityId = mesh.expressId >>> 0;
|
|
1063
|
-
if (!perVertexEntityIds && entityId > MAX_ENCODED_ENTITY_ID) {
|
|
1064
|
-
if (!warnedEntityIdRange) {
|
|
1065
|
-
warnedEntityIdRange = true;
|
|
1066
|
-
console.warn('[Renderer] expressId exceeds 24-bit seam-ID encoding range; seam lines may collide.');
|
|
1067
|
-
}
|
|
1068
|
-
entityId = entityId & MAX_ENCODED_ENTITY_ID;
|
|
1069
|
-
}
|
|
1070
|
-
const hasNormals = normals.length > 0;
|
|
1071
|
-
for (let i = 0; i < vertexCount; i++) {
|
|
1072
|
-
const srcIdx = i * 3;
|
|
1073
|
-
const px = positions[srcIdx];
|
|
1074
|
-
const py = positions[srcIdx + 1];
|
|
1075
|
-
const pz = positions[srcIdx + 2];
|
|
1076
|
-
vertexData[outIdx++] = px;
|
|
1077
|
-
vertexData[outIdx++] = py;
|
|
1078
|
-
vertexData[outIdx++] = pz;
|
|
1079
|
-
vertexData[outIdx++] = hasNormals ? normals[srcIdx] : 0;
|
|
1080
|
-
vertexData[outIdx++] = hasNormals ? normals[srcIdx + 1] : 0;
|
|
1081
|
-
vertexData[outIdx++] = hasNormals ? normals[srcIdx + 2] : 0;
|
|
1082
|
-
vertexDataU32[outIdx++] = perVertexEntityIds ? (perVertexEntityIds[i] >>> 0) : entityId;
|
|
1083
|
-
// Update bounds
|
|
1084
|
-
if (px < minX)
|
|
1085
|
-
minX = px;
|
|
1086
|
-
if (py < minY)
|
|
1087
|
-
minY = py;
|
|
1088
|
-
if (pz < minZ)
|
|
1089
|
-
minZ = pz;
|
|
1090
|
-
if (px > maxX)
|
|
1091
|
-
maxX = px;
|
|
1092
|
-
if (py > maxY)
|
|
1093
|
-
maxY = py;
|
|
1094
|
-
if (pz > maxZ)
|
|
1095
|
-
maxZ = pz;
|
|
1096
|
-
}
|
|
1097
|
-
// Copy indices with vertex base offset
|
|
1098
|
-
// Use subarray for slightly better cache locality
|
|
1099
|
-
const meshIndices = mesh.indices;
|
|
1100
|
-
const indexCount = meshIndices.length;
|
|
1101
|
-
for (let i = 0; i < indexCount; i++) {
|
|
1102
|
-
indices[indexOffset + i] = meshIndices[i] + vertexBase;
|
|
1103
|
-
}
|
|
1104
|
-
vertexBase += vertexCount;
|
|
1105
|
-
indexOffset += indexCount;
|
|
1106
|
-
}
|
|
1107
|
-
return {
|
|
1108
|
-
vertexData,
|
|
1109
|
-
indices,
|
|
1110
|
-
bounds: {
|
|
1111
|
-
min: [minX, minY, minZ],
|
|
1112
|
-
max: [maxX, maxY, maxZ],
|
|
1113
|
-
},
|
|
1114
|
-
};
|
|
1035
|
+
return mergeGeometry(meshDataArray);
|
|
1115
1036
|
}
|
|
1116
1037
|
/**
|
|
1117
1038
|
* Get the effective max buffer size for this GPU device, with a safety margin.
|
|
@@ -1121,50 +1042,11 @@ export class Scene {
|
|
|
1121
1042
|
return Math.floor(deviceMax * BATCH_CONSTANTS.BUFFER_SIZE_SAFETY_FACTOR);
|
|
1122
1043
|
}
|
|
1123
1044
|
/**
|
|
1124
|
-
* Split a meshDataArray into chunks
|
|
1125
|
-
*
|
|
1126
|
-
*
|
|
1127
|
-
* Each mesh is kept intact — we never split a single element's geometry.
|
|
1128
|
-
* If a single mesh exceeds the limit on its own it is placed in a solo chunk
|
|
1129
|
-
* (WebGPU will clamp or error, but we don't silently drop geometry).
|
|
1045
|
+
* Split a meshDataArray into chunks that fit within GPU buffer limits.
|
|
1046
|
+
* Delegates to the extracted splitMeshDataForBufferLimit() utility.
|
|
1130
1047
|
*/
|
|
1131
1048
|
splitMeshDataForBufferLimit(meshDataArray, maxBufferSize) {
|
|
1132
|
-
|
|
1133
|
-
let totalVertexBytes = 0;
|
|
1134
|
-
let totalIndexBytes = 0;
|
|
1135
|
-
for (const mesh of meshDataArray) {
|
|
1136
|
-
totalVertexBytes += (mesh.positions.length / 3) * BATCH_CONSTANTS.BYTES_PER_VERTEX;
|
|
1137
|
-
totalIndexBytes += mesh.indices.length * BATCH_CONSTANTS.BYTES_PER_INDEX;
|
|
1138
|
-
}
|
|
1139
|
-
if (totalVertexBytes <= maxBufferSize && totalIndexBytes <= maxBufferSize) {
|
|
1140
|
-
return [meshDataArray];
|
|
1141
|
-
}
|
|
1142
|
-
// Slow path: partition into chunks
|
|
1143
|
-
const chunks = [];
|
|
1144
|
-
let currentChunk = [];
|
|
1145
|
-
let currentVertexBytes = 0;
|
|
1146
|
-
let currentIndexBytes = 0;
|
|
1147
|
-
for (const mesh of meshDataArray) {
|
|
1148
|
-
const meshVertexBytes = (mesh.positions.length / 3) * BATCH_CONSTANTS.BYTES_PER_VERTEX;
|
|
1149
|
-
const meshIndexBytes = mesh.indices.length * BATCH_CONSTANTS.BYTES_PER_INDEX;
|
|
1150
|
-
// Would adding this mesh exceed the limit? Start a new chunk.
|
|
1151
|
-
// (Skip check when chunk is empty — a single mesh must always be included.)
|
|
1152
|
-
if (currentChunk.length > 0 &&
|
|
1153
|
-
(currentVertexBytes + meshVertexBytes > maxBufferSize ||
|
|
1154
|
-
currentIndexBytes + meshIndexBytes > maxBufferSize)) {
|
|
1155
|
-
chunks.push(currentChunk);
|
|
1156
|
-
currentChunk = [];
|
|
1157
|
-
currentVertexBytes = 0;
|
|
1158
|
-
currentIndexBytes = 0;
|
|
1159
|
-
}
|
|
1160
|
-
currentChunk.push(mesh);
|
|
1161
|
-
currentVertexBytes += meshVertexBytes;
|
|
1162
|
-
currentIndexBytes += meshIndexBytes;
|
|
1163
|
-
}
|
|
1164
|
-
if (currentChunk.length > 0) {
|
|
1165
|
-
chunks.push(currentChunk);
|
|
1166
|
-
}
|
|
1167
|
-
return chunks;
|
|
1049
|
+
return splitMeshDataForBufferLimit(meshDataArray, maxBufferSize);
|
|
1168
1050
|
}
|
|
1169
1051
|
/**
|
|
1170
1052
|
* Resolve which bucket a mesh should be added to.
|
|
@@ -1543,224 +1425,18 @@ export class Scene {
|
|
|
1543
1425
|
return bbox;
|
|
1544
1426
|
}
|
|
1545
1427
|
/**
|
|
1546
|
-
*
|
|
1547
|
-
*
|
|
1548
|
-
|
|
1549
|
-
rayIntersectsBox(rayOrigin, rayDirInv, // 1/rayDir for efficiency
|
|
1550
|
-
rayDirSign, box) {
|
|
1551
|
-
const bounds = [box.min, box.max];
|
|
1552
|
-
let tmin = -Infinity;
|
|
1553
|
-
let tmax = Infinity;
|
|
1554
|
-
// X axis
|
|
1555
|
-
if (!isFinite(rayDirInv.x)) {
|
|
1556
|
-
if (rayOrigin.x < box.min.x || rayOrigin.x > box.max.x)
|
|
1557
|
-
return false;
|
|
1558
|
-
}
|
|
1559
|
-
else {
|
|
1560
|
-
tmin = (bounds[rayDirSign[0]].x - rayOrigin.x) * rayDirInv.x;
|
|
1561
|
-
tmax = (bounds[1 - rayDirSign[0]].x - rayOrigin.x) * rayDirInv.x;
|
|
1562
|
-
}
|
|
1563
|
-
// Y axis
|
|
1564
|
-
if (!isFinite(rayDirInv.y)) {
|
|
1565
|
-
if (rayOrigin.y < box.min.y || rayOrigin.y > box.max.y)
|
|
1566
|
-
return false;
|
|
1567
|
-
}
|
|
1568
|
-
else {
|
|
1569
|
-
const tymin = (bounds[rayDirSign[1]].y - rayOrigin.y) * rayDirInv.y;
|
|
1570
|
-
const tymax = (bounds[1 - rayDirSign[1]].y - rayOrigin.y) * rayDirInv.y;
|
|
1571
|
-
if (tmin > tymax || tymin > tmax)
|
|
1572
|
-
return false;
|
|
1573
|
-
if (tymin > tmin)
|
|
1574
|
-
tmin = tymin;
|
|
1575
|
-
if (tymax < tmax)
|
|
1576
|
-
tmax = tymax;
|
|
1577
|
-
}
|
|
1578
|
-
// Z axis
|
|
1579
|
-
if (!isFinite(rayDirInv.z)) {
|
|
1580
|
-
if (rayOrigin.z < box.min.z || rayOrigin.z > box.max.z)
|
|
1581
|
-
return false;
|
|
1582
|
-
}
|
|
1583
|
-
else {
|
|
1584
|
-
const tzmin = (bounds[rayDirSign[2]].z - rayOrigin.z) * rayDirInv.z;
|
|
1585
|
-
const tzmax = (bounds[1 - rayDirSign[2]].z - rayOrigin.z) * rayDirInv.z;
|
|
1586
|
-
if (tmin > tzmax || tzmin > tmax)
|
|
1587
|
-
return false;
|
|
1588
|
-
if (tzmin > tmin)
|
|
1589
|
-
tmin = tzmin;
|
|
1590
|
-
if (tzmax < tmax)
|
|
1591
|
-
tmax = tzmax;
|
|
1592
|
-
}
|
|
1593
|
-
return tmax >= 0;
|
|
1594
|
-
}
|
|
1595
|
-
/**
|
|
1596
|
-
* Ray-box intersection returning entry distance (tNear).
|
|
1597
|
-
* Returns null if no intersection, otherwise the distance along the ray
|
|
1598
|
-
* to the entry point (clamped to 0 if the ray originates inside the box).
|
|
1599
|
-
* Handles zero ray direction components (axis-aligned rays) safely.
|
|
1600
|
-
*/
|
|
1601
|
-
rayBoxDistance(rayOrigin, rayDirInv, rayDirSign, box) {
|
|
1602
|
-
const bounds = [box.min, box.max];
|
|
1603
|
-
let tmin = -Infinity;
|
|
1604
|
-
let tmax = Infinity;
|
|
1605
|
-
// X axis
|
|
1606
|
-
if (!isFinite(rayDirInv.x)) {
|
|
1607
|
-
// Ray parallel to X: miss if origin outside X slab
|
|
1608
|
-
if (rayOrigin.x < box.min.x || rayOrigin.x > box.max.x)
|
|
1609
|
-
return null;
|
|
1610
|
-
}
|
|
1611
|
-
else {
|
|
1612
|
-
const t1 = (bounds[rayDirSign[0]].x - rayOrigin.x) * rayDirInv.x;
|
|
1613
|
-
const t2 = (bounds[1 - rayDirSign[0]].x - rayOrigin.x) * rayDirInv.x;
|
|
1614
|
-
tmin = t1;
|
|
1615
|
-
tmax = t2;
|
|
1616
|
-
}
|
|
1617
|
-
// Y axis
|
|
1618
|
-
if (!isFinite(rayDirInv.y)) {
|
|
1619
|
-
if (rayOrigin.y < box.min.y || rayOrigin.y > box.max.y)
|
|
1620
|
-
return null;
|
|
1621
|
-
}
|
|
1622
|
-
else {
|
|
1623
|
-
const tymin = (bounds[rayDirSign[1]].y - rayOrigin.y) * rayDirInv.y;
|
|
1624
|
-
const tymax = (bounds[1 - rayDirSign[1]].y - rayOrigin.y) * rayDirInv.y;
|
|
1625
|
-
if (tmin > tymax || tymin > tmax)
|
|
1626
|
-
return null;
|
|
1627
|
-
if (tymin > tmin)
|
|
1628
|
-
tmin = tymin;
|
|
1629
|
-
if (tymax < tmax)
|
|
1630
|
-
tmax = tymax;
|
|
1631
|
-
}
|
|
1632
|
-
// Z axis
|
|
1633
|
-
if (!isFinite(rayDirInv.z)) {
|
|
1634
|
-
if (rayOrigin.z < box.min.z || rayOrigin.z > box.max.z)
|
|
1635
|
-
return null;
|
|
1636
|
-
}
|
|
1637
|
-
else {
|
|
1638
|
-
const tzmin = (bounds[rayDirSign[2]].z - rayOrigin.z) * rayDirInv.z;
|
|
1639
|
-
const tzmax = (bounds[1 - rayDirSign[2]].z - rayOrigin.z) * rayDirInv.z;
|
|
1640
|
-
if (tmin > tzmax || tzmin > tmax)
|
|
1641
|
-
return null;
|
|
1642
|
-
if (tzmin > tmin)
|
|
1643
|
-
tmin = tzmin;
|
|
1644
|
-
if (tzmax < tmax)
|
|
1645
|
-
tmax = tzmax;
|
|
1646
|
-
}
|
|
1647
|
-
if (tmax < 0)
|
|
1648
|
-
return null;
|
|
1649
|
-
return tmin < 0 ? 0 : tmin;
|
|
1650
|
-
}
|
|
1651
|
-
/**
|
|
1652
|
-
* Möller–Trumbore ray-triangle intersection
|
|
1653
|
-
* Returns distance to intersection or null if no hit
|
|
1654
|
-
*/
|
|
1655
|
-
rayTriangleIntersect(rayOrigin, rayDir, v0, v1, v2) {
|
|
1656
|
-
const EPSILON = 1e-7;
|
|
1657
|
-
const edge1 = MathUtils.subtract(v1, v0);
|
|
1658
|
-
const edge2 = MathUtils.subtract(v2, v0);
|
|
1659
|
-
const h = MathUtils.cross(rayDir, edge2);
|
|
1660
|
-
const a = MathUtils.dot(edge1, h);
|
|
1661
|
-
if (a > -EPSILON && a < EPSILON)
|
|
1662
|
-
return null; // Ray parallel to triangle
|
|
1663
|
-
const f = 1.0 / a;
|
|
1664
|
-
const s = MathUtils.subtract(rayOrigin, v0);
|
|
1665
|
-
const u = f * MathUtils.dot(s, h);
|
|
1666
|
-
if (u < 0.0 || u > 1.0)
|
|
1667
|
-
return null;
|
|
1668
|
-
const q = MathUtils.cross(s, edge1);
|
|
1669
|
-
const v = f * MathUtils.dot(rayDir, q);
|
|
1670
|
-
if (v < 0.0 || u + v > 1.0)
|
|
1671
|
-
return null;
|
|
1672
|
-
const t = f * MathUtils.dot(edge2, q);
|
|
1673
|
-
if (t > EPSILON)
|
|
1674
|
-
return t; // Ray intersection
|
|
1675
|
-
return null;
|
|
1676
|
-
}
|
|
1677
|
-
/**
|
|
1678
|
-
* CPU raycast against all mesh data
|
|
1679
|
-
* Returns expressId and modelIndex of closest hit, or null
|
|
1680
|
-
* For multi-model support: tracks which model's geometry was hit
|
|
1428
|
+
* CPU raycast against all mesh data.
|
|
1429
|
+
* Returns expressId and modelIndex of closest hit, or null.
|
|
1430
|
+
* Delegates to extracted raycaster utilities.
|
|
1681
1431
|
*/
|
|
1682
1432
|
raycast(rayOrigin, rayDir, hiddenIds, isolatedIds) {
|
|
1683
|
-
|
|
1684
|
-
const rayDirInv = {
|
|
1685
|
-
x: rayDir.x !== 0 ? 1.0 / rayDir.x : Infinity,
|
|
1686
|
-
y: rayDir.y !== 0 ? 1.0 / rayDir.y : Infinity,
|
|
1687
|
-
z: rayDir.z !== 0 ? 1.0 / rayDir.z : Infinity,
|
|
1688
|
-
};
|
|
1689
|
-
const rayDirSign = [
|
|
1690
|
-
rayDirInv.x < 0 ? 1 : 0,
|
|
1691
|
-
rayDirInv.y < 0 ? 1 : 0,
|
|
1692
|
-
rayDirInv.z < 0 ? 1 : 0,
|
|
1693
|
-
];
|
|
1694
|
-
let closestHit = null;
|
|
1695
|
-
let closestDistance = Infinity;
|
|
1433
|
+
const { rayDirInv, rayDirSign } = prepareRayDirInv(rayDir);
|
|
1696
1434
|
// When geometry data has been released, use bounding-box-only raycast.
|
|
1697
|
-
// This is less accurate but uses only the precomputed bounding boxes.
|
|
1698
|
-
// For precise picking, callers should use GPU picking instead.
|
|
1699
1435
|
if (this.geometryReleased) {
|
|
1700
|
-
|
|
1701
|
-
if (hiddenIds?.has(expressId))
|
|
1702
|
-
continue;
|
|
1703
|
-
if (isolatedIds !== null && isolatedIds !== undefined && !isolatedIds.has(expressId))
|
|
1704
|
-
continue;
|
|
1705
|
-
const tNear = this.rayBoxDistance(rayOrigin, rayDirInv, rayDirSign, bbox);
|
|
1706
|
-
if (tNear !== null && tNear < closestDistance) {
|
|
1707
|
-
closestDistance = tNear;
|
|
1708
|
-
closestHit = { expressId, distance: tNear };
|
|
1709
|
-
}
|
|
1710
|
-
}
|
|
1711
|
-
return closestHit;
|
|
1712
|
-
}
|
|
1713
|
-
// First pass: filter by bounding box (fast)
|
|
1714
|
-
const candidates = [];
|
|
1715
|
-
for (const expressId of this.meshDataMap.keys()) {
|
|
1716
|
-
// Skip hidden elements
|
|
1717
|
-
if (hiddenIds?.has(expressId))
|
|
1718
|
-
continue;
|
|
1719
|
-
// Skip non-isolated elements if isolation is active
|
|
1720
|
-
if (isolatedIds !== null && isolatedIds !== undefined && !isolatedIds.has(expressId))
|
|
1721
|
-
continue;
|
|
1722
|
-
const bbox = this.getEntityBoundingBox(expressId);
|
|
1723
|
-
if (!bbox)
|
|
1724
|
-
continue;
|
|
1725
|
-
if (this.rayIntersectsBox(rayOrigin, rayDirInv, rayDirSign, bbox)) {
|
|
1726
|
-
candidates.push(expressId);
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
// Second pass: test triangles for candidates (accurate)
|
|
1730
|
-
for (const expressId of candidates) {
|
|
1731
|
-
const pieces = this.meshDataMap.get(expressId);
|
|
1732
|
-
if (!pieces)
|
|
1733
|
-
continue;
|
|
1734
|
-
for (const piece of pieces) {
|
|
1735
|
-
const positions = piece.positions;
|
|
1736
|
-
const indices = piece.indices;
|
|
1737
|
-
const pieceEntityIds = piece.entityIds; // per-vertex IDs for merged meshes
|
|
1738
|
-
// Test each triangle
|
|
1739
|
-
for (let i = 0; i < indices.length; i += 3) {
|
|
1740
|
-
// For color-merged meshes, skip triangles that don't belong to
|
|
1741
|
-
// this entity. Without this check, hitting ANY triangle in the
|
|
1742
|
-
// merged batch would attribute it to the candidate expressId.
|
|
1743
|
-
if (pieceEntityIds) {
|
|
1744
|
-
const vertIdx = indices[i];
|
|
1745
|
-
if (pieceEntityIds[vertIdx] !== expressId)
|
|
1746
|
-
continue;
|
|
1747
|
-
}
|
|
1748
|
-
const i0 = indices[i] * 3;
|
|
1749
|
-
const i1 = indices[i + 1] * 3;
|
|
1750
|
-
const i2 = indices[i + 2] * 3;
|
|
1751
|
-
const v0 = { x: positions[i0], y: positions[i0 + 1], z: positions[i0 + 2] };
|
|
1752
|
-
const v1 = { x: positions[i1], y: positions[i1 + 1], z: positions[i1 + 2] };
|
|
1753
|
-
const v2 = { x: positions[i2], y: positions[i2 + 1], z: positions[i2 + 2] };
|
|
1754
|
-
const t = this.rayTriangleIntersect(rayOrigin, rayDir, v0, v1, v2);
|
|
1755
|
-
if (t !== null && t < closestDistance) {
|
|
1756
|
-
closestDistance = t;
|
|
1757
|
-
// Track modelIndex from the piece that was actually hit
|
|
1758
|
-
closestHit = { expressId, distance: t, modelIndex: piece.modelIndex };
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1436
|
+
return raycastBoundingBoxes(rayOrigin, rayDirInv, rayDirSign, this.boundingBoxes, hiddenIds, isolatedIds);
|
|
1762
1437
|
}
|
|
1763
|
-
|
|
1438
|
+
// Full triangle-level raycast with bounding-box pre-filter
|
|
1439
|
+
return raycastTriangles(rayOrigin, rayDir, rayDirInv, rayDirSign, this.meshDataMap, (id) => this.getEntityBoundingBox(id), hiddenIds, isolatedIds);
|
|
1764
1440
|
}
|
|
1765
1441
|
}
|
|
1766
1442
|
//# sourceMappingURL=scene.js.map
|