@needle-tools/engine 3.19.2 → 3.19.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/needle-engine.js +8937 -8894
- package/dist/needle-engine.light.js +8639 -8596
- package/dist/needle-engine.light.min.js +183 -183
- package/dist/needle-engine.light.umd.cjs +188 -188
- package/dist/needle-engine.min.js +184 -184
- package/dist/needle-engine.umd.cjs +189 -189
- package/lib/engine/extensions/NEEDLE_progressive.js +14 -12
- package/lib/engine/extensions/NEEDLE_progressive.js.map +1 -1
- package/lib/engine-components/Animation.js +6 -6
- package/lib/engine-components/Animation.js.map +1 -1
- package/lib/engine-components/Collider.d.ts +2 -0
- package/lib/engine-components/Collider.js +4 -0
- package/lib/engine-components/Collider.js.map +1 -1
- package/lib/engine-components/Interactable.js +0 -1
- package/lib/engine-components/Interactable.js.map +1 -1
- package/lib/engine-components/OrbitControls.d.ts +11 -1
- package/lib/engine-components/OrbitControls.js +53 -3
- package/lib/engine-components/OrbitControls.js.map +1 -1
- package/lib/engine-components/Renderer.js +4 -1
- package/lib/engine-components/Renderer.js.map +1 -1
- package/lib/engine-components/VideoPlayer.d.ts +1 -2
- package/lib/engine-components/VideoPlayer.js +9 -7
- package/lib/engine-components/VideoPlayer.js.map +1 -1
- package/lib/engine-components/timeline/PlayableDirector.js +50 -36
- package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
- package/lib/engine-components/timeline/TimelineModels.d.ts +2 -2
- package/lib/engine-components/webxr/WebARSessionRoot.js +2 -2
- package/lib/engine-components/webxr/WebARSessionRoot.js.map +1 -1
- package/lib/engine-components/webxr/WebXRController.d.ts +0 -1
- package/lib/engine-components/webxr/WebXRController.js +0 -14
- package/lib/engine-components/webxr/WebXRController.js.map +1 -1
- package/package.json +1 -1
- package/src/engine/extensions/NEEDLE_progressive.ts +14 -12
- package/src/engine-components/Animation.ts +5 -4
- package/src/engine-components/Collider.ts +5 -0
- package/src/engine-components/Interactable.ts +1 -10
- package/src/engine-components/OrbitControls.ts +29 -3
- package/src/engine-components/Renderer.ts +4 -1
- package/src/engine-components/VideoPlayer.ts +7 -5
- package/src/engine-components/timeline/PlayableDirector.ts +48 -34
- package/src/engine-components/timeline/TimelineModels.ts +2 -2
- package/src/engine-components/webxr/WebARSessionRoot.ts +2 -2
- package/src/engine-components/webxr/WebXRController.ts +1141 -1156
|
@@ -1,1156 +1,1141 @@
|
|
|
1
|
-
import { BoxHelper, BufferGeometry, Color, Euler, Group, Intersection, Layers, Line, LineBasicMaterial, Material, Mesh, MeshBasicMaterial, Object3D, PerspectiveCamera, Quaternion, Ray, SphereGeometry, Vector2, Vector3 } from "three";
|
|
2
|
-
import { OculusHandModel } from 'three/examples/jsm/webxr/OculusHandModel.js';
|
|
3
|
-
import { OculusHandPointerModel } from 'three/examples/jsm/webxr/OculusHandPointerModel.js';
|
|
4
|
-
import { XRControllerModel, XRControllerModelFactory } from 'three/examples/jsm/webxr/XRControllerModelFactory.js';
|
|
5
|
-
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
|
6
|
-
|
|
7
|
-
import { InstancingUtil } from "../../engine/engine_instancing.js";
|
|
8
|
-
import { Mathf } from "../../engine/engine_math.js";
|
|
9
|
-
import { RaycastOptions } from "../../engine/engine_physics.js";
|
|
10
|
-
import { getWorldPosition, getWorldQuaternion, setWorldPosition, setWorldQuaternion } from "../../engine/engine_three_utils.js";
|
|
11
|
-
import { getParam, resolveUrl } from "../../engine/engine_utils.js";
|
|
12
|
-
import { addDracoAndKTX2Loaders } from "../../engine/engine_loaders.js";
|
|
13
|
-
|
|
14
|
-
import { Avatar_POI } from "../avatar/Avatar_Brain_LookAt.js";
|
|
15
|
-
import { Behaviour, GameObject } from "../Component.js";
|
|
16
|
-
import { Interactable, UsageMarker } from "../Interactable.js";
|
|
17
|
-
import { Rigidbody } from "../RigidBody.js";
|
|
18
|
-
import { SyncedTransform } from "../SyncedTransform.js";
|
|
19
|
-
import { UIRaycastUtils } from "../ui/RaycastUtils.js";
|
|
20
|
-
import { WebXR } from "./WebXR.js";
|
|
21
|
-
import { XRRig } from "./WebXRRig.js";
|
|
22
|
-
|
|
23
|
-
const debug = getParam("debugwebxrcontroller");
|
|
24
|
-
|
|
25
|
-
export enum ControllerType {
|
|
26
|
-
PhysicalDevice = 0,
|
|
27
|
-
Touch = 1,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export enum ControllerEvents {
|
|
31
|
-
SelectStart = "select-start",
|
|
32
|
-
SelectEnd = "select-end",
|
|
33
|
-
Update = "update",
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export class TeleportTarget extends Behaviour {
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export class WebXRController extends Behaviour {
|
|
41
|
-
|
|
42
|
-
public static Factory: XRControllerModelFactory = new XRControllerModelFactory();
|
|
43
|
-
|
|
44
|
-
private static raycastColor: Color = new Color(.9, .3, .3);
|
|
45
|
-
private static raycastNoHitColor: Color = new Color(.6, .6, .6);
|
|
46
|
-
private static geometry = new BufferGeometry().setFromPoints([new Vector3(0, 0, 0), new Vector3(0, 0, -1)]);
|
|
47
|
-
private static handModels: { [index: number]: OculusHandPointerModel } = {};
|
|
48
|
-
|
|
49
|
-
private static CreateRaycastLine(): Line {
|
|
50
|
-
const line = new Line(this.geometry);
|
|
51
|
-
const mat = line.material as LineBasicMaterial;
|
|
52
|
-
mat.color = this.raycastColor;
|
|
53
|
-
// mat.linewidth = 10;
|
|
54
|
-
line.layers.set(2);
|
|
55
|
-
line.name = 'line';
|
|
56
|
-
line.scale.z = 1;
|
|
57
|
-
return line;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
private static CreateRaycastHitPoint(): Mesh {
|
|
61
|
-
const geometry = new SphereGeometry(.5, 22, 22);
|
|
62
|
-
const material = new MeshBasicMaterial({ color: this.raycastColor });
|
|
63
|
-
const sphere = new Mesh(geometry, material);
|
|
64
|
-
sphere.visible = false;
|
|
65
|
-
sphere.layers.set(2);
|
|
66
|
-
return sphere;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
public static Create(owner: WebXR, index: number, addTo: GameObject, type: ControllerType): WebXRController {
|
|
70
|
-
const ctrl = addTo ? GameObject.addNewComponent(addTo, WebXRController, false) : new WebXRController();
|
|
71
|
-
|
|
72
|
-
ctrl.webXR = owner;
|
|
73
|
-
ctrl.index = index;
|
|
74
|
-
ctrl.type = type;
|
|
75
|
-
|
|
76
|
-
const context = owner.context;
|
|
77
|
-
// from https://github.com/mrdoob/js/blob/master/examples/webxr_vr_dragging.html
|
|
78
|
-
// controllers
|
|
79
|
-
ctrl.controller = context.renderer.xr.getController(index);
|
|
80
|
-
ctrl.controllerGrip = context.renderer.xr.getControllerGrip(index);
|
|
81
|
-
ctrl.controllerModel = this.Factory.createControllerModel(ctrl.controller);
|
|
82
|
-
ctrl.controllerGrip.add(ctrl.controllerModel);
|
|
83
|
-
|
|
84
|
-
ctrl.hand = context.renderer.xr.getHand(index);
|
|
85
|
-
|
|
86
|
-
const loader = new GLTFLoader();
|
|
87
|
-
addDracoAndKTX2Loaders(loader, context);
|
|
88
|
-
if (ctrl.webXR.handModelPath && ctrl.webXR.handModelPath !== "")
|
|
89
|
-
loader.setPath(resolveUrl(owner.sourceId, ctrl.webXR.handModelPath));
|
|
90
|
-
else
|
|
91
|
-
// from XRHandMeshModel.js
|
|
92
|
-
loader.setPath('https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@1.0/dist/profiles/generic-hand/');
|
|
93
|
-
//@ts-ignore
|
|
94
|
-
const hand = new OculusHandModel(ctrl.hand, loader);
|
|
95
|
-
|
|
96
|
-
ctrl.hand.add(hand);
|
|
97
|
-
ctrl.hand.traverse(x => x.layers.set(2));
|
|
98
|
-
|
|
99
|
-
ctrl.handPointerModel = new OculusHandPointerModel(ctrl.hand, ctrl.controller);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
// TODO remove all these once https://github.com/mrdoob/js/pull/23279 lands
|
|
103
|
-
ctrl.controller.addEventListener('connected', (_) => {
|
|
104
|
-
ctrl.setControllerLayers(ctrl.controllerModel, 2);
|
|
105
|
-
ctrl.setControllerLayers(ctrl.controllerGrip, 2);
|
|
106
|
-
ctrl.setControllerLayers(ctrl.hand, 2);
|
|
107
|
-
setTimeout(() => {
|
|
108
|
-
ctrl.setControllerLayers(ctrl.controllerModel, 2);
|
|
109
|
-
ctrl.setControllerLayers(ctrl.controllerGrip, 2);
|
|
110
|
-
ctrl.setControllerLayers(ctrl.hand, 2);
|
|
111
|
-
}, 1000);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
// TODO: unsubscribe! this should be moved into onenable and ondisable!
|
|
115
|
-
// TODO remove all these once https://github.com/mrdoob/js/pull/23279 lands
|
|
116
|
-
ctrl.hand.addEventListener('connected', (event) => {
|
|
117
|
-
const xrInputSource = event.data;
|
|
118
|
-
if (xrInputSource.hand) {
|
|
119
|
-
if (owner.Rig) owner.Rig.add(ctrl.hand);
|
|
120
|
-
ctrl.type = ControllerType.PhysicalDevice;
|
|
121
|
-
ctrl.handPointerModel.traverse(x => x.layers.set(2)); // ignore raycast
|
|
122
|
-
ctrl.handPointerModel.pointerObject?.traverse(x => x.layers.set(2));
|
|
123
|
-
|
|
124
|
-
// when exiting and re-entering xr the joints are not parented to the hand anymore
|
|
125
|
-
// this is a workaround to fix that temporarely
|
|
126
|
-
// see https://github.com/needle-tools/needle-tiny-playground/issues/123
|
|
127
|
-
const jnts = ctrl.hand["joints"];
|
|
128
|
-
if (jnts) {
|
|
129
|
-
for (const key of Object.keys(jnts)) {
|
|
130
|
-
const joint = jnts[key];
|
|
131
|
-
if (joint.parent) continue;
|
|
132
|
-
ctrl.hand.add(joint);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
return ctrl;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// TODO: replace with component events
|
|
142
|
-
public static addEventListener(evt: ControllerEvents, callback: (controller: WebXRController, args: any) => void) {
|
|
143
|
-
const list = this.eventSubs[evt] ?? [];
|
|
144
|
-
list.push(callback);
|
|
145
|
-
this.eventSubs[evt] = list;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// TODO: replace with component events
|
|
149
|
-
public static removeEventListener(evt: ControllerEvents, callback: (controller: WebXRController, args: any) => void) {
|
|
150
|
-
if (!callback) return;
|
|
151
|
-
const list = this.eventSubs[evt] ?? [];
|
|
152
|
-
const idx = list.indexOf(callback);
|
|
153
|
-
if (idx >= 0) list.splice(idx, 1);
|
|
154
|
-
this.eventSubs[evt] = list;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
private static eventSubs: { [key: string]: Function[] } = {};
|
|
158
|
-
|
|
159
|
-
public webXR?: WebXR;
|
|
160
|
-
public index: number = -1;
|
|
161
|
-
public controllerModel!: XRControllerModel;
|
|
162
|
-
public controller!: Group;
|
|
163
|
-
public controllerGrip!: Group;
|
|
164
|
-
public hand!: Group;
|
|
165
|
-
public handPointerModel!: OculusHandPointerModel;
|
|
166
|
-
public grabbed: AttachedObject | null = null;
|
|
167
|
-
public input: XRInputSource | null = null;
|
|
168
|
-
public type: ControllerType = ControllerType.PhysicalDevice;
|
|
169
|
-
public showRaycastLine: boolean = true;
|
|
170
|
-
|
|
171
|
-
get isUsingHands(): boolean {
|
|
172
|
-
const r = this.input?.hand;
|
|
173
|
-
return r !== null && r !== undefined;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
get wrist(): Object3D | null {
|
|
177
|
-
if (!this.hand) return null;
|
|
178
|
-
const jnts = this.hand["joints"];
|
|
179
|
-
if (!jnts) return null;
|
|
180
|
-
return jnts["wrist"];
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
private _wristQuaternion: Quaternion | null = null;
|
|
184
|
-
getWristQuaternion(): Quaternion | null {
|
|
185
|
-
const wrist = this.wrist;
|
|
186
|
-
if (!wrist) return null;
|
|
187
|
-
if (!this._wristQuaternion) this._wristQuaternion = new Quaternion();
|
|
188
|
-
const wr = getWorldQuaternion(wrist).multiply(this._wristQuaternion.setFromEuler(new Euler(-Math.PI / 4, 0, 0)));
|
|
189
|
-
return wr;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
private movementVector: Vector3 = new Vector3();
|
|
193
|
-
private worldRot: Quaternion = new Quaternion();
|
|
194
|
-
private joystick: Vector2 = new Vector2();
|
|
195
|
-
private didRotate: boolean = false;
|
|
196
|
-
private didTeleport: boolean = false;
|
|
197
|
-
private didChangeScale: boolean = false;
|
|
198
|
-
private static PreviousCameraFarDistance: number | undefined = undefined;
|
|
199
|
-
private static MovementSpeedFactor: number = 1;
|
|
200
|
-
|
|
201
|
-
private lastHit: Intersection | null = null;
|
|
202
|
-
|
|
203
|
-
private raycastLine: Line | null = null;
|
|
204
|
-
private _raycastHitPoint: Object3D | null = null;
|
|
205
|
-
private _connnectedCallback: any | null = null;
|
|
206
|
-
private _disconnectedCallback: any | null = null;
|
|
207
|
-
private _selectStartEvt: any | null = null;
|
|
208
|
-
private _selectEndEvt: any | null = null;
|
|
209
|
-
|
|
210
|
-
public get selectionDown(): boolean { return this._selectionPressed && !this._selectionPressedLastFrame; }
|
|
211
|
-
public get selectionUp(): boolean { return !this._selectionPressed && this._selectionPressedLastFrame; }
|
|
212
|
-
public get selectionPressed(): boolean { return this._selectionPressed; }
|
|
213
|
-
public get selectionClick(): boolean { return this._selectionEndTime - this._selectionStartTime < 0.3; }
|
|
214
|
-
public get raycastHitPoint(): Object3D | null { return this._raycastHitPoint; }
|
|
215
|
-
|
|
216
|
-
private _selectionPressed: boolean = false;
|
|
217
|
-
private _selectionPressedLastFrame: boolean = false;
|
|
218
|
-
private _selectionStartTime: number = 0;
|
|
219
|
-
private _selectionEndTime: number = 0;
|
|
220
|
-
|
|
221
|
-
public get useSmoothing(): boolean { return this._useSmoothing };
|
|
222
|
-
private _useSmoothing: boolean = true;
|
|
223
|
-
|
|
224
|
-
awake(): void {
|
|
225
|
-
if (!this.controller) {
|
|
226
|
-
console.warn("WebXRController: Missing controller object.", this);
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
this._connnectedCallback = this.onSourceConnected.bind(this);
|
|
230
|
-
this._disconnectedCallback = this.onSourceDisconnected.bind(this);
|
|
231
|
-
this._selectStartEvt = this.onSelectStart.bind(this);
|
|
232
|
-
this._selectEndEvt = this.onSelectEnd.bind(this);
|
|
233
|
-
if (this.type === ControllerType.Touch) {
|
|
234
|
-
this.controllerGrip.addEventListener("connected", this._connnectedCallback);
|
|
235
|
-
this.controllerGrip.addEventListener("disconnected", this._disconnectedCallback);
|
|
236
|
-
this.controller.addEventListener('selectstart', this._selectStartEvt);
|
|
237
|
-
this.controller.addEventListener('selectend', this._selectEndEvt);
|
|
238
|
-
}
|
|
239
|
-
if (this.type === ControllerType.PhysicalDevice) {
|
|
240
|
-
this.controller.addEventListener('selectstart', this._selectStartEvt);
|
|
241
|
-
this.controller.addEventListener('selectend', this._selectEndEvt);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
onDestroy(): void {
|
|
246
|
-
if (this.type === ControllerType.Touch) {
|
|
247
|
-
this.controllerGrip.removeEventListener("connected", this._connnectedCallback);
|
|
248
|
-
this.controllerGrip.removeEventListener("disconnected", this._disconnectedCallback);
|
|
249
|
-
this.controller.removeEventListener('selectstart', this._selectStartEvt);
|
|
250
|
-
this.controller.removeEventListener('selectend', this._selectEndEvt);
|
|
251
|
-
}
|
|
252
|
-
if (this.type === ControllerType.PhysicalDevice) {
|
|
253
|
-
this.controller.removeEventListener('selectstart', this._selectStartEvt);
|
|
254
|
-
this.controller.removeEventListener('selectend', this._selectEndEvt);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
this.hand?.clear();
|
|
258
|
-
this.controllerGrip?.clear();
|
|
259
|
-
this.controller?.clear();
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
public onEnable(): void {
|
|
263
|
-
if (!this.webXR) {
|
|
264
|
-
console.warn("No WebXR component assigned to WebXRController.");
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (this.hand)
|
|
269
|
-
this.hand.name = "Hand";
|
|
270
|
-
if (this.controllerGrip)
|
|
271
|
-
this.controllerGrip.name = "ControllerGrip";
|
|
272
|
-
if (this.controller)
|
|
273
|
-
this.controller.name = "Controller";
|
|
274
|
-
if (this.raycastLine)
|
|
275
|
-
this.raycastLine.name = "RaycastLine;"
|
|
276
|
-
|
|
277
|
-
if (this.webXR.Controllers.indexOf(this) < 0)
|
|
278
|
-
this.webXR.Controllers.push(this);
|
|
279
|
-
|
|
280
|
-
if (!this.raycastLine)
|
|
281
|
-
this.raycastLine = WebXRController.CreateRaycastLine();
|
|
282
|
-
if (!this._raycastHitPoint)
|
|
283
|
-
this._raycastHitPoint = WebXRController.CreateRaycastHitPoint();
|
|
284
|
-
|
|
285
|
-
this.webXR.Rig?.add(this.hand);
|
|
286
|
-
this.webXR.Rig?.add(this.controllerGrip);
|
|
287
|
-
this.webXR.Rig?.add(this.controller);
|
|
288
|
-
this.webXR.Rig?.add(this.raycastLine);
|
|
289
|
-
this.raycastLine?.add(this._raycastHitPoint);
|
|
290
|
-
this._raycastHitPoint.visible = false;
|
|
291
|
-
this.hand.add(this.handPointerModel);
|
|
292
|
-
if (debug)
|
|
293
|
-
console.log("ADDED TO RIG", this.webXR.Rig);
|
|
294
|
-
|
|
295
|
-
// // console.log("enable", this.index, this.controllerGrip.uuid)
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
onDisable(): void {
|
|
299
|
-
// console.log("XR controller disabled", this);
|
|
300
|
-
this.hand?.removeFromParent();
|
|
301
|
-
this.controllerGrip?.removeFromParent();
|
|
302
|
-
this.controller?.removeFromParent();
|
|
303
|
-
this.raycastLine?.removeFromParent();
|
|
304
|
-
this._raycastHitPoint?.removeFromParent();
|
|
305
|
-
// console.log("Disable", this._connnectedCallback, this._disconnectedCallback);
|
|
306
|
-
// this.controllerGrip.removeEventListener("connected", this._connnectedCallback);
|
|
307
|
-
// this.controllerGrip.removeEventListener("disconnected", this._disconnectedCallback);
|
|
308
|
-
|
|
309
|
-
if (this.webXR) {
|
|
310
|
-
const i = this.webXR.Controllers.indexOf(this);
|
|
311
|
-
if (i >= 0)
|
|
312
|
-
this.webXR.Controllers.splice(i, 1);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// onDestroy(): void {
|
|
317
|
-
// console.log("destroyed", this.index);
|
|
318
|
-
// }
|
|
319
|
-
|
|
320
|
-
private _isConnected: boolean = false;
|
|
321
|
-
|
|
322
|
-
private onSourceConnected(e: { data: XRInputSource, target: any }) {
|
|
323
|
-
if (this._isConnected) {
|
|
324
|
-
console.warn("Received connected event for controller that is already connected", this.index, e);
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
this._isConnected = true;
|
|
328
|
-
this.input = e.data;
|
|
329
|
-
|
|
330
|
-
if (this.type === ControllerType.Touch) {
|
|
331
|
-
this.onSelectStart();
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
this.
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
private
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
raycastLine.
|
|
368
|
-
}
|
|
369
|
-
else
|
|
370
|
-
raycastLine.visible =
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
if (this.
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
this.
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
if (
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
if (
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
//
|
|
651
|
-
|
|
652
|
-
//
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
if (
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
if (
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
const
|
|
843
|
-
if (
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
if (
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
const
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
this.
|
|
947
|
-
this.
|
|
948
|
-
this.
|
|
949
|
-
this.
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
//
|
|
954
|
-
//
|
|
955
|
-
this.
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
this.
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
AttachedObject.
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
const
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
if (
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
this.
|
|
1022
|
-
this.
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
this.
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
if (this.
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
this.
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
//
|
|
1113
|
-
|
|
1114
|
-
if (this.
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
target.sub(this.localPositionOffsetToGrab_worldSpace);
|
|
1143
|
-
}
|
|
1144
|
-
setWorldPosition(this.selected, target);
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
if (this.rigidbodies != null) {
|
|
1149
|
-
for (const rb of this.rigidbodies) {
|
|
1150
|
-
rb.wakeUp();
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
InstancingUtil.markDirty(this.selected, true);
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1
|
+
import { BoxHelper, BufferGeometry, Color, Euler, Group, Intersection, Layers, Line, LineBasicMaterial, Material, Mesh, MeshBasicMaterial, Object3D, PerspectiveCamera, Quaternion, Ray, SphereGeometry, Vector2, Vector3 } from "three";
|
|
2
|
+
import { OculusHandModel } from 'three/examples/jsm/webxr/OculusHandModel.js';
|
|
3
|
+
import { OculusHandPointerModel } from 'three/examples/jsm/webxr/OculusHandPointerModel.js';
|
|
4
|
+
import { XRControllerModel, XRControllerModelFactory } from 'three/examples/jsm/webxr/XRControllerModelFactory.js';
|
|
5
|
+
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
|
6
|
+
|
|
7
|
+
import { InstancingUtil } from "../../engine/engine_instancing.js";
|
|
8
|
+
import { Mathf } from "../../engine/engine_math.js";
|
|
9
|
+
import { RaycastOptions } from "../../engine/engine_physics.js";
|
|
10
|
+
import { getWorldPosition, getWorldQuaternion, setWorldPosition, setWorldQuaternion } from "../../engine/engine_three_utils.js";
|
|
11
|
+
import { getParam, resolveUrl } from "../../engine/engine_utils.js";
|
|
12
|
+
import { addDracoAndKTX2Loaders } from "../../engine/engine_loaders.js";
|
|
13
|
+
|
|
14
|
+
import { Avatar_POI } from "../avatar/Avatar_Brain_LookAt.js";
|
|
15
|
+
import { Behaviour, GameObject } from "../Component.js";
|
|
16
|
+
import { Interactable, UsageMarker } from "../Interactable.js";
|
|
17
|
+
import { Rigidbody } from "../RigidBody.js";
|
|
18
|
+
import { SyncedTransform } from "../SyncedTransform.js";
|
|
19
|
+
import { UIRaycastUtils } from "../ui/RaycastUtils.js";
|
|
20
|
+
import { WebXR } from "./WebXR.js";
|
|
21
|
+
import { XRRig } from "./WebXRRig.js";
|
|
22
|
+
|
|
23
|
+
const debug = getParam("debugwebxrcontroller");
|
|
24
|
+
|
|
25
|
+
export enum ControllerType {
|
|
26
|
+
PhysicalDevice = 0,
|
|
27
|
+
Touch = 1,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export enum ControllerEvents {
|
|
31
|
+
SelectStart = "select-start",
|
|
32
|
+
SelectEnd = "select-end",
|
|
33
|
+
Update = "update",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class TeleportTarget extends Behaviour {
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class WebXRController extends Behaviour {
|
|
41
|
+
|
|
42
|
+
public static Factory: XRControllerModelFactory = new XRControllerModelFactory();
|
|
43
|
+
|
|
44
|
+
private static raycastColor: Color = new Color(.9, .3, .3);
|
|
45
|
+
private static raycastNoHitColor: Color = new Color(.6, .6, .6);
|
|
46
|
+
private static geometry = new BufferGeometry().setFromPoints([new Vector3(0, 0, 0), new Vector3(0, 0, -1)]);
|
|
47
|
+
private static handModels: { [index: number]: OculusHandPointerModel } = {};
|
|
48
|
+
|
|
49
|
+
private static CreateRaycastLine(): Line {
|
|
50
|
+
const line = new Line(this.geometry);
|
|
51
|
+
const mat = line.material as LineBasicMaterial;
|
|
52
|
+
mat.color = this.raycastColor;
|
|
53
|
+
// mat.linewidth = 10;
|
|
54
|
+
line.layers.set(2);
|
|
55
|
+
line.name = 'line';
|
|
56
|
+
line.scale.z = 1;
|
|
57
|
+
return line;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private static CreateRaycastHitPoint(): Mesh {
|
|
61
|
+
const geometry = new SphereGeometry(.5, 22, 22);
|
|
62
|
+
const material = new MeshBasicMaterial({ color: this.raycastColor });
|
|
63
|
+
const sphere = new Mesh(geometry, material);
|
|
64
|
+
sphere.visible = false;
|
|
65
|
+
sphere.layers.set(2);
|
|
66
|
+
return sphere;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public static Create(owner: WebXR, index: number, addTo: GameObject, type: ControllerType): WebXRController {
|
|
70
|
+
const ctrl = addTo ? GameObject.addNewComponent(addTo, WebXRController, false) : new WebXRController();
|
|
71
|
+
|
|
72
|
+
ctrl.webXR = owner;
|
|
73
|
+
ctrl.index = index;
|
|
74
|
+
ctrl.type = type;
|
|
75
|
+
|
|
76
|
+
const context = owner.context;
|
|
77
|
+
// from https://github.com/mrdoob/js/blob/master/examples/webxr_vr_dragging.html
|
|
78
|
+
// controllers
|
|
79
|
+
ctrl.controller = context.renderer.xr.getController(index);
|
|
80
|
+
ctrl.controllerGrip = context.renderer.xr.getControllerGrip(index);
|
|
81
|
+
ctrl.controllerModel = this.Factory.createControllerModel(ctrl.controller);
|
|
82
|
+
ctrl.controllerGrip.add(ctrl.controllerModel);
|
|
83
|
+
|
|
84
|
+
ctrl.hand = context.renderer.xr.getHand(index);
|
|
85
|
+
|
|
86
|
+
const loader = new GLTFLoader();
|
|
87
|
+
addDracoAndKTX2Loaders(loader, context);
|
|
88
|
+
if (ctrl.webXR.handModelPath && ctrl.webXR.handModelPath !== "")
|
|
89
|
+
loader.setPath(resolveUrl(owner.sourceId, ctrl.webXR.handModelPath));
|
|
90
|
+
else
|
|
91
|
+
// from XRHandMeshModel.js
|
|
92
|
+
loader.setPath('https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@1.0/dist/profiles/generic-hand/');
|
|
93
|
+
//@ts-ignore
|
|
94
|
+
const hand = new OculusHandModel(ctrl.hand, loader);
|
|
95
|
+
|
|
96
|
+
ctrl.hand.add(hand);
|
|
97
|
+
ctrl.hand.traverse(x => x.layers.set(2));
|
|
98
|
+
|
|
99
|
+
ctrl.handPointerModel = new OculusHandPointerModel(ctrl.hand, ctrl.controller);
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
// TODO remove all these once https://github.com/mrdoob/js/pull/23279 lands
|
|
103
|
+
ctrl.controller.addEventListener('connected', (_) => {
|
|
104
|
+
ctrl.setControllerLayers(ctrl.controllerModel, 2);
|
|
105
|
+
ctrl.setControllerLayers(ctrl.controllerGrip, 2);
|
|
106
|
+
ctrl.setControllerLayers(ctrl.hand, 2);
|
|
107
|
+
setTimeout(() => {
|
|
108
|
+
ctrl.setControllerLayers(ctrl.controllerModel, 2);
|
|
109
|
+
ctrl.setControllerLayers(ctrl.controllerGrip, 2);
|
|
110
|
+
ctrl.setControllerLayers(ctrl.hand, 2);
|
|
111
|
+
}, 1000);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// TODO: unsubscribe! this should be moved into onenable and ondisable!
|
|
115
|
+
// TODO remove all these once https://github.com/mrdoob/js/pull/23279 lands
|
|
116
|
+
ctrl.hand.addEventListener('connected', (event) => {
|
|
117
|
+
const xrInputSource = event.data;
|
|
118
|
+
if (xrInputSource.hand) {
|
|
119
|
+
if (owner.Rig) owner.Rig.add(ctrl.hand);
|
|
120
|
+
ctrl.type = ControllerType.PhysicalDevice;
|
|
121
|
+
ctrl.handPointerModel.traverse(x => x.layers.set(2)); // ignore raycast
|
|
122
|
+
ctrl.handPointerModel.pointerObject?.traverse(x => x.layers.set(2));
|
|
123
|
+
|
|
124
|
+
// when exiting and re-entering xr the joints are not parented to the hand anymore
|
|
125
|
+
// this is a workaround to fix that temporarely
|
|
126
|
+
// see https://github.com/needle-tools/needle-tiny-playground/issues/123
|
|
127
|
+
const jnts = ctrl.hand["joints"];
|
|
128
|
+
if (jnts) {
|
|
129
|
+
for (const key of Object.keys(jnts)) {
|
|
130
|
+
const joint = jnts[key];
|
|
131
|
+
if (joint.parent) continue;
|
|
132
|
+
ctrl.hand.add(joint);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return ctrl;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// TODO: replace with component events
|
|
142
|
+
public static addEventListener(evt: ControllerEvents, callback: (controller: WebXRController, args: any) => void) {
|
|
143
|
+
const list = this.eventSubs[evt] ?? [];
|
|
144
|
+
list.push(callback);
|
|
145
|
+
this.eventSubs[evt] = list;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// TODO: replace with component events
|
|
149
|
+
public static removeEventListener(evt: ControllerEvents, callback: (controller: WebXRController, args: any) => void) {
|
|
150
|
+
if (!callback) return;
|
|
151
|
+
const list = this.eventSubs[evt] ?? [];
|
|
152
|
+
const idx = list.indexOf(callback);
|
|
153
|
+
if (idx >= 0) list.splice(idx, 1);
|
|
154
|
+
this.eventSubs[evt] = list;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private static eventSubs: { [key: string]: Function[] } = {};
|
|
158
|
+
|
|
159
|
+
public webXR?: WebXR;
|
|
160
|
+
public index: number = -1;
|
|
161
|
+
public controllerModel!: XRControllerModel;
|
|
162
|
+
public controller!: Group;
|
|
163
|
+
public controllerGrip!: Group;
|
|
164
|
+
public hand!: Group;
|
|
165
|
+
public handPointerModel!: OculusHandPointerModel;
|
|
166
|
+
public grabbed: AttachedObject | null = null;
|
|
167
|
+
public input: XRInputSource | null = null;
|
|
168
|
+
public type: ControllerType = ControllerType.PhysicalDevice;
|
|
169
|
+
public showRaycastLine: boolean = true;
|
|
170
|
+
|
|
171
|
+
get isUsingHands(): boolean {
|
|
172
|
+
const r = this.input?.hand;
|
|
173
|
+
return r !== null && r !== undefined;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
get wrist(): Object3D | null {
|
|
177
|
+
if (!this.hand) return null;
|
|
178
|
+
const jnts = this.hand["joints"];
|
|
179
|
+
if (!jnts) return null;
|
|
180
|
+
return jnts["wrist"];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private _wristQuaternion: Quaternion | null = null;
|
|
184
|
+
getWristQuaternion(): Quaternion | null {
|
|
185
|
+
const wrist = this.wrist;
|
|
186
|
+
if (!wrist) return null;
|
|
187
|
+
if (!this._wristQuaternion) this._wristQuaternion = new Quaternion();
|
|
188
|
+
const wr = getWorldQuaternion(wrist).multiply(this._wristQuaternion.setFromEuler(new Euler(-Math.PI / 4, 0, 0)));
|
|
189
|
+
return wr;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private movementVector: Vector3 = new Vector3();
|
|
193
|
+
private worldRot: Quaternion = new Quaternion();
|
|
194
|
+
private joystick: Vector2 = new Vector2();
|
|
195
|
+
private didRotate: boolean = false;
|
|
196
|
+
private didTeleport: boolean = false;
|
|
197
|
+
private didChangeScale: boolean = false;
|
|
198
|
+
private static PreviousCameraFarDistance: number | undefined = undefined;
|
|
199
|
+
private static MovementSpeedFactor: number = 1;
|
|
200
|
+
|
|
201
|
+
private lastHit: Intersection | null = null;
|
|
202
|
+
|
|
203
|
+
private raycastLine: Line | null = null;
|
|
204
|
+
private _raycastHitPoint: Object3D | null = null;
|
|
205
|
+
private _connnectedCallback: any | null = null;
|
|
206
|
+
private _disconnectedCallback: any | null = null;
|
|
207
|
+
private _selectStartEvt: any | null = null;
|
|
208
|
+
private _selectEndEvt: any | null = null;
|
|
209
|
+
|
|
210
|
+
public get selectionDown(): boolean { return this._selectionPressed && !this._selectionPressedLastFrame; }
|
|
211
|
+
public get selectionUp(): boolean { return !this._selectionPressed && this._selectionPressedLastFrame; }
|
|
212
|
+
public get selectionPressed(): boolean { return this._selectionPressed; }
|
|
213
|
+
public get selectionClick(): boolean { return this._selectionEndTime - this._selectionStartTime < 0.3; }
|
|
214
|
+
public get raycastHitPoint(): Object3D | null { return this._raycastHitPoint; }
|
|
215
|
+
|
|
216
|
+
private _selectionPressed: boolean = false;
|
|
217
|
+
private _selectionPressedLastFrame: boolean = false;
|
|
218
|
+
private _selectionStartTime: number = 0;
|
|
219
|
+
private _selectionEndTime: number = 0;
|
|
220
|
+
|
|
221
|
+
public get useSmoothing(): boolean { return this._useSmoothing };
|
|
222
|
+
private _useSmoothing: boolean = true;
|
|
223
|
+
|
|
224
|
+
awake(): void {
|
|
225
|
+
if (!this.controller) {
|
|
226
|
+
console.warn("WebXRController: Missing controller object.", this);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
this._connnectedCallback = this.onSourceConnected.bind(this);
|
|
230
|
+
this._disconnectedCallback = this.onSourceDisconnected.bind(this);
|
|
231
|
+
this._selectStartEvt = this.onSelectStart.bind(this);
|
|
232
|
+
this._selectEndEvt = this.onSelectEnd.bind(this);
|
|
233
|
+
if (this.type === ControllerType.Touch) {
|
|
234
|
+
this.controllerGrip.addEventListener("connected", this._connnectedCallback);
|
|
235
|
+
this.controllerGrip.addEventListener("disconnected", this._disconnectedCallback);
|
|
236
|
+
this.controller.addEventListener('selectstart', this._selectStartEvt);
|
|
237
|
+
this.controller.addEventListener('selectend', this._selectEndEvt);
|
|
238
|
+
}
|
|
239
|
+
if (this.type === ControllerType.PhysicalDevice) {
|
|
240
|
+
this.controller.addEventListener('selectstart', this._selectStartEvt);
|
|
241
|
+
this.controller.addEventListener('selectend', this._selectEndEvt);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
onDestroy(): void {
|
|
246
|
+
if (this.type === ControllerType.Touch) {
|
|
247
|
+
this.controllerGrip.removeEventListener("connected", this._connnectedCallback);
|
|
248
|
+
this.controllerGrip.removeEventListener("disconnected", this._disconnectedCallback);
|
|
249
|
+
this.controller.removeEventListener('selectstart', this._selectStartEvt);
|
|
250
|
+
this.controller.removeEventListener('selectend', this._selectEndEvt);
|
|
251
|
+
}
|
|
252
|
+
if (this.type === ControllerType.PhysicalDevice) {
|
|
253
|
+
this.controller.removeEventListener('selectstart', this._selectStartEvt);
|
|
254
|
+
this.controller.removeEventListener('selectend', this._selectEndEvt);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
this.hand?.clear();
|
|
258
|
+
this.controllerGrip?.clear();
|
|
259
|
+
this.controller?.clear();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
public onEnable(): void {
|
|
263
|
+
if (!this.webXR) {
|
|
264
|
+
console.warn("No WebXR component assigned to WebXRController.");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (this.hand)
|
|
269
|
+
this.hand.name = "Hand";
|
|
270
|
+
if (this.controllerGrip)
|
|
271
|
+
this.controllerGrip.name = "ControllerGrip";
|
|
272
|
+
if (this.controller)
|
|
273
|
+
this.controller.name = "Controller";
|
|
274
|
+
if (this.raycastLine)
|
|
275
|
+
this.raycastLine.name = "RaycastLine;"
|
|
276
|
+
|
|
277
|
+
if (this.webXR.Controllers.indexOf(this) < 0)
|
|
278
|
+
this.webXR.Controllers.push(this);
|
|
279
|
+
|
|
280
|
+
if (!this.raycastLine)
|
|
281
|
+
this.raycastLine = WebXRController.CreateRaycastLine();
|
|
282
|
+
if (!this._raycastHitPoint)
|
|
283
|
+
this._raycastHitPoint = WebXRController.CreateRaycastHitPoint();
|
|
284
|
+
|
|
285
|
+
this.webXR.Rig?.add(this.hand);
|
|
286
|
+
this.webXR.Rig?.add(this.controllerGrip);
|
|
287
|
+
this.webXR.Rig?.add(this.controller);
|
|
288
|
+
this.webXR.Rig?.add(this.raycastLine);
|
|
289
|
+
this.raycastLine?.add(this._raycastHitPoint);
|
|
290
|
+
this._raycastHitPoint.visible = false;
|
|
291
|
+
this.hand.add(this.handPointerModel);
|
|
292
|
+
if (debug)
|
|
293
|
+
console.log("ADDED TO RIG", this.webXR.Rig);
|
|
294
|
+
|
|
295
|
+
// // console.log("enable", this.index, this.controllerGrip.uuid)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
onDisable(): void {
|
|
299
|
+
// console.log("XR controller disabled", this);
|
|
300
|
+
this.hand?.removeFromParent();
|
|
301
|
+
this.controllerGrip?.removeFromParent();
|
|
302
|
+
this.controller?.removeFromParent();
|
|
303
|
+
this.raycastLine?.removeFromParent();
|
|
304
|
+
this._raycastHitPoint?.removeFromParent();
|
|
305
|
+
// console.log("Disable", this._connnectedCallback, this._disconnectedCallback);
|
|
306
|
+
// this.controllerGrip.removeEventListener("connected", this._connnectedCallback);
|
|
307
|
+
// this.controllerGrip.removeEventListener("disconnected", this._disconnectedCallback);
|
|
308
|
+
|
|
309
|
+
if (this.webXR) {
|
|
310
|
+
const i = this.webXR.Controllers.indexOf(this);
|
|
311
|
+
if (i >= 0)
|
|
312
|
+
this.webXR.Controllers.splice(i, 1);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// onDestroy(): void {
|
|
317
|
+
// console.log("destroyed", this.index);
|
|
318
|
+
// }
|
|
319
|
+
|
|
320
|
+
private _isConnected: boolean = false;
|
|
321
|
+
|
|
322
|
+
private onSourceConnected(e: { data: XRInputSource, target: any }) {
|
|
323
|
+
if (this._isConnected) {
|
|
324
|
+
console.warn("Received connected event for controller that is already connected", this.index, e);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
this._isConnected = true;
|
|
328
|
+
this.input = e.data;
|
|
329
|
+
|
|
330
|
+
if (this.type === ControllerType.Touch) {
|
|
331
|
+
this.onSelectStart();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private onSourceDisconnected(_e: any) {
|
|
336
|
+
if (!this._isConnected) {
|
|
337
|
+
console.warn("Received discnnected event for controller that is not connected", _e);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
this._isConnected = false;
|
|
341
|
+
if (this.type === ControllerType.Touch) {
|
|
342
|
+
this.onSelectEnd();
|
|
343
|
+
}
|
|
344
|
+
this.input = null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
rayRotation: Quaternion = new Quaternion();
|
|
348
|
+
|
|
349
|
+
private raycastUpdate(raycastLine: Line, wp: Vector3) {
|
|
350
|
+
const allowRaycastLineVisible = this.showRaycastLine && this.type !== ControllerType.Touch;
|
|
351
|
+
if (this.type === ControllerType.Touch) {
|
|
352
|
+
raycastLine.visible = false;
|
|
353
|
+
}
|
|
354
|
+
else if (this.isUsingHands) {
|
|
355
|
+
raycastLine.visible = !this.grabbed && allowRaycastLineVisible;
|
|
356
|
+
setWorldPosition(raycastLine, wp);
|
|
357
|
+
const jnts = this.hand!['joints'];
|
|
358
|
+
if (jnts) {
|
|
359
|
+
const wrist = jnts['wrist'];
|
|
360
|
+
if (wrist && this.grabbed && this.grabbed.isCloseGrab) {
|
|
361
|
+
const wr = this.getWristQuaternion();
|
|
362
|
+
if (wr)
|
|
363
|
+
this.rayRotation.copy(wr);
|
|
364
|
+
// this.rayRotation.slerp(wr, this.useSmoothing ? t * 2 : 1);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
setWorldQuaternion(raycastLine, this.rayRotation);
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
raycastLine.visible = allowRaycastLineVisible;
|
|
371
|
+
setWorldQuaternion(raycastLine, this.rayRotation);
|
|
372
|
+
setWorldPosition(raycastLine, wp);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
update(): void {
|
|
377
|
+
if (!this.webXR) return;
|
|
378
|
+
|
|
379
|
+
// TODO: we should wait until we actually have models, this is just a workaround
|
|
380
|
+
if (this.context.time.frameCount % 60 === 0) {
|
|
381
|
+
this.setControllerLayers(this.controller, 2);
|
|
382
|
+
this.setControllerLayers(this.controllerGrip, 2);
|
|
383
|
+
this.setControllerLayers(this.hand, 2);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const subs = WebXRController.eventSubs[ControllerEvents.Update];
|
|
387
|
+
if (subs && subs.length > 0) {
|
|
388
|
+
for (const sub of subs) {
|
|
389
|
+
sub(this);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
let t = 1;
|
|
394
|
+
if (this.type === ControllerType.PhysicalDevice) t = this.context.time.deltaTime / .1;
|
|
395
|
+
else if (this.isUsingHands && this.handPointerModel.pinched) t = this.context.time.deltaTime / .3;
|
|
396
|
+
this.rayRotation.slerp(getWorldQuaternion(this.controller), this.useSmoothing ? t : 1.0);
|
|
397
|
+
const wp = getWorldPosition(this.controller);
|
|
398
|
+
|
|
399
|
+
// hide hand pointer model, it's giant and doesn't really help
|
|
400
|
+
if (this.isUsingHands && this.handPointerModel.cursorObject) {
|
|
401
|
+
this.handPointerModel.cursorObject.visible = false;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (this.raycastLine) {
|
|
405
|
+
this.raycastUpdate(this.raycastLine, wp);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
this.lastHit = this.updateLastHit();
|
|
409
|
+
|
|
410
|
+
if (this.grabbed) {
|
|
411
|
+
this.grabbed.update();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
this._selectionPressedLastFrame = this._selectionPressed;
|
|
415
|
+
|
|
416
|
+
if (this.selectStartCallback) {
|
|
417
|
+
this.selectStartCallback();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
onUpdate(session: XRSession) {
|
|
422
|
+
this.lastHit = null;
|
|
423
|
+
|
|
424
|
+
if (!session || session.inputSources.length <= this.index) {
|
|
425
|
+
this.input = null;
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (this.type === ControllerType.PhysicalDevice)
|
|
429
|
+
this.input = session.inputSources[this.index];
|
|
430
|
+
if (!this.input) return;
|
|
431
|
+
const rig = this.webXR!.Rig;
|
|
432
|
+
if (!rig) return;
|
|
433
|
+
|
|
434
|
+
if (this._didNotEndSelection && !this.handPointerModel.pinched) {
|
|
435
|
+
this._didNotEndSelection = false;
|
|
436
|
+
this.onSelectEnd();
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
this.updateStick(this.input);
|
|
440
|
+
|
|
441
|
+
const buttons = this.input?.gamepad?.buttons;
|
|
442
|
+
|
|
443
|
+
switch (this.input.handedness) {
|
|
444
|
+
case "left":
|
|
445
|
+
this.movementUpdate(rig, buttons);
|
|
446
|
+
break;
|
|
447
|
+
|
|
448
|
+
case "right":
|
|
449
|
+
this.rotationUpdate(rig, buttons);
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
private movementUpdate(rig: Object3D, buttons?: readonly GamepadButton[]) {
|
|
456
|
+
const speedFactor = 3 * WebXRController.MovementSpeedFactor;
|
|
457
|
+
const powFactor = 2;
|
|
458
|
+
const speed = Mathf.clamp01(this.joystick.length() * 2);
|
|
459
|
+
|
|
460
|
+
const sideDir = this.joystick.x > 0 ? 1 : -1;
|
|
461
|
+
let side = Math.pow(this.joystick.x, powFactor);
|
|
462
|
+
side *= sideDir;
|
|
463
|
+
side *= speed;
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
const forwardDir = this.joystick.y > 0 ? 1 : -1;
|
|
467
|
+
let forward = Math.pow(this.joystick.y, powFactor);
|
|
468
|
+
forward *= forwardDir;
|
|
469
|
+
side *= speed;
|
|
470
|
+
|
|
471
|
+
rig.getWorldQuaternion(this.worldRot);
|
|
472
|
+
this.movementVector.set(side, 0, forward);
|
|
473
|
+
this.movementVector.applyQuaternion(this.webXR!.TransformOrientation);
|
|
474
|
+
this.movementVector.y = 0;
|
|
475
|
+
this.movementVector.applyQuaternion(this.worldRot);
|
|
476
|
+
this.movementVector.multiplyScalar(speedFactor * this.context.time.deltaTime);
|
|
477
|
+
rig.position.add(this.movementVector);
|
|
478
|
+
|
|
479
|
+
if (this.isUsingHands)
|
|
480
|
+
this.runTeleport(rig, buttons);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
private rotationUpdate(rig: Object3D, buttons?: readonly GamepadButton[]) {
|
|
484
|
+
const rotate = this.joystick.x;
|
|
485
|
+
const rotAbs = Math.abs(rotate);
|
|
486
|
+
if (rotAbs < 0.4) {
|
|
487
|
+
this.didRotate = false;
|
|
488
|
+
}
|
|
489
|
+
else if (rotAbs > .5 && !this.didRotate) {
|
|
490
|
+
const dir = rotate > 0 ? -1 : 1;
|
|
491
|
+
rig.rotateY(Mathf.toRadians(30 * dir));
|
|
492
|
+
this.didRotate = true;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
this.runTeleport(rig, buttons);
|
|
496
|
+
}
|
|
497
|
+
private _pinchStartTime: number | undefined = undefined;
|
|
498
|
+
|
|
499
|
+
private runTeleport(rig: Object3D, buttons?: readonly GamepadButton[]) {
|
|
500
|
+
let teleport = -this.joystick.y;
|
|
501
|
+
if (this.hand?.visible && !this.grabbed) {
|
|
502
|
+
const pinched = this.handPointerModel.isPinched();
|
|
503
|
+
if (pinched && this._pinchStartTime === undefined) {
|
|
504
|
+
this._pinchStartTime = this.context.time.time;
|
|
505
|
+
}
|
|
506
|
+
if (pinched && this._pinchStartTime && this.context.time.time - this._pinchStartTime > .8) {
|
|
507
|
+
// hacky approach for basic hand teleportation -
|
|
508
|
+
// we teleport if we pinch and the back of the hand points down (open hand gesture)
|
|
509
|
+
// const v1 = new Vector3();
|
|
510
|
+
// const worldQuaternion = new Quaternion();
|
|
511
|
+
// this.controller.getWorldQuaternion(worldQuaternion);
|
|
512
|
+
// v1.copy(this.controller.up).applyQuaternion(worldQuaternion);
|
|
513
|
+
// const dotPr = -v1.dot(this.controller.up);
|
|
514
|
+
teleport = this.handPointerModel.isPinched() ? 1 : 0;
|
|
515
|
+
}
|
|
516
|
+
if (!pinched) this._pinchStartTime = undefined;
|
|
517
|
+
}
|
|
518
|
+
else this._pinchStartTime = undefined;
|
|
519
|
+
|
|
520
|
+
const inVR = this.webXR!.IsInVR;
|
|
521
|
+
const xrRig = this.webXR!.Rig;
|
|
522
|
+
let doTeleport = teleport > .5 && inVR;
|
|
523
|
+
let isInMiniatureMode = xrRig ? xrRig?.scale?.x < .999 : false;
|
|
524
|
+
let newRigScale: number | null = null;
|
|
525
|
+
|
|
526
|
+
if (buttons && this.input && !this.input.hand) {
|
|
527
|
+
for (let i = 0; i < buttons.length; i++) {
|
|
528
|
+
const btn = buttons[i];
|
|
529
|
+
// button[4] seems to be the A button if it exists. On hololens it's randomly pressed though for hands
|
|
530
|
+
// see https://www.w3.org/TR/webxr-gamepads-module-1/#xr-standard-gamepad-mapping
|
|
531
|
+
if (i === 4) {
|
|
532
|
+
if (btn.pressed && !this.didChangeScale && inVR) {
|
|
533
|
+
this.didChangeScale = true;
|
|
534
|
+
const rig = xrRig;
|
|
535
|
+
if (rig) {
|
|
536
|
+
const args = this.switchScale(rig, doTeleport, isInMiniatureMode, newRigScale);
|
|
537
|
+
doTeleport = args.doTeleport;
|
|
538
|
+
isInMiniatureMode = args.isInMiniatureMode;
|
|
539
|
+
newRigScale = args.newRigScale;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
else if (!btn.pressed)
|
|
543
|
+
this.didChangeScale = false;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (doTeleport) {
|
|
549
|
+
if (!this.didTeleport) {
|
|
550
|
+
const rc = this.raycast();
|
|
551
|
+
this.didTeleport = true;
|
|
552
|
+
if (rc && rc.length > 0) {
|
|
553
|
+
const hit = rc[0];
|
|
554
|
+
if (isInMiniatureMode || this.isValidTeleportTarget(hit.object)) {
|
|
555
|
+
const point = hit.point;
|
|
556
|
+
setWorldPosition(rig, point);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
else if (teleport < .1) {
|
|
562
|
+
this.didTeleport = false;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (newRigScale !== null) {
|
|
566
|
+
rig.scale.set(newRigScale, newRigScale, newRigScale);
|
|
567
|
+
rig.updateMatrixWorld();
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
private isValidTeleportTarget(obj: Object3D): boolean {
|
|
573
|
+
return GameObject.getComponentInParent(obj, TeleportTarget) != null;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
private switchScale(rig: Object3D, doTeleport: boolean, isInMiniatureMode: boolean, newRigScale: number | null) {
|
|
577
|
+
if (!isInMiniatureMode) {
|
|
578
|
+
isInMiniatureMode = true;
|
|
579
|
+
doTeleport = true;
|
|
580
|
+
newRigScale = .1;
|
|
581
|
+
WebXRController.MovementSpeedFactor = newRigScale * 2;
|
|
582
|
+
const cam = this.context.mainCamera as PerspectiveCamera;
|
|
583
|
+
WebXRController.PreviousCameraFarDistance = cam.far;
|
|
584
|
+
cam.far /= newRigScale;
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
isInMiniatureMode = false;
|
|
588
|
+
rig.scale.set(1, 1, 1);
|
|
589
|
+
newRigScale = 1;
|
|
590
|
+
WebXRController.MovementSpeedFactor = 1;
|
|
591
|
+
const cam = this.context.mainCamera as PerspectiveCamera;
|
|
592
|
+
if (WebXRController.PreviousCameraFarDistance)
|
|
593
|
+
cam.far = WebXRController.PreviousCameraFarDistance;
|
|
594
|
+
}
|
|
595
|
+
return { doTeleport, isInMiniatureMode, newRigScale }
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
private updateStick(inputSource: XRInputSource) {
|
|
599
|
+
if (!inputSource || !inputSource.gamepad || inputSource.gamepad.axes?.length < 4) return;
|
|
600
|
+
this.joystick.x = inputSource.gamepad.axes[2];
|
|
601
|
+
this.joystick.y = inputSource.gamepad.axes[3];
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
private updateLastHit(): Intersection | null {
|
|
605
|
+
const rc = this.raycast();
|
|
606
|
+
const hit = rc ? rc[0] : null;
|
|
607
|
+
this.lastHit = hit;
|
|
608
|
+
let factor = 1;
|
|
609
|
+
if (this.webXR!.Rig) {
|
|
610
|
+
factor /= this.webXR!.Rig.scale.x;
|
|
611
|
+
}
|
|
612
|
+
// if (!hit) factor = 0;
|
|
613
|
+
|
|
614
|
+
if (this.raycastLine) {
|
|
615
|
+
this.raycastLine.scale.z = factor * (this.lastHit?.distance ?? 9999);
|
|
616
|
+
const mat = this.raycastLine.material as LineBasicMaterial;
|
|
617
|
+
if (hit != null) mat.color = WebXRController.raycastColor;
|
|
618
|
+
else mat.color = WebXRController.raycastNoHitColor;
|
|
619
|
+
}
|
|
620
|
+
if (this._raycastHitPoint) {
|
|
621
|
+
if (this.lastHit != null) {
|
|
622
|
+
this._raycastHitPoint.position.z = -1;// -this.lastHit.distance;
|
|
623
|
+
const scale = Mathf.clamp(this.lastHit.distance * .01 * factor, .015, .1);
|
|
624
|
+
this._raycastHitPoint.scale.set(scale, scale, scale);
|
|
625
|
+
}
|
|
626
|
+
this._raycastHitPoint.visible = this.lastHit !== null && this.lastHit !== undefined;
|
|
627
|
+
}
|
|
628
|
+
return hit;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
private onSelectStart() {
|
|
632
|
+
if (!this.context.connection.allowEditing) return;
|
|
633
|
+
// console.log("SELECT START", _event);
|
|
634
|
+
// if we process the event immediately the controller
|
|
635
|
+
// world positions are not yet correctly updated and we have info from the last frame
|
|
636
|
+
// so we delay the event processing one frame
|
|
637
|
+
// only necessary for AR - ideally we can get it to work right here
|
|
638
|
+
// but should be fine as a workaround for now
|
|
639
|
+
this.selectStartCallback = () => this.onHandleSelectStart();
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
private selectStartCallback: Function | null = null;
|
|
643
|
+
private lastSelectStartObject: Object3D | null = null;;
|
|
644
|
+
|
|
645
|
+
private onHandleSelectStart() {
|
|
646
|
+
this.selectStartCallback = null;
|
|
647
|
+
this._selectionPressed = true;
|
|
648
|
+
this._selectionStartTime = this.context.time.time;
|
|
649
|
+
this._selectionEndTime = 1000;
|
|
650
|
+
// console.log("DOWN", this.index, WebXRController.eventSubs);
|
|
651
|
+
|
|
652
|
+
// let maxDistance = this.isUsingHands ? .1 : undefined;
|
|
653
|
+
let intersections: Intersection[] | null = null;
|
|
654
|
+
let closeGrab: boolean = false;
|
|
655
|
+
if (this.isUsingHands) {
|
|
656
|
+
intersections = this.overlap();
|
|
657
|
+
if (intersections.length <= 0) {
|
|
658
|
+
intersections = this.raycast();
|
|
659
|
+
closeGrab = false;
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
closeGrab = true;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
else intersections = this.raycast();
|
|
666
|
+
|
|
667
|
+
if (debug)
|
|
668
|
+
console.log("onHandleSelectStart", "close grab? " + closeGrab, "intersections", intersections);
|
|
669
|
+
|
|
670
|
+
if (intersections && intersections.length > 0) {
|
|
671
|
+
for (const intersection of intersections) {
|
|
672
|
+
const object = intersection.object;
|
|
673
|
+
this.lastSelectStartObject = object;
|
|
674
|
+
const args = { selected: object, grab: object };
|
|
675
|
+
const subs = WebXRController.eventSubs[ControllerEvents.SelectStart];
|
|
676
|
+
if (subs && subs.length > 0) {
|
|
677
|
+
for (const sub of subs) {
|
|
678
|
+
sub(this, args);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
if (args.grab !== object && debug)
|
|
682
|
+
console.log("Grabbed object changed", "original", object, "new", args.grab);
|
|
683
|
+
if (args.grab) {
|
|
684
|
+
this.grabbed = AttachedObject.TryTake(this, args.grab, intersection, closeGrab);
|
|
685
|
+
}
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
const subs = WebXRController.eventSubs[ControllerEvents.SelectStart];
|
|
691
|
+
const args = { selected: null, grab: null };
|
|
692
|
+
if (subs && subs.length > 0) {
|
|
693
|
+
for (const sub of subs) {
|
|
694
|
+
sub(this, args);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
private _didNotEndSelection: boolean = false;
|
|
701
|
+
|
|
702
|
+
private onSelectEnd() {
|
|
703
|
+
if (this.isUsingHands) {
|
|
704
|
+
if (this.handPointerModel.pinched) {
|
|
705
|
+
this._didNotEndSelection = true;
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
if (!this._selectionPressed) return;
|
|
711
|
+
this.selectStartCallback = null;
|
|
712
|
+
this._selectionPressed = false;
|
|
713
|
+
this._selectionEndTime = this.context.time.time;
|
|
714
|
+
|
|
715
|
+
const args = { grab: this.grabbed?.selected ?? this.lastSelectStartObject };
|
|
716
|
+
const subs = WebXRController.eventSubs[ControllerEvents.SelectEnd];
|
|
717
|
+
if (subs && subs.length > 0) {
|
|
718
|
+
for (const sub of subs) {
|
|
719
|
+
sub(this, args);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (this.grabbed) {
|
|
724
|
+
this.grabbed.free();
|
|
725
|
+
this.grabbed = null;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
private testIsVisible(obj: Object3D | null): boolean {
|
|
730
|
+
if (!obj) return false;
|
|
731
|
+
if (GameObject.isActiveInHierarchy(obj) === false) return false;
|
|
732
|
+
if (UIRaycastUtils.isInteractable(obj) === false) {
|
|
733
|
+
return false;
|
|
734
|
+
}
|
|
735
|
+
return true;
|
|
736
|
+
// if (!obj.visible) return false;
|
|
737
|
+
// return this.testIsVisible(obj.parent);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
private setControllerLayers(obj: Object3D, layer: number) {
|
|
741
|
+
if (!obj) return;
|
|
742
|
+
obj.layers.set(layer);
|
|
743
|
+
if (obj.children) {
|
|
744
|
+
for (const ch of obj.children) {
|
|
745
|
+
if (this.grabbed?.selected === ch || this.grabbed?.selectedMesh === ch) {
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
this.setControllerLayers(ch, layer);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
public getRay(): Ray {
|
|
754
|
+
const ray = new Ray();
|
|
755
|
+
// this.tempMatrix.identity().extractRotation(this.controller.matrixWorld);
|
|
756
|
+
// ray.origin.setFromMatrixPosition(this.controller.matrixWorld);
|
|
757
|
+
ray.origin.copy(getWorldPosition(this.controller));
|
|
758
|
+
ray.direction.set(0, 0, -1).applyQuaternion(this.rayRotation);
|
|
759
|
+
return ray;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
private closeGrabBoundingBoxHelper?: BoxHelper;
|
|
763
|
+
|
|
764
|
+
public overlap(): Intersection[] {
|
|
765
|
+
const overlapCenter = (this.isUsingHands && this.handPointerModel) ? this.handPointerModel.pointerObject : this.controllerGrip;
|
|
766
|
+
|
|
767
|
+
if (debug) {
|
|
768
|
+
if (!this.closeGrabBoundingBoxHelper && overlapCenter) {
|
|
769
|
+
this.closeGrabBoundingBoxHelper = new BoxHelper(overlapCenter, 0xffff00);
|
|
770
|
+
this.scene.add(this.closeGrabBoundingBoxHelper);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (this.closeGrabBoundingBoxHelper && overlapCenter) {
|
|
774
|
+
this.closeGrabBoundingBoxHelper.setFromObject(overlapCenter);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
if (!overlapCenter)
|
|
779
|
+
return new Array<Intersection>();
|
|
780
|
+
|
|
781
|
+
const wp = getWorldPosition(overlapCenter).clone();
|
|
782
|
+
return this.context.physics.sphereOverlap(wp, .02);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
public raycast(): Intersection[] {
|
|
786
|
+
const opts = new RaycastOptions();
|
|
787
|
+
opts.layerMask = new Layers();
|
|
788
|
+
opts.layerMask.enableAll();
|
|
789
|
+
opts.layerMask.disable(2);
|
|
790
|
+
opts.ray = this.getRay();
|
|
791
|
+
const hits = this.context.physics.raycast(opts);
|
|
792
|
+
for (let i = 0; i < hits.length; i++) {
|
|
793
|
+
const hit = hits[i];
|
|
794
|
+
const obj = hit.object;
|
|
795
|
+
if (!this.testIsVisible(obj)) {
|
|
796
|
+
hits.splice(i, 1);
|
|
797
|
+
i--;
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
hit.object = UIRaycastUtils.getObject(obj);
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
803
|
+
// console.log(...hits);
|
|
804
|
+
return hits;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
export enum AttachedObjectEvents {
|
|
810
|
+
WillTake = "WillTake",
|
|
811
|
+
DidTake = "DidTake",
|
|
812
|
+
WillFree = "WillFree",
|
|
813
|
+
DidFree = "DidFree",
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
export class AttachedObject {
|
|
817
|
+
|
|
818
|
+
public static Events: { [key: string]: Function[] } = {};
|
|
819
|
+
public static AddEventListener(event: AttachedObjectEvents, callback: Function): Function {
|
|
820
|
+
if (!AttachedObject.Events[event]) AttachedObject.Events[event] = [];
|
|
821
|
+
AttachedObject.Events[event].push(callback);
|
|
822
|
+
return callback;
|
|
823
|
+
}
|
|
824
|
+
public static RemoveEventListener(event: AttachedObjectEvents, callback: Function | null) {
|
|
825
|
+
if (!callback) return;
|
|
826
|
+
if (!AttachedObject.Events[event]) return;
|
|
827
|
+
const idx = AttachedObject.Events[event].indexOf(callback);
|
|
828
|
+
if (idx >= 0) AttachedObject.Events[event].splice(idx, 1);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
public static Current: AttachedObject[] = [];
|
|
833
|
+
|
|
834
|
+
private static Register(obj: AttachedObject) {
|
|
835
|
+
|
|
836
|
+
if (!this.Current.find(x => x === obj)) {
|
|
837
|
+
this.Current.push(obj);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
private static Remove(obj: AttachedObject) {
|
|
842
|
+
const i = this.Current.indexOf(obj);
|
|
843
|
+
if (i >= 0) {
|
|
844
|
+
this.Current.splice(i, 1);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
public static TryTake(controller: WebXRController, candidate: Object3D, intersection: Intersection, closeGrab: boolean): AttachedObject | null {
|
|
849
|
+
const interactable = GameObject.getComponentInParent(candidate, Interactable);
|
|
850
|
+
if (!interactable) {
|
|
851
|
+
if (debug)
|
|
852
|
+
console.warn("Prevented taking object that is not interactable", candidate);
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
855
|
+
else candidate = interactable.gameObject;
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
let objectToAttach = candidate;
|
|
859
|
+
const sync = GameObject.getComponentInParent(candidate, SyncedTransform);
|
|
860
|
+
if (sync) {
|
|
861
|
+
sync.requestOwnership();
|
|
862
|
+
objectToAttach = sync.gameObject;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
for (const o of this.Current) {
|
|
866
|
+
if (o.selected === objectToAttach) {
|
|
867
|
+
if (o.controller === controller) return o;
|
|
868
|
+
o.free();
|
|
869
|
+
o.Take(controller, objectToAttach, candidate, sync, interactable, intersection, closeGrab);
|
|
870
|
+
return o;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const att = new AttachedObject();
|
|
875
|
+
att.Take(controller, objectToAttach, candidate, sync, interactable, intersection, closeGrab);
|
|
876
|
+
return att;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
public sync: SyncedTransform | null = null;
|
|
881
|
+
public selected: Object3D | null = null;
|
|
882
|
+
public selectedParent: Object3D | null = null;
|
|
883
|
+
public selectedMesh: Mesh | null = null;
|
|
884
|
+
public controller: WebXRController | null = null;
|
|
885
|
+
public grabTime: number = 0;
|
|
886
|
+
public grabUUID: string = "";
|
|
887
|
+
public isCloseGrab: boolean = false; // when taken via sphere cast with hands
|
|
888
|
+
|
|
889
|
+
private originalMaterial: Material | Material[] | null = null;
|
|
890
|
+
private usageMarker: UsageMarker | null = null;
|
|
891
|
+
private rigidbodies: Rigidbody[] | null = null;
|
|
892
|
+
private didReparent: boolean = false;
|
|
893
|
+
private grabDistance: number = 0;
|
|
894
|
+
private interactable: Interactable | null = null;
|
|
895
|
+
private positionSource: Object3D | null = null;
|
|
896
|
+
|
|
897
|
+
private Take(controller: WebXRController, take: Object3D, hit: Object3D, sync: SyncedTransform | null, _interactable: Interactable,
|
|
898
|
+
intersection: Intersection, closeGrab: boolean)
|
|
899
|
+
: AttachedObject {
|
|
900
|
+
console.assert(take !== null, "Expected object to be taken but was", take);
|
|
901
|
+
|
|
902
|
+
if (controller.isUsingHands) {
|
|
903
|
+
this.positionSource = closeGrab ? controller.wrist : controller.controller;
|
|
904
|
+
}
|
|
905
|
+
else {
|
|
906
|
+
this.positionSource = controller.controller;
|
|
907
|
+
}
|
|
908
|
+
if (!this.positionSource) {
|
|
909
|
+
console.warn("No position source");
|
|
910
|
+
return this;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
const args = { controller, take, hit, sync, interactable: _interactable };
|
|
914
|
+
AttachedObject.Events.WillTake?.forEach(x => x(this, args));
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
const mesh = hit as Mesh;
|
|
918
|
+
if (mesh?.material) {
|
|
919
|
+
this.originalMaterial = mesh.material;
|
|
920
|
+
if (!Array.isArray(mesh.material)) {
|
|
921
|
+
mesh.material = (mesh.material as Material).clone();
|
|
922
|
+
if (mesh.material && mesh.material["emissive"])
|
|
923
|
+
mesh.material["emissive"].b = .2;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
this.selected = take;
|
|
928
|
+
if (!this.selectedParent) {
|
|
929
|
+
this.selectedParent = take.parent;
|
|
930
|
+
}
|
|
931
|
+
this.selectedMesh = mesh;
|
|
932
|
+
this.controller = controller;
|
|
933
|
+
this.interactable = _interactable;
|
|
934
|
+
this.isCloseGrab = closeGrab;
|
|
935
|
+
// if (interactable.canGrab) {
|
|
936
|
+
// this.didReparent = true;
|
|
937
|
+
// this.device.controller.attach(take);
|
|
938
|
+
// }
|
|
939
|
+
// else
|
|
940
|
+
this.didReparent = false;
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
this.sync = sync;
|
|
944
|
+
this.grabTime = controller.context.time.time;
|
|
945
|
+
this.grabUUID = Date.now().toString();
|
|
946
|
+
this.usageMarker = GameObject.addNewComponent(this.selected, UsageMarker);
|
|
947
|
+
this.rigidbodies = GameObject.getComponentsInChildren(this.selected, Rigidbody);
|
|
948
|
+
getWorldPosition(this.positionSource, this.lastControllerWorldPos);
|
|
949
|
+
const getGrabPoint = () => closeGrab ? this.lastControllerWorldPos.clone() : intersection.point.clone();
|
|
950
|
+
this.grabDistance = getGrabPoint().distanceTo(this.lastControllerWorldPos);
|
|
951
|
+
this.totalChangeAlongDirection = 0.0;
|
|
952
|
+
|
|
953
|
+
// we're storing position relative to the grab point
|
|
954
|
+
// we're storing rotation relative to the ray
|
|
955
|
+
this.localPositionOffsetToGrab = this.selected.worldToLocal(getGrabPoint());
|
|
956
|
+
const rot = controller.isUsingHands && closeGrab ? this.controller.getWristQuaternion()!.clone() : controller.rayRotation.clone();
|
|
957
|
+
getWorldQuaternion(this.selected, this.localQuaternionToGrab).premultiply(rot.invert());
|
|
958
|
+
|
|
959
|
+
const rig = this.controller.webXR!.Rig;
|
|
960
|
+
if (rig)
|
|
961
|
+
this.rigPositionLastFrame.copy(getWorldPosition(rig))
|
|
962
|
+
|
|
963
|
+
Avatar_POI.Add(controller.context, this.selected);
|
|
964
|
+
AttachedObject.Register(this);
|
|
965
|
+
|
|
966
|
+
if (this.sync) {
|
|
967
|
+
this.sync.fastMode = true;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
AttachedObject.Events.DidTake?.forEach(x => x(this, args));
|
|
971
|
+
|
|
972
|
+
return this;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
public free(): void {
|
|
976
|
+
if (!this.selected) return;
|
|
977
|
+
|
|
978
|
+
const args = { controller: this.controller, take: this.selected, hit: this.selected, sync: this.sync, interactable: null };
|
|
979
|
+
AttachedObject.Events.WillFree?.forEach(x => x(this, args));
|
|
980
|
+
|
|
981
|
+
Avatar_POI.Remove(this.controller!.context, this.selected);
|
|
982
|
+
AttachedObject.Remove(this);
|
|
983
|
+
|
|
984
|
+
if (this.sync) {
|
|
985
|
+
this.sync.fastMode = false;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const mesh = this.selectedMesh;
|
|
989
|
+
if (mesh && this.originalMaterial && mesh.material) {
|
|
990
|
+
mesh.material = this.originalMaterial;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
const object = this.selected;
|
|
994
|
+
// only attach the object back if it has a parent
|
|
995
|
+
// no parent means it was destroyed while holding it!
|
|
996
|
+
if (this.didReparent && object.parent) {
|
|
997
|
+
const prevParent = this.selectedParent;
|
|
998
|
+
if (prevParent) prevParent.attach(object);
|
|
999
|
+
else this.controller?.context.scene.attach(object);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
this.usageMarker?.destroy();
|
|
1003
|
+
|
|
1004
|
+
if (this.controller)
|
|
1005
|
+
this.controller.grabbed = null;
|
|
1006
|
+
this.selected = null;
|
|
1007
|
+
this.selectedParent = null;
|
|
1008
|
+
this.selectedMesh = null;
|
|
1009
|
+
this.sync = null;
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
// TODO: make throwing work again
|
|
1013
|
+
if (this.rigidbodies) {
|
|
1014
|
+
for (const rb of this.rigidbodies) {
|
|
1015
|
+
rb.wakeUp();
|
|
1016
|
+
rb.setVelocity(rb.smoothedVelocity);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
this.rigidbodies = null;
|
|
1020
|
+
|
|
1021
|
+
this.localPositionOffsetToGrab = null;
|
|
1022
|
+
this.quaternionLerp = null;
|
|
1023
|
+
|
|
1024
|
+
AttachedObject.Events.DidFree?.forEach(x => x(this, args));
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
public grabPoint: Vector3 = new Vector3();
|
|
1028
|
+
|
|
1029
|
+
private localPositionOffsetToGrab: Vector3 | null = null;
|
|
1030
|
+
private localPositionOffsetToGrab_worldSpace: Vector3 = new Vector3();
|
|
1031
|
+
private localQuaternionToGrab: Quaternion = new Quaternion(0, 0, 0, 1);
|
|
1032
|
+
private targetDir: Vector3 | null = null;
|
|
1033
|
+
private quaternionLerp: Quaternion | null = null;
|
|
1034
|
+
|
|
1035
|
+
private controllerDir = new Vector3();
|
|
1036
|
+
private controllerWorldPos = new Vector3();
|
|
1037
|
+
private lastControllerWorldPos = new Vector3();
|
|
1038
|
+
private controllerPosDelta = new Vector3();
|
|
1039
|
+
private totalChangeAlongDirection = 0.0;
|
|
1040
|
+
private rigPositionLastFrame = new Vector3();
|
|
1041
|
+
|
|
1042
|
+
private controllerMovementSinceLastFrame() {
|
|
1043
|
+
if (!this.positionSource || !this.controller) return 0.0;
|
|
1044
|
+
|
|
1045
|
+
// controller direction
|
|
1046
|
+
this.controllerDir.set(0, 0, -1);
|
|
1047
|
+
this.controllerDir.applyQuaternion(this.controller.rayRotation);
|
|
1048
|
+
|
|
1049
|
+
// controller delta
|
|
1050
|
+
getWorldPosition(this.positionSource, this.controllerWorldPos);
|
|
1051
|
+
this.controllerPosDelta.copy(this.controllerWorldPos);
|
|
1052
|
+
this.controllerPosDelta.sub(this.lastControllerWorldPos);
|
|
1053
|
+
this.lastControllerWorldPos.copy(this.controllerWorldPos);
|
|
1054
|
+
const rig = this.controller.webXR!.Rig;
|
|
1055
|
+
if (rig) {
|
|
1056
|
+
const rigPos = getWorldPosition(rig);
|
|
1057
|
+
const rigDelta = this.rigPositionLastFrame.sub(rigPos);
|
|
1058
|
+
this.controllerPosDelta.add(rigDelta);
|
|
1059
|
+
this.rigPositionLastFrame.copy(rigPos);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// calculate delta along direction
|
|
1063
|
+
const changeAlongControllerDirection = this.controllerDir.dot(this.controllerPosDelta);
|
|
1064
|
+
|
|
1065
|
+
return changeAlongControllerDirection;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
public update() {
|
|
1069
|
+
if (this.rigidbodies)
|
|
1070
|
+
for (const rb of this.rigidbodies)
|
|
1071
|
+
rb.resetVelocities();
|
|
1072
|
+
// TODO: add/use sync lost ownership event
|
|
1073
|
+
if (this.sync && this.controller && this.controller.context.connection.isInRoom) {
|
|
1074
|
+
const td = this.controller.context.time.time - this.grabTime;
|
|
1075
|
+
// if (time.frameCount % 60 === 0) {
|
|
1076
|
+
// console.log("ownership?", this.selected.name, this.sync.hasOwnership(), td)
|
|
1077
|
+
// }
|
|
1078
|
+
if (td > 3) {
|
|
1079
|
+
// if (time.frameCount % 60 === 0) {
|
|
1080
|
+
// console.log(this.sync.hasOwnership())
|
|
1081
|
+
// }
|
|
1082
|
+
if (this.sync.hasOwnership() === false) {
|
|
1083
|
+
console.log("no ownership, will leave", this.sync.guid);
|
|
1084
|
+
this.free();
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
if (this.interactable && !this.interactable.canGrab) return;
|
|
1089
|
+
|
|
1090
|
+
if (!this.didReparent && this.selected && this.controller) {
|
|
1091
|
+
|
|
1092
|
+
const rigScale = this.controller.webXR!.Rig?.scale.x ?? 1.0;
|
|
1093
|
+
|
|
1094
|
+
this.totalChangeAlongDirection += this.controllerMovementSinceLastFrame();
|
|
1095
|
+
// console.log(this.totalChangeAlongDirection);
|
|
1096
|
+
|
|
1097
|
+
// alert("yo: " + this.controller.webXR.Rig?.scale.x); // this is 0.1 on Hololens
|
|
1098
|
+
let currentDist = 1.0;
|
|
1099
|
+
if (this.controller.type === ControllerType.PhysicalDevice) // only for controllers and not on touch (AR touches are controllers)
|
|
1100
|
+
{
|
|
1101
|
+
currentDist = Math.max(0.0, 1 + this.totalChangeAlongDirection * 2.0 / rigScale);
|
|
1102
|
+
currentDist = currentDist * currentDist * currentDist;
|
|
1103
|
+
}
|
|
1104
|
+
if (this.grabDistance / rigScale < 0.8) currentDist = 1.0; // don't accelerate if this is a close grab, want full control
|
|
1105
|
+
|
|
1106
|
+
if (!this.targetDir) {
|
|
1107
|
+
this.targetDir = new Vector3();
|
|
1108
|
+
}
|
|
1109
|
+
this.targetDir.set(0, 0, -this.grabDistance * currentDist);
|
|
1110
|
+
const target = this.targetDir.applyQuaternion(this.controller.rayRotation).add(this.controllerWorldPos);
|
|
1111
|
+
|
|
1112
|
+
// apply rotation
|
|
1113
|
+
const targetQuat = this.controller.rayRotation.clone().multiplyQuaternions(this.controller.rayRotation, this.localQuaternionToGrab);
|
|
1114
|
+
if (!this.quaternionLerp) {
|
|
1115
|
+
this.quaternionLerp = targetQuat.clone();
|
|
1116
|
+
}
|
|
1117
|
+
this.quaternionLerp.slerp(targetQuat, this.controller.useSmoothing ? this.controller.context.time.deltaTime / .03 : 1.0);
|
|
1118
|
+
setWorldQuaternion(this.selected, this.quaternionLerp);
|
|
1119
|
+
this.selected.updateWorldMatrix(false, false); // necessary so that rotation is correct for the following position update
|
|
1120
|
+
|
|
1121
|
+
// apply position
|
|
1122
|
+
this.grabPoint.copy(target);
|
|
1123
|
+
// apply local grab offset
|
|
1124
|
+
if (this.localPositionOffsetToGrab) {
|
|
1125
|
+
this.localPositionOffsetToGrab_worldSpace.copy(this.localPositionOffsetToGrab);
|
|
1126
|
+
this.selected.localToWorld(this.localPositionOffsetToGrab_worldSpace).sub(getWorldPosition(this.selected));
|
|
1127
|
+
target.sub(this.localPositionOffsetToGrab_worldSpace);
|
|
1128
|
+
}
|
|
1129
|
+
setWorldPosition(this.selected, target);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
|
|
1133
|
+
if (this.rigidbodies != null) {
|
|
1134
|
+
for (const rb of this.rigidbodies) {
|
|
1135
|
+
rb.wakeUp();
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
InstancingUtil.markDirty(this.selected, true);
|
|
1140
|
+
}
|
|
1141
|
+
}
|