@needle-tools/engine 3.5.0-alpha → 3.5.2-alpha
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 +11 -0
- package/dist/needle-engine.js +10447 -10303
- package/dist/needle-engine.min.js +375 -372
- package/dist/needle-engine.umd.cjs +319 -316
- package/lib/engine/codegen/register_types.js +0 -2
- package/lib/engine/codegen/register_types.js.map +1 -1
- package/lib/engine/engine_context.d.ts +8 -3
- package/lib/engine/engine_context.js +36 -17
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_element_loading.js +2 -2
- package/lib/engine/engine_element_loading.js.map +1 -1
- package/lib/engine/engine_license.d.ts +2 -0
- package/lib/engine/engine_license.js +25 -4
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine-components/ReflectionProbe.js +16 -7
- package/lib/engine-components/ReflectionProbe.js.map +1 -1
- package/lib/engine-components/Renderer.js +3 -4
- package/lib/engine-components/Renderer.js.map +1 -1
- package/lib/engine-components/SceneSwitcher.d.ts +9 -0
- package/lib/engine-components/SceneSwitcher.js +128 -0
- package/lib/engine-components/SceneSwitcher.js.map +1 -1
- package/lib/engine-components/codegen/components.d.ts +0 -1
- package/lib/engine-components/codegen/components.js +0 -1
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/lib/engine-components/export/usdz/ThreeUSDZExporter.d.ts +9 -5
- package/lib/engine-components/export/usdz/ThreeUSDZExporter.js +25 -11
- package/lib/engine-components/export/usdz/ThreeUSDZExporter.js.map +1 -1
- package/lib/engine-components/export/usdz/USDZExporter.d.ts +1 -0
- package/lib/engine-components/export/usdz/USDZExporter.js +14 -5
- package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
- package/lib/engine-components/export/usdz/extensions/behavior/Behaviour.d.ts +1 -5
- package/lib/engine-components/export/usdz/extensions/behavior/Behaviour.js +1 -10
- package/lib/engine-components/export/usdz/extensions/behavior/Behaviour.js.map +1 -1
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +4 -4
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -1
- package/lib/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.js.map +1 -1
- package/lib/engine-components/ui/Canvas.js +29 -16
- package/lib/engine-components/ui/Canvas.js.map +1 -1
- package/lib/engine-components/ui/Layout.js +10 -5
- package/lib/engine-components/ui/Layout.js.map +1 -1
- package/lib/engine-components/ui/RectTransform.js +9 -6
- package/lib/engine-components/ui/RectTransform.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/plugins/vite/license.js +2 -2
- package/src/engine/codegen/register_types.js +2 -4
- package/src/engine/engine_context.ts +43 -19
- package/src/engine/engine_element_loading.ts +2 -2
- package/src/engine/engine_license.ts +25 -4
- package/src/engine-components/ReflectionProbe.ts +17 -7
- package/src/engine-components/Renderer.ts +5 -5
- package/src/engine-components/RendererLightmap.ts +1 -1
- package/src/engine-components/SceneSwitcher.ts +136 -1
- package/src/engine-components/codegen/components.ts +0 -1
- package/src/engine-components/export/usdz/ThreeUSDZExporter.ts +38 -12
- package/src/engine-components/export/usdz/USDZExporter.ts +14 -7
- package/src/engine-components/export/usdz/extensions/behavior/Behaviour.ts +5 -15
- package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +19 -19
- package/src/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.ts +2 -2
- package/src/engine-components/ui/Canvas.ts +29 -16
- package/src/engine-components/ui/Layout.ts +10 -5
- package/src/engine-components/ui/RectTransform.ts +10 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/engine",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.2-alpha",
|
|
4
4
|
"description": "Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in",
|
|
5
5
|
"main": "dist/needle-engine.umd.cjs",
|
|
6
6
|
"type": "module",
|
package/plugins/vite/license.js
CHANGED
|
@@ -13,8 +13,8 @@ export const needleLicense = (command, config, userSettings) => {
|
|
|
13
13
|
if (isNeedleEngineFile || isViteChunkFile) {
|
|
14
14
|
const needleConfig = await loadConfig();
|
|
15
15
|
if (needleConfig) {
|
|
16
|
-
if (needleConfig.
|
|
17
|
-
src = src.replace("
|
|
16
|
+
if (typeof needleConfig.license === "string") {
|
|
17
|
+
src = src.replace("const NEEDLE_ENGINE_LICENSE_TYPE: string = \"\";", "const NEEDLE_ENGINE_LICENSE_TYPE: string = \"" + needleConfig.license + "\";");
|
|
18
18
|
return { code: src, map: null }
|
|
19
19
|
}
|
|
20
20
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TypeStore } from "./../engine_typestore"
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
// Import types
|
|
4
4
|
import { __Ignore } from "../../engine-components/codegen/components";
|
|
5
5
|
import { ActionBuilder } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder";
|
|
@@ -184,7 +184,6 @@ import { TriggerModel } from "../../engine-components/export/usdz/extensions/beh
|
|
|
184
184
|
import { UIRaycastUtils } from "../../engine-components/ui/RaycastUtils";
|
|
185
185
|
import { UIRootComponent } from "../../engine-components/ui/BaseUIComponent";
|
|
186
186
|
import { UsageMarker } from "../../engine-components/Interactable";
|
|
187
|
-
import { USDZBehaviours } from "../../engine-components/export/usdz/extensions/behavior/Behaviour";
|
|
188
187
|
import { USDZExporter } from "../../engine-components/export/usdz/USDZExporter";
|
|
189
188
|
import { USDZText } from "../../engine-components/export/usdz/extensions/USDZText";
|
|
190
189
|
import { VariantAction } from "../../engine-components/export/usdz/extensions/behavior/Actions";
|
|
@@ -214,7 +213,7 @@ import { XRGrabModel } from "../../engine-components/webxr/WebXRGrabRendering";
|
|
|
214
213
|
import { XRGrabRendering } from "../../engine-components/webxr/WebXRGrabRendering";
|
|
215
214
|
import { XRRig } from "../../engine-components/webxr/WebXRRig";
|
|
216
215
|
import { XRState } from "../../engine-components/XRFlag";
|
|
217
|
-
|
|
216
|
+
|
|
218
217
|
// Register types
|
|
219
218
|
TypeStore.add("__Ignore", __Ignore);
|
|
220
219
|
TypeStore.add("ActionBuilder", ActionBuilder);
|
|
@@ -399,7 +398,6 @@ TypeStore.add("TriggerModel", TriggerModel);
|
|
|
399
398
|
TypeStore.add("UIRaycastUtils", UIRaycastUtils);
|
|
400
399
|
TypeStore.add("UIRootComponent", UIRootComponent);
|
|
401
400
|
TypeStore.add("UsageMarker", UsageMarker);
|
|
402
|
-
TypeStore.add("USDZBehaviours", USDZBehaviours);
|
|
403
401
|
TypeStore.add("USDZExporter", USDZExporter);
|
|
404
402
|
TypeStore.add("USDZText", USDZText);
|
|
405
403
|
TypeStore.add("VariantAction", VariantAction);
|
|
@@ -80,7 +80,7 @@ export enum XRSessionMode {
|
|
|
80
80
|
ImmersiveAR = "immersive-ar",
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
export declare type
|
|
83
|
+
export declare type OnRenderCallback = (renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: Group) => void
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
export function registerComponent(script: IComponent, context?: Context) {
|
|
@@ -440,33 +440,57 @@ export class Context implements IContext {
|
|
|
440
440
|
}
|
|
441
441
|
}
|
|
442
442
|
|
|
443
|
-
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
private _onBeforeRenderListeners = new Map<string, OnRenderCallback[]>();
|
|
446
|
+
private _onAfterRenderListeners = new Map<string, OnRenderCallback[]>();
|
|
444
447
|
|
|
445
448
|
/** use this to subscribe to onBeforeRender events on threejs objects */
|
|
446
|
-
addBeforeRenderListener(target: Object3D, callback:
|
|
447
|
-
if (!this._onBeforeRenderListeners
|
|
448
|
-
this._onBeforeRenderListeners
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
449
|
+
addBeforeRenderListener(target: Object3D, callback: OnRenderCallback) {
|
|
450
|
+
if (!this._onBeforeRenderListeners.has(target.uuid)) {
|
|
451
|
+
this._onBeforeRenderListeners.set(target.uuid, []);
|
|
452
|
+
target.onBeforeRender = this._createRenderCallbackWrapper(target, this._onBeforeRenderListeners);
|
|
453
|
+
}
|
|
454
|
+
this._onBeforeRenderListeners.get(target.uuid)?.push(callback);
|
|
455
|
+
}
|
|
456
|
+
removeBeforeRenderListener(target: Object3D, callback: OnRenderCallback) {
|
|
457
|
+
if (this._onBeforeRenderListeners.has(target.uuid)) {
|
|
458
|
+
const arr = this._onBeforeRenderListeners.get(target.uuid)!;
|
|
459
|
+
const idx = arr.indexOf(callback);
|
|
460
|
+
if (idx >= 0) arr.splice(idx, 1);
|
|
458
461
|
}
|
|
459
|
-
this._onBeforeRenderListeners[target.uuid].push(callback);
|
|
460
462
|
}
|
|
461
463
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
464
|
+
/** use this to subscribe to onAfterRender events on threejs objects */
|
|
465
|
+
addAfterRenderListener(target: Object3D, callback: OnRenderCallback) {
|
|
466
|
+
if (!this._onAfterRenderListeners.has(target.uuid)) {
|
|
467
|
+
this._onAfterRenderListeners.set(target.uuid, []);
|
|
468
|
+
target.onAfterRender = this._createRenderCallbackWrapper(target, this._onAfterRenderListeners);
|
|
469
|
+
}
|
|
470
|
+
this._onAfterRenderListeners.get(target.uuid)?.push(callback);
|
|
471
|
+
}
|
|
472
|
+
removeAfterRenderListener(target: Object3D, callback: OnRenderCallback) {
|
|
473
|
+
if (this._onAfterRenderListeners.has(target.uuid)) {
|
|
474
|
+
const arr = this._onAfterRenderListeners.get(target.uuid)!;
|
|
465
475
|
const idx = arr.indexOf(callback);
|
|
466
476
|
if (idx >= 0) arr.splice(idx, 1);
|
|
467
477
|
}
|
|
468
478
|
}
|
|
469
479
|
|
|
480
|
+
|
|
481
|
+
private _createRenderCallbackWrapper(target: Object3D, array: Map<string, OnRenderCallback[]>): OnRenderCallback {
|
|
482
|
+
return (renderer, scene, camera, geometry, material, group) => {
|
|
483
|
+
const arr = array.get(target.uuid);
|
|
484
|
+
if (!arr) return;
|
|
485
|
+
for (let i = 0; i < arr.length; i++) {
|
|
486
|
+
const fn = arr[i];
|
|
487
|
+
fn(renderer, scene, camera, geometry, material, group);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
|
|
470
494
|
private _requireDepthTexture: boolean = false;
|
|
471
495
|
private _requireColorTexture: boolean = false;
|
|
472
496
|
private _renderTarget?: WebGLRenderTarget;
|
|
@@ -584,7 +608,7 @@ export class Context implements IContext {
|
|
|
584
608
|
else {
|
|
585
609
|
ContextRegistry.dispatchCallback(ContextEvent.MissingCamera, this);
|
|
586
610
|
if (!this.mainCamera && !this.isManagedExternally)
|
|
587
|
-
console.
|
|
611
|
+
console.warn("Missing camera in main scene", this);
|
|
588
612
|
}
|
|
589
613
|
}
|
|
590
614
|
|
|
@@ -3,7 +3,7 @@ import { Mathf } from "./engine_math";
|
|
|
3
3
|
import { LoadingProgressArgs } from "./engine_setup";
|
|
4
4
|
import { getParam } from "./engine_utils";
|
|
5
5
|
import { logoSVG } from "./assets"
|
|
6
|
-
import { hasProLicense } from "./engine_license";
|
|
6
|
+
import { hasCommercialLicense, hasProLicense } from "./engine_license";
|
|
7
7
|
|
|
8
8
|
const debug = getParam("debugloadingbar");
|
|
9
9
|
const debugRendering = getParam("debugloadingbarrendering");
|
|
@@ -301,7 +301,7 @@ export class EngineLoadingView implements ILoadingViewHandler {
|
|
|
301
301
|
}
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
-
if (!
|
|
304
|
+
if (!hasCommercialLicense()) {
|
|
305
305
|
const nonCommercialContainer = document.createElement("div");
|
|
306
306
|
nonCommercialContainer.style.paddingTop = ".6em";
|
|
307
307
|
nonCommercialContainer.style.fontSize = ".8em";
|
|
@@ -7,10 +7,28 @@ const debug = getParam("debuglicense");
|
|
|
7
7
|
|
|
8
8
|
// This is modified by a bundler (e.g. vite)
|
|
9
9
|
// Do not edit manually
|
|
10
|
-
const
|
|
10
|
+
const NEEDLE_ENGINE_LICENSE_TYPE: string = "";
|
|
11
|
+
if (debug) console.log("License Type: " + NEEDLE_ENGINE_LICENSE_TYPE)
|
|
11
12
|
|
|
12
13
|
export function hasProLicense() {
|
|
13
|
-
|
|
14
|
+
switch (NEEDLE_ENGINE_LICENSE_TYPE) {
|
|
15
|
+
case "pro":
|
|
16
|
+
case "enterprise":
|
|
17
|
+
return true;
|
|
18
|
+
};
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function hasIndieLicense() {
|
|
23
|
+
switch (NEEDLE_ENGINE_LICENSE_TYPE) {
|
|
24
|
+
case "indie":
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function hasCommercialLicense() {
|
|
31
|
+
return hasProLicense() || hasIndieLicense();
|
|
14
32
|
}
|
|
15
33
|
|
|
16
34
|
|
|
@@ -52,7 +70,8 @@ function insertNonCommercialUseHint(ctx: IContext) {
|
|
|
52
70
|
}
|
|
53
71
|
}, 100);
|
|
54
72
|
|
|
55
|
-
|
|
73
|
+
if (!hasCommercialLicense())
|
|
74
|
+
logNonCommercialUse();
|
|
56
75
|
|
|
57
76
|
let svg = `<img class="logo" src="${logoSVG}" style="width: 40px; height: 40px; margin-right: 2px; vertical-align: middle; margin-bottom: 2px;"/>`;
|
|
58
77
|
const logoElement = document.createElement("div");
|
|
@@ -66,7 +85,9 @@ function insertNonCommercialUseHint(ctx: IContext) {
|
|
|
66
85
|
// textElement.innerHTML = "Needle Engine<br/><span class=\"non-commercial\">Non Commercial</span>";
|
|
67
86
|
licenseElement.appendChild(textElement);
|
|
68
87
|
|
|
69
|
-
licenseElement.title = "Needle Engine
|
|
88
|
+
licenseElement.title = "Needle Engine";
|
|
89
|
+
if (!hasCommercialLicense())
|
|
90
|
+
licenseElement.title += " non commercial version";
|
|
70
91
|
licenseElement.addEventListener("click", () => {
|
|
71
92
|
globalThis.open("https://needle.tools", "_blank");
|
|
72
93
|
});
|
|
@@ -129,17 +129,28 @@ export class ReflectionProbe extends Behaviour {
|
|
|
129
129
|
// otherwise some renderers outside of the probe will be affected or vice versa
|
|
130
130
|
for (let i = 0; i < _rend.sharedMaterials.length; i++) {
|
|
131
131
|
const material = _rend.sharedMaterials[i];
|
|
132
|
-
if (!material)
|
|
133
|
-
if (material["envMap"] === undefined) continue;
|
|
134
|
-
if (material["envMap"] === this.texture) {
|
|
132
|
+
if (!material) {
|
|
135
133
|
continue;
|
|
136
134
|
}
|
|
135
|
+
if (material["envMap"] === undefined) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
137
139
|
let cached = rendererCache[i];
|
|
138
140
|
|
|
139
141
|
// make sure we have the currently assigned material cached (and an up to date clone of that)
|
|
140
142
|
// TODO: this is causing problems with progressive textures sometimes (depending on the order) when the version changes and we re-create materials over and over. We might want to just set the material envmap instead of making a clone
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
let isCachedInstance = material === cached?.copy;
|
|
144
|
+
let hasChanged = !cached || cached.material.uuid !== material.uuid || cached.copy.version !== material.version;
|
|
145
|
+
if (!isCachedInstance && hasChanged) {
|
|
146
|
+
if (debug) {
|
|
147
|
+
let reason = "";
|
|
148
|
+
if (!cached) reason = "not cached";
|
|
149
|
+
else if (cached.material !== material) reason = "reference changed; cached instance?: " + isCachedInstance;
|
|
150
|
+
else if (cached.copy.version !== material.version) reason = "version changed";
|
|
151
|
+
console.warn("Cloning material", material.name, material.version, "Reason:", reason, "\n", material.uuid, "\n", cached?.copy.uuid, "\n", _rend.name);
|
|
152
|
+
}
|
|
153
|
+
|
|
143
154
|
const clone = material.clone();
|
|
144
155
|
clone.version = material.version;
|
|
145
156
|
|
|
@@ -158,8 +169,7 @@ export class ReflectionProbe extends Behaviour {
|
|
|
158
169
|
clone[$reflectionProbeKey] = this;
|
|
159
170
|
clone[$originalMaterial] = material;
|
|
160
171
|
|
|
161
|
-
if (debug)
|
|
162
|
-
console.log("Set reflection", _rend.name, _rend.guid);
|
|
172
|
+
if (debug) console.log("Set reflection", _rend.name, _rend.guid);
|
|
163
173
|
}
|
|
164
174
|
|
|
165
175
|
/** this is the material that we copied and that has the reflection probe */
|
|
@@ -605,15 +605,15 @@ export class Renderer extends Behaviour implements IRenderer {
|
|
|
605
605
|
}
|
|
606
606
|
}
|
|
607
607
|
|
|
608
|
-
|
|
608
|
+
|
|
609
|
+
if (this.reflectionProbeUsage !== ReflectionProbeUsage.Off && this._reflectionProbe) {
|
|
610
|
+
this._reflectionProbe.onSet(this);
|
|
611
|
+
}
|
|
609
612
|
|
|
613
|
+
}
|
|
610
614
|
onBeforeRenderThree(_renderer, _scene, _camera, _geometry, material, _group) {
|
|
611
615
|
|
|
612
616
|
this.loadProgressiveTextures(material);
|
|
613
|
-
// TODO: we might want to just set the material.envMap for one material during rendering instead of cloning the material
|
|
614
|
-
if (this.reflectionProbeUsage !== ReflectionProbeUsage.Off && this._reflectionProbe) {
|
|
615
|
-
this._reflectionProbe.onSet(this);
|
|
616
|
-
}
|
|
617
617
|
|
|
618
618
|
if (material.envMapIntensity !== undefined) {
|
|
619
619
|
const factor = this.hasLightmap ? Math.PI : 1;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Behaviour, GameObject } from "./Component";
|
|
2
2
|
import * as THREE from "three";
|
|
3
3
|
import { Texture } from "three";
|
|
4
|
-
import { Context,
|
|
4
|
+
import { Context, OnRenderCallback } from "../engine/engine_setup";
|
|
5
5
|
|
|
6
6
|
// this component is automatically added by the Renderer if the object has lightmap uvs AND we have a lightmap
|
|
7
7
|
// for multimaterial objects GLTF exports a "Group" with the renderer component
|
|
@@ -56,11 +56,27 @@ export class SceneSwitcher extends Behaviour {
|
|
|
56
56
|
@serializable()
|
|
57
57
|
useSceneLighting: boolean = true;
|
|
58
58
|
|
|
59
|
+
/** how many scenes after the currently active scene should be preloaded */
|
|
60
|
+
@serializable()
|
|
61
|
+
preloadNext: number = 1;
|
|
62
|
+
|
|
63
|
+
/** how many scenes before the currently active scene should be preloaded */
|
|
64
|
+
@serializable()
|
|
65
|
+
preloadPrevious: number = 1;
|
|
66
|
+
|
|
67
|
+
/** how many scenes can be loaded in parallel */
|
|
68
|
+
@serializable()
|
|
69
|
+
preloadConcurrent: number = 2;
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
get currentIndex(): number { return this._currentIndex; }
|
|
59
73
|
|
|
60
74
|
private _currentIndex: number = -1;
|
|
61
75
|
private _currentScene: AssetReference | undefined = undefined;
|
|
62
76
|
private _engineElementOverserver: MutationObserver | undefined = undefined;
|
|
63
77
|
|
|
78
|
+
private _preloadScheduler?: PreLoadScheduler;
|
|
79
|
+
|
|
64
80
|
async start() {
|
|
65
81
|
if (this._currentIndex === -1 && !await this.tryLoadFromQueryParam()) {
|
|
66
82
|
const value = this.context.domElement.getAttribute(ENGINE_ELEMENT_SCENE_ATTRIBUTE_NAME);
|
|
@@ -100,6 +116,13 @@ export class SceneSwitcher extends Behaviour {
|
|
|
100
116
|
this._engineElementOverserver.observe(this.context.domElement, {
|
|
101
117
|
attributes: true
|
|
102
118
|
});
|
|
119
|
+
|
|
120
|
+
if (!this._preloadScheduler)
|
|
121
|
+
this._preloadScheduler = new PreLoadScheduler(this);
|
|
122
|
+
this._preloadScheduler.maxLoadAhead = this.preloadNext;
|
|
123
|
+
this._preloadScheduler.maxLoadBehind = this.preloadPrevious;
|
|
124
|
+
this._preloadScheduler.maxConcurrent = this.preloadConcurrent;
|
|
125
|
+
this._preloadScheduler.begin();
|
|
103
126
|
}
|
|
104
127
|
|
|
105
128
|
onDisable(): void {
|
|
@@ -107,6 +130,7 @@ export class SceneSwitcher extends Behaviour {
|
|
|
107
130
|
this.context.input.removeEventListener(InputEvents.KeyDown, this.onKeyDown);
|
|
108
131
|
this.context.input.removeEventListener(InputEvents.PointerMove, this.onPointerMove);
|
|
109
132
|
this.context.input.removeEventListener(InputEvents.PointerUp, this.onPointerUp);
|
|
133
|
+
this._preloadScheduler?.stop();
|
|
110
134
|
}
|
|
111
135
|
|
|
112
136
|
private onPopState = async (_state: PopStateEvent) => {
|
|
@@ -189,7 +213,7 @@ export class SceneSwitcher extends Behaviour {
|
|
|
189
213
|
select(index: number | string): Promise<boolean> {
|
|
190
214
|
if (debug) console.log("select", index);
|
|
191
215
|
|
|
192
|
-
if(typeof index === "object"){
|
|
216
|
+
if (typeof index === "object") {
|
|
193
217
|
// If a user tries to reference a scene object in a UnityEvent and invoke select(obj)
|
|
194
218
|
// Then the object will be serialized as a object { guid : ... } or with the index json pointer
|
|
195
219
|
// This case is not supported right now. Object references in the editor must not be scene references
|
|
@@ -271,6 +295,15 @@ export class SceneSwitcher extends Behaviour {
|
|
|
271
295
|
return false;
|
|
272
296
|
}
|
|
273
297
|
|
|
298
|
+
preload(index: number) {
|
|
299
|
+
if (index >= 0 && index < this.scenes.length) {
|
|
300
|
+
const scene = this.scenes[index];
|
|
301
|
+
if(scene instanceof AssetReference)
|
|
302
|
+
return scene.preload();
|
|
303
|
+
}
|
|
304
|
+
return couldNotLoadScenePromise;
|
|
305
|
+
}
|
|
306
|
+
|
|
274
307
|
private tryLoadFromQueryParam() {
|
|
275
308
|
if (!this.queryParameterName?.length) return couldNotLoadScenePromise;
|
|
276
309
|
// try restore the scene from the url
|
|
@@ -308,3 +341,105 @@ export class SceneSwitcher extends Behaviour {
|
|
|
308
341
|
return couldNotLoadScenePromise;
|
|
309
342
|
}
|
|
310
343
|
}
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
class PreLoadScheduler {
|
|
349
|
+
maxLoadAhead: number;
|
|
350
|
+
maxLoadBehind: number;
|
|
351
|
+
maxConcurrent: number;
|
|
352
|
+
|
|
353
|
+
private _isRunning: boolean = false;
|
|
354
|
+
private _rooms: SceneSwitcher;
|
|
355
|
+
private _roomTasks: LoadTask[] = [];
|
|
356
|
+
private _maxConcurrentLoads: number = 1;
|
|
357
|
+
|
|
358
|
+
constructor(rooms: SceneSwitcher, ahead: number = 1, behind: number = 1, maxConcurrent: number = 2) {
|
|
359
|
+
this._rooms = rooms;
|
|
360
|
+
this.maxLoadAhead = ahead;
|
|
361
|
+
this.maxLoadBehind = behind;
|
|
362
|
+
this.maxConcurrent = maxConcurrent;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
begin() {
|
|
366
|
+
if (this._isRunning) return;
|
|
367
|
+
if (debug) console.log("Preload begin")
|
|
368
|
+
this._isRunning = true;
|
|
369
|
+
let lastRoom: number = -1;
|
|
370
|
+
let searchDistance: number;
|
|
371
|
+
let searchCall: number;
|
|
372
|
+
const array = this._rooms.scenes;
|
|
373
|
+
let interval = setInterval(() => {
|
|
374
|
+
if (this.allLoaded()) {
|
|
375
|
+
if (debug)
|
|
376
|
+
console.log("All scenes loaded");
|
|
377
|
+
this.stop();
|
|
378
|
+
}
|
|
379
|
+
if (!this._isRunning) {
|
|
380
|
+
clearInterval(interval);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (this.canLoadNewScene() === false) return;
|
|
384
|
+
if (lastRoom !== this._rooms.currentIndex) {
|
|
385
|
+
lastRoom = this._rooms.currentIndex;
|
|
386
|
+
searchCall = 0;
|
|
387
|
+
searchDistance = 0;
|
|
388
|
+
}
|
|
389
|
+
const searchForward = searchCall % 2 === 0;
|
|
390
|
+
if (searchForward) searchDistance += 1;
|
|
391
|
+
searchCall += 1;
|
|
392
|
+
const maxSearchDistance = searchForward ? this.maxLoadAhead : this.maxLoadBehind;
|
|
393
|
+
if (searchDistance > maxSearchDistance) return;
|
|
394
|
+
let roomIndex = searchForward ? lastRoom + searchDistance : lastRoom - searchDistance;
|
|
395
|
+
if (roomIndex < 0) return;
|
|
396
|
+
// if (roomIndex < 0) roomIndex = array.length + roomIndex;
|
|
397
|
+
if (roomIndex < 0 || roomIndex >= array.length) return;
|
|
398
|
+
const scene = array[roomIndex];
|
|
399
|
+
new LoadTask(roomIndex, scene, this._roomTasks);
|
|
400
|
+
}, 200);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
stop() {
|
|
404
|
+
this._isRunning = false;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
canLoadNewScene(): boolean {
|
|
408
|
+
return this._roomTasks.length < this._maxConcurrentLoads;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
allLoaded(): boolean {
|
|
412
|
+
for (const room of this._rooms.scenes) {
|
|
413
|
+
if (room.isLoaded() === false) return false;
|
|
414
|
+
}
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
class LoadTask {
|
|
420
|
+
|
|
421
|
+
index: number;
|
|
422
|
+
asset: AssetReference;
|
|
423
|
+
tasks: LoadTask[];
|
|
424
|
+
|
|
425
|
+
constructor(index: number, asset: AssetReference, tasks: LoadTask[]) {
|
|
426
|
+
this.index = index;
|
|
427
|
+
this.asset = asset;
|
|
428
|
+
this.tasks = tasks;
|
|
429
|
+
tasks.push(this);
|
|
430
|
+
this.awaitLoading();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
private async awaitLoading() {
|
|
434
|
+
if (!this.asset.isLoaded()) {
|
|
435
|
+
if (debug)
|
|
436
|
+
console.log("Preload start: " + this.asset.uri, this.index);
|
|
437
|
+
await this.asset.preload();
|
|
438
|
+
if (debug)
|
|
439
|
+
console.log("Preload finished: " + this.asset.uri, this.index);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const i = this.tasks.indexOf(this);
|
|
443
|
+
if (i >= 0) this.tasks.splice(i, 1);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
@@ -179,7 +179,6 @@ export { TriggerModel } from "../export/usdz/extensions/behavior/BehavioursBuild
|
|
|
179
179
|
export { UIRaycastUtils } from "../ui/RaycastUtils";
|
|
180
180
|
export { UIRootComponent } from "../ui/BaseUIComponent";
|
|
181
181
|
export { UsageMarker } from "../Interactable";
|
|
182
|
-
export { USDZBehaviours } from "../export/usdz/extensions/behavior/Behaviour";
|
|
183
182
|
export { USDZExporter } from "../export/usdz/USDZExporter";
|
|
184
183
|
export { USDZText } from "../export/usdz/extensions/USDZText";
|
|
185
184
|
export { VariantAction } from "../export/usdz/extensions/behavior/Actions";
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
Camera,
|
|
18
18
|
Color,
|
|
19
19
|
MeshStandardMaterial,
|
|
20
|
-
LinearEncoding,
|
|
21
20
|
sRGBEncoding,
|
|
22
21
|
MeshPhysicalMaterial,
|
|
23
22
|
} from 'three';
|
|
@@ -49,7 +48,6 @@ class USDObject {
|
|
|
49
48
|
parent: USDObject | null;
|
|
50
49
|
children: Array<USDObject | null> = [];
|
|
51
50
|
_eventListeners: {};
|
|
52
|
-
mesh: any;
|
|
53
51
|
|
|
54
52
|
static createEmptyParent( object ) {
|
|
55
53
|
|
|
@@ -93,7 +91,7 @@ class USDObject {
|
|
|
93
91
|
|
|
94
92
|
clone() {
|
|
95
93
|
|
|
96
|
-
const clone = new USDObject( MathUtils.generateUUID(), this.name, this.matrix, this.
|
|
94
|
+
const clone = new USDObject( MathUtils.generateUUID(), this.name, this.matrix, this.geometry, this.material );
|
|
97
95
|
clone.isDynamic = this.isDynamic;
|
|
98
96
|
return clone;
|
|
99
97
|
|
|
@@ -384,9 +382,20 @@ class USDZExporterContext {
|
|
|
384
382
|
|
|
385
383
|
}
|
|
386
384
|
|
|
385
|
+
/**[documentation](https://developer.apple.com/documentation/arkit/usdz_schemas_for_ar/preliminary_anchoringapi/preliminary_anchoring_type) */
|
|
386
|
+
export type Anchoring = "plane" | "image" | "face" | "none"
|
|
387
|
+
/**[documentation](https://developer.apple.com/documentation/arkit/usdz_schemas_for_ar/preliminary_anchoringapi/preliminary_planeanchoring_alignment) */
|
|
388
|
+
export type Alignment = "horizontal" | "vertical" | "any";
|
|
389
|
+
|
|
387
390
|
class USDZExporterOptions {
|
|
388
|
-
ar: {
|
|
389
|
-
|
|
391
|
+
ar: {
|
|
392
|
+
anchoring: { type: Anchoring },
|
|
393
|
+
planeAnchoring: { alignment: Alignment },
|
|
394
|
+
} = {
|
|
395
|
+
anchoring: { type: 'plane' },
|
|
396
|
+
planeAnchoring: { alignment: 'horizontal' }
|
|
397
|
+
};
|
|
398
|
+
quickLookCompatible: boolean = false;
|
|
390
399
|
extensions: any[] = [];
|
|
391
400
|
}
|
|
392
401
|
|
|
@@ -595,7 +604,7 @@ function parseDocument( context: USDZExporterContext ) {
|
|
|
595
604
|
|
|
596
605
|
writer.appendLine( `token preliminary:anchoring:type = "${context.exporter.sceneAnchoringOptions.ar.anchoring.type}"` );
|
|
597
606
|
if (context.exporter.sceneAnchoringOptions.ar.anchoring.type === 'plane')
|
|
598
|
-
writer.appendLine( `token preliminary:planeAnchoring:alignment = "${context.exporter.sceneAnchoringOptions.planeAnchoring.alignment}"` );
|
|
607
|
+
writer.appendLine( `token preliminary:planeAnchoring:alignment = "${context.exporter.sceneAnchoringOptions.ar.planeAnchoring.alignment}"` );
|
|
599
608
|
// bit hacky as we don't have a callback here yet. Relies on the fact that the image is named identical in the ImageTracking extension.
|
|
600
609
|
if (context.exporter.sceneAnchoringOptions.ar.anchoring.type === 'image')
|
|
601
610
|
writer.appendLine( `rel preliminary:imageAnchoring:referenceImage = </${context.document.name}/Scenes/Scene/AnchoringReferenceImage>` );
|
|
@@ -1077,7 +1086,7 @@ function buildMaterial( material: MeshStandardMaterial, textures ) {
|
|
|
1077
1086
|
const pad = ' ';
|
|
1078
1087
|
const inputs: Array<string> = [];
|
|
1079
1088
|
const samplers: Array<string> = [];
|
|
1080
|
-
const exportForQuickLook =
|
|
1089
|
+
const exportForQuickLook = false;
|
|
1081
1090
|
|
|
1082
1091
|
function buildTexture( texture, mapType, color: Color | undefined = undefined, opacity: number | undefined = undefined ) {
|
|
1083
1092
|
|
|
@@ -1089,6 +1098,11 @@ function buildMaterial( material: MeshStandardMaterial, textures ) {
|
|
|
1089
1098
|
|
|
1090
1099
|
const repeat = texture.repeat.clone();
|
|
1091
1100
|
const offset = texture.offset.clone();
|
|
1101
|
+
const rotation = texture.rotation;
|
|
1102
|
+
|
|
1103
|
+
// rotation is around the wrong point. after rotation we need to shift offset again so that we're rotating around the right spot
|
|
1104
|
+
let xRotationOffset = Math.sin(rotation);
|
|
1105
|
+
let yRotationOffset = Math.cos(rotation);
|
|
1092
1106
|
|
|
1093
1107
|
// texture coordinates start in the opposite corner, need to correct
|
|
1094
1108
|
offset.y = 1 - offset.y - repeat.y;
|
|
@@ -1097,21 +1111,31 @@ function buildMaterial( material: MeshStandardMaterial, textures ) {
|
|
|
1097
1111
|
// Apple Feedback: FB10036297 and FB11442287
|
|
1098
1112
|
if ( exportForQuickLook ) {
|
|
1099
1113
|
|
|
1114
|
+
// This is NOT correct yet in QuickLook, but comes close for a range of models.
|
|
1115
|
+
// It becomes more incorrect the bigger the offset is
|
|
1116
|
+
|
|
1100
1117
|
offset.x = offset.x / repeat.x;
|
|
1101
1118
|
offset.y = offset.y / repeat.y;
|
|
1102
1119
|
|
|
1120
|
+
offset.x += xRotationOffset / repeat.x;
|
|
1121
|
+
offset.y += yRotationOffset - 1;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
else {
|
|
1125
|
+
|
|
1126
|
+
// results match glTF results exactly. verified correct in usdview.
|
|
1127
|
+
offset.x += xRotationOffset * repeat.x;
|
|
1128
|
+
offset.y += (1 - yRotationOffset) * repeat.y;
|
|
1129
|
+
|
|
1103
1130
|
}
|
|
1104
1131
|
|
|
1105
1132
|
textures[ id ] = texture;
|
|
1106
1133
|
const uvReader = mapType == 'occlusion' ? 'uvReader_st2' : 'uvReader_st';
|
|
1107
1134
|
|
|
1108
|
-
const needsTextureTransform = ( repeat.x != 1 || repeat.y != 1 || offset.x != 0 || offset.y != 0 );
|
|
1135
|
+
const needsTextureTransform = ( repeat.x != 1 || repeat.y != 1 || offset.x != 0 || offset.y != 0 || rotation != 0 );
|
|
1109
1136
|
const textureTransformInput = `</Materials/Material_${material.id}/${uvReader}.outputs:result>`;
|
|
1110
1137
|
const textureTransformOutput = `</Materials/Material_${material.id}/Transform2d_${mapType}.outputs:result>`;
|
|
1111
1138
|
|
|
1112
|
-
const rawTextureExtra = `(
|
|
1113
|
-
colorSpace = "Raw"
|
|
1114
|
-
)`;
|
|
1115
1139
|
const needsTextureScale = mapType !== 'normal' && (color && (color.r !== 1 || color.g !== 1 || color.b !== 1 || opacity !== 1)) || false;
|
|
1116
1140
|
const needsNormalScaleAndBias = mapType === 'normal';
|
|
1117
1141
|
const normalScaleValueString = (material.normalScale ? material.normalScale.x * 2 : 2).toFixed( PRECISION );
|
|
@@ -1127,13 +1151,15 @@ function buildMaterial( material: MeshStandardMaterial, textures ) {
|
|
|
1127
1151
|
float2 inputs:in.connect = ${textureTransformInput}
|
|
1128
1152
|
float2 inputs:scale = ${buildVector2( repeat )}
|
|
1129
1153
|
float2 inputs:translation = ${buildVector2( offset )}
|
|
1154
|
+
float inputs:rotation = ${(rotation / Math.PI * 180).toFixed( PRECISION )}
|
|
1130
1155
|
float2 outputs:result
|
|
1131
1156
|
}
|
|
1132
1157
|
` : '' }
|
|
1133
1158
|
def Shader "Texture_${texture.id}_${mapType}"
|
|
1134
1159
|
{
|
|
1135
1160
|
uniform token info:id = "UsdUVTexture"
|
|
1136
|
-
asset inputs:file = @textures/Texture_${id}.${isRGBA ? 'png' : 'jpg'}@
|
|
1161
|
+
asset inputs:file = @textures/Texture_${id}.${isRGBA ? 'png' : 'jpg'}@
|
|
1162
|
+
token inputs:sourceColorSpace = "${ texture.colorSpace === 'srgb' ? 'sRGB' : 'raw' }"
|
|
1137
1163
|
float2 inputs:st.connect = ${needsTextureTransform ? textureTransformOutput : textureTransformInput}
|
|
1138
1164
|
${needsTextureScale ? `
|
|
1139
1165
|
float4 inputs:scale = (${color ? color.r + ', ' + color.g + ', ' + color.b : '1, 1, 1'}, ${opacity ? opacity : '1'})
|
|
@@ -13,6 +13,7 @@ import { showBalloonMessage, showBalloonWarning } from "../../../engine/debug/de
|
|
|
13
13
|
import { Context } from "../../../engine/engine_setup";
|
|
14
14
|
import { WebARSessionRoot } from "../../webxr/WebARSessionRoot";
|
|
15
15
|
import { hasProLicense } from "../../../engine/engine_license";
|
|
16
|
+
import { BehaviorExtension } from "./extensions/behavior/Behaviour";
|
|
16
17
|
|
|
17
18
|
const debug = getParam("debugusdz");
|
|
18
19
|
|
|
@@ -51,6 +52,9 @@ export class USDZExporter extends Behaviour {
|
|
|
51
52
|
@serializable()
|
|
52
53
|
planeAnchoringAlignment: "horizontal" | "vertical" | "any" = "horizontal";
|
|
53
54
|
|
|
55
|
+
@serializable()
|
|
56
|
+
interactive: boolean = true;
|
|
57
|
+
|
|
54
58
|
extensions: IUSDExporterExtension[] = [];
|
|
55
59
|
|
|
56
60
|
private link!: HTMLAnchorElement;
|
|
@@ -84,9 +88,11 @@ export class USDZExporter extends Behaviour {
|
|
|
84
88
|
this.objectToExport = this.gameObject;
|
|
85
89
|
if (!this.objectToExport?.children?.length && !(this.objectToExport as Mesh)?.isMesh)
|
|
86
90
|
this.objectToExport = this.context.scene;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
91
|
|
|
92
|
+
if (this.interactive) {
|
|
93
|
+
this.extensions.push(new BehaviorExtension());
|
|
94
|
+
}
|
|
95
|
+
}
|
|
90
96
|
|
|
91
97
|
onEnable() {
|
|
92
98
|
const ios = isiOS()
|
|
@@ -157,12 +163,13 @@ export class USDZExporter extends Behaviour {
|
|
|
157
163
|
ar: {
|
|
158
164
|
anchoring: {
|
|
159
165
|
type: this.anchoringType,
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
166
|
+
},
|
|
167
|
+
planeAnchoring: {
|
|
168
|
+
alignment: this.planeAnchoringAlignment,
|
|
169
|
+
},
|
|
164
170
|
},
|
|
165
|
-
extensions: extensions
|
|
171
|
+
extensions: extensions,
|
|
172
|
+
quickLookCompatible: true,
|
|
166
173
|
});
|
|
167
174
|
const blob = new Blob([arraybuffer], { type: 'application/octet-stream' });
|
|
168
175
|
|