@@ -8,7 +8,8 @@
|
|
8
8
|
const debugPhysics = getParam("debugphysics");
|
9
9
|
const layerMaskHelper: Layers = new Layers();
|
10
10
|
|
11
|
-
declare type
|
11
|
+
export declare type RaycastTestObjectReturnType = void | boolean | "continue in children";
|
12
|
+
export declare type RaycastTestObjectCallback = (obj: Object3D) => RaycastTestObjectReturnType;
|
12
13
|
|
13
14
|
export class RaycastOptions {
|
14
15
|
ray: Ray | undefined = undefined;
|
@@ -27,7 +28,7 @@
|
|
27
28
|
/** if defined it's called per object before tested for intersections.
|
28
29
|
* 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)
|
29
30
|
* */
|
30
|
-
testObject?:
|
31
|
+
testObject?: RaycastTestObjectCallback = undefined;
|
31
32
|
|
32
33
|
screenPointFromOffset(ox: number, oy: number) {
|
33
34
|
if (this.screenPoint === undefined) this.screenPoint = new Vector2();
|
@@ -234,8 +235,9 @@
|
|
234
235
|
const testResult = options.testObject?.(obj);
|
235
236
|
if (testResult === false) continue;
|
236
237
|
const checkObject = testResult !== "continue in children";
|
237
|
-
if (checkObject)
|
238
|
+
if (checkObject) {
|
238
239
|
raycaster.intersectObject(obj, false, results);
|
240
|
+
}
|
239
241
|
|
240
242
|
if (options.recursive) {
|
241
243
|
this.intersect(raycaster, obj.children, results, options);
|
@@ -47,8 +47,10 @@
|
|
47
47
|
|
48
48
|
|
49
49
|
const _tempVecs = new CircularBuffer(() => new Vector3(), 100);
|
50
|
-
export function getTempVector() {
|
51
|
-
|
50
|
+
export function getTempVector(value?: Vector3) {
|
51
|
+
const vec = _tempVecs.get();
|
52
|
+
if(value instanceof Vector3) vec.copy(value);
|
53
|
+
return vec;
|
52
54
|
}
|
53
55
|
|
54
56
|
|
@@ -51,14 +51,16 @@
|
|
51
51
|
return new URLSearchParams(globalThis.location?.search);
|
52
52
|
}
|
53
53
|
|
54
|
+
// bit strange that we have to pass T in here as well but otherwise the type parameter is stripped it seems
|
55
|
+
type Param<T extends string> = string | boolean | number | T;
|
56
|
+
|
54
57
|
/** Checks if a url parameter exists.
|
55
58
|
* Returns true if it exists but has no value (e.g. ?help)
|
56
59
|
* Returns false if it does not exist
|
57
60
|
* Returns false if it's set to 0 e.g. ?debug=0
|
58
61
|
* Returns the value if it exists e.g. ?message=hello
|
59
62
|
*/
|
60
|
-
export function getParam(paramName:
|
61
|
-
|
63
|
+
export function getParam<T extends string>(paramName: T): Param<T> {
|
62
64
|
if (saveParams && !requestedParams.includes(paramName))
|
63
65
|
requestedParams.push(paramName);
|
64
66
|
const urlParams = getUrlParams();
|
@@ -1,10 +1,10 @@
|
|
1
|
-
import { RaycastOptions } from "../../engine/engine_physics.js";
|
1
|
+
import { RaycastOptions, RaycastTestObjectReturnType } from "../../engine/engine_physics.js";
|
2
2
|
import { Behaviour, Component, GameObject } from "../Component.js";
|
3
3
|
import { WebXR } from "../webxr/WebXR.js";
|
4
4
|
import { ControllerEvents, WebXRController } from "../webxr/WebXRController.js";
|
5
5
|
import * as ThreeMeshUI from 'three-mesh-ui'
|
6
6
|
import { Context } from "../../engine/engine_setup.js";
|
7
|
-
import { type IPointerEventHandler, PointerEventData } from "./PointerEvents.js";
|
7
|
+
import { type IPointerEventHandler, PointerEventData, hasPointerEventComponent } from "./PointerEvents.js";
|
8
8
|
import { ObjectRaycaster, Raycaster } from "./Raycaster.js";
|
9
9
|
import { InputEvents, NEPointerEvent, PointerType } from "../../engine/engine_input.js";
|
10
10
|
import { Object3D } from "three";
|
@@ -98,19 +98,6 @@
|
|
98
98
|
console.warn("Added an ObjectRaycaster to the scene because no raycaster was found. Skinnedmeshes will be ignored for better performance");
|
99
99
|
}
|
100
100
|
}
|
101
|
-
|
102
|
-
if (isDevEnvironment()) {
|
103
|
-
const foundProblematicObjects: string[] = [];
|
104
|
-
for (const rc of this.raycaster) {
|
105
|
-
if (rc instanceof ObjectRaycaster) {
|
106
|
-
if (rc.ignoreSkinnedMeshes === false) {
|
107
|
-
foundProblematicObjects.push(rc.gameObject.name);
|
108
|
-
}
|
109
|
-
}
|
110
|
-
}
|
111
|
-
if (foundProblematicObjects.length > 0)
|
112
|
-
console.warn("Found ObjectRaycaster that doesn't ignore skinned meshes. This might cause performance issues. Consider enabling \"ignoreSkinnedMeshes\" on the ObjectRaycaster components in your scene", foundProblematicObjects);
|
113
|
-
}
|
114
101
|
}
|
115
102
|
|
116
103
|
register(rc: Raycaster) {
|
@@ -180,7 +167,7 @@
|
|
180
167
|
const controllerRcOpts = new RaycastOptions();
|
181
168
|
this._selectUpdateFn ??= (_ctrl: WebXRController) => {
|
182
169
|
controllerRcOpts.ray = _ctrl.getRay();
|
183
|
-
const rc = this.performRaycast(controllerRcOpts
|
170
|
+
const rc = this.performRaycast(controllerRcOpts);
|
184
171
|
if (!rc) return;
|
185
172
|
const opts = new PointerEventData(this.context.input);
|
186
173
|
opts.inputSource = _ctrl;
|
@@ -252,7 +239,7 @@
|
|
252
239
|
const options = new RaycastOptions();
|
253
240
|
options.screenPoint = this.context.input.getPointerPositionRC(pointerId)!;
|
254
241
|
|
255
|
-
const hits = this.performRaycast(options
|
242
|
+
const hits = this.performRaycast(options);
|
256
243
|
if (!hits) return;
|
257
244
|
|
258
245
|
if (debug && data.isClicked) {
|
@@ -291,29 +278,66 @@
|
|
291
278
|
|
292
279
|
private readonly _sortedHits: THREE.Intersection[] = [];
|
293
280
|
|
294
|
-
|
281
|
+
/**
|
282
|
+
* cache for objects that we want to raycast against. It's cleared before each call to performRaycast invoking raycasters
|
283
|
+
*/
|
284
|
+
private readonly _testObjectsCache = new Map<Object3D, boolean>();
|
285
|
+
/**
|
286
|
+
* Checks if an object that we encounter has an event component and if it does, we add it to our objects cache
|
287
|
+
* If it doesnt we tell our raycasting system to ignore it and continue in the child hierarchy
|
288
|
+
* We do this to avoid raycasts against objects that are not going to be used by the event system
|
289
|
+
* Because there's no component callback to be invoked anyways.
|
290
|
+
* This is especially important to avoid expensive raycasts against SkinnedMeshes
|
291
|
+
*
|
292
|
+
* Further optimizations would be to check what type of event we're dealing with
|
293
|
+
* For example if an event component has only an onPointerClick method we don't need to raycast during movement events
|
294
|
+
* */
|
295
|
+
private shouldRaycastObject = (obj: Object3D): RaycastTestObjectReturnType => {
|
296
|
+
// check if the object was seen previously
|
297
|
+
if (this._testObjectsCache.has(obj)) {
|
298
|
+
// if yes we check if it was previously stored as "YES WE NEED TO RAYCAST THIS"
|
299
|
+
const prev = this._testObjectsCache.get(obj)!;
|
300
|
+
if (!prev) return "continue in children"
|
301
|
+
return true;
|
302
|
+
}
|
303
|
+
else {
|
304
|
+
// the object was not yet seen so we test if it has an event component
|
305
|
+
const hasEventComponent = hasPointerEventComponent(obj);
|
306
|
+
if (hasEventComponent) {
|
307
|
+
// console.log("YES RAYCAST", obj.name)
|
308
|
+
// it has an event component: we add it and all its children to the cache
|
309
|
+
this._testObjectsCache.set(obj, true);
|
310
|
+
obj.traverse((o) => {
|
311
|
+
this._testObjectsCache.set(o, true);
|
312
|
+
})
|
313
|
+
return true;
|
314
|
+
}
|
315
|
+
this._testObjectsCache.set(obj, false);
|
316
|
+
return "continue in children"
|
317
|
+
}
|
318
|
+
}
|
319
|
+
|
320
|
+
/** the raycast filter is always overriden */
|
321
|
+
private performRaycast(opts: RaycastOptions | null): THREE.Intersection[] | null {
|
295
322
|
if (!this.raycaster) return null;
|
323
|
+
|
296
324
|
this._sortedHits.length = 0;
|
325
|
+
|
326
|
+
if (!opts) opts = new RaycastOptions();
|
327
|
+
|
328
|
+
// we clear the cache of previously seen objects
|
329
|
+
this._testObjectsCache.clear();
|
330
|
+
opts.testObject = this.shouldRaycastObject;
|
331
|
+
|
297
332
|
for (const rc of this.raycaster) {
|
298
333
|
if (!rc.activeAndEnabled) continue;
|
299
334
|
|
300
|
-
// TODO: it would be better to filter out the objects that actually have components with callback methods either themselves or in their parents(?)
|
301
|
-
let didIgnoreSkinnedMeshes: boolean | undefined = undefined;;
|
302
|
-
if (rc instanceof ObjectRaycaster) {
|
303
|
-
didIgnoreSkinnedMeshes = rc.ignoreSkinnedMeshes;
|
304
|
-
if (!isClick) rc.ignoreSkinnedMeshes = true;
|
305
|
-
}
|
306
335
|
const res = rc.performRaycast(opts);
|
307
336
|
|
308
|
-
if (
|
309
|
-
|
310
|
-
|
311
|
-
}
|
337
|
+
if (res && res.length > 0) {
|
338
|
+
// console.log(res.length, res.map(r => r.object.name));
|
339
|
+
this._sortedHits.push(...res);
|
312
340
|
}
|
313
|
-
|
314
|
-
|
315
|
-
if (res && res.length > 0)
|
316
|
-
this._sortedHits.push(...res);
|
317
341
|
}
|
318
342
|
this._sortedHits.sort((a, b) => {
|
319
343
|
return a.distance - b.distance;
|
@@ -105,13 +105,14 @@
|
|
105
105
|
}
|
106
106
|
if (debug)
|
107
107
|
console.log("LEVEL", object.name, dist);
|
108
|
+
handler.autoUpdate = false;
|
108
109
|
this.onAddLodLevel(handler, object, lod.model.distance);
|
109
110
|
}
|
110
111
|
}
|
111
112
|
}
|
112
113
|
}
|
113
114
|
|
114
|
-
|
115
|
+
onAfterRender() {
|
115
116
|
if (!this.gameObject) return;
|
116
117
|
if (!this._lodsHandler) return;
|
117
118
|
const cam = this.context.mainCamera;
|
@@ -530,6 +530,7 @@
|
|
530
530
|
// we dont want to check invisible objects
|
531
531
|
if (!obj.visible) return;
|
532
532
|
if (useForAutoFit(obj) === false) return;
|
533
|
+
if(obj.type === "TransformControlsGizmo" || obj.type === "TransformControlsPlane") return;
|
533
534
|
// ignore Box3Helpers
|
534
535
|
if (obj instanceof Box3Helper) allowExpanding = false;
|
535
536
|
if (obj instanceof GridHelper) allowExpanding = false;
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import { GameObject } from "../Component.js";
|
1
2
|
import { Input, NEPointerEvent } from "../../engine/engine_input.js";
|
2
3
|
import { Object3D } from "three";
|
3
4
|
|
@@ -91,13 +92,14 @@
|
|
91
92
|
* @internal tests if the object has any PointerEventComponent used by the EventSystem
|
92
93
|
* This is used to skip raycasting on objects that have no components that use pointer events
|
93
94
|
*/
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
//
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
95
|
+
export function hasPointerEventComponent(obj: Object3D) {
|
96
|
+
const res = GameObject.foreachComponent(obj, comp => {
|
97
|
+
const handler = comp as IPointerEventHandler;
|
98
|
+
if (handler.onPointerDown || handler.onPointerUp || handler.onPointerEnter || handler.onPointerExit || handler.onPointerClick)
|
99
|
+
return true;
|
100
|
+
// undefined means continue
|
101
|
+
return undefined;
|
102
|
+
}, false);
|
103
|
+
if (res === true) return true;
|
104
|
+
return false;
|
105
|
+
}
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import { serializable } from "../../engine/engine_serialization.js";
|
1
2
|
import { RaycastOptions } from "../../engine/engine_physics.js";
|
2
3
|
import { Behaviour, Component } from "../Component.js";
|
3
4
|
import { EventSystem } from "./EventSystem.js";
|
@@ -27,6 +28,7 @@
|
|
27
28
|
private targets: THREE.Object3D[] | null = null;
|
28
29
|
private raycastHits: THREE.Intersection[] = [];
|
29
30
|
|
31
|
+
@serializable()
|
30
32
|
ignoreSkinnedMeshes = false;
|
31
33
|
|
32
34
|
start(): void {
|
@@ -38,14 +40,21 @@
|
|
38
40
|
opts ??= new RaycastOptions();
|
39
41
|
opts.targets = this.targets;
|
40
42
|
opts.results = this.raycastHits;
|
41
|
-
opts.testObject
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
43
|
+
const orig = opts.testObject;
|
44
|
+
if (this.ignoreSkinnedMeshes) {
|
45
|
+
opts.testObject = obj => {
|
46
|
+
// if we are set to ignore skinned meshes, we return false for them
|
47
|
+
if (obj instanceof SkinnedMesh) {
|
48
|
+
return "continue in children";
|
49
|
+
}
|
50
|
+
// call the original testObject function
|
51
|
+
if (orig) return orig(obj);
|
52
|
+
// otherwise allow raycasting
|
53
|
+
return true;
|
54
|
+
};
|
55
|
+
}
|
47
56
|
const hits = this.context.physics.raycast(opts);
|
48
|
-
|
57
|
+
opts.testObject = orig;
|
49
58
|
return hits;
|
50
59
|
}
|
51
60
|
}
|
@@ -14,11 +14,14 @@
|
|
14
14
|
import { setCustomVisibility } from "../engine/js-extensions/Layers.js";
|
15
15
|
import { isLocalNetwork } from "../engine/engine_networking_utils.js";
|
16
16
|
import { showBalloonWarning } from "../engine/debug/index.js";
|
17
|
+
import { Gizmos } from "../engine/engine_gizmos.js";
|
18
|
+
import { getTempVector } from "../engine/engine_three_utils.js";
|
17
19
|
|
18
20
|
// for staying compatible with old code
|
19
21
|
export { InstancingUtil } from "../engine/engine_instancing.js";
|
20
22
|
|
21
23
|
const debugRenderer = getParam("debugrenderer");
|
24
|
+
const debugskinnedmesh = getParam("debugskinnedmesh");
|
22
25
|
const suppressInstancing = getParam("noInstancing");
|
23
26
|
const debugInstancing = getParam("debuginstancing");
|
24
27
|
const debugProgressiveLoading = getParam("debugprogressive");
|
@@ -757,7 +760,17 @@
|
|
757
760
|
super.awake();
|
758
761
|
// disable skinned mesh occlusion because of https://github.com/mrdoob/three.js/issues/14499
|
759
762
|
this.allowOcclusionWhenDynamic = false;
|
763
|
+
// If we don't do that here the bounding sphere matrix used for raycasts will be wrong. Not sure *why* this is necessary
|
764
|
+
this.gameObject.parent?.updateWorldMatrix(false, true);
|
760
765
|
}
|
766
|
+
onBeforeRender(): void {
|
767
|
+
super.onBeforeRender();
|
768
|
+
|
769
|
+
if (debugskinnedmesh && this.gameObject instanceof SkinnedMesh && this.gameObject.boundingSphere) {
|
770
|
+
const tempCenter = getTempVector(this.gameObject.boundingSphere.center).applyMatrix4(this.gameObject.matrixWorld);
|
771
|
+
Gizmos.DrawWireSphere(tempCenter, this.gameObject.boundingSphere.radius, "red");
|
772
|
+
}
|
773
|
+
}
|
761
774
|
}
|
762
775
|
|
763
776
|
export enum ShadowCastingMode {
|