@webspatial/core-sdk 1.4.0 → 1.6.0
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 +34 -1
- package/dist/iife/index.d.ts +171 -12
- package/dist/iife/index.global.js +11 -11
- package/dist/iife/index.global.js.map +1 -1
- package/dist/index.d.ts +171 -12
- package/dist/index.js +460 -114
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/JSBCommand.ts +40 -2
- package/src/Spatial.ts +1 -1
- package/src/SpatialSession.ts +4 -2
- package/src/SpatializedElement.ts +10 -10
- package/src/SpatializedElementCreator.ts +5 -2
- package/src/SpatializedStatic3DElement.test.ts +15 -1
- package/src/SpatializedStatic3DElement.ts +167 -8
- package/src/WebMsgCommand.ts +22 -0
- package/src/platform-adapter/index.ts +2 -2
- package/src/platform-adapter/{xr/XRPlatform.ts → pico-os/PicoOSPlatform.ts} +5 -7
- package/src/reality/entity/SpatialEntity.ts +4 -0
- package/src/reality/entity/SpatialModelEntity.ts +6 -0
- package/src/scene-polyfill.manifest.test.ts +402 -0
- package/src/scene-polyfill.test.ts +108 -18
- package/src/scene-polyfill.ts +284 -26
- package/src/types/global.d.ts +8 -0
- package/src/types/types.ts +71 -0
- package/src/utils.ts +21 -0
package/src/scene-polyfill.ts
CHANGED
|
@@ -9,8 +9,13 @@ import {
|
|
|
9
9
|
isValidWorldScalingType,
|
|
10
10
|
isValidWorldAlignmentType,
|
|
11
11
|
isValidBaseplateVisibilityType,
|
|
12
|
+
PWAManifest,
|
|
13
|
+
XRSpatialSceneConfig,
|
|
14
|
+
XRSpatialSceneDefaults,
|
|
12
15
|
} from './types/types'
|
|
13
16
|
import { SpatialSceneCreationOptionsInternal } from './types/internal'
|
|
17
|
+
import { deepCloneJSON } from './utils'
|
|
18
|
+
import { pointToPhysical, physicalToPoint } from './physicalMetrics'
|
|
14
19
|
|
|
15
20
|
const defaultSceneConfig: SpatialSceneCreationOptions = {
|
|
16
21
|
defaultSize: {
|
|
@@ -21,17 +26,80 @@ const defaultSceneConfig: SpatialSceneCreationOptions = {
|
|
|
21
26
|
|
|
22
27
|
const defaultSceneConfigVolume: SpatialSceneCreationOptions = {
|
|
23
28
|
defaultSize: {
|
|
24
|
-
width: 0.
|
|
25
|
-
height: 0.
|
|
26
|
-
depth: 0.
|
|
29
|
+
width: '0.94m',
|
|
30
|
+
height: '0.94m',
|
|
31
|
+
depth: '0.94m',
|
|
27
32
|
},
|
|
28
33
|
}
|
|
29
34
|
|
|
35
|
+
let xr_window_defaults: SpatialSceneCreationOptions = {
|
|
36
|
+
...defaultSceneConfig,
|
|
37
|
+
}
|
|
38
|
+
let xr_volume_defaults: SpatialSceneCreationOptions = {
|
|
39
|
+
...defaultSceneConfigVolume,
|
|
40
|
+
}
|
|
41
|
+
|
|
30
42
|
const INTERNAL_SCHEMA_PREFIX = 'webspatial://'
|
|
31
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Deep-merge two plain object trees (no arrays, no special classes).
|
|
46
|
+
* - Creates a shallow clone of base, then recursively merges properties from over.
|
|
47
|
+
* - When both sides at a key are plain objects, merges recursively; otherwise, replaces with over.
|
|
48
|
+
* - Ignores arrays (treated as replace).
|
|
49
|
+
* Intended for small configuration objects like manifest overrides.
|
|
50
|
+
*/
|
|
51
|
+
function deepMergePlain<
|
|
52
|
+
T extends Record<string, any>,
|
|
53
|
+
U extends Record<string, any> | undefined,
|
|
54
|
+
>(base: T, over: U): T & (U extends undefined ? {} : U) {
|
|
55
|
+
if (!over) return { ...(base || {}) } as any
|
|
56
|
+
const out: any = { ...(base || {}) }
|
|
57
|
+
for (const k of Object.keys(over)) {
|
|
58
|
+
const bv = out[k]
|
|
59
|
+
const ov = (over as any)[k]
|
|
60
|
+
if (
|
|
61
|
+
ov &&
|
|
62
|
+
typeof ov === 'object' &&
|
|
63
|
+
!Array.isArray(ov) &&
|
|
64
|
+
bv &&
|
|
65
|
+
typeof bv === 'object' &&
|
|
66
|
+
!Array.isArray(bv)
|
|
67
|
+
) {
|
|
68
|
+
out[k] = deepMergePlain(bv, ov)
|
|
69
|
+
} else {
|
|
70
|
+
out[k] = ov
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return out
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Normalize XRSpatialSceneDefaults (manifest shape) into SpatialSceneCreationOptions (runtime shape).
|
|
78
|
+
* - Only remap default_size -> defaultSize when present.
|
|
79
|
+
* - Leaves other keys (resizability, worldScaling, etc.) unchanged.
|
|
80
|
+
* - Units are left as-is; downstream formatting is handled by formatSceneConfig.
|
|
81
|
+
*/
|
|
82
|
+
function normalizeXRDefaultsToSceneOptions(
|
|
83
|
+
src: XRSpatialSceneDefaults | Record<string, any>,
|
|
84
|
+
): SpatialSceneCreationOptions {
|
|
85
|
+
const out: any = { ...(src || {}) }
|
|
86
|
+
const ds =
|
|
87
|
+
(src as any).defaultSize !== undefined
|
|
88
|
+
? (src as any).defaultSize
|
|
89
|
+
: (src as any).default_size
|
|
90
|
+
if (ds !== undefined) {
|
|
91
|
+
out.defaultSize = ds
|
|
92
|
+
}
|
|
93
|
+
if ('default_size' in out) {
|
|
94
|
+
delete out.default_size
|
|
95
|
+
}
|
|
96
|
+
return out
|
|
97
|
+
}
|
|
98
|
+
|
|
32
99
|
class SceneManager {
|
|
33
100
|
private originalOpen: any
|
|
34
101
|
private static instance: SceneManager
|
|
102
|
+
private manifestReady: Promise<void> | null = null
|
|
35
103
|
static getInstance() {
|
|
36
104
|
if (!SceneManager.instance) {
|
|
37
105
|
SceneManager.instance = new SceneManager()
|
|
@@ -40,16 +108,26 @@ class SceneManager {
|
|
|
40
108
|
}
|
|
41
109
|
|
|
42
110
|
init(window: WindowProxy) {
|
|
111
|
+
this.manifestReady = this.setupManifest()
|
|
43
112
|
this.originalOpen = window.open.bind(window)
|
|
44
113
|
;(window as any).open = this.open
|
|
45
114
|
}
|
|
46
115
|
|
|
47
|
-
|
|
116
|
+
// Stores the latest formatted config used by the platform (per scene name).
|
|
117
|
+
// This object contains normalized values and is safe for internal consumption.
|
|
118
|
+
private configMap: Record<string, SpatialSceneCreationOptionsInternal> = {}
|
|
119
|
+
// Stores the raw callback return value (per scene name) to feed into the next initScene call as `pre`.
|
|
120
|
+
// We keep this unformatted so developers receive exactly what they last returned.
|
|
121
|
+
private callbackReturnMap: Record<string, SpatialSceneCreationOptions> = {}
|
|
48
122
|
private getConfig(name?: string) {
|
|
49
123
|
if (name === undefined || !this.configMap[name]) return undefined
|
|
50
124
|
return this.configMap[name]
|
|
51
125
|
}
|
|
52
126
|
|
|
127
|
+
waitManifest(): Promise<void> {
|
|
128
|
+
return this.manifestReady ?? Promise.resolve()
|
|
129
|
+
}
|
|
130
|
+
|
|
53
131
|
// Ensure URL is absolute; only convert when a relative path is provided
|
|
54
132
|
// - Keep external and special schemes untouched (http, https, data, blob, about, file, mailto, etc.)
|
|
55
133
|
// - Handle protocol-relative URLs (//example.com/path)
|
|
@@ -73,9 +151,54 @@ class SceneManager {
|
|
|
73
151
|
}
|
|
74
152
|
}
|
|
75
153
|
|
|
154
|
+
private async setupManifest() {
|
|
155
|
+
const manifest = await this.getPWAManifest()
|
|
156
|
+
try {
|
|
157
|
+
const xr = manifest?.xr_spatial_scene
|
|
158
|
+
if (!xr || typeof xr !== 'object') return
|
|
159
|
+
const { overrides, ...topLevel } = xr as XRSpatialSceneConfig
|
|
160
|
+
// Merge top-level defaults with per-scene overrides.
|
|
161
|
+
const windowRaw = deepMergePlain(topLevel, overrides?.window_scene)
|
|
162
|
+
const volumeRaw = deepMergePlain(topLevel, overrides?.volume_scene)
|
|
163
|
+
const windowNext = normalizeXRDefaultsToSceneOptions(windowRaw)
|
|
164
|
+
|
|
165
|
+
const volumeNext = normalizeXRDefaultsToSceneOptions(volumeRaw)
|
|
166
|
+
if (windowNext && Object.keys(windowNext).length > 0) {
|
|
167
|
+
xr_window_defaults = windowNext
|
|
168
|
+
}
|
|
169
|
+
if (volumeNext && Object.keys(volumeNext).length > 0) {
|
|
170
|
+
xr_volume_defaults = volumeNext
|
|
171
|
+
}
|
|
172
|
+
} catch (error: any) {
|
|
173
|
+
console.warn(
|
|
174
|
+
'SceneManager.setupManifest failed; using built-in defaults.',
|
|
175
|
+
error?.message || error,
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
76
180
|
private open = (url?: string, target?: string, features?: string) => {
|
|
77
181
|
// bypass internal
|
|
78
182
|
if (url?.startsWith(INTERNAL_SCHEMA_PREFIX)) {
|
|
183
|
+
if (
|
|
184
|
+
url.includes('createSpatialized2DElement') ||
|
|
185
|
+
url.includes('createAttachment')
|
|
186
|
+
) {
|
|
187
|
+
const token = //@ts-ignore
|
|
188
|
+
(window.webSpatial || window.__webspatialShell__)?.genToken?.()
|
|
189
|
+
if (token) {
|
|
190
|
+
const command = url.includes('createAttachment')
|
|
191
|
+
? 'createAttachment'
|
|
192
|
+
: 'createSpatialized2DElement'
|
|
193
|
+
const host = window.location.host
|
|
194
|
+
const protocol = window.location.protocol
|
|
195
|
+
const finalURL = `${protocol}//${host}/${token}/?command=${command}`
|
|
196
|
+
const rid = new URL(url).searchParams.get('rid')
|
|
197
|
+
const final = new URL(finalURL)
|
|
198
|
+
if (rid) final.searchParams.set('rid', rid)
|
|
199
|
+
return this.originalOpen(final.toString(), target, features)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
79
202
|
return this.originalOpen(url, target, features)
|
|
80
203
|
}
|
|
81
204
|
|
|
@@ -88,7 +211,16 @@ class SceneManager {
|
|
|
88
211
|
return newWindow
|
|
89
212
|
}
|
|
90
213
|
|
|
91
|
-
|
|
214
|
+
let cfg = target ? this.getConfig(target) : undefined
|
|
215
|
+
|
|
216
|
+
if (cfg === undefined) {
|
|
217
|
+
// if no config, use default window config
|
|
218
|
+
const preFormatted = deepCloneJSON(getSceneDefaultConfig('window'))
|
|
219
|
+
|
|
220
|
+
const [ans] = formatSceneConfig(preFormatted, 'window')
|
|
221
|
+
cfg = { ...ans, type: 'window' }
|
|
222
|
+
}
|
|
223
|
+
|
|
92
224
|
const cmd = new createSpatialSceneCommand(url!, cfg, target, features)
|
|
93
225
|
const result = cmd.executeSync()
|
|
94
226
|
|
|
@@ -108,25 +240,137 @@ class SceneManager {
|
|
|
108
240
|
options?: { type: SpatialSceneType },
|
|
109
241
|
) {
|
|
110
242
|
const sceneType = options?.type ?? 'window'
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
|
|
243
|
+
const defaultConfigRaw = getSceneDefaultConfig(sceneType)
|
|
244
|
+
const previousOrDefault =
|
|
245
|
+
this.callbackReturnMap[name] ??
|
|
246
|
+
((): SpatialSceneCreationOptions => {
|
|
247
|
+
// Clone default config to avoid mutating shared defaults during formatting.
|
|
248
|
+
const cloned = deepCloneJSON(defaultConfigRaw)
|
|
249
|
+
return cloned
|
|
250
|
+
})()
|
|
251
|
+
const rawReturnVal = callback(previousOrDefault)
|
|
252
|
+
const sanitizedReturnVal = sanitizeSceneOptionsUnits(
|
|
253
|
+
deepCloneJSON(rawReturnVal),
|
|
254
|
+
)
|
|
255
|
+
const clonedForFormat = deepCloneJSON(sanitizedReturnVal)
|
|
256
|
+
// Merge normalized user return with scene-type defaults before final formatting.
|
|
257
|
+
// This ensures missing fields fall back to the appropriate xr_window_defaults / xr_volume_defaults.
|
|
258
|
+
const baseDefaults = deepCloneJSON(getSceneDefaultConfig(sceneType))
|
|
259
|
+
const mergedForFormat = deepMergePlain(baseDefaults, clonedForFormat)
|
|
260
|
+
|
|
261
|
+
const [formattedConfig, errors] = formatSceneConfig(
|
|
262
|
+
mergedForFormat,
|
|
263
|
+
sceneType,
|
|
264
|
+
)
|
|
265
|
+
|
|
114
266
|
if (errors.length > 0) {
|
|
115
267
|
console.warn(`initScene ${name} with errors: ${errors.join(', ')}`)
|
|
116
268
|
}
|
|
269
|
+
this.callbackReturnMap[name] = sanitizedReturnVal
|
|
117
270
|
this.configMap[name] = {
|
|
118
271
|
...formattedConfig,
|
|
119
272
|
type: sceneType,
|
|
120
273
|
}
|
|
121
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* Resolve and load a PWA manifest as JSON:
|
|
277
|
+
* 1) Determine href:
|
|
278
|
+
* - Prefer explicit manifestUrl if provided;
|
|
279
|
+
* - Fallback to <link rel="manifest">, preferring the raw attribute over computed href.
|
|
280
|
+
* 2) Normalize href to absolute using ensureAbsoluteUrl (respects <base href>).
|
|
281
|
+
* 3) Handle data URLs inline:
|
|
282
|
+
* - data:...;base64,... → atob then JSON.parse
|
|
283
|
+
* - data:...,... → decodeURIComponent then JSON.parse
|
|
284
|
+
* 4) Fetch with credentials same-origin first; if that fails (e.g., CORS), attempt unauthenticated fetch.
|
|
285
|
+
* 5) Parse as JSON; if response body is text, parse the text as JSON.
|
|
286
|
+
*/
|
|
287
|
+
async getPWAManifest(manifestUrl?: string): Promise<PWAManifest | undefined> {
|
|
288
|
+
let href: string | undefined = manifestUrl
|
|
289
|
+
if (!href) {
|
|
290
|
+
const el = document.querySelector(
|
|
291
|
+
'link[rel="manifest"]',
|
|
292
|
+
) as HTMLLinkElement | null
|
|
293
|
+
href = el?.getAttribute('href') || el?.href
|
|
294
|
+
}
|
|
295
|
+
if (!href) return
|
|
296
|
+
href = this.ensureAbsoluteUrl(href)
|
|
297
|
+
if (!href) return
|
|
298
|
+
if (href.startsWith('data:')) {
|
|
299
|
+
// Inline data URL manifest: data:[<mediatype>][;base64],<data>
|
|
300
|
+
try {
|
|
301
|
+
const comma = href.indexOf(',')
|
|
302
|
+
if (comma < 0) return
|
|
303
|
+
const meta = href.slice(5, comma)
|
|
304
|
+
const data = href.slice(comma + 1)
|
|
305
|
+
const isBase64 = /;base64/i.test(meta)
|
|
306
|
+
const decoded = isBase64 ? atob(data) : decodeURIComponent(data)
|
|
307
|
+
return JSON.parse(decoded)
|
|
308
|
+
} catch {
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
try {
|
|
313
|
+
// Same-origin fetch with credentials first.
|
|
314
|
+
const res = await fetch(href, { credentials: 'same-origin' })
|
|
315
|
+
if (!res.ok) throw new Error(String(res.status))
|
|
316
|
+
try {
|
|
317
|
+
return await res.json()
|
|
318
|
+
} catch {
|
|
319
|
+
const t = await res.text()
|
|
320
|
+
return JSON.parse(t)
|
|
321
|
+
}
|
|
322
|
+
} catch {
|
|
323
|
+
try {
|
|
324
|
+
// Fallback: unauthenticated fetch (may help when same-origin credentials fail due to CORS).
|
|
325
|
+
const res = await fetch(href)
|
|
326
|
+
if (!res.ok) return
|
|
327
|
+
try {
|
|
328
|
+
return await res.json()
|
|
329
|
+
} catch {
|
|
330
|
+
const t = await res.text()
|
|
331
|
+
return JSON.parse(t)
|
|
332
|
+
}
|
|
333
|
+
} catch {
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function sanitizeSceneOptionsUnits(
|
|
341
|
+
val: SpatialSceneCreationOptions,
|
|
342
|
+
): SpatialSceneCreationOptions {
|
|
343
|
+
if (val?.defaultSize) {
|
|
344
|
+
const keys = ['width', 'height', 'depth'] as const
|
|
345
|
+
for (const k of keys) {
|
|
346
|
+
if (
|
|
347
|
+
k in (val.defaultSize as any) &&
|
|
348
|
+
!isValidSceneUnit((val.defaultSize as any)[k])
|
|
349
|
+
) {
|
|
350
|
+
delete (val.defaultSize as any)[k]
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (val?.resizability) {
|
|
355
|
+
const keys = ['minWidth', 'minHeight', 'maxWidth', 'maxHeight'] as const
|
|
356
|
+
for (const k of keys) {
|
|
357
|
+
if (
|
|
358
|
+
k in (val.resizability as any) &&
|
|
359
|
+
!isValidSceneUnit((val.resizability as any)[k])
|
|
360
|
+
) {
|
|
361
|
+
delete (val.resizability as any)[k]
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return val
|
|
122
366
|
}
|
|
123
367
|
|
|
124
368
|
function pxToMeter(px: number): number {
|
|
125
|
-
return px
|
|
369
|
+
return pointToPhysical(px)
|
|
126
370
|
}
|
|
127
371
|
|
|
128
372
|
function meterToPx(meter: number): number {
|
|
129
|
-
return meter
|
|
373
|
+
return physicalToPoint(meter)
|
|
130
374
|
}
|
|
131
375
|
|
|
132
376
|
function formatToNumber(
|
|
@@ -203,12 +447,11 @@ export function formatSceneConfig(
|
|
|
203
447
|
;(config.defaultSize as any)[k] = formatToNumber(
|
|
204
448
|
(config.defaultSize as any)[k],
|
|
205
449
|
isWindow ? 'px' : 'm',
|
|
206
|
-
|
|
450
|
+
'px',
|
|
207
451
|
)
|
|
208
452
|
} else {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
)[k]
|
|
453
|
+
// delete invalid unit
|
|
454
|
+
delete (config.defaultSize as any)[k]
|
|
212
455
|
errors.push(`defaultSize.${k}`)
|
|
213
456
|
}
|
|
214
457
|
}
|
|
@@ -223,10 +466,11 @@ export function formatSceneConfig(
|
|
|
223
466
|
;(config.resizability as any)[k] = formatToNumber(
|
|
224
467
|
(config.resizability as any)[k],
|
|
225
468
|
'px',
|
|
226
|
-
|
|
469
|
+
'px',
|
|
227
470
|
)
|
|
228
471
|
} else {
|
|
229
|
-
|
|
472
|
+
// delete invalid unit
|
|
473
|
+
delete (config.resizability as any)[k]
|
|
230
474
|
errors.push(`resizability.${k}`)
|
|
231
475
|
}
|
|
232
476
|
}
|
|
@@ -265,6 +509,13 @@ export function initScene(
|
|
|
265
509
|
return SceneManager.getInstance().initScene(name, callback, options)
|
|
266
510
|
}
|
|
267
511
|
|
|
512
|
+
export function __getSceneConfigSnapshotForTest(
|
|
513
|
+
name: string,
|
|
514
|
+
): SpatialSceneCreationOptionsInternal | undefined {
|
|
515
|
+
const mgr = SceneManager.getInstance() as any
|
|
516
|
+
return mgr?.configMap?.[name]
|
|
517
|
+
}
|
|
518
|
+
|
|
268
519
|
export function hijackWindowOpen(window: WindowProxy) {
|
|
269
520
|
SceneManager.getInstance().init(window)
|
|
270
521
|
}
|
|
@@ -313,7 +564,9 @@ function handleATag(event: MouseEvent) {
|
|
|
313
564
|
}
|
|
314
565
|
|
|
315
566
|
function getSceneDefaultConfig(sceneType: SpatialSceneType) {
|
|
316
|
-
return sceneType === 'window'
|
|
567
|
+
return sceneType === 'window'
|
|
568
|
+
? xr_window_defaults || defaultSceneConfig
|
|
569
|
+
: xr_volume_defaults || defaultSceneConfigVolume
|
|
317
570
|
}
|
|
318
571
|
|
|
319
572
|
async function injectScenePolyfill() {
|
|
@@ -336,13 +589,16 @@ async function injectScenePolyfill() {
|
|
|
336
589
|
}
|
|
337
590
|
|
|
338
591
|
onContentLoaded(async () => {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
)
|
|
342
|
-
|
|
592
|
+
await SceneManager.getInstance().waitManifest()
|
|
593
|
+
const sceneType = window.xrCurrentSceneType ?? 'window'
|
|
594
|
+
const rawDefault = getSceneDefaultConfig(sceneType)
|
|
595
|
+
// Provide a formatted 'pre' to the callback for consistent units and types.
|
|
596
|
+
const pre = deepCloneJSON(rawDefault)
|
|
597
|
+
|
|
598
|
+
let cfg = pre
|
|
343
599
|
if (typeof window.xrCurrentSceneDefaults === 'function') {
|
|
344
600
|
try {
|
|
345
|
-
cfg = await window.xrCurrentSceneDefaults?.(
|
|
601
|
+
cfg = await window.xrCurrentSceneDefaults?.(pre)
|
|
346
602
|
} catch (error) {
|
|
347
603
|
console.error(error)
|
|
348
604
|
}
|
|
@@ -354,17 +610,19 @@ async function injectScenePolyfill() {
|
|
|
354
610
|
}, 1000)
|
|
355
611
|
})
|
|
356
612
|
|
|
357
|
-
|
|
358
|
-
const
|
|
613
|
+
// Merge callback return with base defaults to ensure missing fields are filled.
|
|
614
|
+
const mergedCfg = deepMergePlain(deepCloneJSON(rawDefault), cfg)
|
|
615
|
+
const [formattedConfig, errors] = formatSceneConfig(mergedCfg, sceneType)
|
|
359
616
|
if (errors.length > 0) {
|
|
360
617
|
console.warn(
|
|
361
618
|
`window.xrCurrentSceneDefaults with errors: ${errors.join(', ')}`,
|
|
362
619
|
)
|
|
363
620
|
}
|
|
364
|
-
|
|
621
|
+
const finalCfg = {
|
|
365
622
|
...formattedConfig,
|
|
366
623
|
type: sceneType,
|
|
367
|
-
}
|
|
624
|
+
}
|
|
625
|
+
await SpatialScene.getInstance().updateSceneCreationConfig(finalCfg)
|
|
368
626
|
})
|
|
369
627
|
}
|
|
370
628
|
|
package/src/types/global.d.ts
CHANGED
|
@@ -22,6 +22,14 @@ declare global {
|
|
|
22
22
|
webkit: any
|
|
23
23
|
webspatialBridge: any
|
|
24
24
|
|
|
25
|
+
// Project Pico OS browser injects this global object to provide internal capabilities.
|
|
26
|
+
webSpatial?: {
|
|
27
|
+
genToken?: () => string
|
|
28
|
+
}
|
|
29
|
+
__webspatialShell__?: {
|
|
30
|
+
genToken?: () => string
|
|
31
|
+
}
|
|
32
|
+
|
|
25
33
|
// Will be removed in favor of __WebSpatialData
|
|
26
34
|
WebSpatailNativeVersion: string
|
|
27
35
|
|
package/src/types/types.ts
CHANGED
|
@@ -95,10 +95,20 @@ export interface Spatialized2DElementProperties
|
|
|
95
95
|
scrollEdgeInsetsMarginRight: number
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
export interface ModelSource {
|
|
99
|
+
src: string
|
|
100
|
+
type?: string
|
|
101
|
+
}
|
|
102
|
+
|
|
98
103
|
export interface SpatializedStatic3DElementProperties
|
|
99
104
|
extends SpatializedElementProperties {
|
|
100
105
|
modelURL: string
|
|
106
|
+
sources?: ModelSource[]
|
|
101
107
|
modelTransform?: number[]
|
|
108
|
+
autoplay?: boolean
|
|
109
|
+
loop?: boolean
|
|
110
|
+
animationPaused?: boolean
|
|
111
|
+
playbackRate?: number
|
|
102
112
|
}
|
|
103
113
|
|
|
104
114
|
export interface SpatialSceneCreationOptions {
|
|
@@ -411,3 +421,64 @@ export interface AttachmentEntityUpdateOptions {
|
|
|
411
421
|
position?: [number, number, number]
|
|
412
422
|
size?: { width: number; height: number }
|
|
413
423
|
}
|
|
424
|
+
|
|
425
|
+
// manifest
|
|
426
|
+
|
|
427
|
+
export type SceneUnitPx = `${number}px`
|
|
428
|
+
export type SceneUnitM = `${number}m`
|
|
429
|
+
export type SceneUnit = number | SceneUnitPx | SceneUnitM
|
|
430
|
+
|
|
431
|
+
export interface XRSceneSize {
|
|
432
|
+
width: SceneUnit
|
|
433
|
+
height: SceneUnit
|
|
434
|
+
depth?: SceneUnit
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export interface XRSceneResizability {
|
|
438
|
+
minWidth?: SceneUnit
|
|
439
|
+
minHeight?: SceneUnit
|
|
440
|
+
maxWidth?: SceneUnit
|
|
441
|
+
maxHeight?: SceneUnit
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export interface XRMainSceneConfig extends XRSpatialSceneDefaults {
|
|
445
|
+
type?: SpatialSceneType
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export interface XRSpatialSceneDefaults {
|
|
449
|
+
default_size?: XRSceneSize
|
|
450
|
+
resizability?: XRSceneResizability
|
|
451
|
+
worldScaling?: WorldScalingType
|
|
452
|
+
worldAlignment?: WorldAlignmentType
|
|
453
|
+
baseplateVisibility?: BaseplateVisibilityType
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export interface XRSpatialSceneOverrides {
|
|
457
|
+
window_scene?: XRSpatialSceneDefaults
|
|
458
|
+
volume_scene?: XRSpatialSceneDefaults
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export interface XRSpatialSceneConfig extends XRSpatialSceneDefaults {
|
|
462
|
+
overrides?: XRSpatialSceneOverrides
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export interface XRPrdConfig {
|
|
466
|
+
xr_main_scene?: XRMainSceneConfig
|
|
467
|
+
xr_spatial_scene?: XRSpatialSceneConfig
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export interface PWAManifest extends XRPrdConfig {
|
|
471
|
+
name?: string
|
|
472
|
+
short_name?: string
|
|
473
|
+
start_url?: string
|
|
474
|
+
display?: string
|
|
475
|
+
icons?: Array<{
|
|
476
|
+
src: string
|
|
477
|
+
sizes?: string
|
|
478
|
+
type?: string
|
|
479
|
+
purpose?: string
|
|
480
|
+
}>
|
|
481
|
+
id?: string
|
|
482
|
+
scope?: string
|
|
483
|
+
[key: string]: any
|
|
484
|
+
}
|
package/src/utils.ts
CHANGED
|
@@ -59,3 +59,24 @@ export function composeSRT(position: Vec3, rotation: Vec3, scale: Vec3) {
|
|
|
59
59
|
m = m.scale(sx, sy, sz)
|
|
60
60
|
return m
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Deep-clone a plain JSON-serializable object by value.
|
|
65
|
+
*
|
|
66
|
+
* Notes:
|
|
67
|
+
* - Only use for data composed of primitives, arrays, and plain objects.
|
|
68
|
+
* - Functions, Dates, Maps/Sets, DOM nodes, and circular structures are not supported.
|
|
69
|
+
*/
|
|
70
|
+
export function deepCloneJSON<T>(value: T): T {
|
|
71
|
+
const sc = (globalThis as any).structuredClone as
|
|
72
|
+
| ((v: any) => any)
|
|
73
|
+
| undefined
|
|
74
|
+
if (typeof sc === 'function') {
|
|
75
|
+
try {
|
|
76
|
+
return sc(value)
|
|
77
|
+
} catch {
|
|
78
|
+
// fall through to JSON method
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return JSON.parse(JSON.stringify(value))
|
|
82
|
+
}
|