@@ -303,6 +303,11 @@
|
|
303
303
|
filterGroups?: number,
|
304
304
|
/** Return false to ignore this collider */
|
305
305
|
filterPredicate?: (c: ICollider) => boolean,
|
306
|
+
/** When enabled the hit object's layer will be tested. If layer 2 is enabled the object will be ignored (Layer 2 == IgnoreRaycast)
|
307
|
+
* If not set the raycast will ignore objects in the IgnoreRaycast layer (default: true)
|
308
|
+
* @default undefined
|
309
|
+
*/
|
310
|
+
useIgnoreRaycastLayer?: boolean
|
306
311
|
})
|
307
312
|
: null | { point: Vector3, collider: ICollider } {
|
308
313
|
|
@@ -320,9 +325,11 @@
|
|
320
325
|
const hit = this.world?.castRay(ray, maxDistance, solid, options?.queryFilterFlags, options?.filterGroups, undefined, undefined, (c) => {
|
321
326
|
const component = c[$componentKey];
|
322
327
|
if (options?.filterPredicate) return options.filterPredicate(component);
|
323
|
-
|
324
|
-
|
325
|
-
|
328
|
+
if (options?.useIgnoreRaycastLayer !== false) {
|
329
|
+
// ignore objects in the IgnoreRaycast=2 layer
|
330
|
+
return !component?.gameObject.layers.isEnabled(2);
|
331
|
+
}
|
332
|
+
return true;
|
326
333
|
});
|
327
334
|
if (hit) {
|
328
335
|
const point = ray.pointAt(hit.toi);
|
@@ -342,6 +349,11 @@
|
|
342
349
|
filterGroups?: number,
|
343
350
|
/** Return false to ignore this collider */
|
344
351
|
filterPredicate?: (c: ICollider) => boolean,
|
352
|
+
/** When enabled the hit object's layer will be tested. If layer 2 is enabled the object will be ignored (Layer 2 == IgnoreRaycast)
|
353
|
+
* If not set the raycast will ignore objects in the IgnoreRaycast layer (default: true)
|
354
|
+
* @default undefined
|
355
|
+
*/
|
356
|
+
useIgnoreRaycastLayer?: boolean
|
345
357
|
})
|
346
358
|
: null | { point: Vector3, normal: Vector3, collider: ICollider } {
|
347
359
|
|
@@ -359,8 +371,11 @@
|
|
359
371
|
const hit = this.world?.castRayAndGetNormal(ray, maxDistance, solid, options?.queryFilterFlags, options?.filterGroups, undefined, undefined, (c) => {
|
360
372
|
const component = c[$componentKey];
|
361
373
|
if (options?.filterPredicate) return options.filterPredicate(component);
|
362
|
-
|
363
|
-
|
374
|
+
if (options?.useIgnoreRaycastLayer !== false) {
|
375
|
+
// ignore objects in the IgnoreRaycast=2 layer
|
376
|
+
return !component?.gameObject.layers.isEnabled(2);
|
377
|
+
}
|
378
|
+
return true;
|
364
379
|
});
|
365
380
|
if (hit) {
|
366
381
|
const point = ray.pointAt(hit.toi);
|
@@ -1165,8 +1180,14 @@
|
|
1165
1180
|
}
|
1166
1181
|
}
|
1167
1182
|
else if (body instanceof Collider) {
|
1168
|
-
|
1169
|
-
|
1183
|
+
if (obj.matrixWorldNeedsUpdate) {
|
1184
|
+
obj.updateWorldMatrix(true, false);
|
1185
|
+
}
|
1186
|
+
obj.matrixWorld.decompose(this._tempPosition, this._tempQuaternion, this._tempScale);
|
1187
|
+
const wp = this._tempPosition;
|
1188
|
+
const wq = this._tempQuaternion;
|
1189
|
+
// const wp = getWorldPosition(obj, this._tempPosition);
|
1190
|
+
// const wq = getWorldQuaternion(obj, this._tempQuaternion);
|
1170
1191
|
const collider = body[$componentKey] as ICollider;
|
1171
1192
|
this.tryApplyCenter(collider, wp);
|
1172
1193
|
|
@@ -1,11 +1,14 @@
|
|
1
1
|
import { getRaycastMesh } from '@needle-tools/gltf-progressive';
|
2
|
-
import { ArrayCamera, AxesHelper, Box3, BufferGeometry, Camera, type Intersection, Layers, Line, Matrix3, Matrix4, Mesh, Object3D, PerspectiveCamera, Plane, Ray, Raycaster, Sphere, SphereGeometry, Vector2, Vector3 } from 'three'
|
2
|
+
import { ArrayCamera, AxesHelper, Box3, BufferGeometry, Camera, type Intersection, Layers, Line, Matrix3, Matrix4, Mesh, Object3D, PerspectiveCamera, Plane, Ray, Raycaster, SkinnedMesh,Sphere, SphereGeometry, Vector2, Vector3 } from 'three'
|
3
3
|
import { GroundedSkybox } from 'three/examples/jsm/objects/GroundedSkybox.js';
|
4
|
+
import type { MeshBVH, MeshBVHOptions, StaticGeometryGenerator } from 'three-mesh-bvh';
|
5
|
+
import type { GenerateMeshBVHWorker } from 'three-mesh-bvh/src/workers/GenerateMeshBVHWorker.js';
|
4
6
|
|
7
|
+
import { isDevEnvironment } from './debug/index.js';
|
5
8
|
import { Gizmos } from './engine_gizmos.js';
|
6
9
|
import { Context } from './engine_setup.js';
|
7
10
|
import { getTempVector, getWorldPosition } from "./engine_three_utils.js"
|
8
|
-
import type { Vec2, Vec3, } from './engine_types.js';
|
11
|
+
import type { ConstructorConcrete, Vec2, Vec3, } from './engine_types.js';
|
9
12
|
import type { IPhysicsEngine } from './engine_types.js';
|
10
13
|
import { getParam } from "./engine_utils.js"
|
11
14
|
|
@@ -62,6 +65,12 @@
|
|
62
65
|
* Return `false` to ignore the object completely or `"continue in children"` to skip the object but continue to traverse its children (if you do raycast with `recursive` enabled)
|
63
66
|
* */
|
64
67
|
testObject?: RaycastTestObjectCallback;
|
68
|
+
|
69
|
+
/**
|
70
|
+
* Use MeshBVH for raycasting. This is faster than the default threejs raycaster but uses more memory.
|
71
|
+
* @default undefined
|
72
|
+
*/
|
73
|
+
useAcceleratedRaycast?: boolean;
|
65
74
|
}
|
66
75
|
|
67
76
|
export class RaycastOptions implements IRaycastOptions {
|
@@ -80,6 +89,7 @@
|
|
80
89
|
layerMask?: Layers | number;
|
81
90
|
ignore?: Object3D[];
|
82
91
|
testObject?: RaycastTestObjectCallback;
|
92
|
+
useAcceleratedRaycast?: boolean | undefined;
|
83
93
|
|
84
94
|
screenPointFromOffset(ox: number, oy: number) {
|
85
95
|
if (this.screenPoint === undefined) this.screenPoint = new Vector2();
|
@@ -163,53 +173,26 @@
|
|
163
173
|
|
164
174
|
private sphereResults: Array<Intersection> = new Array<Intersection>();
|
165
175
|
private sphereMask: Layers = new Layers();
|
176
|
+
private readonly sphere: Sphere = new Sphere();
|
166
177
|
/** Test overlapping of a sphere with the threejs geometry. This does not use colliders. This does not return an exact intersection point (intersections returned contain the object and the world position of the object that is being hit)
|
167
178
|
* For a more accurate test use the physics engine's collider overlap test (see sphereOverlapPhysics)
|
168
179
|
* @param spherePos the center of the sphere in world space
|
169
180
|
* @param radius the radius of the sphere
|
170
181
|
* @param traverseChildsAfterHit if false it will stop after the first hit. If true it will continue to traverse and add all hits to the result array
|
182
|
+
* @param bvh use MeshBVH for raycasting. This is faster than the default threejs raycaster but uses more memory.
|
183
|
+
* @param shouldRaycast optional callback to filter objects. Return `false` to ignore the object completely or `"continue in children"` to skip the object but continue to traverse its children (if you do raycast with `recursive` enabled)
|
171
184
|
*/
|
172
|
-
public sphereOverlap(spherePos: Vector3, radius: number, traverseChildsAfterHit: boolean = true): Array<Intersection> {
|
185
|
+
public sphereOverlap(spherePos: Vector3, radius: number, traverseChildsAfterHit: boolean = true, bvh: boolean = false, shouldRaycast: RaycastTestObjectCallback | null = null): Array<Intersection> {
|
173
186
|
this.sphereResults.length = 0;
|
174
187
|
if (!this.context.scene) return this.sphereResults;
|
175
|
-
const sphere = new Sphere(spherePos, radius);
|
176
188
|
const mask = this.sphereMask;
|
177
189
|
mask.enableAll();
|
178
190
|
mask.disable(2);
|
179
191
|
for (const ch of this.context.scene.children) {
|
180
|
-
this.
|
192
|
+
this.intersectSphere(ch, spherePos, radius, mask, this.sphereResults, traverseChildsAfterHit, bvh, shouldRaycast);
|
181
193
|
}
|
182
194
|
return this.sphereResults.sort((a, b) => a.distance - b.distance);
|
183
195
|
}
|
184
|
-
private tempBoundingBox: Box3 = new Box3();
|
185
|
-
private onSphereOverlap(obj: Object3D, sp: Sphere, mask: Layers, results: Array<Intersection>, traverseChildsAfterHit: boolean): void {
|
186
|
-
if (obj.type === "Mesh" && obj.layers.test(mask) && !Gizmos.isGizmo(obj)) {
|
187
|
-
const mesh = obj as Mesh;
|
188
|
-
const geo = mesh.geometry;
|
189
|
-
if (geo) {
|
190
|
-
if (!geo.boundingBox)
|
191
|
-
geo.computeBoundingBox();
|
192
|
-
if (geo.boundingBox) {
|
193
|
-
if (mesh.matrixWorldNeedsUpdate) mesh.updateMatrixWorld();
|
194
|
-
const test = this.tempBoundingBox.copy(geo.boundingBox).applyMatrix4(mesh.matrixWorld);
|
195
|
-
if (sp.intersectsBox(test)) {
|
196
|
-
const wp = getWorldPosition(obj);
|
197
|
-
const dist = wp.distanceTo(sp.center);
|
198
|
-
const int = new SphereIntersection(obj, dist, wp);
|
199
|
-
results.push(int);
|
200
|
-
if (!traverseChildsAfterHit) return;
|
201
|
-
}
|
202
|
-
}
|
203
|
-
}
|
204
|
-
}
|
205
|
-
if (obj.children) {
|
206
|
-
for (const ch of obj.children) {
|
207
|
-
const len = results.length;
|
208
|
-
this.onSphereOverlap(ch, sp, mask, results, traverseChildsAfterHit);
|
209
|
-
if (len != results.length && !traverseChildsAfterHit) return;
|
210
|
-
}
|
211
|
-
}
|
212
|
-
}
|
213
196
|
|
214
197
|
public raycastFromRay(ray: Ray, options: IRaycastOptions | null = null): Array<Intersection> {
|
215
198
|
const opts = options ?? this.defaultRaycastOptions;
|
@@ -301,7 +284,7 @@
|
|
301
284
|
if (ignorelist !== undefined && ignorelist.length > 0) {
|
302
285
|
results = results.filter(r => !ignorelist.includes(r.object));
|
303
286
|
}
|
304
|
-
|
287
|
+
|
305
288
|
Physics._raycasting--;
|
306
289
|
|
307
290
|
if (debugPhysics) {
|
@@ -349,6 +332,9 @@
|
|
349
332
|
if (!usePrecise && customRaycast(mesh, raycaster, results)) {
|
350
333
|
// did handle raycast
|
351
334
|
}
|
335
|
+
else if (options.useAcceleratedRaycast !== false) {
|
336
|
+
NEMeshBVH.runMeshBVHRaycast(raycaster, mesh, results, this.context);
|
337
|
+
}
|
352
338
|
else {
|
353
339
|
raycaster.intersectObject(obj, false, results);
|
354
340
|
}
|
@@ -365,6 +351,75 @@
|
|
365
351
|
}
|
366
352
|
return results;
|
367
353
|
}
|
354
|
+
|
355
|
+
private tempBoundingBox: Box3 = new Box3();
|
356
|
+
private intersectSphere(obj: Object3D, spherePos: Vector3, radius: number, mask: Layers, results: Array<Intersection>, traverseChildsAfterHit: boolean, useBvh: boolean, shouldRaycast: RaycastTestObjectCallback | null): void {
|
357
|
+
|
358
|
+
let canIntersect = obj && (obj as Mesh).isMesh && obj.layers.test(mask) && !Gizmos.isGizmo(obj);
|
359
|
+
|
360
|
+
canIntersect &&= obj.visible;
|
361
|
+
canIntersect &&= !(obj instanceof Line);
|
362
|
+
canIntersect &&= !(obj instanceof GroundedSkybox);
|
363
|
+
|
364
|
+
if (shouldRaycast) {
|
365
|
+
const testResult = shouldRaycast(obj);
|
366
|
+
if (testResult === false) {
|
367
|
+
return;
|
368
|
+
}
|
369
|
+
const checkObject = testResult !== "continue in children";
|
370
|
+
if (checkObject) {
|
371
|
+
return;
|
372
|
+
}
|
373
|
+
}
|
374
|
+
|
375
|
+
|
376
|
+
if (canIntersect) {
|
377
|
+
const mesh = obj as Mesh;
|
378
|
+
const geo = mesh.geometry;
|
379
|
+
if (geo) {
|
380
|
+
|
381
|
+
if (useBvh) {
|
382
|
+
const sphere = this.sphere;
|
383
|
+
sphere.center.copy(spherePos);
|
384
|
+
sphere.radius = radius;
|
385
|
+
const previousResults = results.length;
|
386
|
+
NEMeshBVH.runMeshBVHRaycast(this.sphere, mesh, results, this.context);
|
387
|
+
if (previousResults != results.length && !traverseChildsAfterHit) {
|
388
|
+
return;
|
389
|
+
}
|
390
|
+
}
|
391
|
+
// Classic sphere intersection test
|
392
|
+
else {
|
393
|
+
if (!geo.boundingBox)
|
394
|
+
geo.computeBoundingBox();
|
395
|
+
|
396
|
+
if (geo.boundingBox) {
|
397
|
+
if (mesh.matrixWorldNeedsUpdate) mesh.updateWorldMatrix(false, false);
|
398
|
+
const test = this.tempBoundingBox.copy(geo.boundingBox).applyMatrix4(mesh.matrixWorld);
|
399
|
+
const sphere = this.sphere;
|
400
|
+
sphere.center.copy(spherePos);
|
401
|
+
sphere.radius = radius;
|
402
|
+
|
403
|
+
if (sphere.intersectsBox(test)) {
|
404
|
+
const wp = getWorldPosition(obj);
|
405
|
+
const dist = wp.distanceTo(sphere.center);
|
406
|
+
const int = new SphereIntersection(obj, dist, wp);
|
407
|
+
results.push(int);
|
408
|
+
if (!traverseChildsAfterHit) return;
|
409
|
+
}
|
410
|
+
}
|
411
|
+
}
|
412
|
+
|
413
|
+
}
|
414
|
+
}
|
415
|
+
if (obj.children) {
|
416
|
+
for (const ch of obj.children) {
|
417
|
+
const len = results.length;
|
418
|
+
this.intersectSphere(ch, spherePos, radius, mask, results, traverseChildsAfterHit, useBvh, shouldRaycast);
|
419
|
+
if (len != results.length && !traverseChildsAfterHit) return;
|
420
|
+
}
|
421
|
+
}
|
422
|
+
}
|
368
423
|
}
|
369
424
|
|
370
425
|
const tempSphere = new Sphere();
|
@@ -426,3 +481,230 @@
|
|
426
481
|
mesh["_computeIntersections"] = originalComputeIntersectionsFn;
|
427
482
|
return true;
|
428
483
|
}
|
484
|
+
|
485
|
+
|
486
|
+
|
487
|
+
declare module 'three' {
|
488
|
+
export interface SkinnedMesh {
|
489
|
+
staticGenerator?: StaticGeometryGenerator;
|
490
|
+
staticGeometry?: BufferGeometry;
|
491
|
+
staticGeometryLastUpdate?: number;
|
492
|
+
bvhNeedsUpdate?: boolean;
|
493
|
+
}
|
494
|
+
export interface Mesh {
|
495
|
+
acceleratedRaycast?: any;
|
496
|
+
}
|
497
|
+
}
|
498
|
+
|
499
|
+
|
500
|
+
|
501
|
+
module NEMeshBVH {
|
502
|
+
export function runMeshBVHRaycast(method: Raycaster | Sphere, mesh: Mesh, results: Intersection[], context: Pick<Context, "xr">): boolean {
|
503
|
+
if (!mesh.geometry) {
|
504
|
+
return false;
|
505
|
+
}
|
506
|
+
// The code below handles generating the mesh bvh structure that is used for raycasting
|
507
|
+
// We first try to setup workers so it can run off the main thread
|
508
|
+
const geom = mesh.geometry;
|
509
|
+
|
510
|
+
if ((mesh as SkinnedMesh)?.isSkinnedMesh) {
|
511
|
+
const skinnedMesh = mesh as SkinnedMesh;
|
512
|
+
const skinnedMeshBVHNeedsUpdate = skinnedMesh.bvhNeedsUpdate;
|
513
|
+
if (!skinnedMesh.staticGenerator) {
|
514
|
+
loadMeshBVHLibrary();
|
515
|
+
if (_StaticGeometryGenerator) {
|
516
|
+
skinnedMesh.staticGenerator = new _StaticGeometryGenerator(mesh);
|
517
|
+
skinnedMesh.staticGenerator.applyWorldTransforms = false;
|
518
|
+
skinnedMesh.staticGeometry = skinnedMesh.staticGenerator.generate();
|
519
|
+
geom.boundsTree = _computeBoundsTree?.call(skinnedMesh.staticGeometry);
|
520
|
+
skinnedMesh.staticGeometryLastUpdate = performance.now() + Math.random() * 200;
|
521
|
+
if (skinnedMesh["autoUpdateMeshBVH"] === undefined)
|
522
|
+
skinnedMesh["autoUpdateMeshBVH"] = false;
|
523
|
+
}
|
524
|
+
}
|
525
|
+
else if (geom.boundsTree && (skinnedMesh["autoUpdateMeshBVH"] === true || skinnedMeshBVHNeedsUpdate === true)) {
|
526
|
+
// automatically refit the tree every 300ms
|
527
|
+
const now = performance.now();
|
528
|
+
const timeSinceLastUpdate = now - skinnedMesh.staticGeometryLastUpdate!;
|
529
|
+
if (skinnedMeshBVHNeedsUpdate || timeSinceLastUpdate > 100) {
|
530
|
+
skinnedMesh.bvhNeedsUpdate = false;
|
531
|
+
skinnedMesh.staticGeometryLastUpdate = now;
|
532
|
+
skinnedMesh.staticGenerator?.generate(skinnedMesh.staticGeometry);
|
533
|
+
geom.boundsTree.refit();
|
534
|
+
}
|
535
|
+
}
|
536
|
+
}
|
537
|
+
|
538
|
+
else if (!geom.boundsTree) {
|
539
|
+
|
540
|
+
// Try to generate the bvh on a worker
|
541
|
+
if (!didSetupWorker) internalSetupWorker();
|
542
|
+
|
543
|
+
let canUseWorker = true;
|
544
|
+
|
545
|
+
if (context.xr) { // < in XR for some reason sometimes geometry (controllers) are broken - maybe this is not exclusive to controller geometry
|
546
|
+
canUseWorker = false;
|
547
|
+
}
|
548
|
+
else if (geom[canUseWorkerSymbol] === false) {
|
549
|
+
canUseWorker = false;
|
550
|
+
}
|
551
|
+
else if (geom.getAttribute('position')["isInterleavedBufferAttribute"]
|
552
|
+
|| geom.index && geom.index["isInterleavedBufferAttribute"]) {
|
553
|
+
canUseWorker = false;
|
554
|
+
}
|
555
|
+
|
556
|
+
// if we have a worker use that
|
557
|
+
if (canUseWorker && _GenerateMeshBVHWorker) {
|
558
|
+
if (geom[workerTaskSymbol] === undefined) {
|
559
|
+
|
560
|
+
// get available worker
|
561
|
+
let workerInstance: GenerateMeshBVHWorker | null = null;
|
562
|
+
// if there are workers available, use one
|
563
|
+
if (availableWorkers.length > 0) {
|
564
|
+
const available = availableWorkers.shift();
|
565
|
+
if (available && !available.running) {
|
566
|
+
workerInstance = available;
|
567
|
+
}
|
568
|
+
}
|
569
|
+
// if there are no workers available, create a new one
|
570
|
+
else if (workerInstances.length < 1) {
|
571
|
+
workerInstance = new _GenerateMeshBVHWorker();
|
572
|
+
workerInstances.push(workerInstance);
|
573
|
+
}
|
574
|
+
|
575
|
+
if (workerInstance != null && !workerInstance.running) {
|
576
|
+
geom[workerTaskSymbol] = "queued";
|
577
|
+
performance.mark("bvh.create.start");
|
578
|
+
workerInstance.generate(geom)
|
579
|
+
.then(bvh => {
|
580
|
+
geom[workerTaskSymbol] = "done";
|
581
|
+
geom.boundsTree = bvh;
|
582
|
+
})
|
583
|
+
.catch(err => {
|
584
|
+
geom[workerTaskSymbol] = "failed - " + err?.message;
|
585
|
+
geom[canUseWorkerSymbol] = false;
|
586
|
+
})
|
587
|
+
.finally(() => {
|
588
|
+
availableWorkers.push(workerInstance);
|
589
|
+
performance.mark("bvh.create.end");
|
590
|
+
performance.measure("bvh.create (worker)", "bvh.create.start", "bvh.create.end");
|
591
|
+
});
|
592
|
+
}
|
593
|
+
else {
|
594
|
+
// we don't want to generate the BVH on the main thread unless workers are not supported
|
595
|
+
// If all workers are currently running we need to run a "slow" raycast
|
596
|
+
if (debugPhysics) console.warn("No worker available");
|
597
|
+
}
|
598
|
+
}
|
599
|
+
}
|
600
|
+
// Fallback to sync bvh generation if workers are not available
|
601
|
+
else if (!isRequestingWorker || !canUseWorker) {
|
602
|
+
loadMeshBVHLibrary();
|
603
|
+
if (_MeshBVH) {
|
604
|
+
performance.mark("bvh.create.start");
|
605
|
+
geom.boundsTree = new _MeshBVH(geom as BufferGeometry);
|
606
|
+
performance.mark("bvh.create.end");
|
607
|
+
performance.measure("bvh.create", "bvh.create.start", "bvh.create.end");
|
608
|
+
}
|
609
|
+
}
|
610
|
+
}
|
611
|
+
|
612
|
+
if (method instanceof Raycaster) {
|
613
|
+
const raycaster = method;
|
614
|
+
// Skinned meshes work when we disable applyWorldTransform in the generator (see applyWorldTransforms = false above)
|
615
|
+
// We do need to set the accelerated raycast method (bind it once)
|
616
|
+
// We could also override it on the prototype - not sure if it's more performant but then it would always run
|
617
|
+
const raycastMesh = mesh.raycast;
|
618
|
+
if (_acceleratedRaycast && geom.boundsTree) {
|
619
|
+
// bind the raycast to the mesh
|
620
|
+
if (!mesh.acceleratedRaycast) {
|
621
|
+
mesh.acceleratedRaycast = _acceleratedRaycast.bind(mesh) as any;
|
622
|
+
}
|
623
|
+
mesh.raycast = mesh.acceleratedRaycast;
|
624
|
+
}
|
625
|
+
const prevFirstHitOnly = raycaster.firstHitOnly;
|
626
|
+
raycaster.firstHitOnly = false;
|
627
|
+
raycaster.intersectObject(mesh, false, results);
|
628
|
+
raycaster.firstHitOnly = prevFirstHitOnly;
|
629
|
+
mesh.raycast = raycastMesh;
|
630
|
+
return true;
|
631
|
+
}
|
632
|
+
else if (method instanceof Sphere) {
|
633
|
+
const bvh = geom.boundsTree;
|
634
|
+
if (bvh) {
|
635
|
+
const sphere = method;
|
636
|
+
// Gizmos.DrawWireSphere(sphere.center, sphere.radius, 0xdddd00, 1, false);
|
637
|
+
|
638
|
+
invMat.copy(mesh.matrixWorld).invert();
|
639
|
+
sphere.applyMatrix4(invMat);
|
640
|
+
const intersects = bvh.intersectsSphere(sphere);
|
641
|
+
if (intersects) {
|
642
|
+
// console.log(intersects, mesh.name);
|
643
|
+
const worldpos = getWorldPosition(mesh);
|
644
|
+
const distance = worldpos.distanceTo(sphere.center);
|
645
|
+
const intersection = new SphereIntersection(mesh, distance, worldpos);
|
646
|
+
results.push(intersection);
|
647
|
+
}
|
648
|
+
}
|
649
|
+
return true;
|
650
|
+
}
|
651
|
+
|
652
|
+
|
653
|
+
return false;
|
654
|
+
}
|
655
|
+
|
656
|
+
let didLoadMeshBVHLibrary: boolean = false;
|
657
|
+
let _acceleratedRaycast: Function | null = null;
|
658
|
+
let _MeshBVH: ConstructorConcrete<MeshBVH> | null = null;
|
659
|
+
let _StaticGeometryGenerator: ConstructorConcrete<StaticGeometryGenerator> | null = null;
|
660
|
+
let _computeBoundsTree: ((_opt?: MeshBVHOptions) => MeshBVH) | null = null;
|
661
|
+
|
662
|
+
function loadMeshBVHLibrary() {
|
663
|
+
if (didLoadMeshBVHLibrary) return;
|
664
|
+
didLoadMeshBVHLibrary = true;
|
665
|
+
import("three-mesh-bvh").then(res => {
|
666
|
+
_acceleratedRaycast = res.acceleratedRaycast;
|
667
|
+
_MeshBVH = res.MeshBVH;
|
668
|
+
_StaticGeometryGenerator = res.StaticGeometryGenerator;
|
669
|
+
_computeBoundsTree = res.computeBoundsTree;
|
670
|
+
}).catch(_err => {
|
671
|
+
if (debugPhysics || isDevEnvironment()) {
|
672
|
+
console.warn("Failed to load mesh bvh library");
|
673
|
+
}
|
674
|
+
})
|
675
|
+
}
|
676
|
+
|
677
|
+
|
678
|
+
const invMat = new Matrix4();
|
679
|
+
|
680
|
+
/** True after the worker has been requested for the first time */
|
681
|
+
let didSetupWorker: boolean = false;
|
682
|
+
/** True while the worker is being requested */
|
683
|
+
let isRequestingWorker: boolean = false;
|
684
|
+
let _GenerateMeshBVHWorker: ConstructorConcrete<GenerateMeshBVHWorker> | null = null;
|
685
|
+
|
686
|
+
const workerTaskSymbol = Symbol("Needle:MeshBVH-Worker");
|
687
|
+
const canUseWorkerSymbol = Symbol("Needle:MeshBVH-CanUseWorker");
|
688
|
+
const workerInstances: GenerateMeshBVHWorker[] = [];
|
689
|
+
const availableWorkers: GenerateMeshBVHWorker[] = [];
|
690
|
+
|
691
|
+
function internalSetupWorker() {
|
692
|
+
didSetupWorker = true;
|
693
|
+
isRequestingWorker = true;
|
694
|
+
// Using local worker. see https://github.com/gkjohnson/three-mesh-bvh/issues/636#issuecomment-2209571751
|
695
|
+
import("./physics/workers/mesh-bvh/GenerateMeshBVHWorker.js")
|
696
|
+
.then(res => {
|
697
|
+
_GenerateMeshBVHWorker = res.GenerateMeshBVHWorker;
|
698
|
+
})
|
699
|
+
.catch(_err => {
|
700
|
+
if (debugPhysics || isDevEnvironment()) {
|
701
|
+
console.warn("Failed to setup mesh bvh worker");
|
702
|
+
}
|
703
|
+
})
|
704
|
+
.finally(() => {
|
705
|
+
isRequestingWorker = false;
|
706
|
+
});
|
707
|
+
}
|
708
|
+
|
709
|
+
|
710
|
+
}
|
@@ -806,19 +806,21 @@
|
|
806
806
|
if (currentFrame === lu.frame) return;
|
807
807
|
lu.frame = currentFrame;
|
808
808
|
let shouldUpdate = this.needsUpdate || currentFrame < 1;
|
809
|
-
if (lu.nextUpdate
|
809
|
+
if (lu.nextUpdate <= currentFrame) shouldUpdate = true;
|
810
810
|
// if (this.needsUpdate) lu.nextUpdate = currentFrame + 3;
|
811
811
|
if (shouldUpdate) {
|
812
|
-
|
813
|
-
|
812
|
+
// console.warn(currentFrame, lu.nextUpdate, this.needsUpdate)
|
813
|
+
if (debug) console.log("Update threemeshui");
|
814
814
|
this.needsUpdate = false;
|
815
|
+
lu.nextUpdate = currentFrame + 60;
|
815
816
|
threeMeshUI.update();
|
816
817
|
}
|
817
818
|
return;
|
818
819
|
}
|
819
820
|
}
|
820
|
-
this.lastUpdateFrame = [{ context, frame: currentFrame, nextUpdate: currentFrame }];
|
821
|
+
this.lastUpdateFrame = [{ context, frame: currentFrame, nextUpdate: currentFrame + 60 }];
|
821
822
|
threeMeshUI.update();
|
823
|
+
this.needsUpdate = false;
|
822
824
|
}
|
823
825
|
|
824
826
|
static updateState(intersect: Object3D, _selectState: boolean): Object3D | null {
|
@@ -44,6 +44,8 @@
|
|
44
44
|
opts ??= new RaycastOptions();
|
45
45
|
opts.targets = this.targets;
|
46
46
|
opts.results = this.raycastHits;
|
47
|
+
opts.useAcceleratedRaycast = true;
|
48
|
+
|
47
49
|
const orig = opts.testObject;
|
48
50
|
if (this.ignoreSkinnedMeshes) {
|
49
51
|
opts.testObject = obj => {
|
@@ -89,6 +91,6 @@
|
|
89
91
|
// draw gizmo around ray origin
|
90
92
|
// Gizmos.DrawSphere(rayOrigin, radius, 0x00ff0022);
|
91
93
|
|
92
|
-
return this.context.physics.sphereOverlap(rayOrigin, radius);
|
94
|
+
return this.context.physics.sphereOverlap(rayOrigin, radius, false, true);
|
93
95
|
}
|
94
96
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Matrix4, Object3D, Vector3 } from "three";
|
1
|
+
import { Matrix4, Object3D, Quaternion, Vector3 } from "three";
|
2
2
|
|
3
3
|
import { CollisionDetectionMode, RigidbodyConstraints } from "../engine/engine_physics.types.js";
|
4
4
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
@@ -485,9 +485,13 @@
|
|
485
485
|
|
486
486
|
private captureVelocity() {
|
487
487
|
const wp = getWorldPosition(this.gameObject);
|
488
|
-
Rigidbody.tempPosition
|
488
|
+
this.gameObject.matrixWorld.decompose(Rigidbody.tempPosition, tempQuaternion, tempScale);
|
489
489
|
const vel = wp.sub(this._lastPosition);
|
490
490
|
this._lastPosition.copy(Rigidbody.tempPosition);
|
491
491
|
this._smoothedVelocity.lerp(vel, this.context.time.deltaTime / .1);
|
492
492
|
}
|
493
|
-
}
|
493
|
+
}
|
494
|
+
|
495
|
+
const tempPosition = new Vector3();
|
496
|
+
const tempQuaternion = new Quaternion();
|
497
|
+
const tempScale = new Vector3();
|
@@ -121,7 +121,7 @@
|
|
121
121
|
const overflow = (this.uiObject as any)?._overflow;
|
122
122
|
if (overflow) {
|
123
123
|
overflow._needsUpdate = true;
|
124
|
-
|
124
|
+
// the screenspace canvas does force an update, no need to mark dirty here
|
125
125
|
}
|
126
126
|
}
|
127
127
|
|
@@ -0,0 +1,122 @@
|
|
1
|
+
import { Box3, BufferAttribute } from 'three';
|
2
|
+
import { MeshBVH } from 'three-mesh-bvh';
|
3
|
+
|
4
|
+
// Modified according to https://github.com/gkjohnson/three-mesh-bvh/issues/636#issuecomment-2209571751
|
5
|
+
import { WorkerBase } from "three-mesh-bvh/src/workers/utils/WorkerBase.js";
|
6
|
+
import generateMeshBVHWorker from "three-mesh-bvh/src/workers/generateMeshBVH.worker.js?worker";
|
7
|
+
|
8
|
+
export class GenerateMeshBVHWorker extends WorkerBase {
|
9
|
+
|
10
|
+
constructor() {
|
11
|
+
|
12
|
+
super(new generateMeshBVHWorker());
|
13
|
+
this.name = 'GenerateMeshBVHWorker';
|
14
|
+
|
15
|
+
}
|
16
|
+
|
17
|
+
runTask( worker, geometry, options = {} ) {
|
18
|
+
|
19
|
+
return new Promise( ( resolve, reject ) => {
|
20
|
+
|
21
|
+
if (
|
22
|
+
geometry.getAttribute( 'position' ).isInterleavedBufferAttribute ||
|
23
|
+
geometry.index && geometry.index.isInterleavedBufferAttribute
|
24
|
+
) {
|
25
|
+
|
26
|
+
throw new Error( 'GenerateMeshBVHWorker: InterleavedBufferAttribute are not supported for the geometry attributes.' );
|
27
|
+
|
28
|
+
}
|
29
|
+
|
30
|
+
worker.onerror = e => {
|
31
|
+
|
32
|
+
reject( new Error( `GenerateMeshBVHWorker: ${ e.message }` ) );
|
33
|
+
|
34
|
+
};
|
35
|
+
|
36
|
+
worker.onmessage = e => {
|
37
|
+
|
38
|
+
const { data } = e;
|
39
|
+
|
40
|
+
if ( data.error ) {
|
41
|
+
|
42
|
+
reject( new Error( data.error ) );
|
43
|
+
worker.onmessage = null;
|
44
|
+
|
45
|
+
} else if ( data.serialized ) {
|
46
|
+
|
47
|
+
const { serialized, position } = data;
|
48
|
+
const bvh = MeshBVH.deserialize( serialized, geometry, { setIndex: false } );
|
49
|
+
const boundsOptions = Object.assign( {
|
50
|
+
|
51
|
+
setBoundingBox: true,
|
52
|
+
|
53
|
+
}, options );
|
54
|
+
|
55
|
+
// we need to replace the arrays because they're neutered entirely by the
|
56
|
+
// webworker transfer.
|
57
|
+
geometry.attributes.position.array = position;
|
58
|
+
if ( serialized.index ) {
|
59
|
+
|
60
|
+
if ( geometry.index ) {
|
61
|
+
|
62
|
+
geometry.index.array = serialized.index;
|
63
|
+
|
64
|
+
} else {
|
65
|
+
|
66
|
+
const newIndex = new BufferAttribute( serialized.index, 1, false );
|
67
|
+
geometry.setIndex( newIndex );
|
68
|
+
|
69
|
+
}
|
70
|
+
|
71
|
+
}
|
72
|
+
|
73
|
+
if ( boundsOptions.setBoundingBox ) {
|
74
|
+
|
75
|
+
geometry.boundingBox = bvh.getBoundingBox( new Box3() );
|
76
|
+
|
77
|
+
}
|
78
|
+
|
79
|
+
if ( options.onProgress ) {
|
80
|
+
|
81
|
+
options.onProgress( data.progress );
|
82
|
+
|
83
|
+
}
|
84
|
+
|
85
|
+
resolve( bvh );
|
86
|
+
worker.onmessage = null;
|
87
|
+
|
88
|
+
} else if ( options.onProgress ) {
|
89
|
+
|
90
|
+
options.onProgress( data.progress );
|
91
|
+
|
92
|
+
}
|
93
|
+
|
94
|
+
};
|
95
|
+
|
96
|
+
const index = geometry.index ? geometry.index.array : null;
|
97
|
+
const position = geometry.attributes.position.array;
|
98
|
+
const transferable = [ position ];
|
99
|
+
if ( index ) {
|
100
|
+
|
101
|
+
transferable.push( index );
|
102
|
+
|
103
|
+
}
|
104
|
+
|
105
|
+
worker.postMessage( {
|
106
|
+
|
107
|
+
index,
|
108
|
+
position,
|
109
|
+
options: {
|
110
|
+
...options,
|
111
|
+
onProgress: null,
|
112
|
+
includedProgressCallback: Boolean( options.onProgress ),
|
113
|
+
groups: [ ... geometry.groups ],
|
114
|
+
},
|
115
|
+
|
116
|
+
}, transferable.map( arr => arr.buffer ).filter( v => ( typeof SharedArrayBuffer === 'undefined' ) || ! ( v instanceof SharedArrayBuffer ) ) );
|
117
|
+
|
118
|
+
} );
|
119
|
+
|
120
|
+
}
|
121
|
+
|
122
|
+
}
|