@@ -13,8 +13,8 @@
|
|
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 @@
|
|
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 @@
|
|
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 @@
|
|
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);
|
@@ -1,9 +1,8 @@
|
|
1
|
-
import {
|
2
|
-
import {
|
1
|
+
import { GameObject } from "../../../../Component";
|
2
|
+
import { IContext } from "../../../../../engine/engine_types";
|
3
3
|
import { IUSDExporterExtension } from "../../Extension";
|
4
4
|
import { USDObject, USDWriter } from "../../ThreeUSDZExporter";
|
5
5
|
import { BehaviorModel } from "./BehavioursBuilder";
|
6
|
-
import { IContext } from "../../../../../engine/engine_types";
|
7
6
|
|
8
7
|
export interface UsdzBehaviour {
|
9
8
|
createBehaviours?(ext: BehaviorExtension, model: USDObject, context: IContext): void;
|
@@ -11,15 +10,6 @@
|
|
11
10
|
afterCreateDocument?(ext: BehaviorExtension, context: IContext): void;
|
12
11
|
}
|
13
12
|
|
14
|
-
export class USDZBehaviours extends Behaviour {
|
15
|
-
start() {
|
16
|
-
const exporter = GameObject.findObjectOfType(USDZExporter);
|
17
|
-
if (exporter) {
|
18
|
-
exporter.extensions.push(new BehaviorExtension());
|
19
|
-
}
|
20
|
-
}
|
21
|
-
}
|
22
|
-
|
23
13
|
export class BehaviorExtension implements IUSDExporterExtension {
|
24
14
|
|
25
15
|
get extensionName(): string {
|
@@ -40,8 +30,8 @@
|
|
40
30
|
GameObject.foreachComponent(e, (comp) => {
|
41
31
|
const c = comp as unknown as UsdzBehaviour;
|
42
32
|
if (
|
43
|
-
typeof c.createBehaviours === "function" ||
|
44
|
-
typeof c.beforeCreateDocument === "function" ||
|
33
|
+
typeof c.createBehaviours === "function" ||
|
34
|
+
typeof c.beforeCreateDocument === "function" ||
|
45
35
|
typeof c.afterCreateDocument === "function"
|
46
36
|
) {
|
47
37
|
this.behaviourComponents.push(c);
|
@@ -66,7 +56,7 @@
|
|
66
56
|
this.behaviourComponents.length = 0;
|
67
57
|
}
|
68
58
|
|
69
|
-
onAfterHierarchy(context, writer
|
59
|
+
onAfterHierarchy(context, writer: USDWriter) {
|
70
60
|
if (this.behaviours?.length) {
|
71
61
|
|
72
62
|
// this.combineBehavioursWithSameTapActions();
|
@@ -7,7 +7,7 @@
|
|
7
7
|
import { getWorldPosition, getWorldQuaternion, getWorldScale, setWorldPosition, setWorldQuaternion, setWorldScale } from "../../../../../engine/engine_three_utils";
|
8
8
|
|
9
9
|
import { Object3D, Material, Vector3, Quaternion, AnimationAction } from "three";
|
10
|
-
import { USDObject } from "../../ThreeUSDZExporter";
|
10
|
+
import { USDDocument, USDObject } from "../../ThreeUSDZExporter";
|
11
11
|
|
12
12
|
import { BehaviorExtension, UsdzBehaviour } from "./Behaviour";
|
13
13
|
import { ActionBuilder, ActionModel, BehaviorModel, IBehaviorElement, MotionType, Space, TriggerBuilder } from "./BehavioursBuilder";
|
@@ -44,7 +44,7 @@
|
|
44
44
|
|
45
45
|
const thisScale = getWorldScale(this.object).clone();
|
46
46
|
const targetScale = getWorldScale(this.target).clone();
|
47
|
-
|
47
|
+
|
48
48
|
const dist = thisPos.distanceTo(targetPos);
|
49
49
|
const rotDist = thisRot.angleTo(targetRot);
|
50
50
|
const scaleDist = thisScale.distanceTo(targetScale);
|
@@ -63,10 +63,10 @@
|
|
63
63
|
|
64
64
|
t01 += this.context.time.deltaTime / this.duration;
|
65
65
|
if (t01 > 1) t01 = 1;
|
66
|
-
|
66
|
+
|
67
67
|
// apply ease-in-out
|
68
68
|
// https://easings.net/
|
69
|
-
eased =
|
69
|
+
eased = t01 < 0.5 ? 4 * t01 * t01 * t01 : 1 - Math.pow(-2 * t01 + 2, 3) / 2;
|
70
70
|
|
71
71
|
this.targetPos.lerpVectors(thisPos, targetPos, eased);
|
72
72
|
this.targetRot.slerpQuaternions(thisRot, targetRot, eased);
|
@@ -83,7 +83,7 @@
|
|
83
83
|
}
|
84
84
|
|
85
85
|
private *moveRelative() {
|
86
|
-
|
86
|
+
|
87
87
|
if (!this.target || !this.object) return;
|
88
88
|
|
89
89
|
const thisPos = this.object.position.clone();
|
@@ -107,10 +107,10 @@
|
|
107
107
|
|
108
108
|
t01 += this.context.time.deltaTime / this.duration;
|
109
109
|
if (t01 > 1) t01 = 1;
|
110
|
-
|
110
|
+
|
111
111
|
// apply ease-in-out
|
112
112
|
// https://easings.net/
|
113
|
-
eased =
|
113
|
+
eased = t01 < 0.5 ? 4 * t01 * t01 * t01 : 1 - Math.pow(-2 * t01 + 2, 3) / 2;
|
114
114
|
|
115
115
|
this.object.position.lerpVectors(thisPos, this.targetPos, eased);
|
116
116
|
this.object.quaternion.slerpQuaternions(thisRot, this.targetRot, eased);
|
@@ -120,7 +120,7 @@
|
|
120
120
|
}
|
121
121
|
|
122
122
|
this.coroutine = null;
|
123
|
-
}
|
123
|
+
}
|
124
124
|
|
125
125
|
onPointerClick() {
|
126
126
|
if (this.coroutine) this.stopCoroutine(this.coroutine);
|
@@ -313,11 +313,12 @@
|
|
313
313
|
hideClickedObject = true;
|
314
314
|
targetState = !this.target.visible;
|
315
315
|
|
316
|
-
|
316
|
+
if (!this.selfModel.parent || this.selfModel.parent.isEmpty())
|
317
|
+
USDDocument.createEmptyParent(this.selfModel);
|
318
|
+
|
317
319
|
this.toggleModel = this.selfModel.clone();
|
318
320
|
this.toggleModel.name += "_toggle";
|
319
|
-
|
320
|
-
this.selfModel.parent.add(this.toggleModel);
|
321
|
+
this.selfModel.parent!.add(this.toggleModel);
|
321
322
|
}
|
322
323
|
|
323
324
|
const sequence: ActionModel[] = [];
|
@@ -344,8 +345,8 @@
|
|
344
345
|
ActionBuilder.sequence(...toggleSequence)
|
345
346
|
));
|
346
347
|
|
347
|
-
ext.addBehavior(new BehaviorModel("HideOnStart_" + this.gameObject.name,
|
348
|
-
TriggerBuilder.sceneStartTrigger(),
|
348
|
+
ext.addBehavior(new BehaviorModel("HideOnStart_" + this.gameObject.name,
|
349
|
+
TriggerBuilder.sceneStartTrigger(),
|
349
350
|
ActionBuilder.fadeAction(this.toggleModel, 0, false)
|
350
351
|
));
|
351
352
|
}
|
@@ -363,8 +364,8 @@
|
|
363
364
|
|
364
365
|
createBehaviours(ext, model, _context) {
|
365
366
|
if (model.uuid === this.gameObject.uuid)
|
366
|
-
ext.addBehavior(new BehaviorModel("HideOnStart_" + this.gameObject.name,
|
367
|
-
TriggerBuilder.sceneStartTrigger(),
|
367
|
+
ext.addBehavior(new BehaviorModel("HideOnStart_" + this.gameObject.name,
|
368
|
+
TriggerBuilder.sceneStartTrigger(),
|
368
369
|
ActionBuilder.fadeAction(model, 0, false)
|
369
370
|
));
|
370
371
|
}
|
@@ -394,8 +395,7 @@
|
|
394
395
|
createBehaviours(ext, model, _context) {
|
395
396
|
if (!this.target) return;
|
396
397
|
|
397
|
-
if (model.uuid === this.gameObject.uuid)
|
398
|
-
{
|
398
|
+
if (model.uuid === this.gameObject.uuid) {
|
399
399
|
const emphasize = new BehaviorModel("emphasize " + this.name,
|
400
400
|
TriggerBuilder.tapTrigger(this.gameObject),
|
401
401
|
ActionBuilder.emphasize(this.target, this.duration, this.motionType, undefined, "basic"),
|
@@ -417,7 +417,7 @@
|
|
417
417
|
|
418
418
|
@serializable()
|
419
419
|
stateName?: string;
|
420
|
-
|
420
|
+
|
421
421
|
@serializable()
|
422
422
|
stateNameAfterPlaying?: string;
|
423
423
|
|
@@ -486,7 +486,7 @@
|
|
486
486
|
|
487
487
|
createAnimation(ext, model, _context) {
|
488
488
|
if (this.target && this.animator) {
|
489
|
-
|
489
|
+
|
490
490
|
const state = this.animator?.runtimeAnimatorController?.findState(this.stateName);
|
491
491
|
this.stateAnimationModel = model;
|
492
492
|
this.stateAnimation = ext.registerAnimation(this.target, state?.motion.clip);
|
@@ -74,7 +74,7 @@
|
|
74
74
|
let obj = targetObject[i];
|
75
75
|
if (typeof obj === "string")
|
76
76
|
str += obj;
|
77
|
-
else if (
|
77
|
+
else if (typeof obj === "object") {
|
78
78
|
//@ts-ignore
|
79
79
|
if (obj.isObject3D) {
|
80
80
|
//@ts-ignore
|
@@ -436,7 +436,7 @@
|
|
436
436
|
act.tokenId = "Emphasize";
|
437
437
|
act.duration = duration;
|
438
438
|
act.style = style ?? "basic";
|
439
|
-
act.motionType = MotionType[motionType];
|
439
|
+
act.motionType = MotionType[motionType];
|
440
440
|
act.moveDistance = moveDistance;
|
441
441
|
return act;
|
442
442
|
}
|
@@ -179,7 +179,6 @@
|
|
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";
|
@@ -3,7 +3,7 @@
|
|
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 @@
|
|
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,13 +7,31 @@
|
|
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;
|
14
20
|
}
|
15
21
|
|
22
|
+
export function hasIndieLicense() {
|
23
|
+
switch (NEEDLE_ENGINE_LICENSE_TYPE) {
|
24
|
+
case "indie":
|
25
|
+
return true;
|
26
|
+
}
|
27
|
+
return false;
|
28
|
+
}
|
16
29
|
|
30
|
+
export function hasCommercialLicense() {
|
31
|
+
return hasProLicense() || hasIndieLicense();
|
32
|
+
}
|
33
|
+
|
34
|
+
|
17
35
|
ContextRegistry.registerCallback(ContextEvent.ContextRegistered, evt => {
|
18
36
|
showLicenseInfo(evt.context);
|
19
37
|
});
|
@@ -52,7 +70,8 @@
|
|
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 @@
|
|
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
|
});
|
@@ -56,11 +56,27 @@
|
|
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;
|
59
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; }
|
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 @@
|
|
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 @@
|
|
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 @@
|
|
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 @@
|
|
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 @@
|
|
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
|
+
}
|
@@ -48,7 +48,6 @@
|
|
48
48
|
parent: USDObject | null;
|
49
49
|
children: Array<USDObject | null> = [];
|
50
50
|
_eventListeners: {};
|
51
|
-
mesh: any;
|
52
51
|
|
53
52
|
static createEmptyParent( object ) {
|
54
53
|
|
@@ -92,7 +91,7 @@
|
|
92
91
|
|
93
92
|
clone() {
|
94
93
|
|
95
|
-
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 );
|
96
95
|
clone.isDynamic = this.isDynamic;
|
97
96
|
return clone;
|
98
97
|
|
@@ -383,9 +382,20 @@
|
|
383
382
|
|
384
383
|
}
|
385
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
|
+
|
386
390
|
class USDZExporterOptions {
|
387
|
-
ar: {
|
388
|
-
|
391
|
+
ar: {
|
392
|
+
anchoring: { type: Anchoring },
|
393
|
+
planeAnchoring: { alignment: Alignment },
|
394
|
+
} = {
|
395
|
+
anchoring: { type: 'plane' },
|
396
|
+
planeAnchoring: { alignment: 'horizontal' }
|
397
|
+
};
|
398
|
+
quickLookCompatible: boolean = false;
|
389
399
|
extensions: any[] = [];
|
390
400
|
}
|
391
401
|
|
@@ -594,7 +604,7 @@
|
|
594
604
|
|
595
605
|
writer.appendLine( `token preliminary:anchoring:type = "${context.exporter.sceneAnchoringOptions.ar.anchoring.type}"` );
|
596
606
|
if (context.exporter.sceneAnchoringOptions.ar.anchoring.type === 'plane')
|
597
|
-
writer.appendLine( `token preliminary:planeAnchoring:alignment = "${context.exporter.sceneAnchoringOptions.planeAnchoring.alignment}"` );
|
607
|
+
writer.appendLine( `token preliminary:planeAnchoring:alignment = "${context.exporter.sceneAnchoringOptions.ar.planeAnchoring.alignment}"` );
|
598
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.
|
599
609
|
if (context.exporter.sceneAnchoringOptions.ar.anchoring.type === 'image')
|
600
610
|
writer.appendLine( `rel preliminary:imageAnchoring:referenceImage = </${context.document.name}/Scenes/Scene/AnchoringReferenceImage>` );
|
@@ -1076,7 +1086,7 @@
|
|
1076
1086
|
const pad = ' ';
|
1077
1087
|
const inputs: Array<string> = [];
|
1078
1088
|
const samplers: Array<string> = [];
|
1079
|
-
const exportForQuickLook =
|
1089
|
+
const exportForQuickLook = false;
|
1080
1090
|
|
1081
1091
|
function buildTexture( texture, mapType, color: Color | undefined = undefined, opacity: number | undefined = undefined ) {
|
1082
1092
|
|
@@ -1088,7 +1098,12 @@
|
|
1088
1098
|
|
1089
1099
|
const repeat = texture.repeat.clone();
|
1090
1100
|
const offset = texture.offset.clone();
|
1101
|
+
const rotation = texture.rotation;
|
1091
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);
|
1106
|
+
|
1092
1107
|
// texture coordinates start in the opposite corner, need to correct
|
1093
1108
|
offset.y = 1 - offset.y - repeat.y;
|
1094
1109
|
|
@@ -1096,15 +1111,28 @@
|
|
1096
1111
|
// Apple Feedback: FB10036297 and FB11442287
|
1097
1112
|
if ( exportForQuickLook ) {
|
1098
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
|
+
|
1099
1117
|
offset.x = offset.x / repeat.x;
|
1100
1118
|
offset.y = offset.y / repeat.y;
|
1101
1119
|
|
1120
|
+
offset.x += xRotationOffset / repeat.x;
|
1121
|
+
offset.y += yRotationOffset - 1;
|
1102
1122
|
}
|
1103
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
|
+
|
1130
|
+
}
|
1131
|
+
|
1104
1132
|
textures[ id ] = texture;
|
1105
1133
|
const uvReader = mapType == 'occlusion' ? 'uvReader_st2' : 'uvReader_st';
|
1106
1134
|
|
1107
|
-
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 );
|
1108
1136
|
const textureTransformInput = `</Materials/Material_${material.id}/${uvReader}.outputs:result>`;
|
1109
1137
|
const textureTransformOutput = `</Materials/Material_${material.id}/Transform2d_${mapType}.outputs:result>`;
|
1110
1138
|
|
@@ -1123,6 +1151,7 @@
|
|
1123
1151
|
float2 inputs:in.connect = ${textureTransformInput}
|
1124
1152
|
float2 inputs:scale = ${buildVector2( repeat )}
|
1125
1153
|
float2 inputs:translation = ${buildVector2( offset )}
|
1154
|
+
float inputs:rotation = ${(rotation / Math.PI * 180).toFixed( PRECISION )}
|
1126
1155
|
float2 outputs:result
|
1127
1156
|
}
|
1128
1157
|
` : '' }
|
@@ -13,6 +13,7 @@
|
|
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 @@
|
|
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,10 +88,12 @@
|
|
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;
|
91
|
+
|
92
|
+
if (this.interactive) {
|
93
|
+
this.extensions.push(new BehaviorExtension());
|
94
|
+
}
|
87
95
|
}
|
88
96
|
|
89
|
-
|
90
|
-
|
91
97
|
onEnable() {
|
92
98
|
const ios = isiOS()
|
93
99
|
const safari = isSafari();
|
@@ -157,12 +163,13 @@
|
|
157
163
|
ar: {
|
158
164
|
anchoring: {
|
159
165
|
type: this.anchoringType,
|
160
|
-
}
|
166
|
+
},
|
167
|
+
planeAnchoring: {
|
168
|
+
alignment: this.planeAnchoringAlignment,
|
169
|
+
},
|
161
170
|
},
|
162
|
-
|
163
|
-
|
164
|
-
},
|
165
|
-
extensions: extensions
|
171
|
+
extensions: extensions,
|
172
|
+
quickLookCompatible: true,
|
166
173
|
});
|
167
174
|
const blob = new Blob([arraybuffer], { type: 'application/octet-stream' });
|