@needle-tools/engine 3.2.12-alpha → 3.2.14-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 +10130 -9995
- package/dist/needle-engine.min.js +279 -279
- package/dist/needle-engine.umd.cjs +273 -273
- package/lib/engine/codegen/register_types.js +4 -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 +2 -0
- package/lib/engine-components/codegen/components.js +2 -0
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/lib/engine-components/export/usdz/USDZExporter.d.ts +9 -5
- package/lib/engine-components/export/usdz/USDZExporter.js +79 -28
- package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
- package/lib/engine-components/ui/EventSystem.js +11 -4
- package/lib/engine-components/ui/EventSystem.js.map +1 -1
- package/lib/engine-components/utils/OpenURL.d.ts +21 -0
- package/lib/engine-components/utils/OpenURL.js +125 -0
- package/lib/engine-components/utils/OpenURL.js.map +1 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- 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 +4 -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 +2 -0
- package/src/engine-components/export/usdz/USDZExporter.ts +75 -31
- package/src/engine-components/ui/EventSystem.ts +11 -5
- package/src/engine-components/utils/OpenURL.ts +119 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/engine",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.14-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",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"postprocessing": "^6.30.1",
|
|
62
62
|
"simplex-noise": "^4.0.1",
|
|
63
63
|
"stats.js": "^0.17.0",
|
|
64
|
-
"three": "npm:@needle-tools/three@^0.146.
|
|
64
|
+
"three": "npm:@needle-tools/three@^0.146.8",
|
|
65
65
|
"three-mesh-ui": "^6.4.5",
|
|
66
66
|
"three.quarks": "^0.7.3",
|
|
67
67
|
"uuid": "^9.0.0",
|
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
|
|
|
@@ -95,6 +95,7 @@ import { Networking } from "../../engine-components/Networking";
|
|
|
95
95
|
import { NoiseModule } from "../../engine-components/ParticleSystemModules";
|
|
96
96
|
import { ObjectRaycaster } from "../../engine-components/ui/Raycaster";
|
|
97
97
|
import { OffsetConstraint } from "../../engine-components/OffsetConstraint";
|
|
98
|
+
import { OpenURL } from "../../engine-components/utils/OpenURL";
|
|
98
99
|
import { OrbitControls } from "../../engine-components/OrbitControls";
|
|
99
100
|
import { ParticleBurst } from "../../engine-components/ParticleSystemModules";
|
|
100
101
|
import { ParticleSubEmitter } from "../../engine-components/ParticleSystemSubEmitter";
|
|
@@ -108,6 +109,7 @@ import { PlayerSync } from "../../engine-components-experimental/networking/Play
|
|
|
108
109
|
import { PointerEventData } from "../../engine-components/ui/PointerEvents";
|
|
109
110
|
import { PostProcessingHandler } from "../../engine-components/postprocessing/PostProcessingHandler";
|
|
110
111
|
import { PresentationMode } from "../../engine-components-experimental/Presentation";
|
|
112
|
+
import { QuickLookOverlay } from "../../engine-components/export/usdz/USDZExporter";
|
|
111
113
|
import { RawImage } from "../../engine-components/ui/Image";
|
|
112
114
|
import { Raycaster } from "../../engine-components/ui/Raycaster";
|
|
113
115
|
import { Rect } from "../../engine-components/ui/RectTransform";
|
|
@@ -283,6 +285,7 @@ TypeStore.add("Networking", Networking);
|
|
|
283
285
|
TypeStore.add("NoiseModule", NoiseModule);
|
|
284
286
|
TypeStore.add("ObjectRaycaster", ObjectRaycaster);
|
|
285
287
|
TypeStore.add("OffsetConstraint", OffsetConstraint);
|
|
288
|
+
TypeStore.add("OpenURL", OpenURL);
|
|
286
289
|
TypeStore.add("OrbitControls", OrbitControls);
|
|
287
290
|
TypeStore.add("ParticleBurst", ParticleBurst);
|
|
288
291
|
TypeStore.add("ParticleSubEmitter", ParticleSubEmitter);
|
|
@@ -296,6 +299,7 @@ TypeStore.add("PlayerSync", PlayerSync);
|
|
|
296
299
|
TypeStore.add("PointerEventData", PointerEventData);
|
|
297
300
|
TypeStore.add("PostProcessingHandler", PostProcessingHandler);
|
|
298
301
|
TypeStore.add("PresentationMode", PresentationMode);
|
|
302
|
+
TypeStore.add("QuickLookOverlay", QuickLookOverlay);
|
|
299
303
|
TypeStore.add("RawImage", RawImage);
|
|
300
304
|
TypeStore.add("Raycaster", Raycaster);
|
|
301
305
|
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
|
}
|
|
@@ -93,6 +93,7 @@ export { Networking } from "../Networking";
|
|
|
93
93
|
export { NoiseModule } from "../ParticleSystemModules";
|
|
94
94
|
export { ObjectRaycaster } from "../ui/Raycaster";
|
|
95
95
|
export { OffsetConstraint } from "../OffsetConstraint";
|
|
96
|
+
export { OpenURL } from "../utils/OpenURL";
|
|
96
97
|
export { OrbitControls } from "../OrbitControls";
|
|
97
98
|
export { ParticleBurst } from "../ParticleSystemModules";
|
|
98
99
|
export { ParticleSubEmitter } from "../ParticleSystemSubEmitter";
|
|
@@ -103,6 +104,7 @@ export { PlayableDirector } from "../timeline/PlayableDirector";
|
|
|
103
104
|
export { PlayerColor } from "../PlayerColor";
|
|
104
105
|
export { PointerEventData } from "../ui/PointerEvents";
|
|
105
106
|
export { PostProcessingHandler } from "../postprocessing/PostProcessingHandler";
|
|
107
|
+
export { QuickLookOverlay } from "../export/usdz/USDZExporter";
|
|
106
108
|
export { RawImage } from "../ui/Image";
|
|
107
109
|
export { Raycaster } from "../ui/Raycaster";
|
|
108
110
|
export { Rect } from "../ui/RectTransform";
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { delay, getParam, isiOS, isMobileDevice, isSafari } from "../../../engine/engine_utils";
|
|
2
|
-
import { Object3D, Color } from "three";
|
|
3
|
-
import * as THREE from "three";
|
|
2
|
+
import { Object3D, Color, Mesh, Matrix4 } from "three";
|
|
4
3
|
import { USDZExporter as ThreeUSDZExporter } from "three/examples/jsm/exporters/USDZExporter";
|
|
5
4
|
import { AnimationExtension } from "./extensions/Animation"
|
|
6
5
|
import { ensureQuicklookLinkIsCreated } from "./utils/quicklook";
|
|
@@ -10,16 +9,23 @@ import { IUSDZExporterExtension } from "./Extension";
|
|
|
10
9
|
import { Behaviour, GameObject } from "../../Component";
|
|
11
10
|
import { WebXR } from "../../webxr/WebXR"
|
|
12
11
|
import { serializable } from "../../../engine/engine_serialization";
|
|
13
|
-
import { showBalloonWarning } from "../../../engine/debug/debug";
|
|
12
|
+
import { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../../../engine/debug/debug";
|
|
14
13
|
import { Context } from "../../../engine/engine_setup";
|
|
15
14
|
import { WebARSessionRoot } from "../../webxr/WebARSessionRoot";
|
|
16
15
|
|
|
17
16
|
const debug = getParam("debugusdz");
|
|
18
17
|
|
|
19
|
-
export
|
|
18
|
+
export class QuickLookOverlay {
|
|
19
|
+
@serializable()
|
|
20
20
|
callToAction?: string;
|
|
21
|
+
@serializable()
|
|
21
22
|
checkoutTitle?: string;
|
|
23
|
+
@serializable()
|
|
22
24
|
checkoutSubtitle?: string;
|
|
25
|
+
|
|
26
|
+
/** if assigned the call to action button in quicklook will open the URL. Otherwise it will just close quicklook. */
|
|
27
|
+
@serializable()
|
|
28
|
+
callToActionURL?: string;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
export class USDZExporter extends Behaviour {
|
|
@@ -30,6 +36,9 @@ export class USDZExporter extends Behaviour {
|
|
|
30
36
|
@serializable()
|
|
31
37
|
autoExportAnimations: boolean = false;
|
|
32
38
|
|
|
39
|
+
@serializable(QuickLookOverlay)
|
|
40
|
+
overlay?: QuickLookOverlay;
|
|
41
|
+
|
|
33
42
|
extensions: IUSDZExporterExtension[] = [];
|
|
34
43
|
|
|
35
44
|
private link!: HTMLAnchorElement;
|
|
@@ -39,6 +48,8 @@ export class USDZExporter extends Behaviour {
|
|
|
39
48
|
|
|
40
49
|
start() {
|
|
41
50
|
if (debug) {
|
|
51
|
+
console.log(this);
|
|
52
|
+
console.log("Debug USDZ, press 't' to export")
|
|
42
53
|
window.addEventListener("keydown", (evt) => {
|
|
43
54
|
switch (evt.key) {
|
|
44
55
|
case "t":
|
|
@@ -58,6 +69,12 @@ export class USDZExporter extends Behaviour {
|
|
|
58
69
|
});
|
|
59
70
|
|
|
60
71
|
if (!this.objectToExport) this.objectToExport = this.gameObject;
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if (isDevEnvironment() && (!this.objectToExport?.children?.length && !(this.objectToExport as Mesh)?.isMesh)) {
|
|
75
|
+
showBalloonWarning("USDZ Exporter has nothing to export");
|
|
76
|
+
console.warn("USDZExporter has no objects to export assigned:", this)
|
|
77
|
+
}
|
|
61
78
|
}
|
|
62
79
|
|
|
63
80
|
|
|
@@ -66,16 +83,25 @@ export class USDZExporter extends Behaviour {
|
|
|
66
83
|
const ios = isiOS()
|
|
67
84
|
const safari = isSafari();
|
|
68
85
|
if (debug || (ios && safari)) {
|
|
69
|
-
this.
|
|
86
|
+
this.addQuicklookButton();
|
|
70
87
|
this.webARSessionRoot = GameObject.findObjectOfType(WebARSessionRoot) ?? undefined;
|
|
71
88
|
this.lastCallback = this.quicklookCallback.bind(this);
|
|
72
89
|
this.link = ensureQuicklookLinkIsCreated(this.context);
|
|
73
90
|
this.link.addEventListener('message', this.lastCallback);
|
|
74
91
|
}
|
|
92
|
+
if (debug)
|
|
93
|
+
showBalloonMessage("USDZ Exporter enabled: " + this.name);
|
|
75
94
|
}
|
|
76
95
|
|
|
77
96
|
onDisable() {
|
|
78
97
|
this.link?.removeEventListener('message', this.lastCallback);
|
|
98
|
+
const ios = isiOS()
|
|
99
|
+
const safari = isSafari();
|
|
100
|
+
if (debug || (ios && safari)) {
|
|
101
|
+
this.removeQuicklookButton();
|
|
102
|
+
}
|
|
103
|
+
if (debug)
|
|
104
|
+
showBalloonMessage("USDZ Exporter disabled: " + this.name);
|
|
79
105
|
}
|
|
80
106
|
|
|
81
107
|
async exportAsync() {
|
|
@@ -87,7 +113,7 @@ export class USDZExporter extends Behaviour {
|
|
|
87
113
|
const scale = 1 / this.webARSessionRoot!.arScale;
|
|
88
114
|
scene.matrix.makeScale(scale, scale, scale);
|
|
89
115
|
if (this.webARSessionRoot.invertForward) {
|
|
90
|
-
scene.matrix.multiply(new
|
|
116
|
+
scene.matrix.multiply(new Matrix4().makeRotationY(Math.PI));
|
|
91
117
|
}
|
|
92
118
|
}
|
|
93
119
|
|
|
@@ -125,14 +151,15 @@ export class USDZExporter extends Behaviour {
|
|
|
125
151
|
|
|
126
152
|
// see https://developer.apple.com/documentation/arkit/adding_an_apple_pay_button_or_a_custom_action_in_ar_quick_look
|
|
127
153
|
const overlay = this.buildQuicklookOverlay();
|
|
154
|
+
console.log(overlay);
|
|
128
155
|
const callToAction = overlay.callToAction ? encodeURIComponent(overlay.callToAction) : "";
|
|
129
156
|
const checkoutTitle = overlay.checkoutTitle ? encodeURIComponent(overlay.checkoutTitle) : "";
|
|
130
157
|
const checkoutSubtitle = overlay.checkoutSubtitle ? encodeURIComponent(overlay.checkoutSubtitle) : "";
|
|
131
|
-
this.link.href = URL.createObjectURL(blob) + `#callToAction=${callToAction}&checkoutTitle=${checkoutTitle}&checkoutSubtitle=${checkoutSubtitle}
|
|
158
|
+
this.link.href = URL.createObjectURL(blob) + `#callToAction=${callToAction}&checkoutTitle=${checkoutTitle}&checkoutSubtitle=${checkoutSubtitle}&callToActionURL=${overlay.callToActionURL}`;
|
|
132
159
|
|
|
133
160
|
|
|
134
161
|
if (!this.lastCallback) {
|
|
135
|
-
this.lastCallback = this.quicklookCallback.bind(this)
|
|
162
|
+
this.lastCallback = this.quicklookCallback.bind(this)
|
|
136
163
|
this.link.addEventListener('message', this.lastCallback);
|
|
137
164
|
}
|
|
138
165
|
|
|
@@ -146,24 +173,44 @@ export class USDZExporter extends Behaviour {
|
|
|
146
173
|
|
|
147
174
|
private lastCallback?: any;
|
|
148
175
|
|
|
149
|
-
private quicklookCallback(event) {
|
|
176
|
+
private quicklookCallback(event: Event) {
|
|
150
177
|
if ((event as any)?.data == '_apple_ar_quicklook_button_tapped') {
|
|
151
178
|
if (debug) showBalloonWarning("Quicklook closed via call to action button");
|
|
152
|
-
|
|
179
|
+
var evt = new CustomEvent("quicklook-button-tapped", { detail: this });
|
|
180
|
+
this.dispatchEvent(evt);
|
|
181
|
+
if (!evt.defaultPrevented) {
|
|
182
|
+
const url = new URLSearchParams(this.link.href);
|
|
183
|
+
if (url) {
|
|
184
|
+
const callToActionURL = url.get("callToActionURL");
|
|
185
|
+
if (debug)
|
|
186
|
+
showBalloonMessage("Quicklook url: " + callToActionURL);
|
|
187
|
+
if (callToActionURL) {
|
|
188
|
+
globalThis.open(callToActionURL, "_blank");
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
153
192
|
}
|
|
154
193
|
}
|
|
155
194
|
|
|
156
195
|
private buildQuicklookOverlay(): QuickLookOverlay {
|
|
157
196
|
const obj: QuickLookOverlay = {};
|
|
158
|
-
|
|
159
|
-
obj.
|
|
160
|
-
|
|
197
|
+
if (this.overlay) Object.assign(obj, this.overlay);
|
|
198
|
+
if (!obj.callToAction?.length)
|
|
199
|
+
obj.callToAction = "Close";
|
|
200
|
+
if (!obj.checkoutTitle?.length)
|
|
201
|
+
obj.checkoutTitle = "🌵 Made with Needle";
|
|
202
|
+
if (!obj.checkoutSubtitle?.length)
|
|
203
|
+
obj.checkoutSubtitle = "_";
|
|
161
204
|
// Use the quicklook-overlay event to customize the overlay
|
|
162
205
|
this.dispatchEvent(new CustomEvent("quicklook-overlay", { detail: obj }));
|
|
163
206
|
return obj;
|
|
164
207
|
}
|
|
165
208
|
|
|
166
|
-
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
private _quicklookButton?: HTMLElement;
|
|
213
|
+
|
|
167
214
|
private async createQuicklookButton() {
|
|
168
215
|
if (!this.webxr) {
|
|
169
216
|
await delay(1);
|
|
@@ -171,8 +218,8 @@ export class USDZExporter extends Behaviour {
|
|
|
171
218
|
if (this.webxr) {
|
|
172
219
|
if (this.webxr.VRButton) this.webxr.VRButton.parentElement?.removeChild(this.webxr.VRButton);
|
|
173
220
|
// check if we have an AR button already and re-use that
|
|
174
|
-
if (this.webxr.ARButton && this.
|
|
175
|
-
this.
|
|
221
|
+
if (this.webxr.ARButton && this._quicklookButton !== this.webxr.ARButton) {
|
|
222
|
+
this._quicklookButton = this.webxr.ARButton;
|
|
176
223
|
// Hack to remove the immersiveweb link
|
|
177
224
|
const linkInButton = this.webxr.ARButton.parentElement?.querySelector("a");
|
|
178
225
|
if (linkInButton) {
|
|
@@ -185,6 +232,7 @@ export class USDZExporter extends Behaviour {
|
|
|
185
232
|
this.exportAsync();
|
|
186
233
|
});
|
|
187
234
|
this.webxr.ARButton.classList.add("quicklook-ar-button");
|
|
235
|
+
this._quicklookButtonContainer = this.webxr.ARButton.parentElement;
|
|
188
236
|
this.dispatchEvent(new CustomEvent("created-button", { detail: this.webxr.ARButton }))
|
|
189
237
|
}
|
|
190
238
|
// create a button if WebXR didnt create one yet
|
|
@@ -205,6 +253,7 @@ export class USDZExporter extends Behaviour {
|
|
|
205
253
|
button.classList.add('webxr-button');
|
|
206
254
|
button.classList.add("quicklook-ar-button");
|
|
207
255
|
container.appendChild(button);
|
|
256
|
+
this._quicklookButtonContainer = container;
|
|
208
257
|
this.dispatchEvent(new CustomEvent("created-button", { detail: button }))
|
|
209
258
|
}
|
|
210
259
|
}
|
|
@@ -214,20 +263,15 @@ export class USDZExporter extends Behaviour {
|
|
|
214
263
|
}
|
|
215
264
|
}
|
|
216
265
|
|
|
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 = "";
|
|
266
|
+
|
|
267
|
+
private _quicklookButtonContainer: Element | null = null;
|
|
268
|
+
private async addQuicklookButton() {
|
|
269
|
+
await this.createQuicklookButton();
|
|
270
|
+
if (this._quicklookButton && this._quicklookButtonContainer) {
|
|
271
|
+
this._quicklookButtonContainer.appendChild(this._quicklookButton);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
private removeQuicklookButton() {
|
|
275
|
+
this._quicklookButton?.remove();
|
|
232
276
|
}
|
|
233
277
|
}
|