File without changes
|
@@ -1,2 +0,0 @@
|
|
1
|
-
Using flatbuffer compiler 2.0
|
2
|
-
https://github.com/google/flatbuffers/releases/tag/v2.0.0
|
@@ -4,7 +4,7 @@
|
|
4
4
|
// We dont want to export everything in the extensions
|
5
5
|
export * from "./js-extensions/RGBAColor.js";
|
6
6
|
export * from "./js-extensions/Object3D.js";
|
7
|
-
export * from "./
|
7
|
+
export * from "./XRFlag.js"
|
8
8
|
|
9
9
|
export * from "./export/index.js"
|
10
10
|
export * from "./postprocessing/index.js"
|
@@ -47,7 +47,6 @@
|
|
47
47
|
export * from "./engine_utils_screenshot.js";
|
48
48
|
export * from "./engine_web_api.js";
|
49
49
|
export * from "./engine_utils.js";
|
50
|
-
export * from "./engine_xr.js";
|
51
50
|
|
52
51
|
export { TypeStore, registerType } from "./engine_typestore.js";
|
53
52
|
|
@@ -411,7 +411,7 @@
|
|
411
411
|
this._hasEnded = true;
|
412
412
|
if (debug)
|
413
413
|
console.log("Audio clip ended", this.clip);
|
414
|
-
this.dispatchEvent(
|
414
|
+
this.sound.dispatchEvent({ type: 'ended', target: this });
|
415
415
|
}
|
416
416
|
|
417
417
|
// this.gameObject.position.x = Math.sin(time.time) * 2;
|
@@ -1,220 +0,0 @@
|
|
1
|
-
import { AssetReference } from "../../engine/engine_addressables.js";
|
2
|
-
import { serializable } from "../../engine/engine_serialization_decorator.js";
|
3
|
-
import { PromiseAllWithErrors, getParam } from "../../engine/engine_utils.js";
|
4
|
-
import { NeedleXREventArgs, NeedleXRSession, NeedleXRUtils } from "../../engine/xr/index.js";
|
5
|
-
import { Behaviour, GameObject } from "../Component.js";
|
6
|
-
import { Object3D, Quaternion, Vector3 } from "three";
|
7
|
-
import { ObjectUtils, PrimitiveType } from "../../engine/engine_create_objects.js";
|
8
|
-
import { SyncedTransform } from "../SyncedTransform.js";
|
9
|
-
import { PlayerState } from "../../engine-components-experimental/networking/PlayerSync.js";
|
10
|
-
import { IGameObject } from "../../engine/engine_types.js";
|
11
|
-
import { XRFlag } from "./XRFlag.js";
|
12
|
-
import { AvatarMarker } from "./WebXRAvatar.js";
|
13
|
-
|
14
|
-
const debug = getParam("debugwebxr");
|
15
|
-
|
16
|
-
const flipForwardQuaternion = new Quaternion().setFromAxisAngle(new Vector3(0, 1, 0), Math.PI);
|
17
|
-
|
18
|
-
export class Avatar extends Behaviour {
|
19
|
-
|
20
|
-
@serializable(AssetReference)
|
21
|
-
head?: AssetReference;
|
22
|
-
|
23
|
-
@serializable(AssetReference)
|
24
|
-
leftHand?: AssetReference;
|
25
|
-
|
26
|
-
@serializable(AssetReference)
|
27
|
-
rightHand?: AssetReference;
|
28
|
-
|
29
|
-
private _syncTransforms?: SyncedTransform[];
|
30
|
-
|
31
|
-
async onEnterXR(_args: NeedleXREventArgs) {
|
32
|
-
if (!this.activeAndEnabled) return;
|
33
|
-
if (debug) console.warn("AVATAR ENTER XR", this.guid, this.sourceId, this, this.activeAndEnabled)
|
34
|
-
if (this._syncTransforms)
|
35
|
-
this._syncTransforms.length = 0;
|
36
|
-
await this.prepareAvatar();
|
37
|
-
|
38
|
-
const playerstate = PlayerState.getFor(this);
|
39
|
-
if (playerstate?.owner) {
|
40
|
-
const marker = this.gameObject.addNewComponent(AvatarMarker)!;
|
41
|
-
marker.avatar = this.gameObject;
|
42
|
-
marker.connectionId = playerstate.owner;
|
43
|
-
}
|
44
|
-
else if(this.context.connection.isConnected) console.error("No player state found for avatar", this);
|
45
|
-
}
|
46
|
-
|
47
|
-
onLeaveXR(_args: NeedleXREventArgs): void {
|
48
|
-
const marker = this.gameObject.getComponent(AvatarMarker);
|
49
|
-
if (marker) {
|
50
|
-
marker.destroy();
|
51
|
-
}
|
52
|
-
}
|
53
|
-
|
54
|
-
onUpdateXR(args: NeedleXREventArgs): void {
|
55
|
-
if (!this.activeAndEnabled) return;
|
56
|
-
|
57
|
-
const isLocalPlayer = PlayerState.isLocalPlayer(this);
|
58
|
-
if (!isLocalPlayer) return;
|
59
|
-
|
60
|
-
const xr = args.xr;
|
61
|
-
// make sure the avatar is inside the active rig
|
62
|
-
if (xr.rig && xr.rig.gameObject !== this.gameObject.parent) {
|
63
|
-
this.gameObject.position.set(0, 0, 0);
|
64
|
-
this.gameObject.rotation.set(0, 0, 0);
|
65
|
-
this.gameObject.scale.set(1, 1, 1);
|
66
|
-
xr.rig.gameObject.add(this.gameObject);
|
67
|
-
}
|
68
|
-
// this.gameObject.position.copy(xr.rig!.gameObject.position);
|
69
|
-
// this.gameObject.quaternion.copy(xr.rig!.gameObject.quaternion);
|
70
|
-
// this.gameObject.scale.set(1, 1, 1);
|
71
|
-
|
72
|
-
|
73
|
-
if (this._syncTransforms && isLocalPlayer) {
|
74
|
-
for (const sync of this._syncTransforms) {
|
75
|
-
sync.fastMode = true;
|
76
|
-
if (!sync.isOwned())
|
77
|
-
sync.requestOwnership();
|
78
|
-
}
|
79
|
-
}
|
80
|
-
|
81
|
-
|
82
|
-
// synchronize head
|
83
|
-
if (this.head && this.context.mainCamera) {
|
84
|
-
const headObj = this.head.asset as IGameObject;
|
85
|
-
headObj.position.copy(this.context.mainCamera.position);
|
86
|
-
headObj.quaternion.copy(this.context.mainCamera.quaternion);
|
87
|
-
headObj.quaternion.x *= -1;
|
88
|
-
|
89
|
-
// HACK: XRFlag limitation workaround to make sure first person user head is never rendered
|
90
|
-
if (this.context.time.frameCount % 10 === 0) {
|
91
|
-
const xrflags = GameObject.getComponentsInChildren(this.head.asset, XRFlag);
|
92
|
-
for (const flag of xrflags) {
|
93
|
-
flag.enabled = false;
|
94
|
-
flag.gameObject.visible = false;
|
95
|
-
}
|
96
|
-
}
|
97
|
-
}
|
98
|
-
|
99
|
-
// synchronize hands
|
100
|
-
const leftCtrl = args.xr.leftController;
|
101
|
-
const leftObj = this.leftHand?.asset as Object3D;
|
102
|
-
if (leftCtrl && leftObj) {
|
103
|
-
leftObj.position.copy(leftCtrl.gripPosition);
|
104
|
-
leftObj.quaternion.copy(leftCtrl.gripQuaternion);
|
105
|
-
leftObj.quaternion.multiply(flipForwardQuaternion);
|
106
|
-
leftObj.visible = leftCtrl.isTracking;
|
107
|
-
}
|
108
|
-
|
109
|
-
const right = args.xr.rightController;
|
110
|
-
if (right && this.rightHand?.asset) {
|
111
|
-
const rightObj = this.rightHand.asset as Object3D;
|
112
|
-
rightObj.position.copy(right.gripPosition);
|
113
|
-
rightObj.quaternion.copy(right.gripQuaternion);
|
114
|
-
rightObj.quaternion.multiply(flipForwardQuaternion);
|
115
|
-
rightObj.visible = right.isTracking;
|
116
|
-
}
|
117
|
-
}
|
118
|
-
|
119
|
-
onBeforeRender(): void {
|
120
|
-
if (this.context.time.frame % 10 === 0)
|
121
|
-
this.updateRemoteAvatarVisibility();
|
122
|
-
}
|
123
|
-
|
124
|
-
|
125
|
-
private updateRemoteAvatarVisibility() {
|
126
|
-
if (this.context.connection.isConnected) {
|
127
|
-
const state = PlayerState.getFor(this);
|
128
|
-
if (state && state.isLocalPlayer == false) {
|
129
|
-
|
130
|
-
const sync = NeedleXRSession.getXRSync(this.context);
|
131
|
-
if (sync) {
|
132
|
-
if (sync.hasState(state.owner)) {
|
133
|
-
this.tryFindAvatarObjectsIfMissing();
|
134
|
-
|
135
|
-
const leftObj = this.leftHand?.asset as Object3D;
|
136
|
-
if (leftObj) {
|
137
|
-
leftObj.visible = sync?.isTracking(state.owner, "left") ?? false;
|
138
|
-
}
|
139
|
-
const rightObj = this.rightHand?.asset as Object3D;
|
140
|
-
if (rightObj) {
|
141
|
-
rightObj.visible = sync?.isTracking(state.owner, "right") ?? false;
|
142
|
-
}
|
143
|
-
}
|
144
|
-
}
|
145
|
-
|
146
|
-
// HACK: XRFlag limitation workaround to make sure first person user head of OTHER users is ALWAYS rendered
|
147
|
-
if (this.head?.asset) {
|
148
|
-
const xrflags = GameObject.getComponentsInChildren(this.head.asset, XRFlag);
|
149
|
-
for (const flag of xrflags) {
|
150
|
-
flag.enabled = false;
|
151
|
-
flag.gameObject.visible = true;
|
152
|
-
}
|
153
|
-
}
|
154
|
-
}
|
155
|
-
}
|
156
|
-
}
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
private tryFindAvatarObjectsIfMissing() {
|
161
|
-
// if no avatar objects are set, try to find them
|
162
|
-
if (!this.head || !this.leftHand || !this.rightHand) {
|
163
|
-
const res = { head: this.head, leftHand: this.leftHand, rightHand: this.rightHand };
|
164
|
-
NeedleXRUtils.tryFindAvatarObjects(this.gameObject, this.sourceId || "", res);
|
165
|
-
if (res.head) this.head = res.head;
|
166
|
-
if (res.leftHand) this.leftHand = res.leftHand;
|
167
|
-
if (res.rightHand) this.rightHand = res.rightHand;
|
168
|
-
}
|
169
|
-
}
|
170
|
-
|
171
|
-
private async prepareAvatar() {
|
172
|
-
// if no avatar objects are set, try to find them
|
173
|
-
this.tryFindAvatarObjectsIfMissing();
|
174
|
-
|
175
|
-
if (!this.head) {
|
176
|
-
const head = new Object3D();
|
177
|
-
head.name = "Head";
|
178
|
-
const cube = ObjectUtils.createPrimitive(PrimitiveType.Cube);
|
179
|
-
head.add(cube);
|
180
|
-
this.gameObject.add(head);
|
181
|
-
this.head = new AssetReference("", this.sourceId, head);
|
182
|
-
if (debug) console.log("Create head", head);
|
183
|
-
}
|
184
|
-
|
185
|
-
if (!this.rightHand) {
|
186
|
-
const rightHand = new Object3D();
|
187
|
-
rightHand.name = "Right Hand";
|
188
|
-
this.gameObject.add(rightHand);
|
189
|
-
this.rightHand = new AssetReference("", this.sourceId, rightHand);
|
190
|
-
if (debug) console.log("Create right hand", rightHand);
|
191
|
-
}
|
192
|
-
|
193
|
-
if (!this.leftHand) {
|
194
|
-
const leftHand = new Object3D();
|
195
|
-
leftHand.name = "Left Hand";
|
196
|
-
this.gameObject.add(leftHand);
|
197
|
-
this.leftHand = new AssetReference("", this.sourceId, leftHand);
|
198
|
-
if (debug) console.log("Create left hand", leftHand);
|
199
|
-
}
|
200
|
-
|
201
|
-
await this.loadAvatarObjects(this.head, this.leftHand, this.rightHand);
|
202
|
-
|
203
|
-
if (PlayerState.isLocalPlayer(this.gameObject)) {
|
204
|
-
this._syncTransforms = GameObject.getComponentsInChildren(this.gameObject, SyncedTransform);
|
205
|
-
}
|
206
|
-
}
|
207
|
-
|
208
|
-
|
209
|
-
private async loadAvatarObjects(head: AssetReference, left: AssetReference, right: AssetReference) {
|
210
|
-
const pHead = head.loadAssetAsync();
|
211
|
-
const pHandLeft = left.loadAssetAsync();
|
212
|
-
const pHandRight = right.loadAssetAsync();
|
213
|
-
const promises = new Array<Promise<any>>();
|
214
|
-
if (pHead) promises.push(pHead);
|
215
|
-
if (pHandLeft) promises.push(pHandLeft);
|
216
|
-
if (pHandRight) promises.push(pHandRight);
|
217
|
-
const res = await PromiseAllWithErrors(promises);
|
218
|
-
if (debug) console.log("Avatar loaded results:", res);
|
219
|
-
}
|
220
|
-
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import { Object3D } from "three";
|
2
2
|
import { Behaviour, GameObject } from "../Component.js";
|
3
|
-
import { XRFlag, XRState } from "../
|
3
|
+
import { XRFlag, XRState } from "../XRFlag.js";
|
4
4
|
import { serializable } from "../../engine/engine_serialization_decorator.js";
|
5
5
|
|
6
6
|
|
@@ -38,7 +38,7 @@
|
|
38
38
|
EventSystem.markUIDirty(this.context);
|
39
39
|
}
|
40
40
|
|
41
|
-
shadowComponent:
|
41
|
+
shadowComponent: ThreeMeshUI.Block | null = null;
|
42
42
|
|
43
43
|
private _controlsChildLayout = true;
|
44
44
|
get controlsChildLayout(): boolean { return this._controlsChildLayout; }
|
@@ -148,7 +148,7 @@
|
|
148
148
|
// })
|
149
149
|
}
|
150
150
|
|
151
|
-
protected setShadowComponentOwner(current:
|
151
|
+
protected setShadowComponentOwner(current: Object3D | null | undefined) {
|
152
152
|
if (!current) return;
|
153
153
|
// TODO: only traverse our own hierarchy, we can stop if we find another owner
|
154
154
|
if (current[$shadowDomOwner] === undefined || current[$shadowDomOwner] === this) {
|
@@ -120,10 +120,10 @@
|
|
120
120
|
}
|
121
121
|
|
122
122
|
onPointerClick(args: PointerEventData) {
|
123
|
-
if (!this.interactable) return;
|
123
|
+
if (!this.interactable || args.pointerId !== 0) return;
|
124
124
|
|
125
|
-
if (args.button !== 0 && args.event.pointerType === PointerType.Mouse) return;
|
126
125
|
// Button clicks should only run with left mouse button while using mouse
|
126
|
+
if(args.pointerId !== 0 && this.context.input.getIsMouse(args.pointerId)) return;
|
127
127
|
if (debug) {
|
128
128
|
console.warn("Button Click", this.onClick);
|
129
129
|
showBalloonMessage("CLICKED button " + this.name + " at " + this.context.time.frameCount);
|
@@ -2,7 +2,7 @@
|
|
2
2
|
import { getParam } from "../engine/engine_utils.js";
|
3
3
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
4
4
|
import { RGBAColor } from "./js-extensions/RGBAColor.js";
|
5
|
-
import { Context } from "../engine/engine_setup.js";
|
5
|
+
import { Context, XRSessionMode } from "../engine/engine_setup.js";
|
6
6
|
import type { ICamera } from "../engine/engine_types.js"
|
7
7
|
import { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../engine/debug/index.js";
|
8
8
|
import { getWorldPosition } from "../engine/engine_three_utils.js";
|
@@ -350,6 +350,7 @@
|
|
350
350
|
if (this._backgroundBlurriness !== undefined)
|
351
351
|
this.context.scene.backgroundBlurriness = this._backgroundBlurriness;
|
352
352
|
if (this._backgroundIntensity !== undefined)
|
353
|
+
//@ts-ignore
|
353
354
|
this.context.scene.backgroundIntensity = this._backgroundIntensity;
|
354
355
|
|
355
356
|
break;
|
@@ -391,7 +392,7 @@
|
|
391
392
|
if (debug)
|
392
393
|
showBalloonMessage("Environment blend mode: " + environmentBlendMode + " on " + navigator.userAgent);
|
393
394
|
let transparent = environmentBlendMode === 'additive' || environmentBlendMode === 'alpha-blend';
|
394
|
-
if (context.
|
395
|
+
if (context.xrSessionMode === XRSessionMode.ImmersiveAR) {
|
395
396
|
if (environmentBlendMode === "opaque") {
|
396
397
|
// workaround for Quest 2 returning opaque when it should be alpha-blend
|
397
398
|
// check user agent if this is the Quest browser and return true if so
|
@@ -12,7 +12,6 @@
|
|
12
12
|
import { getParam } from "../../engine/engine_utils.js";
|
13
13
|
import { LayoutGroup } from "./Layout.js";
|
14
14
|
import { Mathf } from "../../engine/engine_math.js";
|
15
|
-
import { NeedleXREventArgs } from "../../engine/xr/index.js";
|
16
15
|
|
17
16
|
export enum RenderMode {
|
18
17
|
ScreenSpaceOverlay = 0,
|
@@ -201,30 +200,19 @@
|
|
201
200
|
}
|
202
201
|
}
|
203
202
|
|
204
|
-
onEnterXR(args: NeedleXREventArgs): void {
|
205
|
-
if (this.screenspace) {
|
206
|
-
if (args.xr.isVR || args.xr.isPassThrough) {
|
207
|
-
this.gameObject.visible = false;
|
208
|
-
}
|
209
|
-
}
|
210
|
-
}
|
211
|
-
onLeaveXR(args: NeedleXREventArgs): void {
|
212
|
-
if (this.screenspace) {
|
213
|
-
if (args.xr.isVR || args.xr.isPassThrough) {
|
214
|
-
this.gameObject.visible = true;
|
215
|
-
}
|
216
|
-
}
|
217
|
-
}
|
218
|
-
|
219
203
|
onBeforeRenderRoutine = () => {
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
this
|
224
|
-
this.
|
204
|
+
if (this.context.isInVR) {
|
205
|
+
this.onUpdateRenderMode();
|
206
|
+
this.handleLayoutUpdates();
|
207
|
+
// TODO TMUI @swingingtom - For VR this is so we don't have text clipping
|
208
|
+
this.shadowComponent?.updateMatrixWorld(true);
|
209
|
+
this.shadowComponent?.updateWorldMatrix(true, true);
|
210
|
+
this.invokeBeforeRenderEvents();
|
211
|
+
EventSystem.ensureUpdateMeshUI(ThreeMeshUI, this.context, true);
|
225
212
|
return;
|
226
213
|
}
|
227
214
|
|
215
|
+
this.previousParent = this.gameObject.parent;
|
228
216
|
// console.log(this.previousParent?.name + "/" + this.gameObject.name);
|
229
217
|
|
230
218
|
if (this.renderOnTop || this.screenspace) {
|
@@ -243,12 +231,7 @@
|
|
243
231
|
}
|
244
232
|
|
245
233
|
onAfterRenderRoutine = () => {
|
246
|
-
if
|
247
|
-
this.previousParent?.add(this.gameObject);
|
248
|
-
// this is currently causing an error during XR (https://linear.app/needle/issue/NE-4114)
|
249
|
-
// this.gameObject.visible = true;
|
250
|
-
return;
|
251
|
-
}
|
234
|
+
if(this.context.isInVR) return;
|
252
235
|
if ((this.screenspace || this.renderOnTop) && this.previousParent && this.context.mainCamera) {
|
253
236
|
if (this.screenspace) {
|
254
237
|
const camObj = this.context.mainCamera;
|
@@ -293,7 +276,7 @@
|
|
293
276
|
for (const ch of this._rectTransforms) {
|
294
277
|
if (matrixWorldChanged) ch.markDirty();
|
295
278
|
let layout = this._layoutGroups.get(ch.gameObject);
|
296
|
-
if
|
279
|
+
if(ch.isDirty && !layout){
|
297
280
|
layout = ch.gameObject.getComponentInParent(LayoutGroup) as LayoutGroup;
|
298
281
|
}
|
299
282
|
if (ch.isDirty || layout?.isDirty) {
|
@@ -10,8 +10,6 @@
|
|
10
10
|
|
11
11
|
import { Euler, Object3D, Quaternion, Scene, Vector3 } from "three";
|
12
12
|
import { showBalloonWarning, isDevEnvironment } from "../engine/debug/index.js";
|
13
|
-
import { ControllerChangedEvt, INeedleXRSessionEventReceiver, NeedleXRControllerEventArgs, NeedleXREventArgs, NeedleXRSession } from "../engine/engine_xr.js";
|
14
|
-
import { IPointerEventHandler, PointerEventData } from "./ui/PointerEvents.js";
|
15
13
|
|
16
14
|
// export interface ISerializationCallbackReceiver {
|
17
15
|
// onBeforeSerialize?(): object | void;
|
@@ -296,7 +294,7 @@
|
|
296
294
|
abstract set worldQuaternion(val: Quaternion);
|
297
295
|
abstract get worldQuaternion(): Quaternion;
|
298
296
|
abstract set worldRotation(val: Vector3);
|
299
|
-
abstract get worldRotation(): Vector3;
|
297
|
+
abstract get worldRotation(): Vector3;
|
300
298
|
abstract set worldScale(val: Vector3);
|
301
299
|
abstract get worldScale(): Vector3;
|
302
300
|
|
@@ -307,22 +305,17 @@
|
|
307
305
|
|
308
306
|
|
309
307
|
|
310
|
-
export
|
311
|
-
Partial<INeedleXRSessionEventReceiver>,
|
312
|
-
Partial<IPointerEventHandler>
|
313
|
-
{
|
308
|
+
export class Component implements IComponent, EventTarget {
|
314
309
|
|
315
310
|
get isComponent(): boolean { return true; }
|
316
311
|
|
317
312
|
private __context: Context | undefined;
|
318
|
-
/** Use the context to get access to many Needle Engine features and use physics, timing, access the camera or scene */
|
319
313
|
get context(): Context {
|
320
314
|
return this.__context ?? Context.Current;
|
321
315
|
}
|
322
316
|
set context(context: Context) {
|
323
317
|
this.__context = context;
|
324
318
|
}
|
325
|
-
/** shorthand for `this.context.scene` */
|
326
319
|
get scene(): Scene { return this.context.scene; }
|
327
320
|
|
328
321
|
get layer(): number {
|
@@ -362,7 +355,7 @@
|
|
362
355
|
return this.gameObject?.userData.hideFlags;
|
363
356
|
}
|
364
357
|
|
365
|
-
|
358
|
+
|
366
359
|
get activeAndEnabled(): boolean {
|
367
360
|
if (this.destroyed) return false;
|
368
361
|
if (this.__isEnabled === false) return false;
|
@@ -392,27 +385,19 @@
|
|
392
385
|
this.gameObject[activeInHierarchyFieldName] = val;
|
393
386
|
}
|
394
387
|
|
395
|
-
/** the object this component is attached to. Note that this is a threejs Object3D with some additional features */
|
396
388
|
gameObject!: GameObject;
|
397
|
-
/** the unique identifier for this component */
|
398
389
|
guid: string = "invalid";
|
399
|
-
/** holds the source identifier this object was created with/from (e.g. if it was part of a glTF file the sourceId holds the url to the glTF) */
|
400
390
|
sourceId?: SourceIdentifier;
|
401
391
|
// transform: Object3D = nullObject;
|
402
392
|
|
403
393
|
/** called on a component with a map of old to new guids (e.g. when instantiate generated new guids and e.g. timeline track bindings needs to remape them) */
|
404
394
|
resolveGuids?(guidsMap: GuidsMap): void;
|
405
395
|
|
406
|
-
/** called once when the component becomes active for the first time
|
407
|
-
* This is the first callback to be called */
|
396
|
+
/** called once when the component becomes active for the first time */
|
408
397
|
awake() { }
|
409
|
-
/** called every time when the component gets enabled (this is invoked after awake and before start)
|
410
|
-
* or when it becomes active in the hierarchy (e.g. if a parent object or this.gameObject gets set to visible)
|
411
|
-
*/
|
398
|
+
/** called every time when the component gets enabled (this is invoked after awake and before start) */
|
412
399
|
onEnable() { }
|
413
|
-
/** called every time the component gets disabled or if a parent object (or this.gameObject) gets set to invisible */
|
414
400
|
onDisable() { }
|
415
|
-
/** Called when the component gets destroyed */
|
416
401
|
onDestroy() {
|
417
402
|
this.__destroyed = true;
|
418
403
|
}
|
@@ -424,17 +409,11 @@
|
|
424
409
|
/** Called for all scripts when the context gets paused or unpaused */
|
425
410
|
onPausedChanged?(isPaused: boolean, wasPaused: boolean): void;
|
426
411
|
|
427
|
-
/** called at the beginning of a frame (once per component) */
|
428
412
|
start?(): void;
|
429
|
-
/** first callback in a frame (called every frame when implemented) */
|
430
413
|
earlyUpdate?(): void;
|
431
|
-
/** regular callback in a frame (called every frame when implemented) */
|
432
414
|
update?(): void;
|
433
|
-
/** late callback in a frame (called every frame when implemented) */
|
434
415
|
lateUpdate?(): void;
|
435
|
-
/** called before the scene gets rendered in the main update loop */
|
436
416
|
onBeforeRender?(frame: XRFrame | null): void;
|
437
|
-
/** called after the scene was rendered */
|
438
417
|
onAfterRender?(): void;
|
439
418
|
|
440
419
|
onCollisionEnter?(col: Collision);
|
@@ -445,79 +424,18 @@
|
|
445
424
|
onTriggerStay?(col: ICollider);
|
446
425
|
onTriggerExit?(col: ICollider);
|
447
426
|
|
448
|
-
|
449
|
-
/** Optional callback, you can implement this to only get callbacks for VR or AR sessions if necessary.
|
450
|
-
* @returns true if the mode is supported (if false the mode is not supported by this ciomponent and it will not receive XR callbacks for this mode)
|
451
|
-
*/
|
452
|
-
supportsXR?(mode: XRSessionMode): boolean;
|
453
|
-
/** Called before the XR session is requested. Use this callback if you want to modify the session init features */
|
454
|
-
onBeforeXR?(mode: XRSessionMode, args: XRSessionInit): void;
|
455
|
-
/** Callback when this component joins a xr session (or becomes active in a running XR session) */
|
456
|
-
onEnterXR?(args: NeedleXREventArgs): void;
|
457
|
-
/** Callback when a xr session updates (while it is still active in XR session) */
|
458
|
-
onUpdateXR?(args: NeedleXREventArgs): void;
|
459
|
-
/** Callback when this component exists a xr session (or when it becomes inactive in a running XR session) */
|
460
|
-
onLeaveXR?(args: NeedleXREventArgs): void;
|
461
|
-
/** Callback when a controller is connected/added while in a XR session
|
462
|
-
* OR when the component joins a running XR session that has already connected controllers
|
463
|
-
* OR when the component becomes active during a running XR session that has already connected controllers */
|
464
|
-
onXRControllerAdded?(args: NeedleXRControllerEventArgs): void;
|
465
|
-
/** callback when a controller is removed while in a XR session
|
466
|
-
* OR when the component becomes inactive during a running XR session
|
467
|
-
*/
|
468
|
-
onXRControllerRemoved?(args: NeedleXRControllerEventArgs): void;
|
469
|
-
|
470
|
-
|
471
|
-
/* IPointerEventReceiver */
|
472
|
-
/* @inheritdoc */
|
473
|
-
onPointerEnter?(args: PointerEventData);
|
474
|
-
onPointerMove?(args: PointerEventData);
|
475
|
-
onPointerExit?(args: PointerEventData);
|
476
|
-
onPointerDown?(args: PointerEventData);
|
477
|
-
onPointerUp?(args: PointerEventData);
|
478
|
-
onPointerClick?(args: PointerEventData);
|
479
|
-
|
480
|
-
|
481
|
-
/** starts a coroutine (javascript generator function)
|
482
|
-
* `yield` will wait for the next frame:
|
483
|
-
* - Use `yield WaitForSeconds(1)` to wait for 1 second.
|
484
|
-
* - Use `yield WaitForFrames(10)` to wait for 10 frames.
|
485
|
-
* - Use `yield new Promise(...)` to wait for a promise to resolve.
|
486
|
-
* @param routine generator function to start
|
487
|
-
* @param evt event to register the coroutine for (default: FrameEvent.Update). Note that all coroutine FrameEvent callbacks are invoked after the matching regular component callbacks. For example `FrameEvent.Update` will be called after regular component `update()` methods)
|
488
|
-
* @returns the generator function (use it to stop the coroutine with `stopCoroutine`)
|
489
|
-
* @example
|
490
|
-
* ```ts
|
491
|
-
* onEnable() { this.startCoroutine(this.myCoroutine()); }
|
492
|
-
* private *myCoroutine() {
|
493
|
-
* while(this.activeAndEnabled) {
|
494
|
-
* console.log("Hello World", this.context.time.frame);
|
495
|
-
* // wait for 5 frames
|
496
|
-
* for(let i = 0; i < 5; i++) yield;
|
497
|
-
* }
|
498
|
-
* }
|
499
|
-
* ```
|
500
|
-
*/
|
501
427
|
startCoroutine(routine: Generator, evt: FrameEvent = FrameEvent.Update): Generator {
|
502
428
|
return this.context.registerCoroutineUpdate(this, routine, evt);
|
503
429
|
}
|
504
|
-
|
505
|
-
* Stop a coroutine that was previously started with `startCoroutine`
|
506
|
-
* @param routine the routine to be stopped
|
507
|
-
* @param evt the frame event to unregister the routine from (default: FrameEvent.Update)
|
508
|
-
*/
|
430
|
+
|
509
431
|
stopCoroutine(routine: Generator, evt: FrameEvent = FrameEvent.Update): void {
|
510
432
|
this.context.unregisterCoroutineUpdate(routine, evt);
|
511
433
|
}
|
512
434
|
|
513
|
-
/** @returns true if this component was destroyed (`this.destroy()`) or the whole object this component was part of */
|
514
435
|
public get destroyed(): boolean {
|
515
436
|
return this.__destroyed;
|
516
437
|
}
|
517
438
|
|
518
|
-
/**
|
519
|
-
* Destroys this component (and removes it from the object)
|
520
|
-
*/
|
521
439
|
public destroy() {
|
522
440
|
if (this.__destroyed) return;
|
523
441
|
this.__internalDestroy();
|
@@ -748,7 +666,5 @@
|
|
748
666
|
}
|
749
667
|
}
|
750
668
|
|
751
|
-
|
752
|
-
|
753
669
|
export class Behaviour extends Component {
|
754
670
|
}
|
@@ -11,11 +11,11 @@
|
|
11
11
|
export { Animator } from "../Animator.js";
|
12
12
|
export { AnimatorController } from "../AnimatorController.js";
|
13
13
|
export { Antialiasing } from "../postprocessing/Effects/Antialiasing.js";
|
14
|
+
export { AttachedObject } from "../webxr/WebXRController.js";
|
14
15
|
export { AudioExtension } from "../export/usdz/extensions/behavior/AudioExtension.js";
|
15
16
|
export { AudioListener } from "../AudioListener.js";
|
16
17
|
export { AudioSource } from "../AudioSource.js";
|
17
18
|
export { AudioTrackHandler } from "../timeline/TimelineTracks.js";
|
18
|
-
export { Avatar } from "../webxr/Avatar.js";
|
19
19
|
export { Avatar_Brain_LookAt } from "../avatar/Avatar_Brain_LookAt.js";
|
20
20
|
export { Avatar_MouthShapes } from "../avatar/Avatar_MouthShapes.js";
|
21
21
|
export { Avatar_MustacheShake } from "../avatar/Avatar_MustacheShake.js";
|
@@ -51,6 +51,7 @@
|
|
51
51
|
export { ColorAdjustments } from "../postprocessing/Effects/ColorAdjustments.js";
|
52
52
|
export { ColorBySpeedModule } from "../ParticleSystemModules.js";
|
53
53
|
export { ColorOverLifetimeModule } from "../ParticleSystemModules.js";
|
54
|
+
export { Component } from "../Component.js";
|
54
55
|
export { ContactShadows } from "../ContactShadows.js";
|
55
56
|
export { ControlTrackHandler } from "../timeline/TimelineTracks.js";
|
56
57
|
export { CustomBranding } from "../export/usdz/USDZExporter.js";
|
@@ -87,6 +88,7 @@
|
|
87
88
|
export { Image } from "../ui/Image.js";
|
88
89
|
export { InheritVelocityModule } from "../ParticleSystemModules.js";
|
89
90
|
export { InputField } from "../ui/InputField.js";
|
91
|
+
export { Interactable } from "../Interactable.js";
|
90
92
|
export { Light } from "../Light.js";
|
91
93
|
export { LimitVelocityOverLifetimeModule } from "../ParticleSystemModules.js";
|
92
94
|
export { LODGroup } from "../LODGroup.js";
|
@@ -100,7 +102,6 @@
|
|
100
102
|
export { MeshRenderer } from "../Renderer.js";
|
101
103
|
export { MinMaxCurve } from "../ParticleSystemModules.js";
|
102
104
|
export { MinMaxGradient } from "../ParticleSystemModules.js";
|
103
|
-
export { NeedleWebXRHtmlElement } from "../webxr/WebXRButtons.js";
|
104
105
|
export { NestedGltf } from "../NestedGltf.js";
|
105
106
|
export { Networking } from "../Networking.js";
|
106
107
|
export { NoiseModule } from "../ParticleSystemModules.js";
|
@@ -124,6 +125,7 @@
|
|
124
125
|
export { PreliminaryAction } from "../export/usdz/extensions/behavior/BehaviourComponents.js";
|
125
126
|
export { PreliminaryTrigger } from "../export/usdz/extensions/behavior/BehaviourComponents.js";
|
126
127
|
export { RawImage } from "../ui/Image.js";
|
128
|
+
export { Raycaster } from "../ui/Raycaster.js";
|
127
129
|
export { Rect } from "../ui/RectTransform.js";
|
128
130
|
export { RectTransform } from "../ui/RectTransform.js";
|
129
131
|
export { ReflectionProbe } from "../ReflectionProbe.js";
|
@@ -151,7 +153,6 @@
|
|
151
153
|
export { SizeOverLifetimeModule } from "../ParticleSystemModules.js";
|
152
154
|
export { SkinnedMeshRenderer } from "../Renderer.js";
|
153
155
|
export { SmoothFollow } from "../SmoothFollow.js";
|
154
|
-
export { SpatialGrabRaycaster } from "../ui/Raycaster.js";
|
155
156
|
export { SpatialHtml } from "../ui/SpatialHtml.js";
|
156
157
|
export { SpatialTrigger } from "../SpatialTrigger.js";
|
157
158
|
export { SpatialTriggerReceiver } from "../SpatialTrigger.js";
|
@@ -166,7 +167,7 @@
|
|
166
167
|
export { SyncedRoom } from "../SyncedRoom.js";
|
167
168
|
export { SyncedTransform } from "../SyncedTransform.js";
|
168
169
|
export { TapGestureTrigger } from "../export/usdz/extensions/behavior/BehaviourComponents.js";
|
169
|
-
export { TeleportTarget } from "../webxr/
|
170
|
+
export { TeleportTarget } from "../webxr/WebXRController.js";
|
170
171
|
export { TestRunner } from "../TestRunner.js";
|
171
172
|
export { TestSimulateUserData } from "../TestRunner.js";
|
172
173
|
export { Text } from "../ui/Text.js";
|
@@ -196,16 +197,20 @@
|
|
196
197
|
export { Volume } from "../postprocessing/Volume.js";
|
197
198
|
export { VolumeParameter } from "../postprocessing/VolumeParameter.js";
|
198
199
|
export { VolumeProfile } from "../postprocessing/VolumeProfile.js";
|
200
|
+
export { VRUserState } from "../webxr/WebXRSync.js";
|
201
|
+
export { WebAR } from "../webxr/WebXR.js";
|
199
202
|
export { WebARCameraBackground } from "../webxr/WebARCameraBackground.js";
|
200
203
|
export { WebARSessionRoot } from "../webxr/WebARSessionRoot.js";
|
201
204
|
export { WebXR } from "../webxr/WebXR.js";
|
205
|
+
export { WebXRAvatar } from "../webxr/WebXRAvatar.js";
|
206
|
+
export { WebXRController } from "../webxr/WebXRController.js";
|
202
207
|
export { WebXRImageTracking } from "../webxr/WebXRImageTracking.js";
|
203
208
|
export { WebXRImageTrackingModel } from "../webxr/WebXRImageTracking.js";
|
204
209
|
export { WebXRPlaneTracking } from "../webxr/WebXRPlaneTracking.js";
|
210
|
+
export { WebXRSync } from "../webxr/WebXRSync.js";
|
205
211
|
export { WebXRTrackedImage } from "../webxr/WebXRImageTracking.js";
|
206
|
-
export {
|
207
|
-
export {
|
208
|
-
export {
|
209
|
-
export { XRFlag } from "../webxr/XRFlag.js";
|
212
|
+
export { XRFlag } from "../XRFlag.js";
|
213
|
+
export { XRGrabModel } from "../webxr/WebXRGrabRendering.js";
|
214
|
+
export { XRGrabRendering } from "../webxr/WebXRGrabRendering.js";
|
210
215
|
export { XRRig } from "../webxr/WebXRRig.js";
|
211
|
-
export { XRState } from "../
|
216
|
+
export { XRState } from "../XRFlag.js";
|
@@ -1,7 +1,6 @@
|
|
1
|
-
import { getErrorCount
|
2
|
-
import { getParam, isMobileDevice
|
1
|
+
import { getErrorCount } from "./debug_overlay.js";
|
2
|
+
import { getParam, isMobileDevice } from "../engine_utils.js";
|
3
3
|
import { isLocalNetwork } from "../engine_networking_utils.js";
|
4
|
-
import { isDevEnvironment } from "./debug.js";
|
5
4
|
|
6
5
|
let consoleInstance: any = null;
|
7
6
|
let consoleHtmlElement: HTMLElement | null = null;
|
@@ -23,11 +22,8 @@
|
|
23
22
|
currentUrl.searchParams.set("console", "1");
|
24
23
|
console.log("🌵 Tip: You can add the \"?console\" query parameter to the url to show the debug console (on mobile it will automatically open in the bottom right corner when your get errors during development)", "\nOpen this page console: " + currentUrl.toString());
|
25
24
|
}
|
26
|
-
const isMobile = isMobileDevice()
|
25
|
+
const isMobile = isMobileDevice();
|
27
26
|
if (isMobile) {
|
28
|
-
// we need to invoke this here - otherwise we will miss errors that happen after the console is loaded
|
29
|
-
// and calling the method from the root needle-engine.ts import is evaluated later (if we import the method from the toplevel file and then invoke it)
|
30
|
-
makeErrorsVisibleForDevelopment();
|
31
27
|
beginWatchingLogs();
|
32
28
|
createConsole(true);
|
33
29
|
if (isMobile) {
|
@@ -195,7 +191,7 @@
|
|
195
191
|
}
|
196
192
|
`;
|
197
193
|
consoleHtmlElement?.prepend(styles);
|
198
|
-
if (startHidden === true
|
194
|
+
if (startHidden === true)
|
199
195
|
hideDebugConsole();
|
200
196
|
console.log("🌵 Debug console has loaded");
|
201
197
|
}
|
@@ -15,7 +15,7 @@
|
|
15
15
|
}
|
16
16
|
|
17
17
|
export function getErrorCount() {
|
18
|
-
return
|
18
|
+
return errorCount;
|
19
19
|
}
|
20
20
|
|
21
21
|
const originalConsoleError = console.error;
|
@@ -37,10 +37,9 @@
|
|
37
37
|
if (hide) return;
|
38
38
|
const isLocal = isLocalNetwork();
|
39
39
|
if (debug) console.log("Is this a local network?", isLocal);
|
40
|
-
if (isLocal)
|
41
|
-
{
|
40
|
+
if (isLocal) {
|
42
41
|
if (debug)
|
43
|
-
console.
|
42
|
+
console.log(window.location.hostname);
|
44
43
|
console.error = patchedConsoleError;
|
45
44
|
window.addEventListener("error", (event) => {
|
46
45
|
if (hide) return;
|
@@ -67,10 +66,10 @@
|
|
67
66
|
}
|
68
67
|
|
69
68
|
|
70
|
-
let
|
69
|
+
let errorCount = 0;
|
71
70
|
|
72
71
|
function onReceivedError() {
|
73
|
-
|
72
|
+
errorCount += 1;
|
74
73
|
}
|
75
74
|
|
76
75
|
function onParseError(args: Array<any>) {
|
@@ -1,125 +1,104 @@
|
|
1
|
-
import {
|
1
|
+
import { GameObject } from "./Component.js";
|
2
2
|
import { SyncedTransform } from "./SyncedTransform.js";
|
3
|
-
import type { IPointerEventHandler, PointerEventData } from "./ui/PointerEvents.js";
|
3
|
+
import type { IPointerDownHandler, IPointerEnterHandler, IPointerEventHandler, IPointerExitHandler, IPointerUpHandler, PointerEventData } from "./ui/PointerEvents.js";
|
4
4
|
import { Context } from "../engine/engine_setup.js";
|
5
|
-
import { UsageMarker } from "./Interactable.js";
|
5
|
+
import { Interactable, UsageMarker } from "./Interactable.js";
|
6
6
|
import { Rigidbody } from "./RigidBody.js";
|
7
|
+
import { WebXR } from "./webxr/WebXR.js";
|
7
8
|
import { Avatar_POI } from "./avatar/Avatar_Brain_LookAt.js";
|
8
9
|
import { RaycastOptions } from "../engine/engine_physics.js";
|
9
10
|
import { getWorldPosition, setWorldPosition } from "../engine/engine_three_utils.js";
|
11
|
+
import type { KeyCode } from "../engine/engine_input.js";
|
12
|
+
import { nameofFactory } from "../engine/engine_utils.js";
|
10
13
|
import { InstancingUtil } from "../engine/engine_instancing.js";
|
11
14
|
import { OrbitControls } from "./OrbitControls.js";
|
12
|
-
import {
|
15
|
+
import { BufferGeometry, Camera, Color, Line, LineBasicMaterial, Matrix4, Mesh, MeshBasicMaterial, Object3D, Plane, Ray, Raycaster, SphereGeometry, Vector2, Vector3 } from "three";
|
13
16
|
import { ObjectRaycaster } from "./ui/Raycaster.js";
|
14
17
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
15
|
-
import { IGameObject } from "../engine/engine_types.js";
|
16
|
-
import { Mathf } from "../engine/engine_math.js";
|
17
|
-
import { getParam } from "../engine/engine_utils.js";
|
18
|
-
import { Gizmos } from "../engine/engine_gizmos.js";
|
19
|
-
import { NeedleXRSession } from "../engine/engine_xr.js";
|
20
18
|
|
21
|
-
const debug =
|
19
|
+
const debug = false;
|
22
20
|
|
23
|
-
export enum
|
24
|
-
|
25
|
-
|
26
|
-
/** Object is dragged as if it was attached to the pointer. In 2D, that means it's dragged along the camera screen plane. In XR, it's dragged by the controller/hand. */
|
27
|
-
Attached = 1,
|
28
|
-
/** Object is dragged along the initial raycast hit normal. */
|
29
|
-
HitNormal = 2,
|
30
|
-
/** Combination of XZ and Screen based on the viewing angle. Low angles result in Screen dragging and higher angles in XZ dragging. */
|
31
|
-
DynamicViewAngle = 3,
|
32
|
-
/** The drag plane is adjusted dynamically while dragging. */
|
33
|
-
SnapToSurfaces = 4,
|
21
|
+
export enum DragEvents {
|
22
|
+
SelectStart = "selectstart",
|
23
|
+
SelectEnd = "selectend",
|
34
24
|
}
|
35
25
|
|
36
|
-
|
26
|
+
interface SelectArgs {
|
27
|
+
selected: Object3D;
|
28
|
+
attached: Object3D | GameObject | null;
|
29
|
+
}
|
37
30
|
|
38
|
-
// dragPlane (floor, object, view)
|
39
|
-
// snap to surface (snap orientation?)
|
40
|
-
// two-handed drag (scale, rotate, move)
|
41
|
-
// keep upright (no tilt)
|
42
31
|
|
43
|
-
|
44
|
-
|
45
|
-
|
32
|
+
export interface IDragEventListener {
|
33
|
+
onDragStart?();
|
34
|
+
onDragEnd?();
|
35
|
+
}
|
46
36
|
|
47
|
-
|
48
|
-
@serializable()
|
49
|
-
public snapGridResolution: number = 0.0;
|
50
|
-
|
51
|
-
/** Keep the original rotation of the dragged object. */
|
52
|
-
@serializable()
|
53
|
-
public keepRotation: boolean = true;
|
54
|
-
|
55
|
-
/** How and where the object is dragged along while dragging in XR. */
|
56
|
-
@serializable()
|
57
|
-
public xrDragMode: DragMode = DragMode.Attached;
|
37
|
+
export class DragControls extends Interactable implements IPointerEventHandler {
|
58
38
|
|
59
|
-
|
60
|
-
|
61
|
-
public xrKeepRotation: boolean = false;
|
39
|
+
private static _active: number = 0;
|
40
|
+
public static get HasAnySelected(): boolean { return this._active > 0; }
|
62
41
|
|
63
|
-
/**
|
42
|
+
/** Show's drag gizmos when enabled */
|
64
43
|
@serializable()
|
65
|
-
public
|
44
|
+
public showGizmo: boolean = true;
|
66
45
|
|
67
|
-
/** When enabled
|
46
|
+
/** When enabled DragControls will drag vertically when the object is viewed from a low angle */
|
68
47
|
@serializable()
|
69
|
-
public
|
48
|
+
public useViewAngle: boolean = true;
|
70
49
|
|
71
|
-
|
72
|
-
//
|
50
|
+
public transformSelf: boolean = true;
|
51
|
+
// public transformGroup: boolean = true;
|
52
|
+
// public targets: Object3D[] | null = null;
|
73
53
|
|
74
|
-
|
75
|
-
private static _active: number = 0;
|
76
|
-
|
77
|
-
/** The object to be dragged – we pass this to handlers when they are created */
|
78
|
-
private targetObject: GameObject | null = null;
|
54
|
+
// private controls: Control | null = null;
|
79
55
|
private orbit: OrbitControls | null = null;
|
80
|
-
private _dragHelper: LegacyDragVisualsHelper | null = null;
|
81
|
-
private static lastHovered: Object3D;
|
82
|
-
private _draggingRigidbodies: Rigidbody[] = [];
|
83
|
-
private _potentialDragStartEvt: PointerEventData | null = null;
|
84
|
-
private _dragHandlers: Map<Object3D, IDragHandler> = new Map();
|
85
|
-
private _totalMovement: Vector3 = new Vector3();
|
86
|
-
/** A marker is attached to components that are currently interacted with, to e.g. prevent them from being deleted. */
|
87
|
-
private _marker: UsageMarker | null = null;
|
88
|
-
private _isDragging: boolean = false;
|
89
|
-
private _didDrag: boolean = false;
|
90
56
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
57
|
+
private selectStartEventListener: ((controls: DragControls, args: SelectArgs) => void)[] = [];
|
58
|
+
private selectEndEventListener: Array<Function> = [];
|
59
|
+
private _dragHelper: DragHelper | null = null;
|
60
|
+
|
61
|
+
constructor() {
|
62
|
+
super();
|
63
|
+
this.selectStartEventListener = [];
|
64
|
+
this.selectEndEventListener = [];
|
65
|
+
this._dragDelta = new Vector2();
|
96
66
|
}
|
97
67
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
68
|
+
|
69
|
+
// TODO: Update DragEventListener code
|
70
|
+
addDragEventListener(type: DragEvents, cb: (ctrls: DragControls, args: SelectArgs) => void | Function) {
|
71
|
+
switch (type) {
|
72
|
+
case DragEvents.SelectStart:
|
73
|
+
this.selectStartEventListener.push(cb);
|
74
|
+
break;
|
75
|
+
case DragEvents.SelectEnd:
|
76
|
+
this.selectEndEventListener.push(cb);
|
77
|
+
break;
|
78
|
+
}
|
108
79
|
}
|
109
80
|
|
81
|
+
|
82
|
+
|
110
83
|
start() {
|
111
84
|
this.orbit = GameObject.findObjectOfType(OrbitControls, this.context);
|
112
|
-
if (!this.gameObject.getComponentInParent(ObjectRaycaster))
|
85
|
+
if (!this.gameObject.getComponentInParent(ObjectRaycaster)) {
|
113
86
|
this.gameObject.addNewComponent(ObjectRaycaster);
|
87
|
+
}
|
114
88
|
}
|
115
89
|
|
90
|
+
private static lastHovered: Object3D;
|
91
|
+
private _draggingRigidbodies: Rigidbody[] = [];
|
92
|
+
|
116
93
|
private allowEdit(_obj: Object3D | null = null) {
|
117
94
|
return this.context.connection.allowEditing;
|
118
95
|
}
|
119
96
|
|
120
97
|
onPointerEnter(evt: PointerEventData) {
|
121
98
|
if (!this.allowEdit(this.gameObject)) return;
|
122
|
-
if (
|
99
|
+
if (WebXR.IsInWebXR) return;
|
100
|
+
// const interactable = GameObject.getComponentInParent(evt.object, Interactable);
|
101
|
+
// if (!interactable) return;
|
123
102
|
const dc = GameObject.getComponentInParent(evt.object, DragControls);
|
124
103
|
if (!dc || dc !== this) return;
|
125
104
|
DragControls.lastHovered = evt.object;
|
@@ -128,120 +107,83 @@
|
|
128
107
|
|
129
108
|
onPointerExit(evt: PointerEventData) {
|
130
109
|
if (!this.allowEdit(this.gameObject)) return;
|
131
|
-
if (
|
110
|
+
if (WebXR.IsInWebXR) return;
|
132
111
|
if (DragControls.lastHovered !== evt.object) return;
|
112
|
+
// const interactable = GameObject.getComponentInParent(evt.object, Interactable);
|
113
|
+
// if (!interactable) return;
|
133
114
|
this.context.domElement.style.cursor = 'auto';
|
134
115
|
}
|
135
116
|
|
117
|
+
private _waitingForDragStart: PointerEventData | null = null;
|
118
|
+
|
136
119
|
onPointerDown(args: PointerEventData) {
|
137
120
|
if (!this.allowEdit(this.gameObject)) return;
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
DragControls._active += 1;
|
148
|
-
|
149
|
-
const newDragHandler = new DragPointerHandler(this, this.targetObject || this.gameObject);
|
150
|
-
this._dragHandlers.set(args.event.space, newDragHandler);
|
151
|
-
|
152
|
-
// We need to turn off OrbitControls immediately, otherwise they still get data for a short moment
|
153
|
-
// and they don't properly handle being disabled while already processing data (smoothing happens when enabling again)
|
154
|
-
if (this.orbit) this.orbit.enabled = false;
|
155
|
-
|
156
|
-
newDragHandler.onDragStart(args);
|
157
|
-
|
158
|
-
if (this._dragHandlers.size === 2) {
|
159
|
-
const iterator = this._dragHandlers.values();
|
160
|
-
const a = iterator.next().value;
|
161
|
-
const b = iterator.next().value;
|
162
|
-
const mtHandler = new MultiTouchDragHandler(this, this.targetObject || this.gameObject, a, b);
|
163
|
-
this._dragHandlers.set(this.gameObject, mtHandler);
|
164
|
-
|
165
|
-
mtHandler.onDragStart(args);
|
166
|
-
}
|
167
|
-
|
168
|
-
args.use();
|
169
|
-
}
|
121
|
+
if (WebXR.IsInWebXR) return;
|
122
|
+
DragControls._active += 1;
|
123
|
+
this._dragDelta.set(0, 0);
|
124
|
+
this._didDrag = false;
|
125
|
+
// Clone to not modify the original event (and this event is used in the actual onDragStart method)
|
126
|
+
this._waitingForDragStart = args.clone();
|
127
|
+
args.stopPropagation();
|
128
|
+
// disabling pointer controls here already, otherwise we get a few frames of movement event in orbit controls and this will rotate the camera sligthly AFTER drag controls dragging ends.
|
129
|
+
if (this.orbit) this.orbit.enabled = false;
|
170
130
|
}
|
171
131
|
|
172
132
|
onPointerMove(args: PointerEventData) {
|
173
|
-
if
|
133
|
+
if(this._isDragging || this._waitingForDragStart !== null) args.use();
|
174
134
|
}
|
175
135
|
|
176
136
|
onPointerUp(args: PointerEventData) {
|
177
|
-
|
178
|
-
if(debug) Gizmos.DrawLabel(args.point ?? this.gameObject.worldPosition, "POINTERUP:" + args.pointerId + ", " + args.button, .03, 3);
|
179
|
-
|
137
|
+
this._waitingForDragStart = null;
|
180
138
|
if (!this.allowEdit(this.gameObject)) return;
|
181
|
-
if (
|
182
|
-
|
139
|
+
if (DragControls._active > 0)
|
140
|
+
DragControls._active -= 1;
|
141
|
+
if (WebXR.IsInWebXR) return;
|
142
|
+
this.onDragEnd(args);
|
143
|
+
args.stopPropagation();
|
144
|
+
if (this.orbit) this.orbit.enabled = true;
|
145
|
+
}
|
183
146
|
|
184
|
-
const handler = this._dragHandlers.get(args.event.space);
|
185
|
-
const mtHandler = this._dragHandlers.get(this.gameObject) as MultiTouchDragHandler;
|
186
|
-
if (mtHandler && (mtHandler.handlerA === handler || mtHandler.handlerB === handler)) {
|
187
|
-
// any of the two handlers has been released, so we can remove the multi-touch handler
|
188
|
-
this._dragHandlers.delete(this.gameObject);
|
189
|
-
mtHandler.onDragEnd(args);
|
190
|
-
}
|
191
147
|
|
192
|
-
if (handler) {
|
193
|
-
if (DragControls._active > 0)
|
194
|
-
DragControls._active -= 1;
|
195
|
-
|
196
|
-
if (handler.onDragEnd) handler.onDragEnd(args);
|
197
|
-
this._dragHandlers.delete(args.event.space);
|
198
|
-
|
199
|
-
if (this._dragHandlers.size === 0) {
|
200
|
-
this.onLastDragEnd(args);
|
201
|
-
}
|
202
|
-
args.use();
|
203
|
-
}
|
204
|
-
|
205
|
-
if (DragControls._active === 0) {
|
206
|
-
if (this.orbit) this.orbit.enabled = true;
|
207
|
-
}
|
208
|
-
}
|
209
|
-
|
210
148
|
update(): void {
|
149
|
+
if (WebXR.IsInWebXR) return;
|
211
150
|
|
212
|
-
for (const handler of this._dragHandlers.values()) {
|
213
|
-
if (handler.collectMovementInfo) handler.collectMovementInfo();
|
214
|
-
// TODO this doesn't make sense, we should instead just use the max here
|
215
|
-
// or even better, each handler can decide on their own how to handle this
|
216
|
-
if (handler.getTotalMovement) this._totalMovement.add(handler.getTotalMovement());
|
217
|
-
}
|
218
|
-
|
219
151
|
// drag start only after having dragged for some pixels
|
220
|
-
if (this.
|
152
|
+
if (this._waitingForDragStart) {
|
221
153
|
if (!this._didDrag) {
|
222
|
-
// this is so we can e.g. process clicks without having a drag change the position
|
223
|
-
//
|
224
|
-
|
154
|
+
// this is so we can e.g. process clicks without having a drag change the position
|
155
|
+
// e.g. a click to rotate the object
|
156
|
+
const delta = this.context.input.getPointerPositionDelta(0);
|
157
|
+
if (delta)
|
158
|
+
this._dragDelta.add(delta);
|
159
|
+
if (this._dragDelta.length() > 2)
|
225
160
|
this._didDrag = true;
|
226
161
|
else return;
|
227
162
|
}
|
228
|
-
const args = this.
|
229
|
-
this.
|
230
|
-
this.
|
163
|
+
const args = this._waitingForDragStart;
|
164
|
+
this._waitingForDragStart = null;
|
165
|
+
this.onDragStart(args);
|
231
166
|
}
|
232
167
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
168
|
+
if (this._dragHelper && this._dragHelper.hasSelected) {
|
169
|
+
this.onUpdateDrag();
|
170
|
+
}
|
171
|
+
|
172
|
+
if (this._dragHelper?.hasSelected === false || (this._activePointerId !== undefined && this.context.input.getPointerPressed(this._activePointerId) === false)) {
|
173
|
+
this.onDragEnd(null);
|
174
|
+
}
|
238
175
|
}
|
239
176
|
|
240
|
-
|
241
|
-
private
|
177
|
+
private _isDragging: boolean = false;
|
178
|
+
private _marker: UsageMarker | null = null;
|
179
|
+
private _dragDelta!: Vector2;
|
180
|
+
private _didDrag: boolean = false;
|
181
|
+
private _activePointerId?: number;
|
182
|
+
|
183
|
+
private onDragStart(evt: PointerEventData) {
|
242
184
|
if (!this._dragHelper) {
|
243
185
|
if (this.context.mainCamera)
|
244
|
-
this._dragHelper = new
|
186
|
+
this._dragHelper = new DragHelper(this.context.mainCamera);
|
245
187
|
else
|
246
188
|
return;
|
247
189
|
}
|
@@ -250,17 +192,46 @@
|
|
250
192
|
const dc = GameObject.getComponentInParent(evt.object, DragControls);
|
251
193
|
if (!dc || dc !== this) return;
|
252
194
|
|
253
|
-
const object = this.targetObject || this.gameObject;
|
254
195
|
|
255
|
-
|
196
|
+
let object: Object3D = evt.object;
|
256
197
|
|
198
|
+
if (this.transformSelf) {
|
199
|
+
object = this.gameObject;
|
200
|
+
}
|
201
|
+
|
202
|
+
// raise event
|
203
|
+
const args: { selected: Object3D, attached: Object3D | null } = { selected: object, attached: object };
|
204
|
+
for (const listener of this.selectStartEventListener) {
|
205
|
+
listener(this, args);
|
206
|
+
}
|
207
|
+
|
208
|
+
this._activePointerId = evt.pointerId;
|
209
|
+
|
210
|
+
if (!args.attached) return;
|
211
|
+
if (args.attached !== object) {
|
212
|
+
// // if duplicatable changes the object being dragged
|
213
|
+
// // should it also change the active drag controls (e.g. if it has a own one)
|
214
|
+
// const drag = GameObject.getComponentInParent(args.attached, DragControls);
|
215
|
+
// if (drag && drag !== this) {
|
216
|
+
// // incredibly ugly code to pass the drag controls event to another drag controls instance
|
217
|
+
// // This is necessary since we dont call the onPointerUp events anymore for all objects
|
218
|
+
// // that have previously received the onPointerDown event.
|
219
|
+
// // NOTE: added the EventSystem.raisedPointerDownEvents array again because of this uglyness here. The code was originally removed in 757fc5e5bafd02aa13d6cd35dd5e8729c841465a and now we're adding it in 8ce886d8344d1abd5ebb89ae3e1fb8d6d47293da
|
220
|
+
// this.onDragEnd(null);
|
221
|
+
// drag.onPointerDown(evt);
|
222
|
+
// evt.object = args.attached;
|
223
|
+
// drag.onDragStart(evt);
|
224
|
+
// return;
|
225
|
+
// }
|
226
|
+
}
|
227
|
+
object = args.attached;
|
257
228
|
this._isDragging = true;
|
258
229
|
this._dragHelper.setSelected(object, this.context);
|
259
230
|
if (this.orbit) this.orbit.enabled = false;
|
260
231
|
|
261
232
|
const sync = GameObject.getComponentInChildren(object, SyncedTransform);
|
262
|
-
if (debug)
|
263
|
-
|
233
|
+
if (debug)
|
234
|
+
console.log("DRAG START", sync, object);
|
264
235
|
if (sync) {
|
265
236
|
sync.fastMode = true;
|
266
237
|
sync?.requestOwnership();
|
@@ -268,31 +239,30 @@
|
|
268
239
|
|
269
240
|
this._marker = GameObject.addNewComponent(object, UsageMarker);
|
270
241
|
|
242
|
+
// console.log(object, this._marker);
|
243
|
+
|
271
244
|
this._draggingRigidbodies.length = 0;
|
272
245
|
const rbs = GameObject.getComponentsInChildren(object, Rigidbody);
|
273
246
|
if (rbs)
|
274
247
|
this._draggingRigidbodies.push(...rbs);
|
248
|
+
|
249
|
+
const l = nameofFactory<IDragEventListener>();
|
250
|
+
GameObject.invokeOnChildren(this._dragHelper.selected, l("onDragStart"));
|
275
251
|
}
|
276
252
|
|
277
|
-
|
278
|
-
private onAnyDragUpdate() {
|
253
|
+
private onUpdateDrag() {
|
279
254
|
if (!this._dragHelper) return;
|
280
255
|
this._dragHelper.showGizmo = this.showGizmo;
|
256
|
+
this._dragHelper.useViewAngle = this.useViewAngle;
|
281
257
|
|
282
258
|
this._dragHelper.onUpdate(this.context);
|
283
259
|
for (const rb of this._draggingRigidbodies) {
|
284
260
|
rb.wakeUp();
|
285
261
|
rb.resetVelocities();
|
286
|
-
rb.resetForcesAndTorques();
|
287
262
|
}
|
288
|
-
|
289
|
-
const object = this.targetObject || this.gameObject;
|
290
|
-
|
291
|
-
InstancingUtil.markDirty(object);
|
292
263
|
}
|
293
264
|
|
294
|
-
|
295
|
-
private onLastDragEnd(evt: PointerEventData | null) {
|
265
|
+
private onDragEnd(evt: PointerEventData | null) {
|
296
266
|
if (!this || !this._isDragging) return;
|
297
267
|
this._isDragging = false;
|
298
268
|
if (!this._dragHelper) return;
|
@@ -301,7 +271,8 @@
|
|
301
271
|
}
|
302
272
|
this._draggingRigidbodies.length = 0;
|
303
273
|
const selected = this._dragHelper.selected;
|
304
|
-
if (debug)
|
274
|
+
if (debug)
|
275
|
+
console.log("DRAG END", selected, selected?.visible)
|
305
276
|
this._dragHelper.setSelected(null, this.context);
|
306
277
|
if (this.orbit) this.orbit.enabled = true;
|
307
278
|
if (evt?.object) {
|
@@ -311,751 +282,23 @@
|
|
311
282
|
// sync?.requestOwnership();
|
312
283
|
}
|
313
284
|
}
|
314
|
-
if (this._marker)
|
285
|
+
if (this._marker) {
|
315
286
|
this._marker.destroy();
|
316
|
-
}
|
317
|
-
}
|
318
|
-
|
319
|
-
/** Handles two touch points affecting one object. Allows movement, scale and rotation of objects. */
|
320
|
-
class MultiTouchDragHandler implements IDragHandler {
|
321
|
-
|
322
|
-
handlerA: DragPointerHandler;
|
323
|
-
handlerB: DragPointerHandler;
|
324
|
-
|
325
|
-
private context: Context;
|
326
|
-
private settings: DragControls;
|
327
|
-
private gameObject: GameObject;
|
328
|
-
private _handlerAAttachmentPoint: Vector3 = new Vector3();
|
329
|
-
private _handlerBAttachmentPoint: Vector3 = new Vector3();
|
330
|
-
|
331
|
-
private _followObject: GameObject;
|
332
|
-
private _manipulatorObject: GameObject;
|
333
|
-
private _deviceMode!: XRTargetRayMode;
|
334
|
-
private _followObjectStartWorldQuaternion: Quaternion = new Quaternion();
|
335
|
-
|
336
|
-
constructor(dragControls: DragControls, gameObject: GameObject, pointerA: DragPointerHandler, pointerB: DragPointerHandler) {
|
337
|
-
this.context = dragControls.context;
|
338
|
-
this.settings = dragControls;
|
339
|
-
this.gameObject = gameObject;
|
340
|
-
this.handlerA = pointerA;
|
341
|
-
this.handlerB = pointerB;
|
342
|
-
|
343
|
-
this._followObject = new Object3D() as GameObject;
|
344
|
-
this._manipulatorObject = new Object3D() as GameObject;
|
345
|
-
|
346
|
-
this.context.scene.add(this._manipulatorObject);
|
347
|
-
|
348
|
-
const rig = NeedleXRSession.active?.rig?.gameObject;
|
349
|
-
|
350
|
-
if (!this.handlerA || !this.handlerB || !this.handlerA.hitPointInLocalSpace || !this.handlerB.hitPointInLocalSpace) {
|
351
|
-
console.error("Invalid: MultiTouchDragHandler needs two valid DragPointerHandlers with hitPointInLocalSpace set.");
|
352
|
-
return;
|
353
287
|
}
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
this.gameObject.localToWorld(this._tempVec1);
|
358
|
-
this.gameObject.localToWorld(this._tempVec2);
|
359
|
-
if (rig) {
|
360
|
-
rig.worldToLocal(this._tempVec1);
|
361
|
-
rig.worldToLocal(this._tempVec2);
|
288
|
+
// raise event
|
289
|
+
for (const listener of this.selectEndEventListener) {
|
290
|
+
listener(this);
|
362
291
|
}
|
363
|
-
this._initialDistance = this._tempVec1.distanceTo(this._tempVec2);
|
364
|
-
|
365
|
-
if (this._initialDistance < 0.02) {
|
366
|
-
if (debug) {
|
367
|
-
console.log("Finding alternative drag attachment points since initial distance is too low: " + this._initialDistance.toFixed(2));
|
368
|
-
}
|
369
|
-
// We want two reasonable pointer attachment points here.
|
370
|
-
// But if the hitPointInLocalSpace are very close to each other, we instead fall back to controller positions.
|
371
|
-
this.handlerA.followObject.parent!.getWorldPosition(this._tempVec1);
|
372
|
-
this.handlerB.followObject.parent!.getWorldPosition(this._tempVec2);
|
373
|
-
this._handlerAAttachmentPoint.copy(this._tempVec1);
|
374
|
-
this._handlerBAttachmentPoint.copy(this._tempVec2);
|
375
|
-
this.gameObject.worldToLocal(this._handlerAAttachmentPoint);
|
376
|
-
this.gameObject.worldToLocal(this._handlerBAttachmentPoint);
|
377
|
-
this._initialDistance = this._tempVec1.distanceTo(this._tempVec2);
|
378
292
|
|
379
|
-
|
380
|
-
|
381
|
-
this._initialDistance = 1;
|
382
|
-
}
|
383
|
-
}
|
384
|
-
else {
|
385
|
-
this._handlerAAttachmentPoint.copy(this.handlerA.hitPointInLocalSpace);
|
386
|
-
this._handlerBAttachmentPoint.copy(this.handlerB.hitPointInLocalSpace);
|
387
|
-
}
|
388
|
-
|
389
|
-
this._tempVec3.lerpVectors(this._tempVec1, this._tempVec2, 0.5);
|
390
|
-
this._initialScale.copy(gameObject.scale);
|
391
|
-
|
392
|
-
if (debug) {
|
393
|
-
this._followObject.add(new AxesHelper(2));
|
394
|
-
this._manipulatorObject.add(new AxesHelper(5));
|
395
|
-
|
396
|
-
const formatVec = (v: Vector3) => `${v.x.toFixed(2)}, ${v.y.toFixed(2)}, ${v.z.toFixed(2)}`;
|
397
|
-
Gizmos.DrawLine(this._tempVec1, this._tempVec2, 0x00ffff, 0, false);
|
398
|
-
Gizmos.DrawLabel(this._tempVec3, "A:B " + this._initialDistance.toFixed(2) + "\n" + formatVec(this._tempVec1) + "\n" + formatVec(this._tempVec2), 0.03, 5);
|
399
|
-
}
|
293
|
+
const l = nameofFactory<IDragEventListener>();
|
294
|
+
GameObject.invokeOnChildren(selected, l("onDragEnd"));
|
400
295
|
}
|
401
|
-
|
402
|
-
onDragStart(_args: PointerEventData): void {
|
403
|
-
// align _followObject with the object we want to drag
|
404
|
-
this.gameObject.add(this._followObject);
|
405
|
-
this._followObject.matrixAutoUpdate = false;
|
406
|
-
this._followObject.matrix.identity();
|
407
|
-
this._deviceMode = _args.mode;
|
408
|
-
this._followObjectStartWorldQuaternion.copy(this._followObject.worldQuaternion);
|
409
|
-
|
410
|
-
// align _manipulatorObject in the same way it would if this was a drag update
|
411
|
-
this.alignManipulator();
|
412
|
-
|
413
|
-
// and then parent it to the space object so it follows along.
|
414
|
-
this._manipulatorObject.attach(this._followObject);
|
415
|
-
|
416
|
-
// store offsets in local space
|
417
|
-
this._manipulatorPosOffset.copy(this._followObject.position);
|
418
|
-
this._manipulatorRotOffset.copy(this._followObject.quaternion);
|
419
|
-
this._manipulatorScaleOffset.copy(this._followObject.scale);
|
420
|
-
}
|
421
|
-
|
422
|
-
onDragEnd(_args: PointerEventData): void {
|
423
|
-
if (!this.handlerA || !this.handlerB) {
|
424
|
-
console.error("onDragEnd called on MultiTouchDragHandler without valid handlers. This is likely a bug.");
|
425
|
-
return;
|
426
|
-
}
|
427
|
-
|
428
|
-
// we want to initialize the drag points for these handlers again.
|
429
|
-
// one of them will be removed, but we don't know here which one
|
430
|
-
this.handlerA.recenter();
|
431
|
-
this.handlerB.recenter();
|
432
|
-
|
433
|
-
// destroy helper objects
|
434
|
-
this._manipulatorObject.removeFromParent();
|
435
|
-
this._followObject.removeFromParent();
|
436
|
-
this._manipulatorObject.destroy();
|
437
|
-
this._followObject.destroy();
|
438
|
-
}
|
439
|
-
|
440
|
-
private _manipulatorPosOffset: Vector3 = new Vector3();
|
441
|
-
private _manipulatorRotOffset: Quaternion = new Quaternion();
|
442
|
-
private _manipulatorScaleOffset: Vector3 = new Vector3();
|
443
|
-
|
444
|
-
private _tempVec1: Vector3 = new Vector3();
|
445
|
-
private _tempVec2: Vector3 = new Vector3();
|
446
|
-
private _tempVec3: Vector3 = new Vector3();
|
447
|
-
private tempLookMatrix: Matrix4 = new Matrix4();
|
448
|
-
private _initialScale: Vector3 = new Vector3();
|
449
|
-
private _initialDistance: number = 0;
|
450
|
-
|
451
|
-
private alignManipulator() {
|
452
|
-
this._tempVec1.copy(this._handlerAAttachmentPoint);
|
453
|
-
this._tempVec2.copy(this._handlerBAttachmentPoint);
|
454
|
-
this.handlerA.followObject.localToWorld(this._tempVec1);
|
455
|
-
this.handlerB.followObject.localToWorld(this._tempVec2);
|
456
|
-
this._tempVec3.lerpVectors(this._tempVec1, this._tempVec2, 0.5);
|
457
|
-
|
458
|
-
this._manipulatorObject.position.copy(this._tempVec3);
|
459
|
-
|
460
|
-
// - lookAt the second point on handlerB
|
461
|
-
const camera = this.context.mainCamera;
|
462
|
-
this.tempLookMatrix.lookAt(this._tempVec3, this._tempVec2, (camera as any as IGameObject).worldUp);
|
463
|
-
this._manipulatorObject.quaternion.setFromRotationMatrix(this.tempLookMatrix);
|
464
|
-
|
465
|
-
// - scale based on the distance between the two points
|
466
|
-
const dist = this._tempVec1.distanceTo(this._tempVec2);
|
467
|
-
this._manipulatorObject.scale.copy(this._initialScale).multiplyScalar(dist / this._initialDistance);
|
468
|
-
|
469
|
-
this._manipulatorObject.updateMatrix();
|
470
|
-
this._manipulatorObject.updateMatrixWorld(true);
|
471
|
-
|
472
|
-
if (debug) {
|
473
|
-
Gizmos.DrawLabel(this._tempVec3.clone().add(new Vector3(0,0.2,0)), "A:B " + dist.toFixed(2), 0.03);
|
474
|
-
Gizmos.DrawLine(this._tempVec1, this._tempVec2, 0x00ff00, 0, false);
|
475
|
-
|
476
|
-
// const wp = this._manipulatorObject.worldPosition;
|
477
|
-
// Gizmos.DrawWireSphere(wp, this._initialScale.length() * dist / this._initialDistance, 0x00ff00, 0, false);
|
478
|
-
}
|
479
|
-
}
|
480
|
-
|
481
|
-
onDragUpdate() {
|
482
|
-
// At this point we've run both the other handlers, but their effects have been suppressed because they can't handle
|
483
|
-
// two events at the same time. They're basically providing us with two Object3D's and we can combine these here
|
484
|
-
// into a reasonable two-handed translation/rotation/scale.
|
485
|
-
// One approach:
|
486
|
-
// - position our control object on the center between the two pointer control objects
|
487
|
-
|
488
|
-
// TODO close grab needs to be handled differently because there we don't have a hit point -
|
489
|
-
// Hit point is just the center of the object
|
490
|
-
// So probably we should fix that close grab has a better hit point approximation (point on bounds?)
|
491
|
-
|
492
|
-
this.alignManipulator();
|
493
|
-
|
494
|
-
// apply (smoothed) to the gameObject
|
495
|
-
const lerpStrength = 30;
|
496
|
-
const lerpFactor = 1.0;
|
497
|
-
|
498
|
-
this._followObject.position.copy(this._manipulatorPosOffset);
|
499
|
-
this._followObject.quaternion.copy(this._manipulatorRotOffset);
|
500
|
-
this._followObject.scale.copy(this._manipulatorScaleOffset);
|
501
|
-
|
502
|
-
const draggedObject = this.gameObject;
|
503
|
-
const targetObject = this._followObject;
|
504
|
-
|
505
|
-
targetObject.updateMatrix();
|
506
|
-
targetObject.updateMatrixWorld(true);
|
507
|
-
|
508
|
-
const isSpatialInput = this._deviceMode === "tracked-pointer";
|
509
|
-
const keepRotation = isSpatialInput ? this.settings.xrKeepRotation : this.settings.keepRotation;
|
510
|
-
|
511
|
-
// TODO refactor to a common place
|
512
|
-
// apply constraints (position grid snap, rotation, ...)
|
513
|
-
if (this.settings.snapGridResolution > 0) {
|
514
|
-
const wp = this._followObject.worldPosition;
|
515
|
-
const snap = this.settings.snapGridResolution;
|
516
|
-
wp.x = Math.round(wp.x / snap) * snap;
|
517
|
-
wp.y = Math.round(wp.y / snap) * snap;
|
518
|
-
wp.z = Math.round(wp.z / snap) * snap;
|
519
|
-
this._followObject.worldPosition = wp;
|
520
|
-
this._followObject.updateMatrix();
|
521
|
-
}
|
522
|
-
if (keepRotation) {
|
523
|
-
this._followObject.worldQuaternion = this._followObjectStartWorldQuaternion;
|
524
|
-
this._followObject.updateMatrix();
|
525
|
-
}
|
526
|
-
|
527
|
-
// TODO refactor to a common place
|
528
|
-
// TODO should use unscaled time here // some test for lerp speed depending on distance
|
529
|
-
const t = Mathf.clamp01(this.context.time.deltaTime * lerpStrength * lerpFactor);// / (currentDist - 1 + 0.01));
|
530
|
-
|
531
|
-
const wp = draggedObject.worldPosition;
|
532
|
-
wp.lerp(targetObject.worldPosition, t);
|
533
|
-
draggedObject.worldPosition = wp;
|
534
|
-
|
535
|
-
const rot = draggedObject.worldQuaternion;
|
536
|
-
rot.slerp(targetObject.worldQuaternion, t);
|
537
|
-
draggedObject.worldQuaternion = rot;
|
538
|
-
|
539
|
-
const scl = draggedObject.worldScale;
|
540
|
-
scl.lerp(targetObject.worldScale, t);
|
541
|
-
draggedObject.worldScale = scl;
|
542
|
-
}
|
543
|
-
|
544
|
-
setTargetObject(obj: Object3D | null): void {
|
545
|
-
this.gameObject = obj as GameObject;
|
546
|
-
}
|
547
296
|
}
|
548
297
|
|
549
|
-
/** Common interface for pointer handlers (single touch and multi touch) */
|
550
|
-
interface IDragHandler {
|
551
|
-
/** Used to determine if a drag has happened for this handler */
|
552
|
-
getTotalMovement?(): Vector3;
|
553
|
-
/** Target object can change mid-flight (e.g. in Duplicatable), handlers should react properly to that */
|
554
|
-
setTargetObject(obj: Object3D | null): void;
|
555
|
-
|
556
|
-
/** Prewarms the drag – can already move internal points around here but should not move the object itself */
|
557
|
-
collectMovementInfo?(): void;
|
558
|
-
onDragStart?(args: PointerEventData): void;
|
559
|
-
onDragEnd?(args: PointerEventData): void;
|
560
|
-
/** The target object is moved around */
|
561
|
-
onDragUpdate?(numberOfPointers: number): void;
|
562
|
-
}
|
563
298
|
|
564
|
-
/** Handles a single pointer on an object. DragPointerHandlers are created on pointer enter,
|
565
|
-
* help with determining if there's actually a drag, and then perform operations based on spatial pointer data.
|
566
|
-
*/
|
567
|
-
class DragPointerHandler implements IDragHandler {
|
568
299
|
|
569
|
-
|
570
|
-
* This is in world units, so very small for screens (near-plane space change) */
|
571
|
-
getTotalMovement(): Vector3 { return this._totalMovement; }
|
572
|
-
get followObject(): GameObject { return this._followObject; }
|
573
|
-
get hitPointInLocalSpace(): Vector3 { return this._hitPointInLocalSpace; }
|
300
|
+
class DragHelper {
|
574
301
|
|
575
|
-
private context: Context;
|
576
|
-
private gameObject: GameObject;
|
577
|
-
private settings: DragControls;
|
578
|
-
private _lastRig: IGameObject | undefined = undefined;
|
579
|
-
|
580
|
-
/** This object is placed at the pivot of the dragged object, and parented to the control space. */
|
581
|
-
private _followObject: GameObject;
|
582
|
-
private _totalMovement: Vector3 = new Vector3();
|
583
|
-
/** Motion along the pointer ray. On screens this doesn't change. In XR it can be used to determine how much
|
584
|
-
* effort someone is putting into moving an object closer or further away. */
|
585
|
-
private _totalMovementAlongRayDirection: number = 0;
|
586
|
-
/** Distance between _followObject and its parent at grab start, in local space */
|
587
|
-
private _grabStartDistance: number = 0;
|
588
|
-
private _deviceMode!: XRTargetRayMode;
|
589
|
-
private _followObjectStartPosition: Vector3 = new Vector3();
|
590
|
-
private _followObjectStartQuaternion: Quaternion = new Quaternion();
|
591
|
-
private _followObjectStartWorldQuaternion: Quaternion = new Quaternion();
|
592
|
-
private _lastDragPosRigSpace: Vector3 | undefined;
|
593
|
-
private _tempVec: Vector3 = new Vector3();
|
594
|
-
private _tempMat: Matrix4 = new Matrix4();
|
595
|
-
|
596
|
-
private _hitPointInLocalSpace: Vector3 = new Vector3();
|
597
|
-
private _hitNormalInLocalSpace: Vector3 = new Vector3();
|
598
|
-
private _bottomCenter = new Vector3();
|
599
|
-
private _backCenter = new Vector3();
|
600
|
-
private _backBottomCenter = new Vector3();
|
601
|
-
private _bounds = new Box3();
|
602
|
-
private _dragPlane = new Plane(new Vector3(0, 1, 0));
|
603
|
-
private _draggedOverObject: Object3D | null = null;
|
604
|
-
private _draggedOverObjectLastSetUp: Object3D | null = null;
|
605
|
-
private _draggedOverObjectLastNormal: Vector3 = new Vector3();
|
606
|
-
private _draggedOverObjectDuration: number = 0;
|
607
|
-
|
608
|
-
/** Allows overriding which object is dragged while a drag is already ongoing. Used for example by Duplicatable */
|
609
|
-
setTargetObject(obj: Object3D | null) {
|
610
|
-
this.gameObject = obj as GameObject;
|
611
|
-
}
|
612
|
-
|
613
|
-
constructor(dragControls: DragControls, gameObject: GameObject) {
|
614
|
-
this.settings = dragControls;
|
615
|
-
this.context = dragControls.context;
|
616
|
-
this.gameObject = gameObject;
|
617
|
-
this._followObject = new Object3D() as GameObject;
|
618
|
-
}
|
619
|
-
|
620
|
-
recenter() {
|
621
|
-
if (!this._followObject.parent) {
|
622
|
-
console.warn("Error: space follow object doesn't have parent but recenter() is called. This is likely a bug");
|
623
|
-
return;
|
624
|
-
}
|
625
|
-
|
626
|
-
const p = this._followObject.parent as GameObject;
|
627
|
-
|
628
|
-
this.gameObject.add(this._followObject);
|
629
|
-
this._followObject.matrixAutoUpdate = false;
|
630
|
-
|
631
|
-
this._followObject.position.set(0, 0, 0);
|
632
|
-
this._followObject.quaternion.set(0, 0, 0, 1);
|
633
|
-
this._followObject.scale.set(1, 1, 1);
|
634
|
-
|
635
|
-
this._followObject.updateMatrix();
|
636
|
-
this._followObject.updateMatrixWorld(true);
|
637
|
-
|
638
|
-
p.attach(this._followObject);
|
639
|
-
|
640
|
-
this._followObjectStartPosition.copy(this._followObject.position);
|
641
|
-
this._followObjectStartQuaternion.copy(this._followObject.quaternion);
|
642
|
-
this._followObjectStartWorldQuaternion.copy(this._followObject.worldQuaternion);
|
643
|
-
|
644
|
-
this._followObject.updateMatrix();
|
645
|
-
this._followObject.updateMatrixWorld(true);
|
646
|
-
|
647
|
-
const hitPointWP = this._hitPointInLocalSpace.clone();
|
648
|
-
this.gameObject.localToWorld(hitPointWP);
|
649
|
-
this._grabStartDistance = hitPointWP.distanceTo(p.worldPosition);
|
650
|
-
const rig = NeedleXRSession.active?.rig?.gameObject;
|
651
|
-
const rigScale = rig?.worldScale.x || 1;
|
652
|
-
this._grabStartDistance /= rigScale;
|
653
|
-
|
654
|
-
this._totalMovementAlongRayDirection = 0;
|
655
|
-
this._lastDragPosRigSpace = undefined;
|
656
|
-
|
657
|
-
if (debug)
|
658
|
-
{
|
659
|
-
Gizmos.DrawLine(hitPointWP, p.worldPosition, 0x00ff00, 0.5, false);
|
660
|
-
Gizmos.DrawLabel(p.worldPosition.add(new Vector3(0,0.1,0)), this._grabStartDistance.toFixed(2), 0.03, 0.5);
|
661
|
-
}
|
662
|
-
}
|
663
|
-
|
664
|
-
onDragStart(args: PointerEventData) {
|
665
|
-
|
666
|
-
args.event.space.add(this._followObject);
|
667
|
-
|
668
|
-
// prepare for drag, we will start dragging after an object has been dragged for a few centimeters
|
669
|
-
this._lastDragPosRigSpace = undefined;
|
670
|
-
|
671
|
-
if (args.point && args.normal) {
|
672
|
-
this._hitPointInLocalSpace.copy(args.point);
|
673
|
-
this.gameObject.worldToLocal(this._hitPointInLocalSpace);
|
674
|
-
this._hitNormalInLocalSpace.copy(args.normal);
|
675
|
-
}
|
676
|
-
else if (args) {
|
677
|
-
// can happen for e.g. close grabs; we can assume/guess a good hit point and normal based on the object's bounds or so
|
678
|
-
// convert controller world position to local space instead and use that as hit point
|
679
|
-
const controller = args.event.space as GameObject;
|
680
|
-
const controllerWp = controller.worldPosition;
|
681
|
-
this.gameObject.worldToLocal(controllerWp);
|
682
|
-
this._hitPointInLocalSpace.copy(controllerWp);
|
683
|
-
|
684
|
-
const controllerUp = controller.worldUp;
|
685
|
-
this._tempMat.copy(this.gameObject.matrixWorld).invert();
|
686
|
-
controllerUp.transformDirection(this._tempMat);
|
687
|
-
this._hitNormalInLocalSpace.copy(controllerUp);
|
688
|
-
}
|
689
|
-
|
690
|
-
this.recenter();
|
691
|
-
|
692
|
-
this._totalMovement.set(0, 0, 0);
|
693
|
-
this._deviceMode = args.mode;
|
694
|
-
|
695
|
-
|
696
|
-
const dragSource = this._followObject.parent as IGameObject;
|
697
|
-
const rayDirection = dragSource.worldForward;
|
698
|
-
|
699
|
-
const isSpatialInput = this._deviceMode === "tracked-pointer";
|
700
|
-
const dragMode = isSpatialInput ? this.settings.xrDragMode : this.settings.dragMode;
|
701
|
-
|
702
|
-
// set up drag plane; we don't really know the normal yet but we can already set the point
|
703
|
-
const hitWP = this._hitPointInLocalSpace.clone();
|
704
|
-
this.gameObject.localToWorld(hitWP);
|
705
|
-
|
706
|
-
switch (dragMode) {
|
707
|
-
case DragMode.XZPlane:
|
708
|
-
const up = new Vector3(0,1,0);
|
709
|
-
if (this.gameObject.parent) {
|
710
|
-
// TODO in this case _dragPlane should be in parent space, not world space,
|
711
|
-
// otherwise dragging the parent and this object at the same time doesn't keep the plane constrained
|
712
|
-
up.transformDirection(this.gameObject.parent.matrixWorld.clone().invert());
|
713
|
-
}
|
714
|
-
this._dragPlane.setFromNormalAndCoplanarPoint(up, hitWP);
|
715
|
-
break;
|
716
|
-
case DragMode.HitNormal:
|
717
|
-
const hitNormal = this._hitNormalInLocalSpace.clone();
|
718
|
-
hitNormal.transformDirection(this.gameObject.matrixWorld);
|
719
|
-
this._dragPlane.setFromNormalAndCoplanarPoint(hitNormal, hitWP);
|
720
|
-
break;
|
721
|
-
case DragMode.Attached:
|
722
|
-
this._dragPlane.setFromNormalAndCoplanarPoint(rayDirection, hitWP);
|
723
|
-
break;
|
724
|
-
case DragMode.DynamicViewAngle:
|
725
|
-
const v0 = new Vector3(0, 1, 0);
|
726
|
-
const v1 = rayDirection;
|
727
|
-
const angle = v0.angleTo(v1);
|
728
|
-
const angleThreshold = 0.5;
|
729
|
-
if (angle > Math.PI / 2 + angleThreshold || angle < Math.PI / 2 - angleThreshold)
|
730
|
-
this._dragPlane.setFromNormalAndCoplanarPoint(new Vector3(0, 1, 0), hitWP);
|
731
|
-
else
|
732
|
-
this._dragPlane.setFromNormalAndCoplanarPoint(rayDirection, hitWP);
|
733
|
-
break;
|
734
|
-
}
|
735
|
-
|
736
|
-
// calculate bounding box and snapping points. We want to either snap the "back" point or the "bottom" point.
|
737
|
-
const bbox = new Box3();
|
738
|
-
const p = this.gameObject.parent;
|
739
|
-
const localP = this.gameObject.position.clone();
|
740
|
-
const localQ = this.gameObject.quaternion.clone();
|
741
|
-
const localS = this.gameObject.scale.clone();
|
742
|
-
if (p) p.remove(this.gameObject);
|
743
|
-
this.gameObject.position.set(0, 0, 0);
|
744
|
-
this.gameObject.quaternion.set(0, 0, 0, 1);
|
745
|
-
this.gameObject.scale.set(1, 1, 1);
|
746
|
-
bbox.setFromObject(this.gameObject);
|
747
|
-
|
748
|
-
// get front center point of the bbox. basically (0, 0, 1) in local space
|
749
|
-
const bboxCenter = new Vector3();
|
750
|
-
bbox.getCenter(bboxCenter);
|
751
|
-
const bboxSize = new Vector3();
|
752
|
-
bbox.getSize(bboxSize);
|
753
|
-
|
754
|
-
// attachment points for dragging
|
755
|
-
this._bottomCenter.copy(bboxCenter.clone().add(new Vector3(0, -bboxSize.y / 2, 0)));
|
756
|
-
this._backCenter.copy(bboxCenter.clone().add(new Vector3(0, 0, bboxSize.z / 2)));
|
757
|
-
this._backBottomCenter.copy(bboxCenter.clone().add(new Vector3(0, -bboxSize.y / 2, bboxSize.z / 2)));
|
758
|
-
|
759
|
-
this._bounds.copy(bbox);
|
760
|
-
|
761
|
-
// restore original transform
|
762
|
-
if (p) p.add(this.gameObject);
|
763
|
-
this.gameObject.position.copy(localP);
|
764
|
-
this.gameObject.quaternion.copy(localQ);
|
765
|
-
this.gameObject.scale.copy(localS);
|
766
|
-
|
767
|
-
// surface snapping
|
768
|
-
this._draggedOverObject = null;
|
769
|
-
this._draggedOverObjectLastSetUp = null;
|
770
|
-
this._draggedOverObjectLastNormal.set(0, 1, 0);
|
771
|
-
this._draggedOverObjectDuration = 0;
|
772
|
-
}
|
773
|
-
|
774
|
-
collectMovementInfo() {
|
775
|
-
// we're dragging - there is a controlling object
|
776
|
-
if (!this._followObject.parent) return;
|
777
|
-
|
778
|
-
// TODO This should all be handled properly per-pointer
|
779
|
-
// and we want to have a chance to react to multiple pointers being on the same object.
|
780
|
-
// some common stuff (calculating of movement offsets, etc) could be done by default
|
781
|
-
// and then the main thing to override is the actual movement of the object based on N _followObjects
|
782
|
-
|
783
|
-
const dragSource = this._followObject.parent as IGameObject;
|
784
|
-
|
785
|
-
// modify _followObject with constraints, e.g.
|
786
|
-
// - dragging on a plane, e.g. the floor (keeping the distance to the floor plane constant)
|
787
|
-
/* TODO fix jump on drag start
|
788
|
-
const p0 = this._followObject.parent as GameObject;
|
789
|
-
const ray = new Ray(p0.worldPosition, p0.worldForward.multiplyScalar(-1));
|
790
|
-
const p = new Vector3();
|
791
|
-
const t0 = ray.intersectPlane(new Plane(new Vector3(0, 1, 0)), p);
|
792
|
-
if (t0 !== null)
|
793
|
-
this._followObject.worldPosition = t0;
|
794
|
-
*/
|
795
|
-
|
796
|
-
this._followObject.updateMatrix();
|
797
|
-
const dragPosRigSpace = dragSource.worldPosition;
|
798
|
-
const rig = NeedleXRSession.active?.rig?.gameObject;
|
799
|
-
if (rig)
|
800
|
-
rig.worldToLocal(dragPosRigSpace);
|
801
|
-
|
802
|
-
// sum up delta
|
803
|
-
// TODO We need to do all/most of these calculations in Rig Space instead of world space
|
804
|
-
// moving the rig while holding an object should not affect _rayDelta / _dragDelta
|
805
|
-
if (this._lastDragPosRigSpace === undefined || rig != this._lastRig) {
|
806
|
-
this._lastDragPosRigSpace = dragPosRigSpace.clone();
|
807
|
-
this._lastRig = rig;
|
808
|
-
}
|
809
|
-
this._tempVec.copy(dragPosRigSpace).sub(this._lastDragPosRigSpace);
|
810
|
-
|
811
|
-
const rayDirectionRigSpace = dragSource.worldForward;
|
812
|
-
if (rig) {
|
813
|
-
this._tempMat.copy(rig.matrixWorld).invert();
|
814
|
-
rayDirectionRigSpace.transformDirection(this._tempMat);
|
815
|
-
}
|
816
|
-
// sum up delta movement along ray
|
817
|
-
this._totalMovementAlongRayDirection += rayDirectionRigSpace.dot(this._tempVec);
|
818
|
-
this._tempVec.x = Math.abs(this._tempVec.x);
|
819
|
-
this._tempVec.y = Math.abs(this._tempVec.y);
|
820
|
-
this._tempVec.z = Math.abs(this._tempVec.z);
|
821
|
-
|
822
|
-
// sum up absolute total movement
|
823
|
-
this._totalMovement.add(this._tempVec);
|
824
|
-
this._lastDragPosRigSpace.copy(dragPosRigSpace);
|
825
|
-
|
826
|
-
if (debug) {
|
827
|
-
let wp = dragPosRigSpace;
|
828
|
-
// ray direction of the input source object
|
829
|
-
if (rig) {
|
830
|
-
wp = wp.clone();
|
831
|
-
wp.transformDirection(rig.matrixWorld);
|
832
|
-
}
|
833
|
-
Gizmos.DrawRay(wp, rayDirectionRigSpace, 0x0000ff);
|
834
|
-
}
|
835
|
-
}
|
836
|
-
|
837
|
-
onDragUpdate(numberOfPointers: number) {
|
838
|
-
|
839
|
-
// can only handle a single pointer
|
840
|
-
// if there's more, we defer to multi-touch drag handlers
|
841
|
-
if (numberOfPointers > 1) return;
|
842
|
-
|
843
|
-
const draggedObject = this.gameObject as IGameObject;
|
844
|
-
const dragSource = this._followObject.parent as IGameObject;
|
845
|
-
this._followObject.updateMatrix();
|
846
|
-
const dragSourceWP = dragSource.worldPosition;
|
847
|
-
const rayDirection = dragSource.worldForward;
|
848
|
-
|
849
|
-
|
850
|
-
// Actually move and rotate draggedObject
|
851
|
-
const isSpatialInput = this._deviceMode === "tracked-pointer";
|
852
|
-
const keepRotation = isSpatialInput ? this.settings.xrKeepRotation : this.settings.keepRotation;
|
853
|
-
const dragMode = isSpatialInput ? this.settings.xrDragMode : this.settings.dragMode;
|
854
|
-
|
855
|
-
const lerpStrength = 10;
|
856
|
-
// - keeping rotation constant during dragging
|
857
|
-
if (keepRotation) this._followObject.worldQuaternion = this._followObjectStartWorldQuaternion;
|
858
|
-
this._followObject.updateMatrix();
|
859
|
-
this._followObject.updateMatrixWorld(true);
|
860
|
-
|
861
|
-
// Acceleration for moving the object - move followObject along the ray distance by _totalMovementAlongRayDirection
|
862
|
-
let currentDist = 1.0;
|
863
|
-
let lerpFactor = 1.0;
|
864
|
-
if (this._deviceMode === "tracked-pointer" && this._grabStartDistance > 0.5) // hands and controllers, but not touches
|
865
|
-
{
|
866
|
-
const factor = 1 + this._totalMovementAlongRayDirection * (2 * this.settings.xrDistanceDragFactor);
|
867
|
-
currentDist = Math.max(0.0, factor);
|
868
|
-
currentDist = currentDist * currentDist * currentDist;
|
869
|
-
}
|
870
|
-
else if (this._grabStartDistance <= 0.5)
|
871
|
-
{
|
872
|
-
// TODO there's still a frame delay between dragged objects and the hand models
|
873
|
-
lerpFactor = 3.0;
|
874
|
-
}
|
875
|
-
|
876
|
-
// reset _followObject to its original position and rotation
|
877
|
-
this._followObject.position.copy(this._followObjectStartPosition);
|
878
|
-
if (!keepRotation)
|
879
|
-
this._followObject.quaternion.copy(this._followObjectStartQuaternion);
|
880
|
-
|
881
|
-
// TODO restore previous functionality:
|
882
|
-
// When distance dragging, the HIT POINT should move along the ray until it reaches the controller;
|
883
|
-
// NOT the pivot point of the dragged object. E.g. grabbing a large cube and pulling towards you should at most
|
884
|
-
// move the grabbed point to your head and not slap the cube in your head.
|
885
|
-
this._followObject.position.multiplyScalar(currentDist);
|
886
|
-
this._followObject.updateMatrix();
|
887
|
-
|
888
|
-
const ray = new Ray(dragSourceWP, rayDirection);
|
889
|
-
|
890
|
-
// Surface snapping.
|
891
|
-
// Feels quite weird in VR right now!
|
892
|
-
if (dragMode == DragMode.SnapToSurfaces) {
|
893
|
-
// Idea: Do a sphere cast if we're still in the proximity of the current draggedObject.
|
894
|
-
// This would allow dragging slightly out of the object's bounds and still continue snapping to it.
|
895
|
-
// Do a regular raycast (without the dragged object) to determine if we should change what is dragged onto.
|
896
|
-
const opts = new RaycastOptions();
|
897
|
-
opts.ignore = [draggedObject];
|
898
|
-
const hits = this.context.physics.raycastFromRay(ray, opts);
|
899
|
-
|
900
|
-
if (hits.length > 0) {
|
901
|
-
const hit = hits[0];
|
902
|
-
// if we're above the same surface for a specified time, adjust drag options:
|
903
|
-
// - set that surface as the drag "plane". We will follow that object's surface instead now (raycast onto only that)
|
904
|
-
// - if the drag plane is an object, we also want to
|
905
|
-
// - calculate an initial rotation offset matching what surface/face the user originally started the drag on
|
906
|
-
// - rotate the dragged object to match the surface normal
|
907
|
-
if (this._draggedOverObject === hit.object)
|
908
|
-
this._draggedOverObjectDuration += this.context.time.deltaTime;
|
909
|
-
else {
|
910
|
-
this._draggedOverObject = hit.object;
|
911
|
-
this._draggedOverObjectDuration = 0;
|
912
|
-
}
|
913
|
-
|
914
|
-
if (hit.face) {
|
915
|
-
// Adjust drag plane if we're dragging over a different object (for a certain amount of time)
|
916
|
-
// or if the surface normal changed
|
917
|
-
if (this._draggedOverObjectDuration > 0.15 &&
|
918
|
-
(this._draggedOverObjectLastSetUp !== this._draggedOverObject ||
|
919
|
-
this._draggedOverObjectLastNormal.dot(hit.face.normal) < 0.999999)
|
920
|
-
) {
|
921
|
-
this._draggedOverObjectLastSetUp = this._draggedOverObject;
|
922
|
-
this._draggedOverObjectLastNormal.copy(hit.face.normal);
|
923
|
-
|
924
|
-
const center = new Vector3();
|
925
|
-
const size = new Vector3();
|
926
|
-
|
927
|
-
this._bounds.getCenter(center);
|
928
|
-
this._bounds.getSize(size);
|
929
|
-
center.sub(size.multiplyScalar(0.5).multiply(hit.face.normal));
|
930
|
-
this._hitPointInLocalSpace.copy(center);
|
931
|
-
this._hitNormalInLocalSpace.copy(hit.face.normal);
|
932
|
-
|
933
|
-
// ensure plane is far enough up that we don't drag into the surface
|
934
|
-
// Which offset we use here depends on the face normal direction we hit
|
935
|
-
// If we hit the bottom, we want to use the top, and vice versa
|
936
|
-
// To do this dynamically, we can find the intersection between our local bounds and the hit face normal (which is already in local space)
|
937
|
-
this._bounds.getCenter(center);
|
938
|
-
this._bounds.getSize(size);
|
939
|
-
center.add(size.multiplyScalar(0.5).multiply(hit.face.normal));
|
940
|
-
|
941
|
-
const offset = this._hitPointInLocalSpace.clone().add(center);
|
942
|
-
this._followObject.localToWorld(offset);
|
943
|
-
const offsetWP = this._followObject.worldPosition.sub(offset);
|
944
|
-
|
945
|
-
this._dragPlane.setFromNormalAndCoplanarPoint(hit.face.normal, hit.point.sub(offsetWP));
|
946
|
-
}
|
947
|
-
}
|
948
|
-
}
|
949
|
-
}
|
950
|
-
|
951
|
-
// Objects could also serve as "slots" for dragging other objects into. In that case, we don't want to snap to the surface,
|
952
|
-
// we want to snap to the pivot of that object. These dragged-over objects could also need to be invisible (a "slot")
|
953
|
-
// Raycast along the ray to the drag plane and move _followObject so that the grabbed point stays at the hit point
|
954
|
-
if (dragMode !== DragMode.Attached && ray.intersectPlane(this._dragPlane, this._tempVec)) {
|
955
|
-
|
956
|
-
this._followObject.worldPosition = this._tempVec;
|
957
|
-
this._followObject.updateMatrix();
|
958
|
-
this._followObject.updateMatrixWorld(true);
|
959
|
-
|
960
|
-
const newWP = this._hitPointInLocalSpace.clone();
|
961
|
-
this._followObject.localToWorld(newWP);
|
962
|
-
|
963
|
-
if (debug) {
|
964
|
-
Gizmos.DrawLine(newWP, this._tempVec, 0x00ffff, 0, false);
|
965
|
-
}
|
966
|
-
|
967
|
-
this._followObject.worldPosition = this._tempVec.multiplyScalar(2).sub(newWP);
|
968
|
-
this._followObject.updateMatrix();
|
969
|
-
|
970
|
-
/*
|
971
|
-
// TODO figure out nicer look rotation here
|
972
|
-
const normal = this._dragPlane.normal;
|
973
|
-
const lookPoint = normal.clone().multiplyScalar(1000).add(this._tempVec);
|
974
|
-
if (lookPoint) {
|
975
|
-
this._followObject.lookAt(lookPoint);
|
976
|
-
this._followObject.rotateX(Math.PI / 2);
|
977
|
-
}
|
978
|
-
*/
|
979
|
-
this._followObject.updateMatrix();
|
980
|
-
}
|
981
|
-
|
982
|
-
// TODO refactor to a common place
|
983
|
-
// apply constraints (position grid snap, rotation, ...)
|
984
|
-
if (this.settings.snapGridResolution > 0) {
|
985
|
-
const wp = this._followObject.worldPosition;
|
986
|
-
const snap = this.settings.snapGridResolution;
|
987
|
-
wp.x = Math.round(wp.x / snap) * snap;
|
988
|
-
wp.y = Math.round(wp.y / snap) * snap;
|
989
|
-
wp.z = Math.round(wp.z / snap) * snap;
|
990
|
-
this._followObject.worldPosition = wp;
|
991
|
-
this._followObject.updateMatrix();
|
992
|
-
}
|
993
|
-
if (keepRotation) {
|
994
|
-
this._followObject.worldQuaternion = this._followObjectStartWorldQuaternion;
|
995
|
-
this._followObject.updateMatrix();
|
996
|
-
}
|
997
|
-
|
998
|
-
// TODO refactor to a common place
|
999
|
-
// TODO should use unscaled time here // some test for lerp speed depending on distance
|
1000
|
-
const t = Mathf.clamp01(this.context.time.deltaTime * lerpStrength * lerpFactor);// / (currentDist - 1 + 0.01));
|
1001
|
-
|
1002
|
-
const wp = draggedObject.worldPosition;
|
1003
|
-
wp.lerp(this._followObject.worldPosition, t);
|
1004
|
-
draggedObject.worldPosition = wp;
|
1005
|
-
|
1006
|
-
const rot = draggedObject.worldQuaternion;
|
1007
|
-
rot.slerp(this._followObject.worldQuaternion, t);
|
1008
|
-
draggedObject.worldQuaternion = rot;
|
1009
|
-
|
1010
|
-
|
1011
|
-
if (debug)
|
1012
|
-
{
|
1013
|
-
const hitPointWP = this._hitPointInLocalSpace.clone();
|
1014
|
-
draggedObject.localToWorld(hitPointWP);
|
1015
|
-
// draw grab attachment point and normal. They are in grabbed object space
|
1016
|
-
Gizmos.DrawSphere(hitPointWP, 0.02, 0xff0000);
|
1017
|
-
const hitNormalWP = this._hitNormalInLocalSpace.clone();
|
1018
|
-
hitNormalWP.applyQuaternion(rot);
|
1019
|
-
Gizmos.DrawRay(hitPointWP, hitNormalWP, 0xff0000);
|
1020
|
-
|
1021
|
-
// debug info
|
1022
|
-
Gizmos.DrawLabel(wp.add(new Vector3(0, 0.25, 0)),
|
1023
|
-
`Distance: ${this._totalMovement.length().toFixed(2)}\n
|
1024
|
-
Along Ray: ${this._totalMovementAlongRayDirection.toFixed(2)}\n
|
1025
|
-
Session: ${!!NeedleXRSession.active}\n
|
1026
|
-
Device: ${this._deviceMode}\n
|
1027
|
-
`,
|
1028
|
-
0.03
|
1029
|
-
);
|
1030
|
-
|
1031
|
-
// draw bottom/back snap points
|
1032
|
-
const bottomCenter = this._bottomCenter.clone();
|
1033
|
-
const backCenter = this._backCenter.clone();
|
1034
|
-
const backBottomCenter = this._backBottomCenter.clone();
|
1035
|
-
draggedObject.localToWorld(bottomCenter);
|
1036
|
-
draggedObject.localToWorld(backCenter);
|
1037
|
-
draggedObject.localToWorld(backBottomCenter);
|
1038
|
-
Gizmos.DrawSphere(bottomCenter, 0.01, 0x00ff00, 0, false);
|
1039
|
-
Gizmos.DrawSphere(backCenter, 0.01, 0x0000ff, 0, false);
|
1040
|
-
Gizmos.DrawSphere(backBottomCenter, 0.01, 0xff00ff, 0, false);
|
1041
|
-
Gizmos.DrawLine(bottomCenter, backBottomCenter, 0x00ffff, 0, false);
|
1042
|
-
Gizmos.DrawLine(backBottomCenter, backCenter, 0x00ffff, 0, false);
|
1043
|
-
}
|
1044
|
-
}
|
1045
|
-
|
1046
|
-
onDragEnd(args: PointerEventData) {
|
1047
|
-
console.assert(this._followObject.parent === args.event.space, "Drag end: _followObject is not parented to the space object");
|
1048
|
-
this._followObject.removeFromParent();
|
1049
|
-
this._followObject.destroy();
|
1050
|
-
this._lastDragPosRigSpace = undefined;
|
1051
|
-
}
|
1052
|
-
}
|
1053
|
-
|
1054
|
-
/** Currently does _only_ provide visuals support for DragControls operations.
|
1055
|
-
* Previously it also provided the actual drag functionality, but that has been moved to DragControls for now.
|
1056
|
-
*/
|
1057
|
-
class LegacyDragVisualsHelper {
|
1058
|
-
|
1059
302
|
showGizmo: boolean = true;
|
1060
303
|
useViewAngle: boolean = true;
|
1061
304
|
|
@@ -1093,12 +336,13 @@
|
|
1093
336
|
constructor(camera: Camera) {
|
1094
337
|
this._camera = camera;
|
1095
338
|
|
1096
|
-
const line = new Line(
|
339
|
+
const line = new Line(DragHelper.geometry);
|
1097
340
|
const mat = line.material as LineBasicMaterial;
|
1098
341
|
mat.color = new Color(.4, .4, .4);
|
1099
342
|
line.layers.set(2);
|
1100
343
|
line.name = 'line';
|
1101
344
|
line.scale.y = 1;
|
345
|
+
// line.matrixAutoUpdate = false;
|
1102
346
|
this._groundLine = line;
|
1103
347
|
|
1104
348
|
const geometry = new SphereGeometry(.5, 22, 22);
|
@@ -1113,12 +357,13 @@
|
|
1113
357
|
if (this._selected && context) {
|
1114
358
|
for (const rb of this._rbs) {
|
1115
359
|
rb.wakeUp();
|
360
|
+
// if (!rb.smoothedVelocity) continue;
|
1116
361
|
rb.setVelocity(0, 0, 0);
|
1117
362
|
}
|
1118
363
|
}
|
1119
364
|
|
1120
365
|
if (this._selected) {
|
1121
|
-
|
366
|
+
|
1122
367
|
Avatar_POI.Remove(context, this._selected);
|
1123
368
|
}
|
1124
369
|
|
@@ -1140,8 +385,6 @@
|
|
1140
385
|
console.error("DragHelper: no context");
|
1141
386
|
return;
|
1142
387
|
}
|
1143
|
-
|
1144
|
-
// TODO move somewhere else
|
1145
388
|
Avatar_POI.Add(context, this._selected, null);
|
1146
389
|
|
1147
390
|
this._groundOffsetFactor = 0;
|
@@ -1149,6 +392,7 @@
|
|
1149
392
|
this._groundOffset.set(0, 0, 0);
|
1150
393
|
this._requireUpdateGroundPlane = true;
|
1151
394
|
|
395
|
+
// this._rbs = GameObject.getComponentsInChildren(this._selected, Rigidbody);
|
1152
396
|
this.onUpdateScreenSpacePlane();
|
1153
397
|
}
|
1154
398
|
}
|
@@ -1158,16 +402,6 @@
|
|
1158
402
|
private _didDragOnGroundPlaneLastFrame: boolean = false;
|
1159
403
|
|
1160
404
|
onUpdate(_context: Context) {
|
1161
|
-
|
1162
|
-
if (!this._selected) return;
|
1163
|
-
|
1164
|
-
const wp = getWorldPosition(this._selected);
|
1165
|
-
this.onUpdateWorldPosition(wp, this._groundPlanePoint, false);
|
1166
|
-
this.onUpdateGroundPlane();
|
1167
|
-
this._didDragOnGroundPlaneLastFrame = true;
|
1168
|
-
this._hasGroundPlane = true;
|
1169
|
-
|
1170
|
-
/*
|
1171
405
|
if (!this._context) return;
|
1172
406
|
|
1173
407
|
const mainKey: KeyCode = "Space";
|
@@ -1254,7 +488,6 @@
|
|
1254
488
|
this.onDidUpdate();
|
1255
489
|
}
|
1256
490
|
}
|
1257
|
-
*/
|
1258
491
|
}
|
1259
492
|
|
1260
493
|
private onUpdateWorldPosition(wp: Vector3, pointOnPlane: Vector3 | null, heightOnly: boolean) {
|
@@ -1316,6 +549,18 @@
|
|
1316
549
|
this._groundOffset.copy(this._intersection).sub(wp);
|
1317
550
|
}
|
1318
551
|
|
552
|
+
private onDidUpdate() {
|
553
|
+
// todo: when using instancing we need to mark the matrix to update
|
554
|
+
InstancingUtil.markDirty(this._selected);
|
555
|
+
|
556
|
+
for (const rb of this._rbs) {
|
557
|
+
rb.wakeUp();
|
558
|
+
rb.resetForcesAndTorques();
|
559
|
+
// rb.setBodyFromGameObject({ x: 0, y: 0, z: 0 });
|
560
|
+
rb.setAngularVelocity(0, 0, 0);
|
561
|
+
}
|
562
|
+
}
|
563
|
+
|
1319
564
|
private contains(obj: Object3D, toSearch: Object3D): boolean {
|
1320
565
|
if (obj === toSearch) return true;
|
1321
566
|
if (obj.children) {
|
@@ -1,25 +1,22 @@
|
|
1
1
|
import { Behaviour, GameObject } from "./Component.js";
|
2
|
-
import {
|
2
|
+
import { WebXRController, ControllerEvents } from "./webxr/WebXRController.js";
|
3
|
+
import { DragControls, DragEvents } from "./DragControls.js";
|
4
|
+
import { Interactable } from "./Interactable.js";
|
5
|
+
import { Animation } from "./Animation.js";
|
3
6
|
import { Vector3, Quaternion, Object3D } from "three";
|
4
7
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
5
8
|
import { InstantiateOptions } from "../engine/engine_gameobject.js";
|
6
|
-
import { IPointerEventHandler, PointerEventData } from "./ui/PointerEvents.js";
|
7
|
-
import { ObjectRaycaster } from "./ui/Raycaster.js";
|
8
9
|
|
9
|
-
export class Duplicatable extends
|
10
|
+
export class Duplicatable extends Interactable {
|
10
11
|
|
11
|
-
/** Duplicates will be parented into the set object. If not defined, this GameObject will be used as parent. */
|
12
12
|
@serializable(Object3D)
|
13
13
|
parent: GameObject | null = null;
|
14
|
-
|
15
|
-
/** The object to be duplicated */
|
16
14
|
@serializable(Object3D)
|
17
15
|
object: GameObject | null = null;
|
18
16
|
|
19
17
|
// limit max object spawn count per interval
|
20
18
|
@serializable()
|
21
19
|
limitCount = 10;
|
22
|
-
|
23
20
|
@serializable()
|
24
21
|
limitInterval = 60;
|
25
22
|
|
@@ -27,7 +24,17 @@
|
|
27
24
|
private _startPosition: THREE.Vector3 | null = null;
|
28
25
|
private _startQuaternion: THREE.Quaternion | null = null;
|
29
26
|
|
30
|
-
|
27
|
+
awake(): void {
|
28
|
+
// TODO: add support to not having to assign a object to clone
|
29
|
+
// if(!this.object){
|
30
|
+
// const opts = new InstantiateOptions();
|
31
|
+
// opts.parent = this.gameObject;
|
32
|
+
// opts.idProvider = InstantiateIdProvider.createFromString(this.guid);
|
33
|
+
// const clone = GameObject.instantiate(this.gameObject, opts);
|
34
|
+
// const duplicatable =
|
35
|
+
// this.object = clone;
|
36
|
+
// }
|
37
|
+
// console.log(this, this.object);
|
31
38
|
if (this.object) {
|
32
39
|
if (this.object as any === this.gameObject) {
|
33
40
|
console.error("Can not duplicate self");
|
@@ -41,43 +48,32 @@
|
|
41
48
|
this._startQuaternion = this.object.quaternion?.clone() ?? new Quaternion(0, 0, 0, 1);
|
42
49
|
}
|
43
50
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
51
|
+
const drag = GameObject.getComponentInParent(this.gameObject, DragControls);
|
52
|
+
if (drag) {
|
53
|
+
drag.addDragEventListener(DragEvents.SelectStart, (_ctrls, args) => {
|
54
|
+
if (this._currentCount >= this.limitCount) {
|
55
|
+
args.attached = null;
|
56
|
+
return;
|
57
|
+
}
|
58
|
+
const res = this.handleDuplication(args.selected);
|
59
|
+
if (res) {
|
60
|
+
console.assert(res !== args.selected, "Duplicated object is original");
|
61
|
+
args.attached = res;
|
62
|
+
}
|
63
|
+
});
|
49
64
|
}
|
50
|
-
|
51
|
-
if (!this.gameObject.getComponentInParent(ObjectRaycaster))
|
52
|
-
this.gameObject.addNewComponent(ObjectRaycaster);
|
65
|
+
else console.warn("Could no find drag controls in parent", this.name);
|
53
66
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
onPointerDown(args: PointerEventData) {
|
60
|
-
if (!this.object) return;
|
61
|
-
if (!this.context.connection.allowEditing) return;
|
62
|
-
if (args.button !== 0) return;
|
63
|
-
|
64
|
-
const res = this.handleDuplication();
|
65
|
-
if (res) {
|
66
|
-
const dragControls = GameObject.getComponent(res, DragControls);
|
67
|
-
if (!dragControls) console.warn("Duplicated object does not have DragControls");
|
68
|
-
else {
|
69
|
-
dragControls.onPointerDown(args);
|
70
|
-
this._forwardPointerEvents.set(args.event.space, dragControls);
|
67
|
+
WebXRController.addEventListener(ControllerEvents.SelectStart, (_controller: WebXRController, args: { selected: THREE.Object3D, grab: THREE.Object3D | GameObject | null }) => {
|
68
|
+
if (this._currentCount >= this.limitCount) {
|
69
|
+
args.grab = null;
|
70
|
+
return;
|
71
71
|
}
|
72
|
-
|
73
|
-
|
72
|
+
const res = this.handleDuplication(args.selected);
|
73
|
+
if (res) args.grab = res;
|
74
|
+
});
|
74
75
|
|
75
|
-
|
76
|
-
const dragControls = this._forwardPointerEvents.get(args.event.space);
|
77
|
-
if (dragControls) {
|
78
|
-
dragControls.onPointerUp(args);
|
79
|
-
this._forwardPointerEvents.delete(args.event.space);
|
80
|
-
}
|
76
|
+
this.cloneLimitIntervalFn();
|
81
77
|
}
|
82
78
|
|
83
79
|
private cloneLimitIntervalFn() {
|
@@ -90,39 +86,62 @@
|
|
90
86
|
}, (this.limitInterval / this.limitCount) * 1000);
|
91
87
|
}
|
92
88
|
|
93
|
-
private handleDuplication(): THREE.Object3D | null {
|
89
|
+
private handleDuplication(selected: THREE.Object3D): THREE.Object3D | null {
|
90
|
+
if (this._currentCount >= this.limitCount) return null;
|
94
91
|
if (!this.object) return null;
|
95
|
-
if (this.
|
96
|
-
if (this.object as any === this.gameObject) return null;
|
92
|
+
if (selected === this.gameObject || this.handleMultiObject(selected)) {
|
97
93
|
|
98
|
-
|
94
|
+
if (this.object as any === this.gameObject) return null;
|
95
|
+
this.object.visible = true;
|
99
96
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
97
|
+
if (this._startPosition)
|
98
|
+
this.object.position.copy(this._startPosition);
|
99
|
+
if (this._startQuaternion)
|
100
|
+
this.object.quaternion.copy(this._startQuaternion);
|
104
101
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
102
|
+
const opts = new InstantiateOptions();
|
103
|
+
if (!this.parent) this.parent = this.gameObject.parent as GameObject;
|
104
|
+
if (this.parent) {
|
105
|
+
opts.parent = this.parent.guid ?? this.parent.userData?.guid;
|
106
|
+
opts.keepWorldPosition = true;
|
107
|
+
}
|
108
|
+
opts.position = this.worldPosition;
|
109
|
+
opts.rotation = this.worldQuaternion;
|
110
|
+
opts.context = this.context;
|
111
|
+
this._currentCount += 1;
|
112
|
+
|
113
|
+
const newInstance = GameObject.instantiateSynced(this.object as GameObject, opts) as GameObject;
|
114
|
+
console.assert(newInstance !== this.object, "Duplicated object is original");
|
115
|
+
this.object.visible = false;
|
116
|
+
|
117
|
+
// see if this fixes object being offset when duplicated and dragged - it looks like three clone has shared position/quaternion objects?
|
118
|
+
if (this._startPosition)
|
119
|
+
this.object.position.clone().copy(this._startPosition);
|
120
|
+
if (this._startQuaternion)
|
121
|
+
this.object.quaternion.clone().copy(this._startQuaternion);
|
122
|
+
|
123
|
+
return newInstance;
|
110
124
|
}
|
111
|
-
|
112
|
-
|
113
|
-
opts.context = this.context;
|
114
|
-
this._currentCount += 1;
|
125
|
+
return null;
|
126
|
+
}
|
115
127
|
|
116
|
-
|
117
|
-
|
118
|
-
|
128
|
+
private handleMultiObject(selected: THREE.Object3D): boolean {
|
129
|
+
const shouldSearchInChildren = this.gameObject.type === "Group" || this.gameObject.type === "Object3D";
|
130
|
+
if (!shouldSearchInChildren) return false;
|
131
|
+
return this.isInChildren(this.gameObject, selected);
|
132
|
+
}
|
119
133
|
|
120
|
-
|
121
|
-
if (
|
122
|
-
|
123
|
-
if (
|
124
|
-
|
134
|
+
private isInChildren(current: THREE.Object3D, search: THREE.Object3D): boolean {
|
135
|
+
if (!current) return false;
|
136
|
+
if (current === search) return true;
|
137
|
+
if (current.children) {
|
138
|
+
for (const child of current.children) {
|
139
|
+
if (this.isInChildren(child, search)) {
|
140
|
+
return true;
|
141
|
+
}
|
142
|
+
}
|
143
|
+
}
|
144
|
+
return false;
|
145
|
+
}
|
125
146
|
|
126
|
-
return newInstance;
|
127
|
-
}
|
128
147
|
}
|
@@ -26,7 +26,7 @@
|
|
26
26
|
import { LightDataRegistry, type ILightDataRegistry } from './engine_lightdata.js';
|
27
27
|
import { PlayerViewManager } from './engine_playerview.js';
|
28
28
|
|
29
|
-
import {
|
29
|
+
import { type CoroutineData, type GLTF, type ICamera, type IComponent, type IContext, type ILight, type LoadedGLTF } from "./engine_types.js";
|
30
30
|
import { destroy, foreachComponent } from './engine_gameobject.js';
|
31
31
|
import { ContextEvent, ContextRegistry } from './engine_context_registry.js';
|
32
32
|
import { delay, getParam } from './engine_utils.js';
|
@@ -36,7 +36,6 @@
|
|
36
36
|
import { isLocalNetwork } from './engine_networking_utils.js';
|
37
37
|
import { WaitForPromise } from './engine_coroutine.js';
|
38
38
|
import { invokeLifecycleFunctions } from './engine_lifecycle_functions_internal.js';
|
39
|
-
import type { INeedleXRSessionEventReceiver } from './engine_xr.js';
|
40
39
|
|
41
40
|
|
42
41
|
const debug = utils.getParam("debugcontext");
|
@@ -102,6 +101,11 @@
|
|
102
101
|
Undefined = -1,
|
103
102
|
}
|
104
103
|
|
104
|
+
export enum XRSessionMode {
|
105
|
+
ImmersiveVR = "immersive-vr",
|
106
|
+
ImmersiveAR = "immersive-ar",
|
107
|
+
}
|
108
|
+
|
105
109
|
/** threejs callback event signature */
|
106
110
|
export declare type OnRenderCallback = (renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: Group) => void
|
107
111
|
|
@@ -209,7 +213,6 @@
|
|
209
213
|
private _boundingClientRectFrame: number = -1;
|
210
214
|
private _boundingClientRect: DOMRect | null = null;
|
211
215
|
private _domX; private _domY;
|
212
|
-
/** update bounding rects + domX, domY */
|
213
216
|
private calculateBoundingClientRect() {
|
214
217
|
// workaround for mozilla webXR viewer
|
215
218
|
if (this.isInAR) {
|
@@ -224,44 +227,30 @@
|
|
224
227
|
this._domY = this._boundingClientRect.y;
|
225
228
|
}
|
226
229
|
|
227
|
-
/** The width of the `<needle-engine>` element on the website */
|
228
230
|
get domWidth(): number {
|
229
231
|
// for mozilla XR
|
230
232
|
if (this.isInAR) return window.innerWidth;
|
231
233
|
return this.domElement.clientWidth;
|
232
234
|
}
|
233
|
-
/** The height of the `<needle-engine>` element on the website */
|
234
235
|
get domHeight(): number {
|
235
236
|
// for mozilla XR
|
236
237
|
if (this.isInAR) return window.innerHeight;
|
237
238
|
return this.domElement.clientHeight;
|
238
239
|
}
|
239
|
-
/** the X position of the Needle Engine element on the website */
|
240
240
|
get domX(): number {
|
241
241
|
this.calculateBoundingClientRect();
|
242
242
|
return this._domX;
|
243
243
|
}
|
244
|
-
/** the Y position of the Needlee Engine element on the website */
|
245
244
|
get domY(): number {
|
246
245
|
this.calculateBoundingClientRect();
|
247
246
|
return this._domY;
|
248
247
|
}
|
249
248
|
get isInXR() { return this.renderer?.xr?.isPresenting || false; }
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
get xrSessionMode() { return this.xr?.mode; }
|
254
|
-
get isInVR() { return this.xrSessionMode === "immersive-vr"; }
|
255
|
-
get isInAR() { return this.xrSessionMode === "immersive-ar"; }
|
256
|
-
/** If a XR session is active and in pass through mode (immersive-ar on e.g. Quest) */
|
257
|
-
get isInPassThrough() { return this.xr ? this.xr.isPassThrough : false; }
|
258
|
-
/** access the raw `XRSession` object (shorthand for `context.renderer.xr.getSession()`). For more control use `NeedleXRSession.active` */
|
249
|
+
xrSessionMode: XRSessionMode | undefined = undefined;
|
250
|
+
get isInVR() { return this.xrSessionMode === XRSessionMode.ImmersiveVR; }
|
251
|
+
get isInAR() { return this.xrSessionMode === XRSessionMode.ImmersiveAR; }
|
259
252
|
get xrSession() { return this.renderer?.xr?.getSession(); }
|
260
|
-
/** @returns the latest XRFrame (if a XRSession is currently active)
|
261
|
-
* @link https://developer.mozilla.org/en-US/docs/Web/API/XRFrame
|
262
|
-
*/
|
263
253
|
get xrFrame() { return this._xrFrame }
|
264
|
-
/** @returns the current WebXR camera (shorthand for `context.renderer.xr.getCamera()`) */
|
265
254
|
get xrCamera(): WebXRArrayCamera | undefined { return this.renderer?.xr?.getCamera(); }
|
266
255
|
private _xrFrame: XRFrame | null = null;
|
267
256
|
get arOverlayElement(): HTMLElement {
|
@@ -281,37 +270,17 @@
|
|
281
270
|
composer: EffectComposer | null = null;
|
282
271
|
|
283
272
|
// all scripts
|
284
|
-
|
285
|
-
|
273
|
+
scripts: IComponent[] = [];
|
274
|
+
scripts_pausedChanged: IComponent[] = [];
|
286
275
|
// scripts with update event
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
readonly scripts_immersive_ar: INeedleXRSessionEventReceiver[] = [];
|
295
|
-
readonly coroutines: { [FrameEvent: number]: Array<CoroutineData> } = {}
|
276
|
+
scripts_earlyUpdate: IComponent[] = [];
|
277
|
+
scripts_update: IComponent[] = [];
|
278
|
+
scripts_lateUpdate: IComponent[] = [];
|
279
|
+
scripts_onBeforeRender: IComponent[] = [];
|
280
|
+
scripts_onAfterRender: IComponent[] = [];
|
281
|
+
scripts_WithCorroutines: IComponent[] = [];
|
282
|
+
coroutines: { [FrameEvent: number]: Array<CoroutineData> } = {}
|
296
283
|
|
297
|
-
/** callbacks called once after the context has been created */
|
298
|
-
readonly post_setup_callbacks: Function[] = [];
|
299
|
-
/** called every frame at the beginning of the frame (after component start events and before earlyUpdate) */
|
300
|
-
readonly pre_update_callbacks: Function[] = [];
|
301
|
-
/** called every frame before rendering (after all component events) */
|
302
|
-
readonly pre_render_callbacks: Array<(frame: XRFrame | null) => void> = [];
|
303
|
-
/** called every frame after rendering (after all component events) */
|
304
|
-
readonly post_render_callbacks: Function[] = [];
|
305
|
-
|
306
|
-
/** called every frame befroe update (this list is emptied every frame) */
|
307
|
-
readonly pre_update_oneshot_callbacks: Function[] = [];
|
308
|
-
|
309
|
-
readonly new_scripts: IComponent[] = [];
|
310
|
-
readonly new_script_start: IComponent[] = [];
|
311
|
-
readonly new_scripts_pre_setup_callbacks: Function[] = [];
|
312
|
-
readonly new_scripts_post_setup_callbacks: Function[] = [];
|
313
|
-
readonly new_scripts_xr: INeedleXRSessionEventReceiver[] = [];
|
314
|
-
|
315
284
|
mainCameraComponent: ICamera | undefined;
|
316
285
|
|
317
286
|
private _camera: Camera | null = null;
|
@@ -331,13 +300,20 @@
|
|
331
300
|
this._camera = cam;
|
332
301
|
}
|
333
302
|
|
303
|
+
post_setup_callbacks: Function[] = [];
|
304
|
+
pre_update_callbacks: Function[] = [];
|
305
|
+
pre_render_callbacks: Function[] = [];
|
306
|
+
post_render_callbacks: Function[] = [];
|
307
|
+
|
308
|
+
new_scripts: IComponent[] = [];
|
309
|
+
new_script_start: IComponent[] = [];
|
310
|
+
new_scripts_pre_setup_callbacks: Function[] = [];
|
311
|
+
new_scripts_post_setup_callbacks: Function[] = [];
|
312
|
+
|
334
313
|
application: Application;
|
335
|
-
/** access timings (current frame number, deltaTime, timeScale, ...) */
|
336
314
|
time: Time;
|
337
315
|
input: Input;
|
338
|
-
/** access physics related methods (e.g. raycasting). To access the phyiscs engine use `context.physics.engine` */
|
339
316
|
physics: Physics;
|
340
|
-
/** access networking methods (use it to send or listen to messages or join a networking backend) */
|
341
317
|
connection: NetworkConnection;
|
342
318
|
/**
|
343
319
|
* @deprecated AssetDataBase is deprecated
|
@@ -417,7 +393,7 @@
|
|
417
393
|
}
|
418
394
|
}
|
419
395
|
}
|
420
|
-
if
|
396
|
+
if(debug) console.log("Using Renderer Parameters:", params, this.domElement)
|
421
397
|
|
422
398
|
this.renderer = new WebGLRenderer(params);
|
423
399
|
|
@@ -436,8 +412,6 @@
|
|
436
412
|
this.renderer.outputColorSpace = SRGBColorSpace;
|
437
413
|
// https://github.com/mrdoob/three.js/pull/25556
|
438
414
|
this.renderer.useLegacyLights = false;
|
439
|
-
|
440
|
-
this.input.bindEvents();
|
441
415
|
}
|
442
416
|
|
443
417
|
|
@@ -449,13 +423,10 @@
|
|
449
423
|
|
450
424
|
private _disposeCallbacks: Function[] = [];
|
451
425
|
|
426
|
+
// private _requestSizeUpdate : boolean = false;
|
452
427
|
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
/** update the renderer and canvas size */
|
457
|
-
updateSize(force: boolean = false) {
|
458
|
-
if (force || (!this.isManagedExternally && this.renderer.xr?.isPresenting === false)) {
|
428
|
+
updateSize() {
|
429
|
+
if (!this.isManagedExternally && this.renderer.xr?.isPresenting === false) {
|
459
430
|
this._sizeChanged = false;
|
460
431
|
const scaleFactor = this.resolutionScaleFactor;
|
461
432
|
const width = this.domWidth * scaleFactor;
|
@@ -506,7 +477,7 @@
|
|
506
477
|
async create(opts?: ContextCreateArgs) {
|
507
478
|
try {
|
508
479
|
this._isCreating = true;
|
509
|
-
if
|
480
|
+
if(opts !== this._originalCreationArgs)
|
510
481
|
this._originalCreationArgs = utils.deepClone(opts);
|
511
482
|
window.addEventListener("unhandledrejection", this.onUnhandledRejection)
|
512
483
|
const res = await this.internalOnCreate(opts);
|
@@ -559,11 +530,11 @@
|
|
559
530
|
if (this.renderer) {
|
560
531
|
this.renderer.setClearAlpha(0);
|
561
532
|
this.renderer.clear();
|
562
|
-
if (!this.isManagedExternally) {
|
563
|
-
if (debug) console.log("Disposing renderer");
|
564
|
-
this.renderer.dispose();
|
565
|
-
}
|
566
533
|
}
|
534
|
+
if (!this.isManagedExternally) {
|
535
|
+
if(debug) console.log("Disposing renderer");
|
536
|
+
this.renderer.dispose();
|
537
|
+
}
|
567
538
|
this.scene = null!;
|
568
539
|
this.renderer = null!;
|
569
540
|
this.input.dispose();
|
@@ -581,10 +552,6 @@
|
|
581
552
|
this._isCreated = false;
|
582
553
|
ContextRegistry.dispatchCallback(ContextEvent.ContextDestroyed, this);
|
583
554
|
ContextRegistry.unregister(this);
|
584
|
-
if (Context.Current === this) {
|
585
|
-
//@ts-ignore
|
586
|
-
Context.Current = null;
|
587
|
-
}
|
588
555
|
}
|
589
556
|
|
590
557
|
registerCoroutineUpdate(script: IComponent, coroutine: Generator, evt: FrameEvent): Generator {
|
@@ -736,7 +703,7 @@
|
|
736
703
|
private async internalOnCreate(opts?: ContextCreateArgs) {
|
737
704
|
const createId = ++this._createId;
|
738
705
|
|
739
|
-
if
|
706
|
+
if(debug) console.log("Creating context", this.name, opts);
|
740
707
|
|
741
708
|
this.clear();
|
742
709
|
// stop the animation loop if its running during creation
|
@@ -843,8 +810,6 @@
|
|
843
810
|
}
|
844
811
|
}
|
845
812
|
|
846
|
-
this.input.bindEvents();
|
847
|
-
|
848
813
|
Context.Current = this;
|
849
814
|
looputils.processNewScripts(this);
|
850
815
|
|
@@ -887,7 +852,7 @@
|
|
887
852
|
this._dispatchReadyAfterFrame = true;
|
888
853
|
const res = ContextRegistry.dispatchCallback(ContextEvent.ContextCreated, this, { files: loadedFiles });
|
889
854
|
if (res) {
|
890
|
-
if
|
855
|
+
if("internalSetLoadingMessage" in this.domElement && typeof this.domElement.internalSetLoadingMessage === "function")
|
891
856
|
this.domElement?.internalSetLoadingMessage("finish loading");
|
892
857
|
await res;
|
893
858
|
}
|
@@ -931,7 +896,7 @@
|
|
931
896
|
}
|
932
897
|
|
933
898
|
args?.onLoadingStart?.call(this, i, file);
|
934
|
-
if
|
899
|
+
if(debug) console.log("Context Load " + file);
|
935
900
|
const res = await loader.loadSync(this, file, file, loadingHash, prog => {
|
936
901
|
progressArg.name = file;
|
937
902
|
progressArg.progress = prog;
|
@@ -1007,7 +972,7 @@
|
|
1007
972
|
catch (err) {
|
1008
973
|
this._renderlooperrors += 1;
|
1009
974
|
if ((isDevEnvironment() || debug) && (err instanceof Error || err instanceof TypeError))
|
1010
|
-
showBalloonMessage(
|
975
|
+
showBalloonMessage("Caught unhandled exception during render-loop.<br/>Stopping renderloop...<br/>See console for details.", LogType.Error);
|
1011
976
|
console.error(err);
|
1012
977
|
if (this._renderlooperrors > 10) {
|
1013
978
|
console.warn("Stopping render loop due to error")
|
@@ -1042,11 +1007,7 @@
|
|
1042
1007
|
|
1043
1008
|
private internalOnBeforeRender(timestamp: DOMHighResTimeStamp, frame: XRFrame | null) {
|
1044
1009
|
|
1045
|
-
const sessionStarted = frame !== null && this._xrFrame === null;
|
1046
1010
|
this._xrFrame = frame;
|
1047
|
-
if (sessionStarted) {
|
1048
|
-
this.domElement.dispatchEvent(new CustomEvent("xr-session-started", { detail: { context: this, session: this.xrSession, frame: frame } }));
|
1049
|
-
}
|
1050
1011
|
|
1051
1012
|
this._currentFrameEvent = FrameEvent.Undefined;
|
1052
1013
|
|
@@ -1085,13 +1046,6 @@
|
|
1085
1046
|
this.setCurrentCamera(last);
|
1086
1047
|
}
|
1087
1048
|
|
1088
|
-
if (this.pre_update_oneshot_callbacks) {
|
1089
|
-
for (const i in this.pre_update_oneshot_callbacks) {
|
1090
|
-
this.pre_update_oneshot_callbacks[i]();
|
1091
|
-
}
|
1092
|
-
this.pre_update_oneshot_callbacks.length = 0;
|
1093
|
-
}
|
1094
|
-
|
1095
1049
|
if (this.pre_update_callbacks) {
|
1096
1050
|
for (const i in this.pre_update_callbacks) {
|
1097
1051
|
this.pre_update_callbacks[i]();
|
@@ -1174,7 +1128,7 @@
|
|
1174
1128
|
|
1175
1129
|
if (this.pre_render_callbacks) {
|
1176
1130
|
for (const i in this.pre_render_callbacks) {
|
1177
|
-
this.pre_render_callbacks[i](
|
1131
|
+
this.pre_render_callbacks[i]();
|
1178
1132
|
}
|
1179
1133
|
}
|
1180
1134
|
|
@@ -1252,8 +1206,8 @@
|
|
1252
1206
|
}
|
1253
1207
|
this._isRendering = true;
|
1254
1208
|
this.renderRequiredTextures();
|
1209
|
+
|
1255
1210
|
|
1256
|
-
|
1257
1211
|
if (this.composer && !this.isInXR) {
|
1258
1212
|
this.composer.render(this.time.deltaTime);
|
1259
1213
|
}
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import { PlaneGeometry, MeshBasicMaterial, DoubleSide, Mesh, Material, MeshStandardMaterial, BoxGeometry |