@@ -136,8 +136,8 @@
|
|
136
136
|
}
|
137
137
|
|
138
138
|
get isPlaying() {
|
139
|
-
for (let i = 0; i < this.
|
140
|
-
if (this.
|
139
|
+
for (let i = 0; i < this.actions.length; i++) {
|
140
|
+
if (this.actions[i].isRunning())
|
141
141
|
return true;
|
142
142
|
}
|
143
143
|
return false;
|
@@ -45,7 +45,7 @@
|
|
45
45
|
if (changed && this._cam) {
|
46
46
|
if (this._cam instanceof PerspectiveCamera) {
|
47
47
|
if (this._fov === undefined) {
|
48
|
-
console.
|
48
|
+
console.warn("Can not set undefined fov on PerspectiveCamera");
|
49
49
|
return;
|
50
50
|
}
|
51
51
|
this._cam.fov = this._fov;
|
@@ -671,7 +671,7 @@
|
|
671
671
|
}
|
672
672
|
|
673
673
|
dispatchEvent(evt: Event): boolean {
|
674
|
-
if (!this._eventListeners[evt.type]) return false;
|
674
|
+
if (!evt || !this._eventListeners[evt.type]) return false;
|
675
675
|
const listeners = this._eventListeners[evt.type];
|
676
676
|
for (let i = 0; i < listeners.length; i++) {
|
677
677
|
listeners[i](evt);
|
@@ -1,3 +1,5 @@
|
|
1
|
+
|
2
|
+
import { isMobileDevice } from "../engine/engine_utils.js";
|
1
3
|
import { serializable } from "../engine/engine_serialization_decorator.js";
|
2
4
|
import { Behaviour, GameObject } from "./Component.js";
|
3
5
|
|
@@ -26,7 +28,7 @@
|
|
26
28
|
|
27
29
|
private test() : boolean {
|
28
30
|
if(this.visibleOn < 0) return true;
|
29
|
-
if(
|
31
|
+
if(isMobileDevice()){
|
30
32
|
return (this.visibleOn & (DeviceType.Mobile)) !== 0;
|
31
33
|
}
|
32
34
|
const allowDesktop = (this.visibleOn & (DeviceType.Desktop)) !== 0;
|
@@ -35,12 +37,7 @@
|
|
35
37
|
|
36
38
|
}
|
37
39
|
|
38
|
-
|
39
|
-
let _isMobile: boolean | undefined = undefined;
|
40
|
+
/**@deprecated use isMobileDevice() */
|
40
41
|
function isMobile() {
|
41
|
-
|
42
|
-
let check = false;
|
43
|
-
(function (a) { if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true; })(navigator.userAgent || navigator.vendor || window["opera"]);
|
44
|
-
_isMobile = check;
|
45
|
-
return check;
|
42
|
+
return isMobileDevice();
|
46
43
|
};
|
@@ -42,7 +42,7 @@
|
|
42
42
|
|
43
43
|
private onDrop = async (evt: DragEvent) => {
|
44
44
|
if (debug) console.log(evt);
|
45
|
-
if (!evt
|
45
|
+
if (!evt?.dataTransfer) return;
|
46
46
|
evt.preventDefault();
|
47
47
|
const items = evt.dataTransfer.items;
|
48
48
|
if (!items) return;
|
@@ -89,6 +89,10 @@
|
|
89
89
|
|
90
90
|
private async addObject(evt: DragEvent | undefined, gltf: GLTF) {
|
91
91
|
if (debug) console.log("Dropped", gltf);
|
92
|
+
if (!gltf?.scene) {
|
93
|
+
console.warn("No object specified to add to scene", gltf);
|
94
|
+
return;
|
95
|
+
}
|
92
96
|
|
93
97
|
const obj = gltf.scene;
|
94
98
|
if (evt !== undefined) {
|
@@ -178,6 +178,7 @@
|
|
178
178
|
return this._rawBinary;
|
179
179
|
}
|
180
180
|
|
181
|
+
// TODO: we need a way to abort loading a resource
|
181
182
|
async loadAssetAsync(prog?: ProgressCallback | null) {
|
182
183
|
if (debug)
|
183
184
|
console.log("loadAssetAsync", this.uri);
|
@@ -5,12 +5,15 @@
|
|
5
5
|
ContextRegistered = "ContextRegistered",
|
6
6
|
/** called before the first glb is loaded, can be used to initialize physics engine, is awaited */
|
7
7
|
ContextCreationStart = "ContextCreationStart",
|
8
|
+
/** Called when the context has been created, before the first frame */
|
8
9
|
ContextCreated = "ContextCreated",
|
10
|
+
/** Called when the context has been destroyed */
|
9
11
|
ContextDestroyed = "ContextDestroyed",
|
12
|
+
/** Called when the context could not find a camera during creation */
|
10
13
|
MissingCamera = "MissingCamera",
|
11
|
-
/** Called before the context is being cleared */
|
14
|
+
/** Called before the context is being cleared (all objects in the scene are being destroyed and state is reset) */
|
12
15
|
ContextClearing = "ContextClearing",
|
13
|
-
/** Called after the context has been cleared */
|
16
|
+
/** Called after the context has been cleared (all objects in the scene have been destroyed and state has been reset) */
|
14
17
|
ContextCleared = "ContextCleared",
|
15
18
|
}
|
16
19
|
|
@@ -36,6 +39,11 @@
|
|
36
39
|
globalThis["NeedleEngine.Context.Current"] = ctx;
|
37
40
|
}
|
38
41
|
|
42
|
+
/** Returns the array of all registered Needle Engine contexts. Do not modify */
|
43
|
+
static get All() {
|
44
|
+
return this.Registered;
|
45
|
+
}
|
46
|
+
|
39
47
|
/** All currently registered Needle Engine contexts. Do not modify */
|
40
48
|
static Registered: IContext[] = [];
|
41
49
|
|
@@ -25,7 +25,7 @@
|
|
25
25
|
import { LightDataRegistry, ILightDataRegistry } from './engine_lightdata.js';
|
26
26
|
import { PlayerViewManager } from './engine_playerview.js';
|
27
27
|
|
28
|
-
import { CoroutineData, GLTF, ICamera, IComponent, IContext, ILight } from "./engine_types.js"
|
28
|
+
import { CoroutineData, GLTF, ICamera, IComponent, IContext, ILight, LoadedGLTF } from "./engine_types.js"
|
29
29
|
import { destroy, foreachComponent } from './engine_gameobject.js';
|
30
30
|
import { ContextEvent, ContextRegistry } from './engine_context_registry.js';
|
31
31
|
import { delay, getParam } from './engine_utils.js';
|
@@ -666,7 +666,7 @@
|
|
666
666
|
|
667
667
|
// load and create scene
|
668
668
|
let prepare_succeeded = true;
|
669
|
-
let loadedFiles!: Array<
|
669
|
+
let loadedFiles!: Array<LoadedGLTF | null>;
|
670
670
|
try {
|
671
671
|
Context.Current = this;
|
672
672
|
if (opts) {
|
@@ -789,15 +789,18 @@
|
|
789
789
|
}
|
790
790
|
else if (debug) console.log("Target framerate set to", this.targetFrameRate);
|
791
791
|
|
792
|
+
this._dispatchReadyAfterFrame = true;
|
793
|
+
const res = ContextRegistry.dispatchCallback(ContextEvent.ContextCreated, this, { files: loadedFiles });
|
794
|
+
if (res) await res;
|
795
|
+
|
792
796
|
this._isCreating = false;
|
793
797
|
if (!this.isManagedExternally)
|
794
798
|
this.restartRenderLoop();
|
795
|
-
|
796
|
-
return ContextRegistry.dispatchCallback(ContextEvent.ContextCreated, this, { files: loadedFiles });
|
799
|
+
return res;
|
797
800
|
}
|
798
801
|
|
799
|
-
private async internalLoadInitialContent(createId: number, args: ContextCreateArgs): Promise<Array<
|
800
|
-
const results = new Array<
|
802
|
+
private async internalLoadInitialContent(createId: number, args: ContextCreateArgs): Promise<Array<LoadedGLTF>> {
|
803
|
+
const results = new Array<LoadedGLTF>();
|
801
804
|
// early out if we dont have any files to load
|
802
805
|
if (args.files.length === 0) return results;
|
803
806
|
|
@@ -837,7 +840,10 @@
|
|
837
840
|
});
|
838
841
|
args?.onLoadingFinished?.call(this, i, file, res ?? null);
|
839
842
|
if (res) {
|
840
|
-
results.push(
|
843
|
+
results.push({
|
844
|
+
src: file,
|
845
|
+
file: res
|
846
|
+
});
|
841
847
|
}
|
842
848
|
else {
|
843
849
|
// a file could not be loaded
|
@@ -849,8 +855,8 @@
|
|
849
855
|
// then we want to cleanup/destroy previously loaded files
|
850
856
|
if (createId !== this._createId) {
|
851
857
|
for (const res of results) {
|
852
|
-
if (res) {
|
853
|
-
for (const scene of res.scenes)
|
858
|
+
if (res && res.file) {
|
859
|
+
for (const scene of res.file.scenes)
|
854
860
|
destroy(scene, true, true);
|
855
861
|
}
|
856
862
|
}
|
@@ -858,8 +864,8 @@
|
|
858
864
|
// otherwise we want to add the loaded files to the current scene
|
859
865
|
else {
|
860
866
|
for (const res of results) {
|
861
|
-
if (res) {
|
862
|
-
this.scene.add(res.scene);
|
867
|
+
if (res && res.file) {
|
868
|
+
this.scene.add(res.file.scene);
|
863
869
|
}
|
864
870
|
}
|
865
871
|
}
|
@@ -6,7 +6,7 @@
|
|
6
6
|
import { setDracoDecoderPath, setDracoDecoderType, setKtx2TranscoderPath } from "./engine_loaders.js";
|
7
7
|
import { getLoader, registerLoader } from "../engine/engine_gltf.js";
|
8
8
|
import { NeedleGltfLoader } from "./engine_scenetools.js";
|
9
|
-
import {
|
9
|
+
import { INeedleEngineComponent, LoadedGLTF } from "./engine_types.js";
|
10
10
|
import { isLocalNetwork } from "./engine_networking_utils.js";
|
11
11
|
import { isDevEnvironment, showBalloonWarning } from "./debug/index.js";
|
12
12
|
import { destroy } from "./engine_gameobject.js";
|
@@ -323,7 +323,6 @@
|
|
323
323
|
files: filesToLoad,
|
324
324
|
onLoadingProgress: evt => {
|
325
325
|
evt.name = getNameFromUrl(evt.name);
|
326
|
-
console.log({...evt})
|
327
326
|
if (useDefaultLoading) this._loadingView?.onLoadingUpdate(evt);
|
328
327
|
progressEventDetail.name = evt.name;
|
329
328
|
progressEventDetail.progress = evt.progress;
|
@@ -218,7 +218,10 @@
|
|
218
218
|
*/
|
219
219
|
export const syncField = function (onFieldChanged?: string | FieldChangedCallbackFn) {
|
220
220
|
|
221
|
-
return function (target: any,
|
221
|
+
return function (target: any, _propertyKey: string | { name: string }) {
|
222
|
+
let propertyKey = "";
|
223
|
+
if (typeof _propertyKey === "string") propertyKey = _propertyKey;
|
224
|
+
else propertyKey = _propertyKey.name;
|
222
225
|
|
223
226
|
let syncer: ComponentPropertiesSyncer | null = null;
|
224
227
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Vector4, EquirectangularReflectionMapping,
|
1
|
+
import { Vector4, EquirectangularReflectionMapping, WebGLCubeRenderTarget, Texture, LightProbe, SphericalHarmonics3, SRGBColorSpace } from "three";
|
2
2
|
import { LightProbeGenerator } from "three/examples/jsm/lights/LightProbeGenerator.js"
|
3
3
|
import { Context } from "./engine_setup.js";
|
4
4
|
import { SceneLightSettings } from "./extensions/NEEDLE_lighting_settings.js";
|
@@ -21,12 +21,21 @@
|
|
21
21
|
}
|
22
22
|
}
|
23
23
|
|
24
|
-
return function (_target: any, _propertyKey: string) {
|
24
|
+
return function (_target: any, _propertyKey: string | { name: string }) {
|
25
|
+
// The _propertyKey parameter is a string in TS4 with experimentalDecorators
|
26
|
+
// but a ClassFieldDecoratorContext in TS5 without.
|
27
|
+
// Seems when a different TS version is used in VSCode for editor checking, we get errors here
|
28
|
+
// if we don't also check for any. See https://github.com/needle-tools/needle-engine-support/issues/179
|
29
|
+
if (typeof _propertyKey !== 'string') {
|
30
|
+
_propertyKey = _propertyKey.name;
|
31
|
+
}
|
32
|
+
|
25
33
|
// this is important so objects with inheritance dont override their serialized type
|
26
34
|
// info if e.g. multiple classes inheriting from the same type implement a member with the same name
|
27
35
|
// and both use @serializable() with different types
|
28
36
|
if (!Object.getOwnPropertyDescriptor(_target, '$serializedTypes'))
|
29
37
|
_target["$serializedTypes"] = {};
|
38
|
+
|
30
39
|
const types = _target["$serializedTypes"] = _target["$serializedTypes"] || {}
|
31
40
|
types[_propertyKey] = type;
|
32
41
|
}
|
@@ -15,7 +15,7 @@
|
|
15
15
|
}
|
16
16
|
|
17
17
|
|
18
|
-
function createPropertyWrapper(target: IComponent | any,
|
18
|
+
function createPropertyWrapper(target: IComponent | any, _propertyKey: string | { name: string }, descriptor?: PropertyDescriptor,
|
19
19
|
set?: setter,
|
20
20
|
get?: getter) {
|
21
21
|
|
@@ -24,11 +24,15 @@
|
|
24
24
|
// this is not undefined when its a property getter or setter already and not just a field
|
25
25
|
// we currently only support validation of fields
|
26
26
|
if (descriptor !== undefined) {
|
27
|
-
console.error("Invalid usage of validate decorator. Only fields can be validated.", target,
|
28
|
-
showBalloonMessage("Invalid usage of validate decorator. Only fields can be validated. Property: " +
|
27
|
+
console.error("Invalid usage of validate decorator. Only fields can be validated.", target, _propertyKey, descriptor);
|
28
|
+
showBalloonMessage("Invalid usage of validate decorator. Only fields can be validated. Property: " + _propertyKey, LogType.Error);
|
29
29
|
return;
|
30
30
|
}
|
31
31
|
|
32
|
+
let propertyKey: string = "";
|
33
|
+
if (typeof _propertyKey === "string") propertyKey = _propertyKey;
|
34
|
+
else propertyKey = _propertyKey.name;
|
35
|
+
|
32
36
|
if (target.__internalAwake) {
|
33
37
|
// this is the hidden key we save the original property to
|
34
38
|
const $prop = Symbol(propertyKey);
|
@@ -75,14 +79,19 @@
|
|
75
79
|
* Return false to prevent the original method from running.
|
76
80
|
*/
|
77
81
|
export const prefix = function <T>(type: Constructor<T>) {
|
78
|
-
return function (target: IComponent | any,
|
82
|
+
return function (target: IComponent | any, _propertyKey: string | { name: string }, _PropertyDescriptor: PropertyDescriptor) {
|
79
83
|
|
84
|
+
let propertyKey: string = "";
|
85
|
+
if (typeof _propertyKey === "string") propertyKey = _propertyKey;
|
86
|
+
else propertyKey = _propertyKey.name;
|
87
|
+
|
80
88
|
const targetType = type.prototype;
|
81
89
|
const originalProp = Object.getOwnPropertyDescriptor(targetType, propertyKey);
|
82
90
|
if (!originalProp?.value) {
|
83
|
-
console.warn("Can not apply prefix: type does not have method named",
|
91
|
+
console.warn("Can not apply prefix: type does not have method named", _propertyKey, type);
|
84
92
|
return;
|
85
93
|
}
|
94
|
+
|
86
95
|
const originalValue = originalProp.value;
|
87
96
|
const prefix = target[propertyKey];
|
88
97
|
Object.defineProperty(targetType, propertyKey, {
|
@@ -512,3 +512,34 @@
|
|
512
512
|
}
|
513
513
|
|
514
514
|
}
|
515
|
+
|
516
|
+
|
517
|
+
export class PromiseErrorResult {
|
518
|
+
readonly reason: string;
|
519
|
+
constructor(reason: string) {
|
520
|
+
this.reason = reason;
|
521
|
+
}
|
522
|
+
}
|
523
|
+
|
524
|
+
/** Can be used to simplify Promise error handling and if errors are acceptable.
|
525
|
+
* Promise.all will just fail if any of the provided promises fails and not return or cancel pending promises or partial results
|
526
|
+
* Using Promise.allSettled (or this method) instead will return a result for each promise and not automatically fail if any of the promises fails.
|
527
|
+
* Instead it will return a promise containing information if any of the promises failed
|
528
|
+
* and the actual results will be available as `results` array
|
529
|
+
**/
|
530
|
+
export async function PromiseAllWithErrors<T>(promise: Promise<T>[]): Promise<{
|
531
|
+
anyFailed: boolean,
|
532
|
+
results: Array<T | PromiseErrorResult>
|
533
|
+
}> {
|
534
|
+
const results = await Promise.allSettled(promise);
|
535
|
+
let anyFailed: boolean = false;
|
536
|
+
const res = results.map(x => {
|
537
|
+
if ("value" in x) return x.value;
|
538
|
+
anyFailed = true;
|
539
|
+
return new PromiseErrorResult(x.reason);
|
540
|
+
});
|
541
|
+
return {
|
542
|
+
anyFailed: anyFailed,
|
543
|
+
results: res,
|
544
|
+
};
|
545
|
+
}
|
@@ -7,7 +7,12 @@
|
|
7
7
|
|
8
8
|
onEnable(): void {
|
9
9
|
const cam = GameObject.getComponent(this.gameObject, Camera)?.cam;
|
10
|
-
|
10
|
+
if (!cam) {
|
11
|
+
console.warn("FlyControls: Requires a Camera component on the same object as this component.");
|
12
|
+
return;
|
13
|
+
}
|
14
|
+
|
15
|
+
this._controls = new ThreeFlyControls(cam, this.context.renderer.domElement);
|
11
16
|
this._controls.rollSpeed = .5;
|
12
17
|
this._controls.movementSpeed = 3;
|
13
18
|
this._controls.dragToLook = true;
|
@@ -8,7 +8,9 @@
|
|
8
8
|
import { getWorldPosition } from "../../../engine/engine_three_utils.js";
|
9
9
|
import { BoxHelperComponent } from "../../BoxHelperComponent.js";
|
10
10
|
import { AnimationClip } from "three";
|
11
|
+
import { getParam } from "../../../engine/engine_utils.js";
|
11
12
|
|
13
|
+
const debugExport = getParam("debuggltfexport");
|
12
14
|
|
13
15
|
declare type ExportOptions = {
|
14
16
|
binary: boolean,
|
@@ -41,7 +43,11 @@
|
|
41
43
|
private ext?: NEEDLE_components;
|
42
44
|
|
43
45
|
async exportNow(name: string) {
|
44
|
-
console.log("
|
46
|
+
if (debugExport) console.log("Exporting objects as glTF", this.objects);
|
47
|
+
if (!name) name = "scene";
|
48
|
+
if (!this.objects || this.objects.length <= 0)
|
49
|
+
this.objects = [this.context.scene];
|
50
|
+
|
45
51
|
const opts = { binary: this.binary, pivot: GltfExport.calculateCenter(this.objects) };
|
46
52
|
const res = await this.export(this.objects, opts);
|
47
53
|
|
@@ -59,8 +65,8 @@
|
|
59
65
|
|
60
66
|
async export(objectsToExport: Object3D[], opts?: ExportOptions): Promise<any> {
|
61
67
|
|
62
|
-
if (objectsToExport
|
63
|
-
console.
|
68
|
+
if (!objectsToExport || objectsToExport.length <= 0) {
|
69
|
+
console.warn("No objects set to export");
|
64
70
|
return;
|
65
71
|
}
|
66
72
|
|
@@ -100,7 +106,7 @@
|
|
100
106
|
// console.log(exportScene.position);
|
101
107
|
|
102
108
|
// add objects for export
|
103
|
-
console.log("EXPORT", objectsToExport);
|
109
|
+
if (debugExport) console.log("EXPORT", objectsToExport);
|
104
110
|
objectsToExport.forEach(obj => {
|
105
111
|
if (obj) {
|
106
112
|
// adding directly does not require us to change parents and mess with the hierarchy actually
|
@@ -143,7 +149,7 @@
|
|
143
149
|
reject(err);
|
144
150
|
}
|
145
151
|
finally {
|
146
|
-
console.log("
|
152
|
+
if (debugExport) console.log("Finished glTF export.");
|
147
153
|
}
|
148
154
|
});
|
149
155
|
|
@@ -73,6 +73,7 @@
|
|
73
73
|
if (!this._rect) {
|
74
74
|
this._rect = GameObject.getComponent(this.gameObject, RectTransform);
|
75
75
|
}
|
76
|
+
if (!this._rect) throw new Error("Not Supported: Make sure to add a RectTransform component before adding a UI Graphic component.");
|
76
77
|
return this._rect!;
|
77
78
|
}
|
78
79
|
|
@@ -54,7 +54,7 @@
|
|
54
54
|
// the write node callback is called after user data is serialized
|
55
55
|
// we could also traverse everything before export and remove components
|
56
56
|
// but doing it like that we avoid traversing multiple times
|
57
|
-
if("serializeUserData" in writer){
|
57
|
+
if ("serializeUserData" in writer) {
|
58
58
|
//@ts-ignore
|
59
59
|
const originalFunction = writer.serializeUserData.bind(writer);
|
60
60
|
this.writer = writer;
|
@@ -105,7 +105,8 @@
|
|
105
105
|
|
106
106
|
writeNode(node: Object3D, nodeDef) {
|
107
107
|
const nodeIndex = this.writer.json.nodes.length;
|
108
|
-
|
108
|
+
if (debug)
|
109
|
+
console.log(node.name, nodeIndex, node.uuid);
|
109
110
|
const context = new ExportData(node, nodeIndex, nodeDef);
|
110
111
|
this.exportContext[nodeIndex] = context;
|
111
112
|
this.objectToNodeMap[node.uuid] = nodeIndex;
|
@@ -171,29 +172,31 @@
|
|
171
172
|
const loadComponents: Array<Promise<void>> = [];
|
172
173
|
if (hasExtension === true) {
|
173
174
|
const nodes = parser.json.nodes;
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
for (let i = 0; i < nodes.length; i++) {
|
180
|
-
const node = nodes[i];
|
181
|
-
const index = i;// node.mesh;
|
182
|
-
const ext = node.extensions;
|
183
|
-
if (!ext) continue;
|
184
|
-
const data = ext[this.name];
|
185
|
-
if (!data) continue;
|
186
|
-
if (debug)
|
187
|
-
console.log("NODE", node);
|
188
|
-
const obj = this.nodeToObjectMap[index];
|
189
|
-
if (!obj) {
|
190
|
-
console.error("Could not find object for node index: " + index, node, parser);
|
191
|
-
continue;
|
175
|
+
if (nodes) {
|
176
|
+
for (let i = 0; i < nodes.length; i++) {
|
177
|
+
const obj = await parser.getDependency('node', i);
|
178
|
+
this.nodeToObjectMap[i] = obj;
|
192
179
|
}
|
193
180
|
|
194
|
-
|
181
|
+
for (let i = 0; i < nodes.length; i++) {
|
182
|
+
const node = nodes[i];
|
183
|
+
const index = i;// node.mesh;
|
184
|
+
const ext = node.extensions;
|
185
|
+
if (!ext) continue;
|
186
|
+
const data = ext[this.name];
|
187
|
+
if (!data) continue;
|
188
|
+
if (debug)
|
189
|
+
console.log("NODE", node);
|
190
|
+
const obj = this.nodeToObjectMap[index];
|
191
|
+
if (!obj) {
|
192
|
+
console.error("Could not find object for node index: " + index, node, parser);
|
193
|
+
continue;
|
194
|
+
}
|
195
195
|
|
196
|
-
|
196
|
+
apply(obj);
|
197
|
+
|
198
|
+
loadComponents.push(this.createComponents(obj, data));
|
199
|
+
}
|
197
200
|
}
|
198
201
|
}
|
199
202
|
await Promise.all(loadComponents);
|
@@ -42,7 +42,7 @@
|
|
42
42
|
afterRoot(_result: GLTF): Promise<void> | null {
|
43
43
|
// console.log("AFTER ROOT", _result);
|
44
44
|
const promises: Promise<void>[] = [];
|
45
|
-
for (let index = 0; index < this.parser.json.nodes
|
45
|
+
for (let index = 0; index < this.parser.json.nodes?.length; index++) {
|
46
46
|
const node = this.parser.json.nodes[index];
|
47
47
|
if (node && node.extensions) {
|
48
48
|
const ext = node.extensions[EXTENSION_NAME];
|
@@ -26,7 +26,7 @@
|
|
26
26
|
|
27
27
|
async open() {
|
28
28
|
if (!this.url) {
|
29
|
-
console.
|
29
|
+
console.warn("OpenURL: URL is not set, can't open.", this);
|
30
30
|
return;
|
31
31
|
}
|
32
32
|
|
@@ -121,7 +121,10 @@
|
|
121
121
|
const cam = cameraComponent?.cam;
|
122
122
|
if (cam) setCameraController(cam, this, true);
|
123
123
|
if (!this._controls) {
|
124
|
-
|
124
|
+
if (!cam) {
|
125
|
+
console.warn("OrbitControls: Requires a Camera component on the same object as this component.");
|
126
|
+
return;
|
127
|
+
}
|
125
128
|
if (cam)
|
126
129
|
this._cameraObject = cam;
|
127
130
|
// Using the parent if possible to make it possible to disable input on the canvas
|
@@ -882,6 +882,10 @@
|
|
882
882
|
awake(): void {
|
883
883
|
this._renderer = this.gameObject.getComponent(ParticleSystemRenderer) as ParticleSystemRenderer;
|
884
884
|
|
885
|
+
if (!this.main)Â {
|
886
|
+
throw new Error("Not Supported: ParticleSystem needs a serialized MainModule. Creating new particle systems at runtime is currently not supported.");
|
887
|
+
}
|
888
|
+
|
885
889
|
this._container = new Object3D();
|
886
890
|
this._container.matrixAutoUpdate = false;
|
887
891
|
// if (this.main.simulationSpace == ParticleSystemSimulationSpace.Local) {
|
@@ -928,6 +932,7 @@
|
|
928
932
|
}
|
929
933
|
|
930
934
|
onEnable() {
|
935
|
+
if (!this.main) return;
|
931
936
|
if (this.inheritVelocity)
|
932
937
|
this.inheritVelocity.system = this;
|
933
938
|
if (this._batchSystem)
|
@@ -943,6 +948,7 @@
|
|
943
948
|
}
|
944
949
|
|
945
950
|
onBeforeRender() {
|
951
|
+
if (!this.main) return;
|
946
952
|
if (this._didPreWarm === false && this.main?.prewarm === true) {
|
947
953
|
this._didPreWarm = true;
|
948
954
|
this.preWarm();
|
@@ -1009,7 +1015,6 @@
|
|
1009
1015
|
|
1010
1016
|
private lastMaterialVersion: number = -1;
|
1011
1017
|
private onUpdate() {
|
1012
|
-
|
1013
1018
|
const mat = this.renderer.getMaterial(this.trails.enabled);
|
1014
1019
|
if (mat && mat.version != this.lastMaterialVersion && this._particleSystem) {
|
1015
1020
|
this.lastMaterialVersion = mat.version;
|
@@ -94,19 +94,23 @@
|
|
94
94
|
}
|
95
95
|
|
96
96
|
// private lastMatrixWorld!: Matrix4;
|
97
|
-
private lastMatrix
|
98
|
-
private rectBlock
|
97
|
+
private lastMatrix: Matrix4;
|
98
|
+
private rectBlock: Object3D;
|
99
99
|
private _transformNeedsUpdate: boolean = false;
|
100
100
|
private _initialPosition!: Vector3;
|
101
101
|
|
102
|
+
constructor() {
|
103
|
+
super();
|
104
|
+
this.lastMatrix = new Matrix4();
|
105
|
+
this.rectBlock = new Object3D();
|
106
|
+
}
|
107
|
+
|
102
108
|
awake() {
|
103
109
|
super.awake();
|
104
110
|
// this is required if an animator animated the transform anchoring
|
105
111
|
if (!this._anchoredPosition)
|
106
112
|
this._anchoredPosition = new Vector2();
|
107
113
|
|
108
|
-
this.lastMatrix = new Matrix4();
|
109
|
-
this.rectBlock = new Object3D();
|
110
114
|
this.rectBlock.name = this.name;
|
111
115
|
|
112
116
|
// TODO: get rid of the initial position
|
@@ -154,10 +154,10 @@
|
|
154
154
|
if (this.queryParameterName)
|
155
155
|
didResolve = await this.tryLoadFromQueryParam();
|
156
156
|
if (!didResolve) {
|
157
|
-
const state = _state
|
158
|
-
if (state
|
157
|
+
const state = _state?.state;
|
158
|
+
if (state && state.startsWith(this.guid)) {
|
159
159
|
const value = state.substr(this.guid.length + 2);
|
160
|
-
console.log(value);
|
160
|
+
if(debug) console.log("PopState", value);
|
161
161
|
await this.trySelectSceneFromValue(value);
|
162
162
|
}
|
163
163
|
}
|
@@ -263,6 +263,11 @@
|
|
263
263
|
return this.switchScene(scene);
|
264
264
|
}
|
265
265
|
|
266
|
+
|
267
|
+
// this is the scene that was requested last
|
268
|
+
private __lastSwitchScene?: AssetReference;
|
269
|
+
private __lastSwitchScenePromise?: Promise<boolean>;
|
270
|
+
|
266
271
|
async switchScene(scene: AssetReference): Promise<boolean> {
|
267
272
|
if (!(scene instanceof AssetReference)) {
|
268
273
|
const type = typeof scene;
|
@@ -273,15 +278,28 @@
|
|
273
278
|
return this.select(scene);
|
274
279
|
}
|
275
280
|
else {
|
276
|
-
console.
|
281
|
+
console.warn("SceneSwitcher: Can't switch to scene", scene, "of type", type);
|
277
282
|
return false;
|
278
283
|
}
|
279
284
|
}
|
280
|
-
|
281
|
-
|
285
|
+
|
286
|
+
// ensure that we never run the same scene switch multiple times (at the same time) for the same requested scene
|
287
|
+
if (this.__lastSwitchScene === scene && this.__lastSwitchScenePromise) {
|
288
|
+
return this.__lastSwitchScenePromise;
|
289
|
+
}
|
290
|
+
this.__lastSwitchScene = scene;
|
291
|
+
this.__lastSwitchScenePromise = this.__internalSwitchScene(scene);
|
292
|
+
const res = await this.__lastSwitchScenePromise;
|
293
|
+
return res;
|
294
|
+
}
|
295
|
+
async __internalSwitchScene(scene: AssetReference): Promise<boolean> {
|
296
|
+
if (this._currentScene) {
|
297
|
+
if(debug) console.log("UNLOAD", scene.uri)
|
282
298
|
this._currentScene.unload();
|
299
|
+
}
|
300
|
+
this._currentScene = undefined;
|
301
|
+
|
283
302
|
const index = this._currentIndex = this.scenes?.indexOf(scene) ?? -1;
|
284
|
-
this._currentScene = scene;
|
285
303
|
try {
|
286
304
|
const loadStartEvt = new CustomEvent<LoadSceneEvent>("loadscene-start", { detail: { scene: scene, switcher: this, index: index } })
|
287
305
|
this.dispatchEvent(loadStartEvt);
|
@@ -297,6 +315,8 @@
|
|
297
315
|
return false;
|
298
316
|
}
|
299
317
|
if (this._currentIndex === index) {
|
318
|
+
if(debug) console.log("ADD", scene.uri)
|
319
|
+
this._currentScene = scene;
|
300
320
|
GameObject.add(scene.asset, this.gameObject);
|
301
321
|
if (this.useSceneLighting)
|
302
322
|
this.context.sceneLighting.enable(scene)
|
@@ -474,7 +494,7 @@
|
|
474
494
|
}
|
475
495
|
|
476
496
|
private async awaitLoading() {
|
477
|
-
if (!this.asset.isLoaded()) {
|
497
|
+
if (this.asset && !this.asset.isLoaded()) {
|
478
498
|
if (debug)
|
479
499
|
console.log("Preload start: " + this.asset.uri, this.index);
|
480
500
|
await this.asset.preload();
|
@@ -128,7 +128,7 @@
|
|
128
128
|
this.videoPlayer = GameObject.getComponent(this.gameObject, VideoPlayer) ?? undefined;
|
129
129
|
}
|
130
130
|
if (!this.videoPlayer) {
|
131
|
-
console.
|
131
|
+
console.warn("ScreenCapture: Requires an assigned VideoPlayer or a VideoPlayer component on the same object as this component.");
|
132
132
|
return;
|
133
133
|
}
|
134
134
|
const handle = PeerHandle.getOrCreate(this.context, this.guid);
|
@@ -5,7 +5,7 @@
|
|
5
5
|
import { EquirectangularRefractionMapping, NeverDepth, SRGBColorSpace, sRGBEncoding, Texture, TextureLoader } from "three"
|
6
6
|
import { syncField } from "../engine/engine_networking_auto.js";
|
7
7
|
import { Camera } from "./Camera.js";
|
8
|
-
import { addAttributeChangeCallback, getParam, removeAttributeChangeCallback } from "../engine/engine_utils.js";
|
8
|
+
import { PromiseAllWithErrors, addAttributeChangeCallback, getParam, removeAttributeChangeCallback } from "../engine/engine_utils.js";
|
9
9
|
import { ContextRegistry } from "../engine/engine_context_registry.js";
|
10
10
|
import { registerObservableAttribute } from "../engine/engine_element_extras.js";
|
11
11
|
import { type IContext } from "../engine/engine_types.js";
|
@@ -53,7 +53,7 @@
|
|
53
53
|
promises.push(promise);
|
54
54
|
}
|
55
55
|
if (promises.length > 0) {
|
56
|
-
return
|
56
|
+
return PromiseAllWithErrors(promises);
|
57
57
|
}
|
58
58
|
return Promise.resolve();
|
59
59
|
});
|
@@ -171,6 +171,7 @@
|
|
171
171
|
}
|
172
172
|
|
173
173
|
private async loadTexture(url: string) {
|
174
|
+
if (!url) return Promise.resolve(null);
|
174
175
|
const cached = tryGetPreviouslyLoadedTexture(url);
|
175
176
|
if (cached) {
|
176
177
|
const res = await cached;
|
@@ -137,7 +137,7 @@
|
|
137
137
|
|
138
138
|
this.cam = GameObject.getComponent(this.gameObject, Camera);
|
139
139
|
if (!this.cam) {
|
140
|
-
console.
|
140
|
+
console.warn("SpectatorCamera: Spectator camera needs camera component on the same object.", this);
|
141
141
|
return;
|
142
142
|
}
|
143
143
|
|
@@ -83,7 +83,13 @@
|
|
83
83
|
|
84
84
|
if (debug)
|
85
85
|
console.log("start voip call");
|
86
|
-
|
86
|
+
try {
|
87
|
+
this.stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
|
88
|
+
}
|
89
|
+
catch (err) {
|
90
|
+
console.error(err);
|
91
|
+
return;
|
92
|
+
}
|
87
93
|
this.updateMute(this.voip.muteOutput);
|
88
94
|
if (debug)
|
89
95
|
console.log(this.stream)
|
@@ -321,7 +327,9 @@
|
|
321
327
|
|
322
328
|
this.context.connection.beginListen(RoomEvents.JoinedRoom, _evt => {
|
323
329
|
// request mic once
|
324
|
-
navigator.mediaDevices.getUserMedia({ audio: true, video: false })
|
330
|
+
navigator.mediaDevices.getUserMedia({ audio: true, video: false }).catch(err => {
|
331
|
+
console.error("Error initializing VoIP connection.", err);
|
332
|
+
});
|
325
333
|
});
|
326
334
|
|
327
335
|
this.context.connection.beginListen(PeerMessage.Update_ID, (cb: IPeerUpdateResponse) => {
|
@@ -414,7 +422,8 @@
|
|
414
422
|
}
|
415
423
|
|
416
424
|
private async onReceiveCall(call) {
|
417
|
-
|
425
|
+
if (!call) return;
|
426
|
+
|
418
427
|
const { metadata } = call;
|
419
428
|
console.assert(metadata.userId);
|
420
429
|
const { userId } = metadata;
|
@@ -428,8 +437,13 @@
|
|
428
437
|
|
429
438
|
// if we have mic permissions we can answer with our own mic
|
430
439
|
if (await Voip.HasMicrophonePermissions()) {
|
431
|
-
|
432
|
-
|
440
|
+
try {
|
441
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
|
442
|
+
call.answer(stream);
|
443
|
+
}
|
444
|
+
catch (err) {
|
445
|
+
console.error("Error initializing VoIP connection.", err);
|
446
|
+
}
|
433
447
|
}
|
434
448
|
// otherwise take the call but dont send any audio ourselves
|
435
449
|
else call.answer(null);
|
@@ -457,11 +471,17 @@
|
|
457
471
|
// }
|
458
472
|
|
459
473
|
public static async HasMicrophonePermissions(): Promise<boolean> {
|
460
|
-
|
461
|
-
|
462
|
-
|
474
|
+
try {
|
475
|
+
//@ts-ignore
|
476
|
+
const res = await navigator.permissions.query({ name: 'microphone' });
|
477
|
+
if (res.state === "denied") {
|
478
|
+
return false;
|
479
|
+
}
|
480
|
+
return true;
|
481
|
+
}
|
482
|
+
catch (err) {
|
483
|
+
console.error("Error querying `microphone` permissions.", err);
|
463
484
|
return false;
|
464
485
|
}
|
465
|
-
return true;
|
466
486
|
}
|
467
487
|
}
|
@@ -145,6 +145,7 @@
|
|
145
145
|
InstancingUtil.markDirty(this.gameObject, true);
|
146
146
|
// HACK to fix physics being not in correct place after exiting AR
|
147
147
|
setTimeout(() => {
|
148
|
+
if (!this.gameObject) return;
|
148
149
|
this.gameObject.matrixAutoUpdate = true;
|
149
150
|
this.gameObject.visible = true;
|
150
151
|
}, 100);
|
@@ -117,7 +117,7 @@
|
|
117
117
|
this.context = context;
|
118
118
|
this.guid = guid;
|
119
119
|
this.webxr = webXR;
|
120
|
-
this.setupCustomAvatar(this.webxr.defaultAvatar
|
120
|
+
this.setupCustomAvatar(this.webxr.defaultAvatar);
|
121
121
|
}
|
122
122
|
|
123
123
|
public updateFlags() {
|
@@ -251,7 +251,7 @@
|
|
251
251
|
}
|
252
252
|
}
|
253
253
|
|
254
|
-
private async setupCustomAvatar(avatarId: string | Object3D | AssetReference): Promise<boolean> {
|
254
|
+
private async setupCustomAvatar(avatarId: string | Object3D | AssetReference | undefined): Promise<boolean> {
|
255
255
|
if (debug)
|
256
256
|
console.log("LOAD", avatarId, this);
|
257
257
|
|
@@ -156,7 +156,7 @@
|
|
156
156
|
|
157
157
|
private static eventSubs: { [key: string]: Function[] } = {};
|
158
158
|
|
159
|
-
public webXR
|
159
|
+
public webXR?: WebXR;
|
160
160
|
public index: number = -1;
|
161
161
|
public controllerModel!: XRControllerModel;
|
162
162
|
public controller!: Group;
|
@@ -223,7 +223,7 @@
|
|
223
223
|
|
224
224
|
awake(): void {
|
225
225
|
if (!this.controller) {
|
226
|
-
console.warn("Missing
|
226
|
+
console.warn("WebXRController: Missing controller object.", this);
|
227
227
|
return;
|
228
228
|
}
|
229
229
|
this._connnectedCallback = this.onSourceConnected.bind(this);
|
@@ -260,6 +260,11 @@
|
|
260
260
|
}
|
261
261
|
|
262
262
|
public onEnable(): void {
|
263
|
+
if (!this.webXR) {
|
264
|
+
console.warn("No WebXR component assigned to WebXRController.");
|
265
|
+
return;
|
266
|
+
}
|
267
|
+
|
263
268
|
if (this.hand)
|
264
269
|
this.hand.name = "Hand";
|
265
270
|
if (this.controllerGrip)
|
@@ -301,9 +306,11 @@
|
|
301
306
|
// this.controllerGrip.removeEventListener("connected", this._connnectedCallback);
|
302
307
|
// this.controllerGrip.removeEventListener("disconnected", this._disconnectedCallback);
|
303
308
|
|
304
|
-
|
305
|
-
|
306
|
-
|
309
|
+
if (this.webXR) {
|
310
|
+
const i = this.webXR.Controllers.indexOf(this);
|
311
|
+
if (i >= 0)
|
312
|
+
this.webXR.Controllers.splice(i, 1);
|
313
|
+
}
|
307
314
|
}
|
308
315
|
|
309
316
|
// onDestroy(): void {
|
@@ -382,6 +389,7 @@
|
|
382
389
|
}
|
383
390
|
|
384
391
|
update(): void {
|
392
|
+
if (!this.webXR) return;
|
385
393
|
|
386
394
|
// TODO: we should wait until we actually have models, this is just a workaround
|
387
395
|
if (this.context.time.frameCount % 60 === 0) {
|
@@ -435,7 +443,7 @@
|
|
435
443
|
if (this.type === ControllerType.PhysicalDevice)
|
436
444
|
this.input = session.inputSources[this.index];
|
437
445
|
if (!this.input) return;
|
438
|
-
const rig = this.webXR
|
446
|
+
const rig = this.webXR!.Rig;
|
439
447
|
if (!rig) return;
|
440
448
|
|
441
449
|
if (this._didNotEndSelection && !this.handPointerModel.pinched) {
|
@@ -477,7 +485,7 @@
|
|
477
485
|
|
478
486
|
rig.getWorldQuaternion(this.worldRot);
|
479
487
|
this.movementVector.set(side, 0, forward);
|
480
|
-
this.movementVector.applyQuaternion(this.webXR
|
488
|
+
this.movementVector.applyQuaternion(this.webXR!.TransformOrientation);
|
481
489
|
this.movementVector.y = 0;
|
482
490
|
this.movementVector.applyQuaternion(this.worldRot);
|
483
491
|
this.movementVector.multiplyScalar(speedFactor * this.context.time.deltaTime);
|
@@ -524,8 +532,10 @@
|
|
524
532
|
}
|
525
533
|
else this._pinchStartTime = undefined;
|
526
534
|
|
527
|
-
|
528
|
-
|
535
|
+
const inVR = this.webXR!.IsInVR;
|
536
|
+
const xrRig = this.webXR!.Rig;
|
537
|
+
let doTeleport = teleport > .5 && inVR;
|
538
|
+
let isInMiniatureMode = xrRig ? xrRig?.scale?.x < .999 : false;
|
529
539
|
let newRigScale: number | null = null;
|
530
540
|
|
531
541
|
if (buttons && this.input && !this.input.hand) {
|
@@ -534,9 +544,9 @@
|
|
534
544
|
// button[4] seems to be the A button if it exists. On hololens it's randomly pressed though for hands
|
535
545
|
// see https://www.w3.org/TR/webxr-gamepads-module-1/#xr-standard-gamepad-mapping
|
536
546
|
if (i === 4) {
|
537
|
-
if (btn.pressed && !this.didChangeScale &&
|
547
|
+
if (btn.pressed && !this.didChangeScale && inVR) {
|
538
548
|
this.didChangeScale = true;
|
539
|
-
const rig =
|
549
|
+
const rig = xrRig;
|
540
550
|
if (rig) {
|
541
551
|
const args = this.switchScale(rig, doTeleport, isInMiniatureMode, newRigScale);
|
542
552
|
doTeleport = args.doTeleport;
|
@@ -611,8 +621,8 @@
|
|
611
621
|
const hit = rc ? rc[0] : null;
|
612
622
|
this.lastHit = hit;
|
613
623
|
let factor = 1;
|
614
|
-
if (this.webXR
|
615
|
-
factor /= this.webXR
|
624
|
+
if (this.webXR!.Rig) {
|
625
|
+
factor /= this.webXR!.Rig.scale.x;
|
616
626
|
}
|
617
627
|
// if (!hit) factor = 0;
|
618
628
|
|
@@ -961,7 +971,7 @@
|
|
961
971
|
const rot = controller.isUsingHands && closeGrab ? this.controller.getWristQuaternion()!.clone() : controller.rayRotation.clone();
|
962
972
|
getWorldQuaternion(this.selected, this.localQuaternionToGrab).premultiply(rot.invert());
|
963
973
|
|
964
|
-
const rig = this.controller.webXR
|
974
|
+
const rig = this.controller.webXR!.Rig;
|
965
975
|
if (rig)
|
966
976
|
this.rigPositionLastFrame.copy(getWorldPosition(rig))
|
967
977
|
|
@@ -1056,7 +1066,7 @@
|
|
1056
1066
|
this.controllerPosDelta.copy(this.controllerWorldPos);
|
1057
1067
|
this.controllerPosDelta.sub(this.lastControllerWorldPos);
|
1058
1068
|
this.lastControllerWorldPos.copy(this.controllerWorldPos);
|
1059
|
-
const rig = this.controller.webXR
|
1069
|
+
const rig = this.controller.webXR!.Rig;
|
1060
1070
|
if (rig) {
|
1061
1071
|
const rigPos = getWorldPosition(rig);
|
1062
1072
|
const rigDelta = this.rigPositionLastFrame.sub(rigPos);
|
@@ -1094,7 +1104,7 @@
|
|
1094
1104
|
|
1095
1105
|
if (!this.didReparent && this.selected && this.controller) {
|
1096
1106
|
|
1097
|
-
const rigScale = this.controller.webXR
|
1107
|
+
const rigScale = this.controller.webXR!.Rig?.scale.x ?? 1.0;
|
1098
1108
|
|
1099
1109
|
this.totalChangeAlongDirection += this.controllerMovementSinceLastFrame();
|
1100
1110
|
// console.log(this.totalChangeAlongDirection);
|
@@ -138,13 +138,14 @@
|
|
138
138
|
export class WebXRImageTracking extends Behaviour {
|
139
139
|
|
140
140
|
@serializable(WebXRImageTrackingModel)
|
141
|
-
trackedImages
|
141
|
+
trackedImages?: WebXRImageTrackingModel[];
|
142
142
|
|
143
143
|
private readonly trackedImageIndexMap: Map<number, WebXRImageTrackingModel> = new Map();
|
144
144
|
|
145
145
|
private static _imageElements: Map<string, ImageBitmap | null> = new Map();
|
146
146
|
|
147
147
|
awake(): void {
|
148
|
+
if (!this.trackedImages) return;
|
148
149
|
for (const trackedImage of this.trackedImages) {
|
149
150
|
if (trackedImage.image) {
|
150
151
|
if (WebXRImageTracking._imageElements.has(trackedImage.image)) {
|
@@ -160,19 +161,19 @@
|
|
160
161
|
|
161
162
|
// read back Uint8Array to use in USDZ -
|
162
163
|
// TODO better would be to do that once we actually need it
|
163
|
-
const canvas = await imageToCanvas(
|
164
|
+
const canvas = await imageToCanvas(img);
|
164
165
|
if (canvas) {
|
165
|
-
const blob = await new Promise(
|
166
|
+
const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png', 1)) as any;
|
166
167
|
const arrayBuffer = await blob.arrayBuffer();
|
167
|
-
|
168
|
+
|
168
169
|
const exporter = GameObject.findObjectOfType(USDZExporter);
|
169
|
-
if (exporter) {
|
170
|
+
if (exporter && this.trackedImages) {
|
170
171
|
exporter.extensions.push(
|
171
|
-
new ImageTrackingExtension("marker.png", new Uint8Array(arrayBuffer), this.trackedImages[0].widthInMeters)
|
172
|
-
);
|
172
|
+
new ImageTrackingExtension("marker.png", new Uint8Array(arrayBuffer), this.trackedImages[0].widthInMeters)
|
173
|
+
);
|
173
174
|
exporter.anchoringType = "image";
|
174
175
|
}
|
175
|
-
}
|
176
|
+
}
|
176
177
|
});
|
177
178
|
}
|
178
179
|
}
|
@@ -197,6 +198,7 @@
|
|
197
198
|
}
|
198
199
|
|
199
200
|
private onModifyAROptions = (event: any) => {
|
201
|
+
if (!this.trackedImages) return;
|
200
202
|
const options = event.detail;
|
201
203
|
const features = options.optionalFeatures || [];
|
202
204
|
if (!features.includes("image-tracking"))
|
@@ -254,7 +256,7 @@
|
|
254
256
|
|
255
257
|
if (asset) {
|
256
258
|
trackedData!.object = asset;
|
257
|
-
|
259
|
+
|
258
260
|
// make sure to parent to the WebXR.rig
|
259
261
|
if (this.xr) {
|
260
262
|
this.xr.Rig.add(asset);
|
@@ -273,9 +275,9 @@
|
|
273
275
|
// to improve the tracking quality a bit.
|
274
276
|
if (model.imageDoesNotMove && trackedData.frames > 10)
|
275
277
|
continue;
|
276
|
-
|
278
|
+
|
277
279
|
if (!trackedData.object) continue;
|
278
|
-
|
280
|
+
|
279
281
|
if (this.xr) {
|
280
282
|
this.xr.Rig.add(trackedData.object);
|
281
283
|
}
|
@@ -187,10 +187,9 @@
|
|
187
187
|
|
188
188
|
if(!this.webXR)
|
189
189
|
{
|
190
|
-
console.log("Missing webxr component");
|
191
190
|
this.webXR = GameObject.findObjectOfType(WebXR, this.context);
|
192
191
|
if(!this.webXR) {
|
193
|
-
console.
|
192
|
+
console.warn("WebXRSync: Could not find WebXR component, won't sync.");
|
194
193
|
return;
|
195
194
|
}
|
196
195
|
}
|
@@ -282,11 +281,12 @@
|
|
282
281
|
console.log("old data", timeDiff, guid)
|
283
282
|
return null;
|
284
283
|
}
|
284
|
+
if (!this.webXR) return null;
|
285
285
|
let user = this.avatars[guid];
|
286
286
|
if (user === undefined) {
|
287
287
|
try {
|
288
288
|
console.log("create new avatar");
|
289
|
-
const newUser = new WebXRAvatar(this.context, guid, this.webXR
|
289
|
+
const newUser = new WebXRAvatar(this.context, guid, this.webXR);
|
290
290
|
user = newUser;
|
291
291
|
this.avatars[guid] = newUser;
|
292
292
|
} catch (err) {
|