@@ -120,19 +120,21 @@
|
|
120
120
|
if (actionModel.tokenId === "StartAnimation") {
|
121
121
|
playAnimationActions.add(actionModel);
|
122
122
|
}
|
123
|
+
let actionType = actionModel.tokenId;
|
124
|
+
if (actionModel.type !== undefined) actionType += ":" + actionModel.type;
|
123
125
|
const affected = actionModel.affectedObjects;
|
124
126
|
if (affected) {
|
125
127
|
if (Array.isArray(affected)) {
|
126
128
|
for (const a of affected) {
|
127
129
|
actionTargets.add(a as Target);
|
128
130
|
//@ts-ignore
|
129
|
-
if (createMermaidGraphForDebugging) mermaidGraphTopLevel += `${actionModel.id}[${actionModel.id}] -- ${
|
131
|
+
if (createMermaidGraphForDebugging) mermaidGraphTopLevel += `${actionModel.id}[${actionModel.id}\n${actionType}] -- ${actionType} --> ${a.uuid}(("${a.displayName || a.name || a.uuid}"))\n`;
|
130
132
|
}
|
131
133
|
}
|
132
134
|
else if (typeof affected === "object") {
|
133
135
|
actionTargets.add(affected as Target);
|
134
136
|
//@ts-ignore
|
135
|
-
if (createMermaidGraphForDebugging) mermaidGraphTopLevel += `${actionModel.id}[${actionModel.id}] -- ${
|
137
|
+
if (createMermaidGraphForDebugging) mermaidGraphTopLevel += `${actionModel.id}[${actionModel.id}\n${actionType}] -- ${actionType} --> ${affected.uuid}(("${affected.displayName || affected.name || affected.uuid}"))\n`;
|
136
138
|
}
|
137
139
|
else if (typeof affected === "string") {
|
138
140
|
actionTargets.add({uuid: affected} as any as Target);
|
@@ -144,7 +146,7 @@
|
|
144
146
|
if (typeof xform === "object") {
|
145
147
|
actionTargets.add(xform as Target);
|
146
148
|
//@ts-ignore
|
147
|
-
if (createMermaidGraphForDebugging) mermaidGraphTopLevel += `${actionModel.id}[${actionModel.id}] -- ${
|
149
|
+
if (createMermaidGraphForDebugging) mermaidGraphTopLevel += `${actionModel.id}[${actionModel.id}\n${actionType}] -- ${actionType} --> ${xform.uuid}(("${xform.displayName || xform.name || xform.uuid}"))\n`;
|
148
150
|
}
|
149
151
|
else if (typeof xform === "string") {
|
150
152
|
actionTargets.add({uuid: xform} as any as Target);
|
@@ -159,19 +161,21 @@
|
|
159
161
|
collectTrigger(t, action);
|
160
162
|
}
|
161
163
|
else if (trigger instanceof TriggerModel) {
|
164
|
+
let triggerType = trigger.tokenId;
|
165
|
+
if (trigger.type !== undefined) triggerType += ":" + trigger.type;
|
162
166
|
if (typeof trigger.targetId === "object") {
|
163
167
|
triggerSources.add(trigger.targetId as Target);
|
164
168
|
//@ts-ignore
|
165
|
-
if (createMermaidGraphForDebugging) mermaidGraphTopLevel += `${trigger.targetId.uuid}(("${trigger.targetId.displayName}")) --> ${trigger.id}[${trigger.id}]\n`;
|
169
|
+
if (createMermaidGraphForDebugging) mermaidGraphTopLevel += `${trigger.targetId.uuid}(("${trigger.targetId.displayName}")) --> ${trigger.id}[${trigger.id}\n${triggerType}]\n`;
|
166
170
|
}
|
167
171
|
//@ts-ignore
|
168
|
-
if (createMermaidGraphForDebugging) mermaidGraph += `${trigger.id}((${trigger.id})) -- ${
|
172
|
+
if (createMermaidGraphForDebugging) mermaidGraph += `${trigger.id}((${trigger.id})) -- ${triggerType} --> ${action.id}[${action.tokenId || action.id}]\n`;
|
169
173
|
}
|
170
174
|
}
|
171
175
|
|
172
176
|
// collect all targets of all triggers and actions
|
173
177
|
for (const beh of this.behaviours) {
|
174
|
-
if (createMermaidGraphForDebugging) mermaidGraph += `subgraph
|
178
|
+
if (createMermaidGraphForDebugging) mermaidGraph += `subgraph ${beh.id}\n`;
|
175
179
|
collectAction(beh.action);
|
176
180
|
collectTrigger(beh.trigger, beh.action);
|
177
181
|
if (createMermaidGraphForDebugging) mermaidGraph += `end\n`;
|
@@ -180,10 +184,12 @@
|
|
180
184
|
|
181
185
|
if (createMermaidGraphForDebugging) {
|
182
186
|
console.log("All USDZ behaviours", this.behaviours);
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
+
if (this.behaviours.length) {
|
188
|
+
console.warn("The Mermaid graph can be pasted into https://massive-mermaid.glitch.me/ or https://mermaid.live/edit. It should be in your clipboard already!");
|
189
|
+
console.log(mermaidGraph);
|
190
|
+
// copy to clipboard, can be pasted into https://massive-mermaid.glitch.me/ or https://mermaid.live/edit
|
191
|
+
navigator.clipboard.writeText(mermaidGraph);
|
192
|
+
}
|
187
193
|
}
|
188
194
|
|
189
195
|
{
|
@@ -212,7 +218,7 @@
|
|
212
218
|
}
|
213
219
|
}
|
214
220
|
|
215
|
-
if (createMermaidGraphForDebugging) {
|
221
|
+
if (createMermaidGraphForDebugging && playAnimationActions.size) {
|
216
222
|
console.log(animationsGraph);
|
217
223
|
}
|
218
224
|
|
@@ -17,7 +17,7 @@
|
|
17
17
|
import { AnimationExtension, RegisteredAnimationInfo, type UsdzAnimation } from "../Animation.js";
|
18
18
|
import { AudioExtension } from "./AudioExtension.js";
|
19
19
|
import type { BehaviorExtension, UsdzBehaviour } from "./Behaviour.js";
|
20
|
-
import { ActionBuilder, ActionModel, BehaviorModel, EmphasizeActionMotionType,type IBehaviorElement, Target, TriggerBuilder } from "./BehavioursBuilder.js";
|
20
|
+
import { ActionBuilder, ActionModel, BehaviorModel, EmphasizeActionMotionType,GroupActionModel,type IBehaviorElement, Target, TriggerBuilder } from "./BehavioursBuilder.js";
|
21
21
|
|
22
22
|
const debug = getParam("debugusdzbehaviours");
|
23
23
|
|
@@ -203,10 +203,10 @@
|
|
203
203
|
}
|
204
204
|
|
205
205
|
onPointerEnter(_args: PointerEventData) {
|
206
|
-
this.context.input.
|
206
|
+
this.context.input.setCursor("pointer");
|
207
207
|
}
|
208
208
|
onPointerExit(_: PointerEventData) {
|
209
|
-
this.context.input.
|
209
|
+
this.context.input.unsetCursor("pointer");
|
210
210
|
}
|
211
211
|
onPointerClick(args: PointerEventData) {
|
212
212
|
args.use();
|
@@ -251,8 +251,9 @@
|
|
251
251
|
private targetModels!: USDObject[];
|
252
252
|
|
253
253
|
private static _materialTriggersPerId: { [key: string]: ChangeMaterialOnClick[] } = {}
|
254
|
+
private static _startHiddenBehaviour: BehaviorModel | null = null;
|
255
|
+
private static _parallelStartHiddenActions: USDObject[] = [];
|
254
256
|
|
255
|
-
|
256
257
|
async beforeCreateDocument(_ext: BehaviorExtension, _context) {
|
257
258
|
this.targetModels = [];
|
258
259
|
ChangeMaterialOnClick._materialTriggersPerId = {}
|
@@ -309,23 +310,25 @@
|
|
309
310
|
}
|
310
311
|
|
311
312
|
private createAndAttachBehaviors(ext: BehaviorExtension, myVariants: Array<USDObject>, otherVariants: Array<USDObject>) {
|
312
|
-
const start: ActionModel[] = [];
|
313
313
|
const select: ActionModel[] = [];
|
314
314
|
|
315
315
|
const fadeDuration = Math.max(0, this.fadeDuration);
|
316
316
|
|
317
317
|
select.push(ActionBuilder.fadeAction([...this.targetModels, ...otherVariants], fadeDuration, false));
|
318
|
-
start.push(ActionBuilder.fadeAction(myVariants, fadeDuration, false));
|
319
318
|
select.push(ActionBuilder.fadeAction(myVariants, fadeDuration, true));
|
320
319
|
|
321
320
|
ext.addBehavior(new BehaviorModel("Select_" + this.selfModel.name,
|
322
321
|
TriggerBuilder.tapTrigger(this.selfModel),
|
323
322
|
ActionBuilder.parallel(...select))
|
324
323
|
);
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
324
|
+
ChangeMaterialOnClick._parallelStartHiddenActions.push(...myVariants);
|
325
|
+
if (!ChangeMaterialOnClick._startHiddenBehaviour) {
|
326
|
+
ChangeMaterialOnClick._startHiddenBehaviour =
|
327
|
+
new BehaviorModel("StartHidden_" + this.selfModel.name,
|
328
|
+
TriggerBuilder.sceneStartTrigger(),
|
329
|
+
ActionBuilder.fadeAction(ChangeMaterialOnClick._parallelStartHiddenActions, fadeDuration, false));
|
330
|
+
ext.addBehavior(ChangeMaterialOnClick._startHiddenBehaviour);
|
331
|
+
}
|
329
332
|
}
|
330
333
|
|
331
334
|
private static getMaterialName(material: Material) {
|
@@ -526,9 +529,9 @@
|
|
526
529
|
sequence.push(ActionBuilder.fadeAction(selfModel, 0, false));
|
527
530
|
sequence.push(ActionBuilder.fadeAction(this.targetModel, 0, targetState));
|
528
531
|
|
529
|
-
ext.addBehavior(new BehaviorModel("Toggle_" + selfModel.name + "
|
532
|
+
ext.addBehavior(new BehaviorModel("Toggle_" + selfModel.name + "_ToggleTo" + (targetState ? "On" : "Off"),
|
530
533
|
TriggerBuilder.tapTrigger(selfModel),
|
531
|
-
ActionBuilder.parallel(...sequence)
|
534
|
+
sequence.length > 1 ? ActionBuilder.parallel(...sequence) : sequence[0],
|
532
535
|
));
|
533
536
|
}
|
534
537
|
// We have a toggleModel, so we need to set up two sequences:
|
@@ -540,7 +543,7 @@
|
|
540
543
|
toggleSequence.push(ActionBuilder.fadeAction(this.toggleModel, 0, true));
|
541
544
|
toggleSequence.push(ActionBuilder.fadeAction(this.targetModel, 0, targetState));
|
542
545
|
|
543
|
-
ext.addBehavior(new BehaviorModel("Toggle_" + selfModel.name + "
|
546
|
+
ext.addBehavior(new BehaviorModel("Toggle_" + selfModel.name + "_ToggleTo" + (targetState ? "On" : "Off"),
|
544
547
|
TriggerBuilder.tapTrigger(selfModel),
|
545
548
|
ActionBuilder.parallel(...toggleSequence)
|
546
549
|
));
|
@@ -550,7 +553,7 @@
|
|
550
553
|
reverseSequence.push(ActionBuilder.fadeAction(selfModel, 0, true));
|
551
554
|
reverseSequence.push(ActionBuilder.fadeAction(this.targetModel, 0, !targetState));
|
552
555
|
|
553
|
-
ext.addBehavior(new BehaviorModel("Toggle_" + selfModel.name + "
|
556
|
+
ext.addBehavior(new BehaviorModel("Toggle_" + selfModel.name + "_ToggleTo" + (!targetState ? "On" : "Off"),
|
554
557
|
TriggerBuilder.tapTrigger(this.toggleModel),
|
555
558
|
ActionBuilder.parallel(...reverseSequence)
|
556
559
|
));
|
@@ -565,10 +568,7 @@
|
|
565
568
|
if (this.toggleModel)
|
566
569
|
objectsToHide.push(this.toggleModel);
|
567
570
|
|
568
|
-
|
569
|
-
TriggerBuilder.sceneStartTrigger(),
|
570
|
-
ActionBuilder.fadeAction(objectsToHide, 0, false)
|
571
|
-
));
|
571
|
+
HideOnStart.add(objectsToHide, ext);
|
572
572
|
}
|
573
573
|
}
|
574
574
|
|
@@ -595,6 +595,27 @@
|
|
595
595
|
*/
|
596
596
|
export class HideOnStart extends Behaviour implements UsdzBehaviour {
|
597
597
|
|
598
|
+
private static _fadeBehaviour?: BehaviorModel;
|
599
|
+
private static _fadeObjects: Array<USDObject | Object3D> = [];
|
600
|
+
|
601
|
+
static add(target: Target, ext: BehaviorExtension) {
|
602
|
+
const arr = Array.isArray(target) ? target : [target];
|
603
|
+
for (const entry of arr) {
|
604
|
+
if (!HideOnStart._fadeObjects.includes(entry)) {
|
605
|
+
console.log("adding hide on start", entry);
|
606
|
+
HideOnStart._fadeObjects.push(entry);
|
607
|
+
}
|
608
|
+
}
|
609
|
+
if (HideOnStart._fadeBehaviour === undefined) {
|
610
|
+
HideOnStart._fadeBehaviour = new BehaviorModel("HideOnStart",
|
611
|
+
TriggerBuilder.sceneStartTrigger(),
|
612
|
+
//@ts-ignore
|
613
|
+
ActionBuilder.fadeAction(HideOnStart._fadeObjects, 0, false)
|
614
|
+
);
|
615
|
+
ext.addBehavior(HideOnStart._fadeBehaviour);
|
616
|
+
}
|
617
|
+
}
|
618
|
+
|
598
619
|
start() {
|
599
620
|
GameObject.setActive(this.gameObject, false);
|
600
621
|
}
|
@@ -603,10 +624,7 @@
|
|
603
624
|
if (model.uuid === this.gameObject.uuid) {
|
604
625
|
// we only want to mark the object as HideOnStart if it's still hidden
|
605
626
|
if (!this.wasVisible) {
|
606
|
-
|
607
|
-
TriggerBuilder.sceneStartTrigger(),
|
608
|
-
ActionBuilder.fadeAction(model, 0, false)
|
609
|
-
));
|
627
|
+
HideOnStart.add(model, ext);
|
610
628
|
}
|
611
629
|
}
|
612
630
|
}
|
@@ -752,7 +770,7 @@
|
|
752
770
|
// automatically play audio on start too if the referenced AudioSource has playOnAwake enabled
|
753
771
|
if (this.target && this.target.playOnAwake && this.target.enabled) {
|
754
772
|
if (anyChildHasGeometry && this.trigger === "tap") {
|
755
|
-
//
|
773
|
+
// WORKAROUND Currently (20240509) we MUST not emit this behaviour if we're also expecting the tap trigger to work.
|
756
774
|
// Seems to be a regression in QuickLook... audio clips can't be stopped anymore as soon as they start playing.
|
757
775
|
console.warn("USDZExport: Audio sources that are played on tap can't also auto-play at scene start due to a QuickLook bug.");
|
758
776
|
}
|
@@ -199,11 +199,14 @@
|
|
199
199
|
|
200
200
|
export class TriggerBuilder {
|
201
201
|
|
202
|
+
private static __sceneStartTrigger?: TriggerModel;
|
203
|
+
|
202
204
|
static sceneStartTrigger(): TriggerModel {
|
203
|
-
|
204
|
-
trigger
|
205
|
+
if (this.__sceneStartTrigger !== undefined) return this.__sceneStartTrigger;
|
206
|
+
const trigger = new TriggerModel(undefined, "SceneStart");
|
205
207
|
trigger.tokenId = "SceneTransition";
|
206
208
|
trigger.type = "enter";
|
209
|
+
this.__sceneStartTrigger = trigger;
|
207
210
|
return trigger;
|
208
211
|
}
|
209
212
|
|
@@ -90,7 +90,7 @@
|
|
90
90
|
else if (this.transition === Transition.ColorTint && this.colors) {
|
91
91
|
this._image?.setState("hovered");
|
92
92
|
}
|
93
|
-
if (canSetCursor) this.context.input.
|
93
|
+
if (canSetCursor) this.context.input.setCursor("pointer");
|
94
94
|
}
|
95
95
|
|
96
96
|
onPointerExit() {
|
@@ -107,7 +107,7 @@
|
|
107
107
|
else if (this.transition === Transition.ColorTint && this.colors) {
|
108
108
|
this._image?.setState("normal");
|
109
109
|
}
|
110
|
-
this.context.input.
|
110
|
+
this.context.input.unsetCursor("pointer");
|
111
111
|
}
|
112
112
|
|
113
113
|
onPointerDown(_) {
|
@@ -211,7 +211,7 @@
|
|
211
211
|
}
|
212
212
|
|
213
213
|
onDestroy(): void {
|
214
|
-
if (this._isHovered) this.context.input.
|
214
|
+
if (this._isHovered) this.context.input.unsetCursor("pointer");
|
215
215
|
}
|
216
216
|
|
217
217
|
private _requestedAnimatorTrigger?: string;
|
@@ -105,14 +105,14 @@
|
|
105
105
|
if (!this.object) return;
|
106
106
|
if (!this.context.connection.allowEditing) return;
|
107
107
|
if (args.button !== 0) return;
|
108
|
-
this.context.input.
|
108
|
+
this.context.input.setCursor("pointer");
|
109
109
|
}
|
110
110
|
onPointerExit(args: PointerEventData) {
|
111
111
|
if (args.used) return;
|
112
112
|
if (!this.object) return;
|
113
113
|
if (!this.context.connection.allowEditing) return;
|
114
114
|
if (args.button !== 0) return;
|
115
|
-
this.context.input.
|
115
|
+
this.context.input.unsetCursor("pointer");
|
116
116
|
}
|
117
117
|
|
118
118
|
/** @internal */
|
@@ -3,7 +3,7 @@
|
|
3
3
|
import { showBalloonMessage, showBalloonWarning } from './debug/debug.js';
|
4
4
|
import { Context } from './engine_setup.js';
|
5
5
|
import { getTempVector, getWorldQuaternion } from './engine_three_utils.js';
|
6
|
-
import type { ButtonName, IGameObject, IInput, MouseButtonName, Vec2 } from './engine_types.js';
|
6
|
+
import type { ButtonName, CursorTypeName, IGameObject, IInput, MouseButtonName, Vec2 } from './engine_types.js';
|
7
7
|
import { DeviceUtilities, type EnumToPrimitiveUnion, getParam } from './engine_utils.js';
|
8
8
|
|
9
9
|
const debug = getParam("debuginput");
|
@@ -394,21 +394,46 @@
|
|
394
394
|
get mouseWheelChanged(): boolean { return this.getMouseWheelChanged(0); }
|
395
395
|
|
396
396
|
/** Is the primary pointer double clicked (usually the left button). This is equivalent to `input.mouseDoubleClick` */
|
397
|
-
get click()
|
397
|
+
get click(): boolean { return this._pointerClick[0]; }
|
398
398
|
/** Was a double click detected for the primary pointer? */
|
399
|
-
get doubleClick()
|
399
|
+
get doubleClick(): boolean { return this._pointerDoubleClick[0]; }
|
400
400
|
|
401
|
-
private
|
401
|
+
private readonly _setCursorTypes: CursorTypeName[] = [];
|
402
402
|
|
403
|
+
/** @deprecated use setCursor("pointer") */
|
403
404
|
setCursorPointer() {
|
404
|
-
this.
|
405
|
-
this.context.domElement.style.cursor = "pointer";
|
405
|
+
this.setCursor("pointer");
|
406
406
|
}
|
407
|
+
/** @deprecated use unsetCursor() */
|
407
408
|
setCursorNormal() {
|
408
|
-
this.
|
409
|
-
|
410
|
-
|
409
|
+
this.unsetCursor("pointer");
|
410
|
+
}
|
411
|
+
/**
|
412
|
+
* Set a custom cursor. This will set the cursor type until unsetCursor is called
|
413
|
+
*/
|
414
|
+
setCursor(type: CursorTypeName) {
|
415
|
+
this._setCursorTypes.push(type);
|
416
|
+
if (this._setCursorTypes.length > 10) {
|
417
|
+
this._setCursorTypes.shift();
|
418
|
+
}
|
419
|
+
this.updateCursor();
|
420
|
+
}
|
421
|
+
/**
|
422
|
+
* Unset a custom cursor. This will set the cursor type to the previous type or default
|
423
|
+
*/
|
424
|
+
unsetCursor(type: CursorTypeName) {
|
425
|
+
for (let i = this._setCursorTypes.length - 1; i >= 0; i--) {
|
426
|
+
if (this._setCursorTypes[i] === type) {
|
427
|
+
this._setCursorTypes.splice(i, 1);
|
428
|
+
this.updateCursor();
|
429
|
+
break;
|
430
|
+
}
|
431
|
+
}
|
432
|
+
}
|
433
|
+
private updateCursor() {
|
434
|
+
if (this._setCursorTypes?.length == 0)
|
411
435
|
this.context.domElement.style.cursor = "default";
|
436
|
+
else this.context.domElement.style.cursor = this._setCursorTypes[this._setCursorTypes.length - 1];
|
412
437
|
}
|
413
438
|
|
414
439
|
/**
|
@@ -756,6 +781,10 @@
|
|
756
781
|
window.removeEventListener('pointerup', this.onPointerUp);
|
757
782
|
window.removeEventListener('pointercancel', this.onPointerCancel);
|
758
783
|
|
784
|
+
window.removeEventListener("touchstart", this.onTouchStart);
|
785
|
+
window.removeEventListener("touchmove", this.onTouchMove);
|
786
|
+
window.removeEventListener("touchend", this.onTouchEnd);
|
787
|
+
|
759
788
|
this._htmlEventSource?.removeEventListener('wheel', this.onMouseWheel, false);
|
760
789
|
window.removeEventListener("wheel", this.onWheelWindow, false);
|
761
790
|
|
@@ -778,7 +807,10 @@
|
|
778
807
|
}
|
779
808
|
}
|
780
809
|
|
810
|
+
private readonly _receivedPointerMoveEventsThisFrame = new Array<number>;
|
811
|
+
|
781
812
|
private onEndOfFrame = () => {
|
813
|
+
this._receivedPointerMoveEventsThisFrame.length = 0;
|
782
814
|
for (let i = 0; i < this._pointerUp.length; i++)
|
783
815
|
this._pointerUp[i] = false;
|
784
816
|
for (let i = 0; i < this._pointerDown.length; i++)
|
@@ -901,8 +933,14 @@
|
|
901
933
|
}
|
902
934
|
private onPointerMove = (evt: PointerEvent) => {
|
903
935
|
if (this.context.isInAR) return;
|
936
|
+
|
937
|
+
// Prevent multiple pointerMove events per frame
|
938
|
+
if (this._receivedPointerMoveEventsThisFrame.includes(evt.pointerId)) return;
|
939
|
+
this._receivedPointerMoveEventsThisFrame.push(evt.pointerId);
|
940
|
+
|
904
941
|
// We want to keep receiving move events until pointerUp and not stop handling events just because we're hovering over *some* HTML element
|
905
942
|
// if (this.canReceiveInput(evt) === false) return;
|
943
|
+
|
906
944
|
let button = evt.button;
|
907
945
|
if (evt.pointerType === "mouse") {
|
908
946
|
const pressedButton = this.getFirstPressedButtonForPointer(0);
|
@@ -1,8 +1,8 @@
|
|
1
|
-
import { AnimationClip, Material, Mesh, Object3D, Texture } from "three";
|
1
|
+
import { AnimationClip, BufferGeometry, InstancedBufferGeometry, Material, Mesh, Object3D, Texture, WireframeGeometry } from "three";
|
2
2
|
import { type GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
|
3
3
|
|
4
4
|
import { debugExtension } from "../engine/engine_default_parameters.js";
|
5
|
-
import { addLog,LogType } from "./debug/debug_overlay.js";
|
5
|
+
import { addLog, LogType } from "./debug/debug_overlay.js";
|
6
6
|
import { isLocalNetwork } from "./engine_networking_utils.js";
|
7
7
|
import { Context } from "./engine_setup.js";
|
8
8
|
import type { Constructor, ConstructorConcrete, SourceIdentifier } from "./engine_types.js";
|
@@ -515,8 +515,17 @@
|
|
515
515
|
|
516
516
|
// e.g. when @serializable(Texture) and the texture is already resolved via json pointer from gltf
|
517
517
|
// then we dont need to do anything else
|
518
|
-
if (!typeIsFunction && currentValue
|
519
|
-
|
518
|
+
if (!typeIsFunction && currentValue) {
|
519
|
+
if (currentValue instanceof Material) return currentValue;
|
520
|
+
if (currentValue instanceof Texture) return currentValue;
|
521
|
+
if (currentValue instanceof Mesh) return currentValue;
|
522
|
+
if (currentValue instanceof BufferGeometry) return currentValue;
|
523
|
+
if (currentValue instanceof AnimationClip) return currentValue;
|
524
|
+
}
|
525
|
+
// Removed this line because it prevents assigning serialized values to existing instances for e.g. EventList
|
526
|
+
// https://linear.app/needle/issue/NE-5350
|
527
|
+
// if (!typeIsFunction && currentValue instanceof type) return currentValue;
|
528
|
+
|
520
529
|
// try to resolve the serializer for a type only once
|
521
530
|
if (!typeContext) {
|
522
531
|
typeContext = {
|
@@ -528,6 +528,11 @@
|
|
528
528
|
debugRenderRaycasts: boolean;
|
529
529
|
}
|
530
530
|
|
531
|
+
/**
|
532
|
+
* Available cursor types
|
533
|
+
* @link https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
|
534
|
+
*/
|
535
|
+
export type CursorTypeName = "auto" | "default" | "none" | "context-menu" | "help" | "pointer" | "progress" | "wait" | "cell" | "crosshair" | "text" | "vertical-text" | "alias" | "copy" | "move" | "no-drop" | "not-allowed" | "grab" | "grabbing" | "all-scroll" | "col-resize" | "row-resize" | "n-resize" | "e-resize" | "s-resize" | "w-resize" | "nw-resize" | "se-resize" | "sw-resize" | "ew-resize" | "ns-resize" | "nesw-resize" | "nwse-resize" | "zoom-in" | "zoom-out";
|
531
536
|
|
532
537
|
/** Typical mouse button names for most devices */
|
533
538
|
export type MouseButtonName = "left" | "right" | "middle";
|
@@ -1,11 +1,12 @@
|
|
1
|
-
import { type Intersection, Mesh,Object3D } from "three";
|
1
|
+
import { type Intersection, Mesh, Object3D } from "three";
|
2
2
|
|
3
3
|
import { isDevEnvironment, showBalloonMessage } from "../../engine/debug/index.js";
|
4
|
-
import { type InputEventNames, InputEvents, NEPointerEvent, NEPointerEventIntersection,PointerType } from "../../engine/engine_input.js";
|
4
|
+
import { type InputEventNames, InputEvents, NEPointerEvent, NEPointerEventIntersection, PointerType } from "../../engine/engine_input.js";
|
5
|
+
import { onInitialized } from "../../engine/engine_lifecycle_api.js";
|
5
6
|
import { Mathf } from "../../engine/engine_math.js";
|
6
7
|
import { RaycastOptions, type RaycastTestObjectReturnType } from "../../engine/engine_physics.js";
|
7
8
|
import { Context } from "../../engine/engine_setup.js";
|
8
|
-
import
|
9
|
+
import { HideFlags, type IComponent } from "../../engine/engine_types.js";
|
9
10
|
import { getParam } from "../../engine/engine_utils.js";
|
10
11
|
import { Behaviour, GameObject } from "../Component.js";
|
11
12
|
import { $shadowDomOwner } from "./BaseUIComponent.js";
|
@@ -30,47 +31,15 @@
|
|
30
31
|
|
31
32
|
declare type IComponentCanMaybeReceiveEvents = IPointerEventHandler & IComponent & { interactable?: boolean };
|
32
33
|
|
34
|
+
onInitialized((ctx) => {
|
35
|
+
EventSystem.createIfNoneExists(ctx);
|
36
|
+
})
|
37
|
+
|
33
38
|
/**
|
34
39
|
* @category User Interface
|
35
40
|
* @group Components
|
36
41
|
*/
|
37
42
|
export class EventSystem extends Behaviour {
|
38
|
-
private static _eventSystemMap = new Map<Context, EventSystem[]>();
|
39
|
-
|
40
|
-
static didSearchEventSystem: boolean = false;
|
41
|
-
static createIfNoneExists(context: Context) {
|
42
|
-
if (!this.didSearchEventSystem) {
|
43
|
-
this.didSearchEventSystem = true;
|
44
|
-
if (EventSystem.systems.length <= 0) {
|
45
|
-
EventSystem.systems.push(...GameObject.findObjectsOfType(EventSystem, context));
|
46
|
-
}
|
47
|
-
}
|
48
|
-
for (const sys of EventSystem.systems) {
|
49
|
-
if (sys.context === context) return; // exists
|
50
|
-
}
|
51
|
-
const go = new Object3D();
|
52
|
-
GameObject.addComponent(go, EventSystem);
|
53
|
-
context.scene.add(go);
|
54
|
-
}
|
55
|
-
|
56
|
-
static get systems(): EventSystem[] {
|
57
|
-
const context = Context.Current;
|
58
|
-
if (!this._eventSystemMap.has(context)) {
|
59
|
-
this._eventSystemMap.set(context, []);
|
60
|
-
}
|
61
|
-
return this._eventSystemMap.get(context)!;
|
62
|
-
}
|
63
|
-
|
64
|
-
static get(ctx: Context): EventSystem | null {
|
65
|
-
const systems = this._eventSystemMap.get(ctx);
|
66
|
-
if (systems && systems.length > 0) return systems[0];
|
67
|
-
return null;
|
68
|
-
}
|
69
|
-
|
70
|
-
static get instance(): EventSystem | null {
|
71
|
-
return this.systems[0];
|
72
|
-
}
|
73
|
-
|
74
43
|
//@ts-ignore
|
75
44
|
static ensureUpdateMeshUI(instance, context: Context, force: boolean = false) {
|
76
45
|
MeshUIHelper.update(instance, context, force);
|
@@ -79,32 +48,22 @@
|
|
79
48
|
MeshUIHelper.markDirty();
|
80
49
|
}
|
81
50
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
EventSystem.systems.push(this);
|
51
|
+
static createIfNoneExists(context: Context) {
|
52
|
+
if (!context.scene.getComponent(EventSystem)) {
|
53
|
+
context.scene.addComponent(EventSystem);
|
54
|
+
}
|
87
55
|
}
|
88
56
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
onDestroy(): void {
|
93
|
-
EventSystem.systems.splice(EventSystem.systems.indexOf(this), 1);
|
57
|
+
static get(ctx: Context): EventSystem | null {
|
58
|
+
this.createIfNoneExists(ctx);
|
59
|
+
return ctx.scene.getComponent(EventSystem);
|
94
60
|
}
|
95
61
|
|
96
|
-
|
97
|
-
|
98
|
-
const res = GameObject.findObjectOfType(Raycaster, this.context);
|
99
|
-
if (!res) {
|
100
|
-
const rc = GameObject.addComponent(this.context.scene, ObjectRaycaster);
|
101
|
-
this.raycaster.push(rc);
|
102
|
-
if (isDevEnvironment() || debug)
|
103
|
-
console.warn("Added an ObjectRaycaster to the scene because no raycaster was found.");
|
104
|
-
}
|
105
|
-
}
|
62
|
+
static get instance(): EventSystem | null {
|
63
|
+
return this.get(Context.Current);
|
106
64
|
}
|
107
65
|
|
66
|
+
private readonly raycaster: Raycaster[] = [];
|
108
67
|
register(rc: Raycaster) {
|
109
68
|
if (rc && this.raycaster && !this.raycaster.includes(rc))
|
110
69
|
this.raycaster?.push(rc);
|
@@ -116,6 +75,23 @@
|
|
116
75
|
}
|
117
76
|
}
|
118
77
|
|
78
|
+
get hasActiveUI() { return this.currentActiveMeshUIComponents.length > 0; }
|
79
|
+
get isHoveringObjects() { return this.hoveredByID.size > 0; }
|
80
|
+
|
81
|
+
awake(): void {
|
82
|
+
// We only want ONE eventsystem on the root scene
|
83
|
+
// as long as this component is not implemented in core we need to check this here
|
84
|
+
if (this.gameObject as Object3D !== this.context.scene) {
|
85
|
+
this.enabled = false;
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
start() {
|
90
|
+
if (!this.context.scene.getComponent(Raycaster)) {
|
91
|
+
this.context.scene.addComponent(ObjectRaycaster);
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
119
95
|
onEnable(): void {
|
120
96
|
this.context.input.addEventListener(InputEvents.PointerDown, this.onPointerEvent);
|
121
97
|
this.context.input.addEventListener(InputEvents.PointerUp, this.onPointerEvent);
|
@@ -183,7 +159,6 @@
|
|
183
159
|
options.screenPoint = this.context.input.getPointerPositionRC(pointerEvent.pointerId)!;
|
184
160
|
}
|
185
161
|
|
186
|
-
|
187
162
|
const hits = this.performRaycast(options) as Array<NEPointerEventIntersection>;
|
188
163
|
if (hits) {
|
189
164
|
for (const hit of hits) {
|
@@ -242,6 +217,14 @@
|
|
242
217
|
// TODO: this implementation below should be removed and we should regularly raycast objects in the scene unless marked as "do not raycast"
|
243
218
|
// with the introduction of the mesh-bvh based raycasting the performance impact should be greatly reduced. But this needs further testing
|
244
219
|
|
220
|
+
const raycasterOnObject = obj && "getComponent" in obj ? obj.getComponent(Raycaster) : null;
|
221
|
+
if (raycasterOnObject && raycasterOnObject != this._currentlyActiveRaycaster) {
|
222
|
+
return false;
|
223
|
+
}
|
224
|
+
// if (this._currentPointerEventName == "pointermove") {
|
225
|
+
// console.log(this.context.time.frame, obj.name, obj.type, obj.guid)
|
226
|
+
// }
|
227
|
+
|
245
228
|
// check if this object is actually a UI shadow hierarchy object
|
246
229
|
let uiOwner: Object3D | null = null;
|
247
230
|
const isUI = isUIObject(obj);
|
@@ -450,7 +433,7 @@
|
|
450
433
|
isShadow = true;
|
451
434
|
}
|
452
435
|
}
|
453
|
-
|
436
|
+
|
454
437
|
// adding this to have a way for allowing to receive events on TMUI elements without shadow hierarchy
|
455
438
|
// if(parent["needle:use_eventsystem"] == true){
|
456
439
|
// // if use_eventsystem is true, we want to handle the event
|
@@ -671,7 +654,7 @@
|
|
671
654
|
comp[symbol] = state;
|
672
655
|
return true;
|
673
656
|
}
|
674
|
-
else
|
657
|
+
else {
|
675
658
|
if (!state || !state.includes(pointerId)) return false;
|
676
659
|
const i = state.indexOf(pointerId);
|
677
660
|
if (i !== -1) {
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { serializable } from "../engine/engine_serialization.js";
|
2
|
+
import { EventSystem } from "./api.js";
|
2
3
|
import { Behaviour } from "./Component.js"
|
3
4
|
import { EventList } from "./EventList.js";
|
4
5
|
import { EventType } from "./EventType.js"
|
@@ -33,6 +34,13 @@
|
|
33
34
|
}
|
34
35
|
}
|
35
36
|
|
37
|
+
private hasTrigger(type: EventType) {
|
38
|
+
return this.triggers?.some(t => t.eventID === type) ?? false;
|
39
|
+
}
|
40
|
+
private shouldChangeCursor() {
|
41
|
+
return this.hasTrigger(EventType.PointerClick) || this.hasTrigger(EventType.PointerDown) || this.hasTrigger(EventType.PointerUp);
|
42
|
+
}
|
43
|
+
|
36
44
|
/** @internal */
|
37
45
|
onPointerClick(_: PointerEventData) {
|
38
46
|
this.invoke(EventType.PointerClick);
|
@@ -40,11 +48,17 @@
|
|
40
48
|
|
41
49
|
/** @internal */
|
42
50
|
onPointerEnter(_: PointerEventData) {
|
51
|
+
if (this.shouldChangeCursor()) {
|
52
|
+
this.context.input.setCursor("pointer");
|
53
|
+
}
|
43
54
|
this.invoke(EventType.PointerEnter);
|
44
55
|
}
|
45
56
|
|
46
57
|
/** @internal */
|
47
58
|
onPointerExit(_: PointerEventData) {
|
59
|
+
if (this.shouldChangeCursor()) {
|
60
|
+
this.context.input.unsetCursor("pointer");
|
61
|
+
}
|
48
62
|
this.invoke(EventType.PointerExit);
|
49
63
|
}
|
50
64
|
|
@@ -3,7 +3,7 @@
|
|
3
3
|
import { DeviceUtilities, getParam } from "../../engine/engine_utils.js";
|
4
4
|
import { Behaviour, GameObject } from "../Component.js";
|
5
5
|
import { EventList } from "../EventList.js";
|
6
|
-
import { type IPointerEventHandler } from "./PointerEvents.js";
|
6
|
+
import { type IPointerEventHandler,PointerEventData } from "./PointerEvents.js";
|
7
7
|
import { Text } from "./Text.js";
|
8
8
|
import { tryGetUIComponent } from "./Utils.js";
|
9
9
|
|
@@ -113,6 +113,14 @@
|
|
113
113
|
this.onDeselected();
|
114
114
|
}
|
115
115
|
|
116
|
+
onPointerEnter(_args: PointerEventData) {
|
117
|
+
const canSetCursor = _args.event.pointerType === "mouse" && _args.button === 0;
|
118
|
+
if(canSetCursor) this.context.input.setCursor("text");
|
119
|
+
}
|
120
|
+
onPointerExit(_args: PointerEventData) {
|
121
|
+
this.context.input.unsetCursor("text")
|
122
|
+
}
|
123
|
+
|
116
124
|
onPointerClick(_args) {
|
117
125
|
if (debug) console.log("CLICK", _args, InputField.active);
|
118
126
|
InputField.activeTime = this.context.time.time;
|
@@ -548,14 +548,14 @@
|
|
548
548
|
}
|
549
549
|
|
550
550
|
onPointerEnter() {
|
551
|
-
this.context.input.
|
551
|
+
this.context.input.setCursor("pointer");
|
552
552
|
if (this.allowModifyUI) {
|
553
553
|
this.element.set({ backgroundOpacity: 1 });
|
554
554
|
ThreeMeshUI.update();
|
555
555
|
}
|
556
556
|
}
|
557
557
|
onPointerExit() {
|
558
|
-
this.context.input.
|
558
|
+
this.context.input.unsetCursor("pointer");
|
559
559
|
if (this.allowModifyUI) {
|
560
560
|
this.element.set({ backgroundOpacity: 0 });
|
561
561
|
ThreeMeshUI.update();
|
@@ -91,12 +91,12 @@
|
|
91
91
|
/** @internal */
|
92
92
|
onPointerEnter(args) {
|
93
93
|
if (!args.used && this.clickable)
|
94
|
-
this.context.input.
|
94
|
+
this.context.input.setCursor("pointer");
|
95
95
|
}
|
96
96
|
/** @internal */
|
97
97
|
onPointerExit() {
|
98
98
|
if (this.clickable)
|
99
|
-
this.context.input.
|
99
|
+
this.context.input.unsetCursor("pointer");
|
100
100
|
}
|
101
101
|
/** @internal */
|
102
102
|
onPointerClick(args: PointerEventData) {
|
@@ -716,6 +716,11 @@
|
|
716
716
|
const factor = this.hasLightmap ? Math.PI : 1;
|
717
717
|
const environmentIntensity = this.context.mainCameraComponent?.environmentIntensity ?? 1;
|
718
718
|
material.envMapIntensity = Math.max(0, environmentIntensity * this.context.sceneLighting.environmentIntensity / factor);
|
719
|
+
|
720
|
+
// since three 163 we need to set the envMap to the scene envMap if it is not set
|
721
|
+
// otherwise the envmapIntensity has no effect: https://github.com/mrdoob/three.js/pull/27903
|
722
|
+
// internal issue: https://linear.app/needle/issue/NE-6363
|
723
|
+
if (!material.envMap) material.envMap = this.context.scene.environment;
|
719
724
|
}
|
720
725
|
|
721
726
|
if (this._lightmaps) {
|
@@ -88,13 +88,13 @@
|
|
88
88
|
onPointerEnter() {
|
89
89
|
if (this.context.connection.allowEditing == false) return;
|
90
90
|
if (!this.allowStartOnClick) return;
|
91
|
-
this.context.input.
|
91
|
+
this.context.input.setCursor("pointer");
|
92
92
|
}
|
93
93
|
/** @internal */
|
94
94
|
onPointerExit() {
|
95
95
|
if (this.context.connection.allowEditing == false) return;
|
96
96
|
if (!this.allowStartOnClick) return;
|
97
|
-
this.context.input.
|
97
|
+
this.context.input.unsetCursor("pointer");
|
98
98
|
}
|
99
99
|
|
100
100
|
/** @internal */
|
@@ -1966,7 +1966,6 @@
|
|
1966
1966
|
|
1967
1967
|
const materialName = getMaterialName(material);
|
1968
1968
|
|
1969
|
-
console.log(material);
|
1970
1969
|
// Special case: occluder material
|
1971
1970
|
// Supported on iOS 18+ and visionOS 1+
|
1972
1971
|
const isShadowCatcherMaterial =
|
@@ -403,7 +403,7 @@
|
|
403
403
|
if (this.autoExportAnimations) {
|
404
404
|
implicitBehaviors.push(...registerAnimatorsImplictly(objectToExport, animExt));
|
405
405
|
}
|
406
|
-
const audioExt =
|
406
|
+
const audioExt = extensions.find(ext => ext.extensionName === "Audio");
|
407
407
|
if (audioExt && this.autoExportAudioSources)
|
408
408
|
implicitBehaviors.push(...registerAudioSourcesImplictly(objectToExport, audioExt as AudioExtension));
|
409
409
|
|
@@ -454,7 +454,7 @@
|
|
454
454
|
});
|
455
455
|
}
|
456
456
|
|
457
|
-
const behaviorExt =
|
457
|
+
const behaviorExt = extensions.find(ext => ext.extensionName === "Behaviour") as BehaviorExtension | undefined;
|
458
458
|
if (this.interactive && behaviorExt && objectsToDisableAtSceneStart.length > 0) {
|
459
459
|
behaviorExt.addBehavior(disableObjectsAtStart(objectsToDisableAtSceneStart));
|
460
460
|
}
|
@@ -160,9 +160,13 @@
|
|
160
160
|
}
|
161
161
|
|
162
162
|
onAfterHierarchy(_context: USDZExporterContext, writer: USDWriter) {
|
163
|
+
const iOSVersion = DeviceUtilities.getiOSVersion();
|
164
|
+
const majorVersion = iOSVersion ? parseInt(iOSVersion.split(".")[0]) : 18;
|
165
|
+
const workaroundForFB16119331 = majorVersion >= 18;
|
166
|
+
const multiplier = workaroundForFB16119331 ? 1 : 100;
|
163
167
|
writer.beginBlock(`def Preliminary_ReferenceImage "AnchoringReferenceImage"`);
|
164
168
|
writer.appendLine(`uniform asset image = @image_tracking/` + this.filename + `@`);
|
165
|
-
writer.appendLine(`uniform double physicalWidth = ` + (this.widthInMeters *
|
169
|
+
writer.appendLine(`uniform double physicalWidth = ` + (this.widthInMeters * multiplier).toFixed(8));
|
166
170
|
writer.closeBlock();
|
167
171
|
}
|