@needle-tools/engine 3.2.12-alpha → 3.2.13-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 +6 -0
- package/dist/needle-engine.js +9265 -9197
- package/dist/needle-engine.min.js +273 -273
- package/dist/needle-engine.umd.cjs +271 -271
- package/lib/engine/codegen/register_types.js +2 -0
- package/lib/engine/codegen/register_types.js.map +1 -1
- package/lib/engine/engine_serialization_core.js +2 -0
- package/lib/engine/engine_serialization_core.js.map +1 -1
- package/lib/engine/engine_utils.d.ts +2 -2
- package/lib/engine/engine_utils.js +4 -4
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine-components/SceneSwitcher.d.ts +5 -0
- package/lib/engine-components/SceneSwitcher.js +32 -4
- package/lib/engine-components/SceneSwitcher.js.map +1 -1
- package/lib/engine-components/codegen/components.d.ts +1 -0
- package/lib/engine-components/codegen/components.js +1 -0
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/lib/engine-components/export/usdz/USDZExporter.d.ts +9 -4
- package/lib/engine-components/export/usdz/USDZExporter.js +77 -25
- package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/plugins/vite/alias.js +1 -4
- package/plugins/vite/copyfiles.js +46 -40
- package/plugins/vite/index.js +2 -0
- package/plugins/vite/meta.js +5 -2
- package/plugins/vite/peer.js +28 -0
- package/plugins/vite/reload.js +17 -13
- package/src/engine/codegen/register_types.js +2 -0
- package/src/engine/engine_serialization_core.ts +1 -1
- package/src/engine/engine_utils.ts +7 -7
- package/src/engine-components/SceneSwitcher.ts +38 -5
- package/src/engine-components/codegen/components.ts +1 -0
- package/src/engine-components/export/usdz/USDZExporter.ts +73 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/engine",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.13-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/alias.js
CHANGED
|
@@ -33,10 +33,7 @@ export const needleViteAlias = (command, config, userSettings) => {
|
|
|
33
33
|
return {
|
|
34
34
|
name: "needle-alias",
|
|
35
35
|
config(config) {
|
|
36
|
-
|
|
37
|
-
console.log('[needle-alias] ProjectDirectory: ' + projectDir);
|
|
38
|
-
}, 150);
|
|
39
|
-
|
|
36
|
+
console.log('[needle-alias] ProjectDirectory: ' + projectDir);
|
|
40
37
|
if (!config.resolve) config.resolve = {};
|
|
41
38
|
if (!config.resolve.alias) config.resolve.alias = {};
|
|
42
39
|
const aliasDict = config.resolve.alias;
|
|
@@ -11,57 +11,63 @@ export const needleCopyFiles = (command, config, userSettings) => {
|
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
const copyIncludesFromEngine = config?.copyIncludesFromEngine ?? true;
|
|
15
|
-
|
|
16
14
|
return {
|
|
17
15
|
name: 'needle-copy-files',
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
buildStart() {
|
|
17
|
+
return run(false, config);
|
|
18
|
+
},
|
|
19
|
+
closeBundle() {
|
|
20
|
+
return run(true, config);
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function run(isBuild, config) {
|
|
26
|
+
const copyIncludesFromEngine = config?.copyIncludesFromEngine ?? true;
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
const baseDir = process.cwd();
|
|
29
|
+
const pluginName = "needle-copy-files";
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
assetsDirName = needleConfig.assetsDirectory;
|
|
29
|
-
}
|
|
31
|
+
let assetsDirName = "assets";
|
|
32
|
+
let outdirName = "dist";
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
const needleConfig = tryLoadProjectConfig();
|
|
35
|
+
if (needleConfig) {
|
|
36
|
+
assetsDirName = needleConfig.assetsDirectory;
|
|
37
|
+
}
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
if (copyIncludesFromEngine !== false) {
|
|
40
|
+
// copy include from engine
|
|
41
|
+
const engineIncludeDir = resolve(baseDir, 'node_modules', '@needle-tools', 'engine', 'src', 'include');
|
|
42
|
+
if (existsSync(engineIncludeDir)) {
|
|
43
|
+
console.log(`[${pluginName}] - Copy engine include to ${baseDir}/include`)
|
|
44
|
+
const projectIncludeDir = resolve(baseDir, 'include');
|
|
45
|
+
copyRecursiveSync(engineIncludeDir, projectIncludeDir);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
if (isBuild) {
|
|
50
|
+
const outDir = resolve(baseDir, outdirName);
|
|
51
|
+
if (!existsSync(outDir)) {
|
|
52
|
+
mkdirSync(outDir);
|
|
53
|
+
}
|
|
54
|
+
// copy assets dir
|
|
55
|
+
const assetsDir = resolve(baseDir, assetsDirName);
|
|
56
|
+
if (existsSync(assetsDir)) {
|
|
57
|
+
console.log(`[${pluginName}] - Copy assets to ${outdirName}/${builtAssetsDirectory()}`)
|
|
58
|
+
const targetDir = resolve(outDir, 'assets');
|
|
59
|
+
copyRecursiveSync(assetsDir, targetDir);
|
|
60
|
+
}
|
|
61
|
+
// copy include dir
|
|
62
|
+
const includeDir = resolve(baseDir, 'include');
|
|
63
|
+
if (existsSync(includeDir)) {
|
|
64
|
+
console.log(`[${pluginName}] - Copy include to ${outdirName}/include`)
|
|
65
|
+
const targetDir = resolve(outDir, 'include');
|
|
66
|
+
copyRecursiveSync(includeDir, targetDir);
|
|
60
67
|
}
|
|
61
68
|
}
|
|
62
69
|
}
|
|
63
70
|
|
|
64
|
-
|
|
65
71
|
function copyRecursiveSync(src, dest) {
|
|
66
72
|
var exists = existsSync(src);
|
|
67
73
|
var stats = exists && statSync(src);
|
package/plugins/vite/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { needleCopyFiles } from "./copyfiles.js";
|
|
|
8
8
|
import { needleViteAlias } from "./alias.js";
|
|
9
9
|
import { needleTransformCodegen } from "./transform-codegen.js";
|
|
10
10
|
import { needleLicense } from "./license.js";
|
|
11
|
+
import { needlePeerjs } from "./peer.js";
|
|
11
12
|
|
|
12
13
|
export * from "./gzip.js";
|
|
13
14
|
export * from "./config.js";
|
|
@@ -30,6 +31,7 @@ export const needlePlugins = async (command, config, userSettings) => {
|
|
|
30
31
|
needleCopyFiles(command, config, userSettings),
|
|
31
32
|
needleTransformCodegen(command, config, userSettings),
|
|
32
33
|
needleDrop(command, config, userSettings),
|
|
34
|
+
needlePeerjs(command, config, userSettings)
|
|
33
35
|
];
|
|
34
36
|
array.push(await editorConnection(command, config, userSettings, array));
|
|
35
37
|
return array;
|
package/plugins/vite/meta.js
CHANGED
|
@@ -91,6 +91,9 @@ export const needleMeta = (command, config, userSettings) => {
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
// if(!tags.filter(t => t.attrs?.name === "generator"))
|
|
95
|
+
tags.push({ tag: 'meta', attrs: { name: 'generator', content: 'Needle' } });
|
|
96
|
+
|
|
94
97
|
return { html, tags }
|
|
95
98
|
},
|
|
96
99
|
}
|
|
@@ -124,8 +127,8 @@ function removeMetaTag(html, name) {
|
|
|
124
127
|
function insertNeedleCredits(html) {
|
|
125
128
|
const needleCredits = `<!-- 🌵 Made with Needle — https://needle.tools -->`;
|
|
126
129
|
html = html.replace(
|
|
127
|
-
/<head
|
|
128
|
-
needleCredits + "\n<head
|
|
130
|
+
/<head/,
|
|
131
|
+
needleCredits + "\n<head",
|
|
129
132
|
);
|
|
130
133
|
return html;
|
|
131
134
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
const peerjsString = `/* needle: injected fix for peerjs */
|
|
3
|
+
window.global = window;
|
|
4
|
+
var parcelRequire;`
|
|
5
|
+
|
|
6
|
+
export const needlePeerjs = (command, config, userSettings) => {
|
|
7
|
+
|
|
8
|
+
if (userSettings.noPeer === true) return;
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
name: 'needle-peerjs',
|
|
12
|
+
transformIndexHtml: {
|
|
13
|
+
enforce: 'pre',
|
|
14
|
+
transform(html, _ctx) {
|
|
15
|
+
return {
|
|
16
|
+
html,
|
|
17
|
+
tags: [
|
|
18
|
+
{
|
|
19
|
+
tag: 'script',
|
|
20
|
+
children: peerjsString,
|
|
21
|
+
injectTo: 'body-prepend',
|
|
22
|
+
},
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
package/plugins/vite/reload.js
CHANGED
|
@@ -61,22 +61,26 @@ export const needleReload = (command, config, userSettings) => {
|
|
|
61
61
|
transformIndexHtml: {
|
|
62
62
|
enforce: 'pre',
|
|
63
63
|
transform(html, _) {
|
|
64
|
-
if (config?.allowHotReload === false) return
|
|
65
|
-
if (userSettings?.allowHotReload === false) return
|
|
64
|
+
if (config?.allowHotReload === false) return html;
|
|
65
|
+
if (userSettings?.allowHotReload === false) return html;
|
|
66
66
|
const file = path.join(__dirname, 'reload-client.js');
|
|
67
|
-
return
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
return {
|
|
68
|
+
html,
|
|
69
|
+
tags: [
|
|
70
|
+
{
|
|
71
|
+
tag: 'script',
|
|
72
|
+
attrs: {
|
|
73
|
+
type: 'module',
|
|
74
|
+
},
|
|
75
|
+
children: readFileSync(file, 'utf8'),
|
|
76
|
+
injectTo: 'body',
|
|
72
77
|
},
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
];
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
|
|
77
81
|
},
|
|
78
|
-
}
|
|
79
|
-
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
|
|
@@ -108,6 +108,7 @@ import { PlayerSync } from "../../engine-components-experimental/networking/Play
|
|
|
108
108
|
import { PointerEventData } from "../../engine-components/ui/PointerEvents";
|
|
109
109
|
import { PostProcessingHandler } from "../../engine-components/postprocessing/PostProcessingHandler";
|
|
110
110
|
import { PresentationMode } from "../../engine-components-experimental/Presentation";
|
|
111
|
+
import { QuickLookOverlay } from "../../engine-components/export/usdz/USDZExporter";
|
|
111
112
|
import { RawImage } from "../../engine-components/ui/Image";
|
|
112
113
|
import { Raycaster } from "../../engine-components/ui/Raycaster";
|
|
113
114
|
import { Rect } from "../../engine-components/ui/RectTransform";
|
|
@@ -296,6 +297,7 @@ TypeStore.add("PlayerSync", PlayerSync);
|
|
|
296
297
|
TypeStore.add("PointerEventData", PointerEventData);
|
|
297
298
|
TypeStore.add("PostProcessingHandler", PostProcessingHandler);
|
|
298
299
|
TypeStore.add("PresentationMode", PresentationMode);
|
|
300
|
+
TypeStore.add("QuickLookOverlay", QuickLookOverlay);
|
|
299
301
|
TypeStore.add("RawImage", RawImage);
|
|
300
302
|
TypeStore.add("Raycaster", Raycaster);
|
|
301
303
|
TypeStore.add("Rect", Rect);
|
|
@@ -279,7 +279,7 @@ export function deserializeObject(obj: ISerializable, serializedData: object, co
|
|
|
279
279
|
for (const key in typeInfo) {
|
|
280
280
|
const serializedEntryInfo = typeInfo[key];
|
|
281
281
|
const data = serializedData[key];
|
|
282
|
-
|
|
282
|
+
if(debug) console.log(key, data, obj, serializedEntryInfo)
|
|
283
283
|
|
|
284
284
|
if (obj[key] !== undefined && data === undefined) {
|
|
285
285
|
// if a field is marked as serialized and has some default value
|
|
@@ -97,12 +97,12 @@ export function setOrAddParamsToUrl(url: URLSearchParams, paramName: string, par
|
|
|
97
97
|
url.append(paramName, paramValue.toString());
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
export function pushState(title: string, urlParams: URLSearchParams) {
|
|
101
|
-
window.history.pushState(
|
|
100
|
+
export function pushState(title: string, urlParams: URLSearchParams, state?: any) {
|
|
101
|
+
window.history.pushState(state, title, "?" + urlParams.toString());
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
export function setState(title: string, urlParams: URLSearchParams) {
|
|
105
|
-
window.history.replaceState(
|
|
104
|
+
export function setState(title: string, urlParams: URLSearchParams, state?: any) {
|
|
105
|
+
window.history.replaceState(state, title, "?" + urlParams.toString());
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
// for room id
|
|
@@ -212,7 +212,7 @@ const debugGetPath = getParam("debugresolveurl");
|
|
|
212
212
|
export const relativePathPrefix = "rel:";
|
|
213
213
|
|
|
214
214
|
/** @deprecated use resolveUrl instead */
|
|
215
|
-
export function getPath(source:SourceIdentifier|undefined, uri:string)
|
|
215
|
+
export function getPath(source: SourceIdentifier | undefined, uri: string): string {
|
|
216
216
|
return resolveUrl(source, uri);
|
|
217
217
|
}
|
|
218
218
|
/**
|
|
@@ -226,7 +226,7 @@ export function resolveUrl(source: SourceIdentifier | undefined, uri: string): s
|
|
|
226
226
|
if (debugGetPath) console.warn("getPath: uri is undefined, returning uri", uri);
|
|
227
227
|
return uri;
|
|
228
228
|
}
|
|
229
|
-
if(uri.startsWith("./")) {
|
|
229
|
+
if (uri.startsWith("./")) {
|
|
230
230
|
return uri;
|
|
231
231
|
}
|
|
232
232
|
if (uri.startsWith("http")) {
|
|
@@ -237,7 +237,7 @@ export function resolveUrl(source: SourceIdentifier | undefined, uri: string): s
|
|
|
237
237
|
if (debugGetPath) console.warn("getPath: source is undefined, returning uri", uri);
|
|
238
238
|
return uri;
|
|
239
239
|
}
|
|
240
|
-
if(uri.startsWith(relativePathPrefix)){
|
|
240
|
+
if (uri.startsWith(relativePathPrefix)) {
|
|
241
241
|
uri = uri.substring(4);
|
|
242
242
|
}
|
|
243
243
|
const pathIndex = source.lastIndexOf("/");
|
|
@@ -14,7 +14,7 @@ ContextRegistry.registerCallback(ContextEvent.ContextRegistered, async _ => {
|
|
|
14
14
|
// We need to defer import to not get issues with circular dependencies
|
|
15
15
|
import("../engine/engine_element").then(res => {
|
|
16
16
|
const webcomponent = res.NeedleEngineHTMLElement;
|
|
17
|
-
if(debug) console.log("SceneSwitcher: registering scene attribute", webcomponent.observedAttributes);
|
|
17
|
+
if (debug) console.log("SceneSwitcher: registering scene attribute", webcomponent.observedAttributes);
|
|
18
18
|
if (!webcomponent.observedAttributes.includes(ENGINE_ELEMENT_SCENE_ATTRIBUTE_NAME))
|
|
19
19
|
webcomponent.observedAttributes.push(ENGINE_ELEMENT_SCENE_ATTRIBUTE_NAME);
|
|
20
20
|
});
|
|
@@ -22,6 +22,12 @@ ContextRegistry.registerCallback(ContextEvent.ContextRegistered, async _ => {
|
|
|
22
22
|
|
|
23
23
|
const couldNotLoadScenePromise = Promise.resolve(false);
|
|
24
24
|
|
|
25
|
+
export type LoadSceneEvent = {
|
|
26
|
+
switcher: SceneSwitcher;
|
|
27
|
+
scene: AssetReference;
|
|
28
|
+
index: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
25
31
|
export class SceneSwitcher extends Behaviour {
|
|
26
32
|
|
|
27
33
|
@serializable(AssetReference)
|
|
@@ -108,7 +114,17 @@ export class SceneSwitcher extends Behaviour {
|
|
|
108
114
|
let wasUsingHistory = this.useHistory;
|
|
109
115
|
try {
|
|
110
116
|
this.useHistory = false;
|
|
111
|
-
|
|
117
|
+
let didResolve = false;
|
|
118
|
+
if (this.queryParameterName)
|
|
119
|
+
didResolve = await this.tryLoadFromQueryParam();
|
|
120
|
+
if (!didResolve) {
|
|
121
|
+
const state = _state.state;
|
|
122
|
+
if (state !== null && state.startsWith(this.guid)) {
|
|
123
|
+
const value = state.substr(this.guid.length + 2);
|
|
124
|
+
console.log(value);
|
|
125
|
+
await this.trySelectSceneFromValue(value);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
112
128
|
}
|
|
113
129
|
finally {
|
|
114
130
|
this.useHistory = wasUsingHistory;
|
|
@@ -192,7 +208,15 @@ export class SceneSwitcher extends Behaviour {
|
|
|
192
208
|
const index = this._currentIndex = this.scenes?.indexOf(scene) ?? -1;
|
|
193
209
|
this._currentScene = scene;
|
|
194
210
|
try {
|
|
211
|
+
const loadStartEvt = new CustomEvent<LoadSceneEvent>("loadscene-start", { detail: { scene: scene, switcher: this, index: index } })
|
|
212
|
+
this.dispatchEvent(loadStartEvt);
|
|
195
213
|
await scene.loadAssetAsync();
|
|
214
|
+
const finishedEvt = new CustomEvent<LoadSceneEvent>("loadscene-finished", { detail: { scene: scene, switcher: this, index: index } });
|
|
215
|
+
this.dispatchEvent(finishedEvt);
|
|
216
|
+
if (finishedEvt.defaultPrevented) {
|
|
217
|
+
if (debug) console.warn("Adding loaded scene prevented:", scene, finishedEvt);
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
196
220
|
if (!scene.asset) {
|
|
197
221
|
if (debug) console.warn("Failed loading scene:", scene);
|
|
198
222
|
return false;
|
|
@@ -201,9 +225,18 @@ export class SceneSwitcher extends Behaviour {
|
|
|
201
225
|
GameObject.add(scene.asset, this.gameObject);
|
|
202
226
|
if (this.useSceneLighting)
|
|
203
227
|
this.context.sceneLighting.enable(scene)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
228
|
+
if (this.useHistory) {
|
|
229
|
+
// save the loaded scene as an url parameter
|
|
230
|
+
if (this.queryParameterName?.length)
|
|
231
|
+
setParamWithoutReload(this.queryParameterName, index.toString(), this.useHistory);
|
|
232
|
+
// or set the history state without updating the url parameter
|
|
233
|
+
else {
|
|
234
|
+
const lastState = history.state;
|
|
235
|
+
const stateName = this.guid + "::" + index;
|
|
236
|
+
if (lastState !== stateName)
|
|
237
|
+
history.pushState(stateName, "unused", location.href);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
207
240
|
return true;
|
|
208
241
|
}
|
|
209
242
|
}
|
|
@@ -103,6 +103,7 @@ export { PlayableDirector } from "../timeline/PlayableDirector";
|
|
|
103
103
|
export { PlayerColor } from "../PlayerColor";
|
|
104
104
|
export { PointerEventData } from "../ui/PointerEvents";
|
|
105
105
|
export { PostProcessingHandler } from "../postprocessing/PostProcessingHandler";
|
|
106
|
+
export { QuickLookOverlay } from "../export/usdz/USDZExporter";
|
|
106
107
|
export { RawImage } from "../ui/Image";
|
|
107
108
|
export { Raycaster } from "../ui/Raycaster";
|
|
108
109
|
export { Rect } from "../ui/RectTransform";
|
|
@@ -10,16 +10,23 @@ import { IUSDZExporterExtension } from "./Extension";
|
|
|
10
10
|
import { Behaviour, GameObject } from "../../Component";
|
|
11
11
|
import { WebXR } from "../../webxr/WebXR"
|
|
12
12
|
import { serializable } from "../../../engine/engine_serialization";
|
|
13
|
-
import { showBalloonWarning } from "../../../engine/debug/debug";
|
|
13
|
+
import { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../../../engine/debug/debug";
|
|
14
14
|
import { Context } from "../../../engine/engine_setup";
|
|
15
15
|
import { WebARSessionRoot } from "../../webxr/WebARSessionRoot";
|
|
16
16
|
|
|
17
17
|
const debug = getParam("debugusdz");
|
|
18
18
|
|
|
19
|
-
export
|
|
19
|
+
export class QuickLookOverlay {
|
|
20
|
+
@serializable()
|
|
20
21
|
callToAction?: string;
|
|
22
|
+
@serializable()
|
|
21
23
|
checkoutTitle?: string;
|
|
24
|
+
@serializable()
|
|
22
25
|
checkoutSubtitle?: string;
|
|
26
|
+
|
|
27
|
+
/** if assigned the call to action button in quicklook will open the URL. Otherwise it will just close quicklook. */
|
|
28
|
+
@serializable()
|
|
29
|
+
callToActionURL?: string;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
export class USDZExporter extends Behaviour {
|
|
@@ -30,6 +37,9 @@ export class USDZExporter extends Behaviour {
|
|
|
30
37
|
@serializable()
|
|
31
38
|
autoExportAnimations: boolean = false;
|
|
32
39
|
|
|
40
|
+
@serializable(QuickLookOverlay)
|
|
41
|
+
overlay?: QuickLookOverlay;
|
|
42
|
+
|
|
33
43
|
extensions: IUSDZExporterExtension[] = [];
|
|
34
44
|
|
|
35
45
|
private link!: HTMLAnchorElement;
|
|
@@ -39,6 +49,8 @@ export class USDZExporter extends Behaviour {
|
|
|
39
49
|
|
|
40
50
|
start() {
|
|
41
51
|
if (debug) {
|
|
52
|
+
console.log(this);
|
|
53
|
+
console.log("Debug USDZ, press 't' to export")
|
|
42
54
|
window.addEventListener("keydown", (evt) => {
|
|
43
55
|
switch (evt.key) {
|
|
44
56
|
case "t":
|
|
@@ -58,6 +70,12 @@ export class USDZExporter extends Behaviour {
|
|
|
58
70
|
});
|
|
59
71
|
|
|
60
72
|
if (!this.objectToExport) this.objectToExport = this.gameObject;
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if (isDevEnvironment() && (!this.objectToExport || this.objectToExport.children.length <= 0)) {
|
|
76
|
+
showBalloonWarning("USDZ Exporter has nothing to export");
|
|
77
|
+
console.warn("USDZExporter has no objects to export assigned:", this)
|
|
78
|
+
}
|
|
61
79
|
}
|
|
62
80
|
|
|
63
81
|
|
|
@@ -66,16 +84,25 @@ export class USDZExporter extends Behaviour {
|
|
|
66
84
|
const ios = isiOS()
|
|
67
85
|
const safari = isSafari();
|
|
68
86
|
if (debug || (ios && safari)) {
|
|
69
|
-
this.
|
|
87
|
+
this.addQuicklookButton();
|
|
70
88
|
this.webARSessionRoot = GameObject.findObjectOfType(WebARSessionRoot) ?? undefined;
|
|
71
89
|
this.lastCallback = this.quicklookCallback.bind(this);
|
|
72
90
|
this.link = ensureQuicklookLinkIsCreated(this.context);
|
|
73
91
|
this.link.addEventListener('message', this.lastCallback);
|
|
74
92
|
}
|
|
93
|
+
if (debug)
|
|
94
|
+
showBalloonMessage("USDZ Exporter enabled: " + this.name);
|
|
75
95
|
}
|
|
76
96
|
|
|
77
97
|
onDisable() {
|
|
78
98
|
this.link?.removeEventListener('message', this.lastCallback);
|
|
99
|
+
const ios = isiOS()
|
|
100
|
+
const safari = isSafari();
|
|
101
|
+
if (debug || (ios && safari)) {
|
|
102
|
+
this.removeQuicklookButton();
|
|
103
|
+
}
|
|
104
|
+
if (debug)
|
|
105
|
+
showBalloonMessage("USDZ Exporter disabled: " + this.name);
|
|
79
106
|
}
|
|
80
107
|
|
|
81
108
|
async exportAsync() {
|
|
@@ -125,14 +152,15 @@ export class USDZExporter extends Behaviour {
|
|
|
125
152
|
|
|
126
153
|
// see https://developer.apple.com/documentation/arkit/adding_an_apple_pay_button_or_a_custom_action_in_ar_quick_look
|
|
127
154
|
const overlay = this.buildQuicklookOverlay();
|
|
155
|
+
console.log(overlay);
|
|
128
156
|
const callToAction = overlay.callToAction ? encodeURIComponent(overlay.callToAction) : "";
|
|
129
157
|
const checkoutTitle = overlay.checkoutTitle ? encodeURIComponent(overlay.checkoutTitle) : "";
|
|
130
158
|
const checkoutSubtitle = overlay.checkoutSubtitle ? encodeURIComponent(overlay.checkoutSubtitle) : "";
|
|
131
|
-
this.link.href = URL.createObjectURL(blob) + `#callToAction=${callToAction}&checkoutTitle=${checkoutTitle}&checkoutSubtitle=${checkoutSubtitle}
|
|
159
|
+
this.link.href = URL.createObjectURL(blob) + `#callToAction=${callToAction}&checkoutTitle=${checkoutTitle}&checkoutSubtitle=${checkoutSubtitle}&callToActionURL=${overlay.callToActionURL}`;
|
|
132
160
|
|
|
133
161
|
|
|
134
162
|
if (!this.lastCallback) {
|
|
135
|
-
this.lastCallback = this.quicklookCallback.bind(this)
|
|
163
|
+
this.lastCallback = this.quicklookCallback.bind(this)
|
|
136
164
|
this.link.addEventListener('message', this.lastCallback);
|
|
137
165
|
}
|
|
138
166
|
|
|
@@ -146,24 +174,44 @@ export class USDZExporter extends Behaviour {
|
|
|
146
174
|
|
|
147
175
|
private lastCallback?: any;
|
|
148
176
|
|
|
149
|
-
private quicklookCallback(event) {
|
|
177
|
+
private quicklookCallback(event: Event) {
|
|
150
178
|
if ((event as any)?.data == '_apple_ar_quicklook_button_tapped') {
|
|
151
179
|
if (debug) showBalloonWarning("Quicklook closed via call to action button");
|
|
152
|
-
|
|
180
|
+
var evt = new CustomEvent("quicklook-button-tapped", { detail: this });
|
|
181
|
+
this.dispatchEvent(evt);
|
|
182
|
+
if (!evt.defaultPrevented) {
|
|
183
|
+
const url = new URLSearchParams(this.link.href);
|
|
184
|
+
if (url) {
|
|
185
|
+
const callToActionURL = url.get("callToActionURL");
|
|
186
|
+
if (debug)
|
|
187
|
+
showBalloonMessage("Quicklook url: " + callToActionURL);
|
|
188
|
+
if (callToActionURL) {
|
|
189
|
+
globalThis.open(callToActionURL, "_blank");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
153
193
|
}
|
|
154
194
|
}
|
|
155
195
|
|
|
156
196
|
private buildQuicklookOverlay(): QuickLookOverlay {
|
|
157
197
|
const obj: QuickLookOverlay = {};
|
|
158
|
-
|
|
159
|
-
obj.
|
|
160
|
-
|
|
198
|
+
if (this.overlay) Object.assign(obj, this.overlay);
|
|
199
|
+
if (!obj.callToAction?.length)
|
|
200
|
+
obj.callToAction = "Close";
|
|
201
|
+
if (!obj.checkoutTitle?.length)
|
|
202
|
+
obj.checkoutTitle = "🌵 Made with Needle";
|
|
203
|
+
if (!obj.checkoutSubtitle?.length)
|
|
204
|
+
obj.checkoutSubtitle = "_";
|
|
161
205
|
// Use the quicklook-overlay event to customize the overlay
|
|
162
206
|
this.dispatchEvent(new CustomEvent("quicklook-overlay", { detail: obj }));
|
|
163
207
|
return obj;
|
|
164
208
|
}
|
|
165
209
|
|
|
166
|
-
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
private _quicklookButton?: HTMLElement;
|
|
214
|
+
|
|
167
215
|
private async createQuicklookButton() {
|
|
168
216
|
if (!this.webxr) {
|
|
169
217
|
await delay(1);
|
|
@@ -171,8 +219,8 @@ export class USDZExporter extends Behaviour {
|
|
|
171
219
|
if (this.webxr) {
|
|
172
220
|
if (this.webxr.VRButton) this.webxr.VRButton.parentElement?.removeChild(this.webxr.VRButton);
|
|
173
221
|
// check if we have an AR button already and re-use that
|
|
174
|
-
if (this.webxr.ARButton && this.
|
|
175
|
-
this.
|
|
222
|
+
if (this.webxr.ARButton && this._quicklookButton !== this.webxr.ARButton) {
|
|
223
|
+
this._quicklookButton = this.webxr.ARButton;
|
|
176
224
|
// Hack to remove the immersiveweb link
|
|
177
225
|
const linkInButton = this.webxr.ARButton.parentElement?.querySelector("a");
|
|
178
226
|
if (linkInButton) {
|
|
@@ -185,6 +233,7 @@ export class USDZExporter extends Behaviour {
|
|
|
185
233
|
this.exportAsync();
|
|
186
234
|
});
|
|
187
235
|
this.webxr.ARButton.classList.add("quicklook-ar-button");
|
|
236
|
+
this._quicklookButtonContainer = this.webxr.ARButton.parentElement;
|
|
188
237
|
this.dispatchEvent(new CustomEvent("created-button", { detail: this.webxr.ARButton }))
|
|
189
238
|
}
|
|
190
239
|
// create a button if WebXR didnt create one yet
|
|
@@ -205,6 +254,7 @@ export class USDZExporter extends Behaviour {
|
|
|
205
254
|
button.classList.add('webxr-button');
|
|
206
255
|
button.classList.add("quicklook-ar-button");
|
|
207
256
|
container.appendChild(button);
|
|
257
|
+
this._quicklookButtonContainer = container;
|
|
208
258
|
this.dispatchEvent(new CustomEvent("created-button", { detail: button }))
|
|
209
259
|
}
|
|
210
260
|
}
|
|
@@ -214,20 +264,15 @@ export class USDZExporter extends Behaviour {
|
|
|
214
264
|
}
|
|
215
265
|
}
|
|
216
266
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
el.style.color = "";
|
|
228
|
-
el.style.font = "";
|
|
229
|
-
el.style.textAlign = "";
|
|
230
|
-
el.style.opacity = "";
|
|
231
|
-
el.style.zIndex = "";
|
|
267
|
+
|
|
268
|
+
private _quicklookButtonContainer: Element | null = null;
|
|
269
|
+
private async addQuicklookButton() {
|
|
270
|
+
await this.createQuicklookButton();
|
|
271
|
+
if (this._quicklookButton && this._quicklookButtonContainer) {
|
|
272
|
+
this._quicklookButtonContainer.appendChild(this._quicklookButton);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
private removeQuicklookButton() {
|
|
276
|
+
this._quicklookButton?.remove();
|
|
232
277
|
}
|
|
233
278
|
}
|