@xeokit/xeokit-sdk 2.6.94 → 2.6.95

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.
@@ -3,24 +3,8 @@ import { Mesh } from "../mesh/Mesh.js";
3
3
  import { ReadableGeometry } from "../geometry/ReadableGeometry.js";
4
4
  import { buildLineGeometry } from "../geometry/index.js";
5
5
  import { PhongMaterial } from "../materials/PhongMaterial.js";
6
- import earcut from '../libs/earcut.js';
7
6
 
8
- const epsilon = 1e-6;
9
- const worldUp = [0, 1, 0];
10
- const worldRight = [1, 0, 0];
11
7
  const tempVec3a = math.vec3();
12
- const tempVec3b = math.vec3();
13
- const tempVec3c = math.vec3();
14
- const tempVec3d = math.vec3();
15
- const planeOff = math.vec3();
16
-
17
- function pointsEqual(p1, p2) {
18
- return (
19
- Math.abs(p1[0] - p2[0]) < epsilon &&
20
- Math.abs(p1[1] - p2[1]) < epsilon &&
21
- Math.abs(p1[2] - p2[2]) < epsilon
22
- );
23
- }
24
8
 
25
9
  /**
26
10
  * @desc Implements hatching for Solid objects on a {@link Scene}.
@@ -120,642 +104,152 @@ class SectionCaps {
120
104
  * @constructor
121
105
  */
122
106
  constructor(scene) {
123
- this.scene = scene;
124
- this._resourcesAllocated = false;
125
- }
126
-
127
- _onCapMaterialUpdated(entityId, modelId) {
128
- if(!this._resourcesAllocated) {
129
- this._resourcesAllocated = true;
130
- this._sectionPlanes = [];
131
- this._sceneModelsData = {};
132
- this._dirtyMap = {};
133
- this._prevIntersectionModelsMap = {};
134
- this._sectionPlaneTimeout = null;
135
- this._updateTimeout = null;
136
-
137
- const handleSectionPlane = (sectionPlane) => {
138
-
139
- const onSectionPlaneUpdated = () => {
140
- this._setAllDirty(true);
141
- this._update();
142
- }
143
- this._sectionPlanes.push(sectionPlane);
144
- sectionPlane.on('pos', onSectionPlaneUpdated);
145
- sectionPlane.on('dir', onSectionPlaneUpdated);
146
- sectionPlane.on('active', onSectionPlaneUpdated);
147
- sectionPlane.once('destroyed', (() => {
148
- const sectionPlaneId = sectionPlane.id;
149
- if (sectionPlaneId) {
150
- this._sectionPlanes = this._sectionPlanes.filter((sectionPlane) => sectionPlane.id !== sectionPlaneId);
151
- this._update();
152
- }
153
- }).bind(this));
154
- }
155
-
156
- for(const key in this.scene.sectionPlanes){
157
- handleSectionPlane(this.scene.sectionPlanes[key]);
158
- }
159
-
160
- this._onSectionPlaneCreated = this.scene.on('sectionPlaneCreated', handleSectionPlane)
161
-
162
- this._onTick = this.scene.on("tick", () => {
163
- //on ticks we only check if there is a model that we have saved vertices for,
164
- //but it's no more available on the scene, or if its visibility changed
165
- let dirty = false;
166
- for(const sceneModelId in this._sceneModelsData) {
167
- if(!this.scene.models[sceneModelId]){
168
- delete this._sceneModelsData[sceneModelId];
169
- dirty = true;
170
- } else if (this._sceneModelsData[sceneModelId].visible !== (!!this.scene.models[sceneModelId].visible)) {
171
- this._sceneModelsData[sceneModelId].visible = !!this.scene.models[sceneModelId].visible;
172
- dirty = true;
173
- }
174
- }
175
- if (dirty) {
176
- this._update();
177
- }
178
- })
179
- }
180
-
181
- if(!this._dirtyMap[modelId])
182
- this._dirtyMap[modelId] = new Map();
183
-
184
- this._dirtyMap[modelId].set(entityId, true);
185
- this._update();
186
- }
187
-
188
- _update() {
189
- clearTimeout(this._updateTimeout);
190
- this._deletePreviousModels();
191
- this._updateTimeout = setTimeout(() => {
192
- clearTimeout(this._updateTimeout);
193
- const sceneModels = Object.values(this.scene.models).filter(sceneModel => sceneModel.visible);
194
- this._addHatches(sceneModels, this._sectionPlanes.filter(sectionPlane => sectionPlane.active));
195
- this._setAllDirty(false);
196
- }, 100);
197
- }
198
-
199
- _setAllDirty(value) {
200
- for(const key in this._dirty) {
201
- this._dirtyMap[key].forEach((_, key2) => this._dirtyMap[key].set(key2, value));
202
- }
203
- }
204
-
205
- _addHatches(sceneModels, planes) {
206
-
207
- planes.forEach((plane) => {
208
- sceneModels.forEach((sceneModel) => {
209
- if(!this._doesPlaneIntersectBoundingBox(sceneModel.aabb, plane)) return;
210
-
211
- if(!this._dirtyMap[sceneModel.id]) return;
212
-
213
- //#region calculating segments in unsorted way
214
- //we calculate the segments by intersecting plane with each triangle
215
- const unsortedSegments = new Map();
216
- const objects = sceneModel.objects;
217
- // Preallocate arrays for triangle vertices to avoid repeated allocation
218
- const triangle = [
219
- math.vec3(),
220
- math.vec3(),
221
- math.vec3()
222
- ];
223
-
224
- this._dirtyMap[sceneModel.id].forEach((isDirty, objectId) => {
225
- if (!isDirty) {
226
- return;
227
- }
228
-
229
- const object = objects[objectId];
230
-
231
- if(!this._doesPlaneIntersectBoundingBox(object.aabb, plane)) return;
232
-
233
- if(!this._sceneModelsData[sceneModel.id]) {
234
- const aabb = sceneModel.aabb;
235
- this._sceneModelsData[sceneModel.id] = {
236
- verticesMap: new Map(),
237
- indicesMap: new Map(),
238
- // modelOrigin is critical to use when handling models with large coordinates.
239
- // See XCD-306 and examples/slicing/SectionCaps_at_distance.html for more details.
240
- modelOrigin: math.vec3([
241
- (aabb[0] + aabb[3]) / 2,
242
- (aabb[1] + aabb[4]) / 2,
243
- (aabb[2] + aabb[5]) / 2
244
- ])
245
- };
246
- }
247
-
248
- const sceneModelData = this._sceneModelsData[sceneModel.id];
249
- const modelOrigin = sceneModelData.modelOrigin;
250
-
251
- if(!sceneModelData.verticesMap.has(objectId)) {
252
- const isSolid = object.meshes[0].isSolid();
253
- const vertices = [ ];
254
- const indices = [ ];
255
- if(isSolid && object.capMaterial) {
256
- object.getEachVertex(v => vertices.push(v[0]-modelOrigin[0], v[1]-modelOrigin[1], v[2]-modelOrigin[2]));
257
- object.getEachIndex(i => indices.push(i));
258
- }
259
- sceneModelData.verticesMap.set(objectId, vertices);
260
- sceneModelData.indicesMap.set(objectId, indices);
261
- }
262
-
263
- const vertices = sceneModelData.verticesMap.get(objectId);
264
- const indices = sceneModelData.indicesMap.get(objectId);
265
- const planeDist = -math.dotVec3(math.subVec3(plane.pos, modelOrigin, tempVec3a), plane.dir);
266
-
267
- const capSegments = [];
268
- const vertCount = indices.length;
269
-
270
- for (let i = 0; i < vertCount; i += 3) {
271
- // Reuse triangle buffer instead of creating new arrays
272
- for (let j = 0; j < 3; j++) {
273
- const idx = indices[i + j] * 3;
274
- triangle[j][0] = vertices[idx];
275
- triangle[j][1] = vertices[idx + 1];
276
- triangle[j][2] = vertices[idx + 2];
277
- }
278
-
279
- // Early null check
280
- if (!triangle[0][0] && !triangle[0][1] && !triangle[0][2]) continue;
281
-
282
- const intersections = [];
283
- for (let i = 0; i < 3; i++) {
284
- const p1 = triangle[i];
285
- const p2 = triangle[(i + 1) % 3];
286
-
287
- const d1 = planeDist + math.dotVec3(plane.dir, p1);
288
- const d2 = planeDist + math.dotVec3(plane.dir, p2);
289
-
290
- if (d1 * d2 > 0) continue;
291
-
292
- const t = -d1 / (d2 - d1);
293
-
294
- intersections.push(math.lerpVec3(t, 0, 1, p1, p2, math.vec3()));
295
- }
296
-
297
- if(intersections.length === 2) capSegments.push(intersections);
298
- }
299
-
300
- if (capSegments.length > 0) {
301
- unsortedSegments.set(objectId, capSegments);
302
- }
303
- })
304
- //#endregion
305
-
306
- //#region sorting the segments
307
- const orderedSegments = new Map();
308
- unsortedSegments.forEach((unsortedSegment, segmentedId) => {
309
- orderedSegments.set(segmentedId, [
310
- [
311
- unsortedSegment[0] //this is also an array of two vectors
312
- ]
313
- ]);
314
- unsortedSegment.splice(0, 1);
315
- let index = 0;
316
- while (unsortedSegment.length > 0) {
317
- const lastPoint = orderedSegments.get(segmentedId)[index][orderedSegments.get(segmentedId)[index].length - 1][1];
318
- let found = false;
319
-
320
- for (let i = 0; i < unsortedSegment.length; i++) {
321
- const [start, end] = unsortedSegment[i];
322
- if (pointsEqual(lastPoint, start)) {
323
- orderedSegments.get(segmentedId)[index].push(unsortedSegment[i]);
324
- unsortedSegment.splice(i, 1);
325
- found = true;
326
- break;
327
- } else if (pointsEqual(lastPoint, end)) {
328
- orderedSegments.get(segmentedId)[index].push([end, start]);
329
- unsortedSegment.splice(i, 1);
330
- found = true;
331
- break;
107
+ let destroy = null;
108
+ const modelCaches = { };
109
+ const sectionPlanes = [ ];
110
+
111
+ this.destroy = () => destroy && destroy();
112
+
113
+ let updateTimeout = null;
114
+
115
+ const update = () => {
116
+ clearTimeout(updateTimeout);
117
+ updateTimeout = setTimeout(() => {
118
+ const visibleSceneModels = Object.values(scene.models).filter(sceneModel => (sceneModel.id in modelCaches) && sceneModel.visible);
119
+ sectionPlanes.forEach((plane) => {
120
+ if (plane.active) {
121
+ const sliceMesh = math.makeSectionPlaneSlicer(plane.pos, plane.quaternion);
122
+ visibleSceneModels.forEach(sceneModel => {
123
+ const modelAABB = sceneModel.aabb;
124
+ if (math.planeIntersectsAABB3(plane, modelAABB)) {
125
+ // modelCenter is critical to use when handling models with large coordinates.
126
+ // See XCD-306 and examples/slicing/SectionCaps_at_distance.html for more details.
127
+ const modelCenter = math.getAABB3Center(modelAABB, math.vec3());
128
+
129
+ modelCaches[sceneModel.id].entityCaches.forEach((entityCache, entityId) => {
130
+ const entity = sceneModel.objects[entityId];
131
+ if (entityCache.generateCaps && entity.capMaterial && math.planeIntersectsAABB3(plane, entity.aabb)) {
132
+ entityCache.meshCaches ||= entity.meshes.filter(mesh => mesh.isSolid()).map(mesh => {
133
+ const meshIndices = [ ];
134
+ const meshVertices = [ ];
135
+ mesh.getEachIndex(i => meshIndices.push(i));
136
+ mesh.getEachVertex(v => meshVertices.push(...math.subVec3(v, modelCenter, tempVec3a)));
137
+ return { mesh: mesh, meshIndices: meshIndices, meshVertices: meshVertices };
138
+ });
139
+
140
+ entityCache.meshCaches.filter(meshCache => math.planeIntersectsAABB3(plane, meshCache.mesh.aabb)).forEach((meshCache, meshIdx) => {
141
+ sliceMesh(modelCenter, meshCache.meshIndices, meshCache.meshVertices).forEach((geo, geoIdx) => {
142
+ entityCache.capMeshes.push(new Mesh(scene, {
143
+ isObject: true,
144
+ id: `${plane.id}-${entityId}-${meshIdx}-${geoIdx}`,
145
+ material: entity.capMaterial,
146
+ origin: math.addVec3(modelCenter, math.mulVec3Scalar(plane.dir, 0.001, tempVec3a), tempVec3a),
147
+ geometry: new ReadableGeometry(scene, {
148
+ primitive: "triangles",
149
+ indices: geo.indices,
150
+ positions: geo.positions,
151
+ normals: geo.normals,
152
+ uv: geo.uv
153
+ })
154
+ }));
155
+ });
156
+ });
157
+ }
158
+ });
332
159
  }
160
+ });
161
+ }
162
+ });
163
+ visibleSceneModels.forEach(sceneModel => modelCaches[sceneModel.id].entityCaches.forEach(entityCache => entityCache.generateCaps = false));
164
+ }, 100);
165
+ };
166
+
167
+ this._onCapMaterialUpdated = (entity) => {
168
+ if (! destroy) {
169
+ updateTimeout = null;
170
+
171
+ const handleSectionPlane = (sectionPlane) => {
172
+ const onSectionPlaneUpdated = () => {
173
+ Object.values(modelCaches).forEach(modelCache => modelCache.entityCaches.forEach(entityCache => {
174
+ entityCache.destroyCaps();
175
+ entityCache.generateCaps = true;
176
+ }));
177
+ update();
178
+ };
179
+ sectionPlanes.push(sectionPlane);
180
+ sectionPlane.on('pos', onSectionPlaneUpdated);
181
+ sectionPlane.on('dir', onSectionPlaneUpdated);
182
+ sectionPlane.on('active', onSectionPlaneUpdated);
183
+ sectionPlane.once('destroyed', () => {
184
+ const idx = sectionPlanes.indexOf(sectionPlane);
185
+ if (idx >= 0) {
186
+ sectionPlanes.splice(idx, 1);
187
+ onSectionPlaneUpdated();
333
188
  }
189
+ });
190
+ };
334
191
 
335
- if (!found) {
336
- if (pointsEqual(lastPoint, orderedSegments.get(segmentedId)[index][0][0])) {
337
- if (unsortedSegment.length > 1) {
338
- orderedSegments.get(segmentedId).push([
339
- unsortedSegments.get(segmentedId)[0]
340
- ]);
341
- unsortedSegment.splice(0, 1);
342
- index++;
343
- continue;
344
- }
345
-
346
- }
347
- }
192
+ for (const key in scene.sectionPlanes){
193
+ handleSectionPlane(scene.sectionPlanes[key]);
194
+ }
348
195
 
349
- if (!found) {
350
- // console.error(`Could not find a matching segment. Loop may not be closed. Key: ${key}`);
351
- break;
196
+ const onSectionPlaneCreated = scene.on('sectionPlaneCreated', handleSectionPlane);
197
+
198
+ const onTick = scene.on("tick", () => {
199
+ //on ticks we only check if there is a model that we have saved vertices for,
200
+ //but it's no more available on the scene, or if its visibility changed
201
+ let doUpdate = false;
202
+ for (const sceneModelId in modelCaches) {
203
+ const sceneModel = scene.models[sceneModelId];
204
+ if (! sceneModel) {
205
+ modelCaches[sceneModelId].entityCaches.forEach(entityCache => entityCache.destroyCaps());
206
+ delete modelCaches[sceneModelId];
207
+ doUpdate = true;
208
+ } else if (modelCaches[sceneModelId].modelVisible != sceneModel.visible) {
209
+ const modelCache = modelCaches[sceneModelId];
210
+ modelCache.modelVisible = !!sceneModel.visible;
211
+ modelCache.entityCaches.forEach(entityCache => {
212
+ entityCache.destroyCaps();
213
+ entityCache.generateCaps = true;
214
+ });
215
+ doUpdate = true;
352
216
  }
353
217
  }
354
- })
355
- //#endregion
356
-
357
- //#region projecting the segments to 2D
358
- const projectedSegments = new Map();
359
- orderedSegments.forEach((orderedSegment, key) => {
360
- const arr = [];
361
- for (let i = 0; i < orderedSegment.length; i++) {
362
- arr.push([]);
363
- orderedSegment[i].forEach((segment) => {
364
- arr[i].push([
365
- this._projectTo2D(segment[0], plane.dir),
366
- this._projectTo2D(segment[1], plane.dir)
367
- ])
368
- })
218
+ if (doUpdate) {
219
+ update();
369
220
  }
370
- projectedSegments.set(key, arr);
371
- })
372
- //#endregion
373
-
374
- //#region creating caps using earcut and then projecting them back to 3D
375
- const caps = new Map();
376
- let arr;
377
- projectedSegments.forEach((segment, segmentId) => {
378
- const modelOrigin = this._sceneModelsData[sceneModel.id].modelOrigin;
379
- arr = [];
380
- const loops = segment;
381
-
382
- // Group related loops (outer boundaries with their holes)
383
-
384
- const groupedLoops = [];
385
- const used = new Set();
386
-
387
- for (let i = 0; i < loops.length; i++) {
388
- if (used.has(i)) continue;
389
-
390
- const group = [loops[i]];
391
- used.add(i);
392
-
393
- // Check remaining loops
394
- for (let j = i + 1; j < loops.length; j++) {
395
- if (used.has(j)) continue;
396
-
397
- if (this._isLoopInside(loops[i], loops[j]) ||
398
- this._isLoopInside(loops[j], loops[i])) {
399
- group.push(loops[j]);
400
- used.add(j);
401
- }
402
- }
403
-
404
- groupedLoops.push(group);
221
+ });
222
+ destroy = () => {
223
+ for (const sceneModelId in modelCaches) {
224
+ modelCaches[sceneModelId].entityCaches.forEach(entityCache => entityCache.destroyCaps());
405
225
  }
226
+ scene.off(onSectionPlaneCreated);
227
+ scene.off(onTick);
228
+ };
229
+ }
406
230
 
407
- // Process each group separately
408
- groupedLoops.forEach(group => {
409
- // Convert the segments into a flat array of vertices and find holes
410
- const vertices = [];
411
- const holes = [];
412
- let currentIndex = 0;
413
-
414
- // First, determine which loop has the largest area - this will be our outer boundary
415
- const areas = group.map(loop => {
416
- let area = 0;
417
- for (let i = 0; i < loop.length; i++) {
418
- const j = (i + 1) % loop.length;
419
- area += loop[i][0][0] * loop[j][0][1];
420
- area -= loop[j][0][0] * loop[i][0][1];
421
- }
422
- return Math.abs(area) / 2;
423
- });
424
-
425
- // Find index of the loop with maximum area
426
- const outerLoopIndex = areas.indexOf(Math.max(...areas));
427
-
428
- // Add the outer boundary first
429
- group[outerLoopIndex].forEach(segment => {
430
- vertices.push(segment[0][0], segment[0][1]);
431
- currentIndex += 2;
432
- });
433
-
434
- // Then add all other loops as holes
435
- for (let i = 0; i < group.length; i++) {
436
- if (i !== outerLoopIndex) {
437
- // Store the starting vertex index for this hole
438
- holes.push(currentIndex / 2);
439
-
440
- group[i].forEach(segment => {
441
- vertices.push(segment[0][0], segment[0][1]);
442
- currentIndex += 2;
443
- });
444
- }
445
- }
446
-
447
- // Triangulate using earcut
448
- const triangles = earcut(vertices, holes);
449
-
450
- // // Convert triangulated 2D points back to 3D
451
- const cap3D = [];
452
-
453
- // Process each triangle
454
- for (let i = 0; i < triangles.length; i += 3) {
455
- const triangle = [];
456
-
457
- // Convert each vertex
458
- for (let j = 0; j < 3; j++) {
459
- const idx = triangles[i + j] * 2;
460
- const point2D = [vertices[idx], vertices[idx + 1]];
461
- const point3D = this._convertTo3D(point2D, plane, modelOrigin);
462
- triangle.push(point3D);
463
- }
464
-
465
- cap3D.push(triangle);
466
- }
467
- arr.push(cap3D);
468
- });
469
- caps.set(segmentId, arr);
470
- })
471
- //#endregion
472
-
473
- //#region converting caps to geometry
474
- const geometryData = new Map();
475
-
476
- caps.forEach((cap, capId) => {
477
- arr = [];
478
- cap.forEach(capTriangles => {
479
- // Create a vertex map to reuse vertices
480
- const vertexMap = new Map();
481
- const vertices = [];
482
- const indices = [];
483
- let currentIndex = 0;
484
-
485
- capTriangles.forEach(triangle => {
486
- const triangleIndices = [];
487
-
488
- // Process each vertex of the triangle
489
- triangle.forEach(vertex => {
490
- // Create a key for the vertex to check for duplicates
491
- const vertexKey = `${vertex[0].toFixed(6)},${vertex[1].toFixed(6)},${vertex[2].toFixed(6)}`;
492
-
493
- if (vertexMap.has(vertexKey)) {
494
- // Reuse existing vertex
495
- triangleIndices.push(vertexMap.get(vertexKey));
496
- } else {
497
- // Add new vertex
498
- vertices.push(vertex[0], vertex[1], vertex[2]);
499
- vertexMap.set(vertexKey, currentIndex);
500
- triangleIndices.push(currentIndex);
501
- currentIndex++;
502
- }
503
- });
504
-
505
- // Add triangle indices
506
- indices.push(...triangleIndices);
507
- });
508
-
509
- arr.push({
510
- positions: vertices,
511
- indices: indices
512
- });
513
- });
514
-
515
- geometryData.set(capId, arr);
516
- })
517
- //#endregion
518
-
519
- //#region adding meshes to the scene
520
- if(!this._prevIntersectionModelsMap[sceneModel.id])
521
- this._prevIntersectionModelsMap[sceneModel.id] = new Map();
522
-
523
- // Cache plane direction values
524
- math.mulVec3Scalar(plane.dir, 0.001, planeOff); // Use dedicated planeOff, as tempVec* are overwritten by _createUVs
525
-
526
- geometryData.forEach((geometries, objectId) => {
527
- const modelOrigin = this._sceneModelsData[sceneModel.id].modelOrigin;
528
- const meshArray = new Array(geometries.size); // Pre-allocate array with known size
529
- let meshIndex = 0;
530
-
531
- geometries.forEach((geometry, index) => {
532
- const vertices = geometry.positions;
533
- const indices = geometry.indices;
534
- const verticesLength = vertices.length;
535
-
536
- // Build normals and UVs in parallel if possible
537
- const meshNormals = math.buildNormals(vertices, indices);
538
- const uvs = this._createUVs(vertices, plane, modelOrigin);
539
-
540
- // Create mesh with transformed vertices
541
- meshArray[meshIndex++] = new Mesh(this.scene, {
542
- id: `${plane.id}-${objectId}-${index}`,
543
- geometry: new ReadableGeometry(this.scene, {
544
- primitive: 'triangles',
545
- positions: vertices, // Only copy what we need
546
- indices,
547
- normals: meshNormals,
548
- uv: uvs
549
- }),
550
- origin: math.addVec3(modelOrigin, planeOff, tempVec3a),
551
- position: [0, 0, 0],
552
- rotation: [0, 0, 0],
553
- material: sceneModel.objects[objectId].capMaterial
554
- });
555
- })
556
- if(this._prevIntersectionModelsMap[sceneModel.id].has(objectId)) {
557
- this._prevIntersectionModelsMap[sceneModel.id].get(objectId).push(...meshArray)
231
+ const model = entity.model;
232
+ const modelId = model.id;
233
+ modelCaches[modelId] ||= { modelVisible: model.visible, entityCaches: new Map() };
234
+ const entityCaches = modelCaches[modelId].entityCaches;
235
+ const entityId = entity.id;
236
+ if (! entityCaches.has(entityId)) {
237
+ const entityCache = {
238
+ capMeshes: [ ],
239
+ meshCaches: null,
240
+ generateCaps: false,
241
+ destroyCaps: () => {
242
+ entityCache.capMeshes.forEach(capMesh => capMesh.destroy());
243
+ entityCache.capMeshes.length = 0;
558
244
  }
559
- else
560
- this._prevIntersectionModelsMap[sceneModel.id].set(objectId, meshArray);
561
- })
562
- //#endregion
563
- })
564
-
565
- })
566
-
567
- }
568
-
569
- _doesPlaneIntersectBoundingBox(bb, plane) {
570
- const min = [bb[0], bb[1], bb[2]];
571
- const max = [bb[3], bb[4], bb[5]];
572
-
573
- const corners = [
574
- [min[0], min[1], min[2]], // 000
575
- [max[0], min[1], min[2]], // 100
576
- [min[0], max[1], min[2]], // 010
577
- [max[0], max[1], min[2]], // 110
578
- [min[0], min[1], max[2]], // 001
579
- [max[0], min[1], max[2]], // 101
580
- [min[0], max[1], max[2]], // 011
581
- [max[0], max[1], max[2]] // 111
582
- ]
583
-
584
- // Calculate distance from each corner to the plane
585
- let hasPositive = false;
586
- let hasNegative = false;
587
-
588
- for (const corner of corners) {
589
- const distance = plane.dist + math.dotVec3(plane.dir, corner);
590
-
591
- if (distance > 0) hasPositive = true;
592
- if (distance < 0) hasNegative = true;
593
-
594
- // If we found points on both sides, the plane intersects the box
595
- if (hasPositive && hasNegative) return true;
596
- }
597
-
598
- // If all points are on the same side, no intersection
599
- return false;
600
- }
601
-
602
- //not used but kept for debugging
603
- _buildLines(sortedSegments) {
604
- for (const key in sortedSegments) {
605
- for (let i = 0; i < sortedSegments[key].length; i++) {
606
- const segments = sortedSegments[key][i];
607
- if (segments.length <= 0) continue;
608
- segments.forEach((segment, index) => {
609
- new Mesh(this.scene, {
610
- clippable: false,
611
- geometry: new ReadableGeometry(this.scene, buildLineGeometry({
612
- startPoint: segment[0],
613
- endPoint: segment[1],
614
- })),
615
- material: new PhongMaterial(this.scene, {
616
- emissive: [1, 0, 0]
617
- })
618
- });
619
- })
245
+ };
246
+ entityCaches.set(entityId, entityCache);
620
247
  }
621
-
622
- }
623
- }
624
-
625
- _projectTo2D(point, normal) {
626
- let u;
627
- if (Math.abs(normal[0]) > Math.abs(normal[1]))
628
- u = [-normal[2], 0, normal[0]];
629
- else
630
- u = [0, normal[2], -normal[1]];
631
-
632
- u = math.normalizeVec3(u);
633
- const normalTemp = math.vec3(normal);
634
- const cross = math.cross3Vec3(normalTemp, u)
635
- const v = math.normalizeVec3(cross);
636
- const x = math.dotVec3(point, u);
637
- const y = math.dotVec3(point, v);
638
-
639
- return [x, y]
640
-
641
- }
642
-
643
- _isLoopInside(loop1, loop2) {
644
- // Simple point-in-polygon test using the first point of loop1
645
- const point = loop1[0][0]; // First point of first segment
646
- let inside = false;
647
- for (let i = 0, j = loop2.length - 1; i < loop2.length; j = i++) {
648
- const xi = loop2[i][0][0], yi = loop2[i][0][1];
649
- const xj = loop2[j][0][0], yj = loop2[j][0][1];
650
-
651
- const intersect = ((yi > point[1]) !== (yj > point[1]))
652
- && (point[0] < (xj - xi) * (point[1] - yi) / (yj - yi) + xi);
653
-
654
- if (intersect) inside = !inside;
655
- }
656
-
657
- return inside;
658
- }
659
-
660
- _convertTo3D(point2D, plane, origin) {
661
- // Reconstruct the same basis vectors used in _projectTo2D
662
- let u, normal = plane.dir, planePosition = plane.pos;
663
- if (Math.abs(normal[0]) > Math.abs(normal[1])) {
664
- u = [-normal[2], 0, normal[0]];
665
- } else {
666
- u = [0, normal[2], -normal[1]];
667
- }
668
-
669
- u = math.normalizeVec3(u);
670
- const normalTemp = math.vec3(normal);
671
- const cross = math.cross3Vec3(normalTemp, u);
672
- const v = math.normalizeVec3(cross);
673
-
674
- // Reconstruct 3D point using the basis vectors
675
- const x = point2D[0];
676
- const y = point2D[1];
677
- const result = [
678
- u[0] * x + v[0] * y,
679
- u[1] * x + v[1] * y,
680
- u[2] * x + v[2] * y
681
- ];
682
-
683
- // Project the point onto the cutting plane
684
-
685
- const t = math.dotVec3(normal, [
686
- planePosition[0] - result[0] - origin[0],
687
- planePosition[1] - result[1] - origin[1],
688
- planePosition[2] - result[2] - origin[2]
689
- ]);
690
-
691
- return [
692
- result[0] + normal[0] * t,
693
- result[1] + normal[1] * t,
694
- result[2] + normal[2] * t
695
- ];
696
- }
697
-
698
- _deletePreviousModels() {
699
-
700
- for(const sceneModelId in this._prevIntersectionModelsMap) {
701
- const objects = this._prevIntersectionModelsMap[sceneModelId];
702
- objects.forEach((value, objectId) => {
703
- if(this._dirtyMap[sceneModelId].get(objectId)) {
704
- value.forEach((mesh) => {
705
- mesh.destroy();
706
- })
707
- this._prevIntersectionModelsMap[sceneModelId].delete(objectId);
708
- }
709
- })
710
- if(this._prevIntersectionModelsMap[sceneModelId].size <= 0)
711
- delete this._prevIntersectionModelsMap[sceneModelId];
712
-
713
- }
714
-
715
- }
716
-
717
- _createUVs(vertices, plane, origin) {
718
- const O = plane.pos;
719
- const D = tempVec3a;
720
- D.set(plane.dir);
721
- math.normalizeVec3(D);
722
- const P = tempVec3b;
723
-
724
- const uvs = [ ];
725
- for (let i = 0; i < vertices.length; i += 3) {
726
- P[0] = vertices[i] + origin[0];
727
- P[1] = vertices[i + 1] + origin[1];
728
- P[2] = vertices[i + 2] + origin[2];
729
-
730
- // Project P onto the plane
731
- const OP = math.subVec3(P, O, tempVec3c);
732
- const dist = math.dotVec3(OP, D);
733
- math.subVec3(P, math.mulVec3Scalar(D, dist, tempVec3c), P);
734
-
735
- const right = ((Math.abs(math.dotVec3(D, worldUp)) < 0.999)
736
- ? math.cross3Vec3(D, worldUp, tempVec3c)
737
- : worldRight);
738
- const v = math.cross3Vec3(D, right, tempVec3c);
739
- math.normalizeVec3(v, v);
740
-
741
- const OP_proj = math.subVec3(P, O, P);
742
- uvs.push(
743
- math.dotVec3(OP_proj, math.normalizeVec3(math.cross3Vec3(v, D, tempVec3d))),
744
- math.dotVec3(OP_proj, v));
745
- }
746
- return uvs;
747
- }
748
-
749
- destroy() {
750
- this._deletePreviousModels();
751
- if(this._resourcesAllocated) {
752
- this.scene.off(this._onModelLoaded);
753
- this.scene.off(this._onModelUnloaded);
754
- this.scene.off(this._onSectionPlaneCreated);
755
- this.scene.off(this._onTick);
756
- }
757
-
248
+ const entityCache = entityCaches.get(entityId);
249
+ entityCache.destroyCaps();
250
+ entityCache.generateCaps = true;
251
+ update();
252
+ };
758
253
  }
759
254
  }
760
-
761
- export { SectionCaps };
255
+ export { SectionCaps };