@webspatial/core-sdk 1.3.0 → 1.5.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 +50 -0
- package/dist/iife/index.d.ts +80 -31
- package/dist/iife/index.global.js +14 -14
- package/dist/iife/index.global.js.map +1 -1
- package/dist/index.d.ts +80 -31
- package/dist/index.js +241 -79
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/JSBCommand.ts +66 -0
- package/src/Spatial.ts +22 -1
- package/src/SpatialScene.ts +20 -0
- package/src/SpatializedDynamic3DElement.ts +19 -0
- package/src/SpatializedElement.ts +0 -29
- package/src/SpatializedStatic3DElement.test.ts +125 -0
- package/src/SpatializedStatic3DElement.ts +2 -0
- package/src/WebMsgCommand.ts +0 -26
- package/src/index.ts +2 -0
- package/src/jsbcommand.coverage.test.ts +0 -43
- package/src/physicalMetrics.test.ts +110 -0
- package/src/physicalMetrics.ts +101 -0
- package/src/platform-adapter/index.ts +2 -2
- package/src/platform-adapter/{xr/XRPlatform.ts → pico-os/PicoOSPlatform.ts} +5 -7
- package/src/platform-adapter/puppeteer/PuppeteerPlatform.ts +52 -52
- package/src/reality/Attachment.ts +2 -0
- package/src/reality/entity/SpatialEntity.ts +49 -24
- package/src/reality/entity/SpatialModelEntity.ts +6 -0
- package/src/scene-polyfill.ts +12 -0
- package/src/types/global.d.ts +12 -5
- package/src/types/types.ts +16 -2
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { SpatialWebEvent } from './SpatialWebEvent'
|
|
2
|
+
export type PhysicalMetricsValueShape = {
|
|
3
|
+
meterToPtUnscaled: number
|
|
4
|
+
meterToPtScaled: number
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
type WorldScalingCompensation = 'unscaled' | 'scaled'
|
|
8
|
+
|
|
9
|
+
type ConvertOption = { worldScalingCompensation: WorldScalingCompensation }
|
|
10
|
+
|
|
11
|
+
// Fallback calibration: 1 meter ≈ 1360 pt for both scaled and unscaled modes.
|
|
12
|
+
// This baseline ensures pointToPhysical(1360) === 1 and physicalToPoint(1) === 1360
|
|
13
|
+
// until native physical metrics are injected into window.__webspatialsdk__.physicalMetrics
|
|
14
|
+
// and a 'WebSpatialPhysicalMetricsUpdate' event updates the snapshot at runtime.
|
|
15
|
+
let snapshot: PhysicalMetricsValueShape = {
|
|
16
|
+
meterToPtUnscaled: 1360,
|
|
17
|
+
meterToPtScaled: 1360,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getWorldScalingCompensation(options?: ConvertOption) {
|
|
21
|
+
return options?.worldScalingCompensation ?? 'scaled' // default to scaled
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Converts scene points (pt) to physical meters (m).
|
|
26
|
+
*
|
|
27
|
+
* @param point Points value to convert.
|
|
28
|
+
* @param options Optional conversion options to select world scaling compensation.
|
|
29
|
+
* @returns Physical length in meters.
|
|
30
|
+
*/
|
|
31
|
+
export function pointToPhysical(point: number, options?: ConvertOption) {
|
|
32
|
+
updateValue()
|
|
33
|
+
const compensation = getWorldScalingCompensation(options)
|
|
34
|
+
if (compensation === 'unscaled') {
|
|
35
|
+
return point / snapshot.meterToPtUnscaled
|
|
36
|
+
}
|
|
37
|
+
return point / snapshot.meterToPtScaled
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Converts physical meters (m) to scene points (pt).
|
|
42
|
+
*
|
|
43
|
+
* @param physical Physical length in meters to convert.
|
|
44
|
+
* @param options Optional conversion options to select world scaling compensation.
|
|
45
|
+
* @returns Points length in the scene.
|
|
46
|
+
*/
|
|
47
|
+
export function physicalToPoint(physical: number, options?: ConvertOption) {
|
|
48
|
+
updateValue()
|
|
49
|
+
const compensation = getWorldScalingCompensation(options)
|
|
50
|
+
if (compensation === 'unscaled') {
|
|
51
|
+
return physical * snapshot.meterToPtUnscaled
|
|
52
|
+
}
|
|
53
|
+
return physical * snapshot.meterToPtScaled
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function updateValue() {
|
|
57
|
+
// ssr protected
|
|
58
|
+
if (typeof window === 'undefined') return
|
|
59
|
+
const src = window.__webspatialsdk__?.physicalMetrics
|
|
60
|
+
if (!src) return
|
|
61
|
+
const next = {
|
|
62
|
+
meterToPtScaled: src.meterToPtScaled ?? snapshot.meterToPtScaled,
|
|
63
|
+
meterToPtUnscaled: src.meterToPtUnscaled ?? snapshot.meterToPtUnscaled,
|
|
64
|
+
}
|
|
65
|
+
// only update if there is a change
|
|
66
|
+
if (
|
|
67
|
+
next.meterToPtScaled !== snapshot.meterToPtScaled ||
|
|
68
|
+
next.meterToPtUnscaled !== snapshot.meterToPtUnscaled
|
|
69
|
+
) {
|
|
70
|
+
snapshot = next
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Returns the current physical metrics used for conversions.
|
|
76
|
+
*
|
|
77
|
+
* @returns The current metrics snapshot `{ meterToPtUnscaled, meterToPtScaled }`.
|
|
78
|
+
*/
|
|
79
|
+
export function getValue(): PhysicalMetricsValueShape {
|
|
80
|
+
updateValue()
|
|
81
|
+
return snapshot
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Subscribes to physical metrics changes.
|
|
86
|
+
*
|
|
87
|
+
* @param cb Callback invoked when metrics update is detected.
|
|
88
|
+
* @returns Unsubscribe function to remove the listener.
|
|
89
|
+
*/
|
|
90
|
+
export function subscribe(cb: () => void) {
|
|
91
|
+
// ssr protected
|
|
92
|
+
if (typeof window === 'undefined') return () => {}
|
|
93
|
+
const handler = () => {
|
|
94
|
+
cb()
|
|
95
|
+
}
|
|
96
|
+
// receive metrics update from native via SpatialWebEvent, id: "window"
|
|
97
|
+
SpatialWebEvent.addEventReceiver('window', handler)
|
|
98
|
+
return () => {
|
|
99
|
+
SpatialWebEvent.removeEventReceiver('window')
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -40,8 +40,8 @@ export function createPlatform(): PlatformAbility {
|
|
|
40
40
|
userAgent.includes('PicoWebApp') &&
|
|
41
41
|
isVersionGreater(webSpatialVersion, [0, 0, 1])
|
|
42
42
|
) {
|
|
43
|
-
const
|
|
44
|
-
return new
|
|
43
|
+
const PicoOSPlatform = require('./pico-os/PicoOSPlatform').PicoOSPlatform
|
|
44
|
+
return new PicoOSPlatform()
|
|
45
45
|
} else if (userAgent.includes('Android') || userAgent.includes('Linux')) {
|
|
46
46
|
const AndroidPlatform = require('./android/AndroidPlatform').AndroidPlatform
|
|
47
47
|
return new AndroidPlatform()
|
|
@@ -23,9 +23,10 @@ function nextRequestId() {
|
|
|
23
23
|
return `rId_${requestId}`
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
// Only supports Pico OS 6
|
|
27
|
+
export class PicoOSPlatform implements PlatformAbility {
|
|
27
28
|
async callJSB(cmd: string, msg: string): Promise<CommandResult> {
|
|
28
|
-
//
|
|
29
|
+
// swan JS Bridge interface only support sync invoking
|
|
29
30
|
// in order to implement promise API, register every request by requestId and remove when resolve/reject.
|
|
30
31
|
return new Promise((resolve, reject) => {
|
|
31
32
|
try {
|
|
@@ -54,7 +55,7 @@ export class XRPlatform implements PlatformAbility {
|
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
} catch (error: unknown) {
|
|
57
|
-
console.error(`
|
|
58
|
+
console.error(`SwanPlatform cmd: ${cmd}, msg: ${msg} error: ${error}`)
|
|
58
59
|
const { code, message } = error as JSBError
|
|
59
60
|
resolve(CommandResultFailure(code, message))
|
|
60
61
|
}
|
|
@@ -75,7 +76,6 @@ export class XRPlatform implements PlatformAbility {
|
|
|
75
76
|
SpatialWebEvent.addEventReceiver(
|
|
76
77
|
createdId,
|
|
77
78
|
(result: { spatialId: string }) => {
|
|
78
|
-
console.log('createdId', createdId, result.spatialId)
|
|
79
79
|
resolve(
|
|
80
80
|
CommandResultSuccess({
|
|
81
81
|
windowProxy: windowProxy,
|
|
@@ -87,13 +87,11 @@ export class XRPlatform implements PlatformAbility {
|
|
|
87
87
|
)
|
|
88
88
|
windowProxy = this.openWindow(
|
|
89
89
|
command,
|
|
90
|
-
|
|
90
|
+
'rid=' + createdId,
|
|
91
91
|
target,
|
|
92
92
|
features,
|
|
93
93
|
).windowProxy
|
|
94
|
-
windowProxy?.open(`about:blank?rid=${createdId}`, '_self')
|
|
95
94
|
} catch (error: unknown) {
|
|
96
|
-
console.error(`open window error: ${error}`)
|
|
97
95
|
const { code, message } = error as JSBError
|
|
98
96
|
SpatialWebEvent.removeEventReceiver(createdId)
|
|
99
97
|
resolve(CommandResultFailure(code, message))
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
CommandResultSuccess,
|
|
5
5
|
} from '../CommandResultUtils'
|
|
6
6
|
|
|
7
|
-
//
|
|
7
|
+
// add window interface for JSB call
|
|
8
8
|
declare global {
|
|
9
9
|
interface Window {
|
|
10
10
|
__handleJSBMessage: (message: string) => any
|
|
@@ -24,7 +24,7 @@ type JSBError = {
|
|
|
24
24
|
console.log('PuppeteerPlatform')
|
|
25
25
|
|
|
26
26
|
export class PuppeteerPlatform implements PlatformAbility {
|
|
27
|
-
//
|
|
27
|
+
// store iframe instance
|
|
28
28
|
private iframeRegistry: Map<string, HTMLIFrameElement> = new Map()
|
|
29
29
|
|
|
30
30
|
constructor() {}
|
|
@@ -32,7 +32,7 @@ export class PuppeteerPlatform implements PlatformAbility {
|
|
|
32
32
|
callJSB(cmd: string, msg: string): Promise<CommandResult> {
|
|
33
33
|
return new Promise(resolve => {
|
|
34
34
|
try {
|
|
35
|
-
//
|
|
35
|
+
// check __handleJSBMessage exist
|
|
36
36
|
if (window.__handleJSBMessage) {
|
|
37
37
|
try {
|
|
38
38
|
console.log(` core-sdk Puppeteer Platform: callJSB: ${cmd}::${msg}`)
|
|
@@ -45,7 +45,7 @@ export class PuppeteerPlatform implements PlatformAbility {
|
|
|
45
45
|
resolve(CommandResultFailure('500', 'JSB execution error'))
|
|
46
46
|
}
|
|
47
47
|
} else {
|
|
48
|
-
//
|
|
48
|
+
// if not exist, return default result
|
|
49
49
|
resolve(CommandResultSuccess('ok'))
|
|
50
50
|
}
|
|
51
51
|
} catch (error: unknown) {
|
|
@@ -58,7 +58,7 @@ export class PuppeteerPlatform implements PlatformAbility {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
*
|
|
61
|
+
* Synchronously create Spatialized2DElement to Puppeteer Runner
|
|
62
62
|
*/
|
|
63
63
|
private createSpatializedElementSync(
|
|
64
64
|
spatialId: string,
|
|
@@ -68,10 +68,10 @@ export class PuppeteerPlatform implements PlatformAbility {
|
|
|
68
68
|
console.log(
|
|
69
69
|
`[Puppeteer Platform] Creating spatialized element sync with id: ${spatialId}, url: ${webspatialUrl}`,
|
|
70
70
|
)
|
|
71
|
-
//
|
|
71
|
+
// directly call Puppeteer Runner method to create element
|
|
72
72
|
const win = window as any
|
|
73
73
|
if (win.__handleJSBMessage) {
|
|
74
|
-
//
|
|
74
|
+
// use simpler format to ensure JSBManager can correctly use our passed spatialId
|
|
75
75
|
const createCommand = {
|
|
76
76
|
id: spatialId,
|
|
77
77
|
url: webspatialUrl,
|
|
@@ -96,9 +96,9 @@ export class PuppeteerPlatform implements PlatformAbility {
|
|
|
96
96
|
)
|
|
97
97
|
return new Promise(resolve => {
|
|
98
98
|
try {
|
|
99
|
-
//
|
|
99
|
+
// create complete webspatial URL
|
|
100
100
|
const webspatialUrl = `webspatial://${command}${query ? `?${query}` : ''}`
|
|
101
|
-
//
|
|
101
|
+
// use iframe to create new window
|
|
102
102
|
const { spatialId, iframe, windowProxy } = this.createIframeWindow(
|
|
103
103
|
webspatialUrl,
|
|
104
104
|
target,
|
|
@@ -112,7 +112,7 @@ export class PuppeteerPlatform implements PlatformAbility {
|
|
|
112
112
|
console.log(
|
|
113
113
|
`[Puppeteer Platform] iframe created with spatialId: ${spatialId}`,
|
|
114
114
|
)
|
|
115
|
-
//
|
|
115
|
+
// store iframe instance
|
|
116
116
|
this.iframeRegistry.set(spatialId, iframe)
|
|
117
117
|
resolve(CommandResultSuccess({ windowProxy, id: spatialId }))
|
|
118
118
|
} catch (error) {
|
|
@@ -131,7 +131,7 @@ export class PuppeteerPlatform implements PlatformAbility {
|
|
|
131
131
|
features?: string,
|
|
132
132
|
): CommandResult {
|
|
133
133
|
try {
|
|
134
|
-
//
|
|
134
|
+
// create complete webspatial URL
|
|
135
135
|
const webspatialUrl = `webspatial://${command}${query ? `?${query}` : ''}`
|
|
136
136
|
console.log(`Calling webspatial protocol sync: ${webspatialUrl}`)
|
|
137
137
|
|
|
@@ -147,7 +147,7 @@ export class PuppeteerPlatform implements PlatformAbility {
|
|
|
147
147
|
this.createSpatializedElementSync(spatialId, webspatialUrl)
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
//
|
|
150
|
+
// store iframe instance
|
|
151
151
|
this.iframeRegistry.set(spatialId, iframe)
|
|
152
152
|
|
|
153
153
|
return CommandResultSuccess({ windowProxy, id: spatialId })
|
|
@@ -161,27 +161,27 @@ export class PuppeteerPlatform implements PlatformAbility {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
/**
|
|
164
|
-
*
|
|
164
|
+
* Synchronously create iframe-based window
|
|
165
165
|
*/
|
|
166
166
|
private createIframeWindow(url: string, target?: string, features?: string) {
|
|
167
|
-
//
|
|
167
|
+
// create iframe element
|
|
168
168
|
const iframe = document.createElement('iframe')
|
|
169
169
|
|
|
170
|
-
//
|
|
170
|
+
// set iframe attributes
|
|
171
171
|
iframe.style.border = 'none'
|
|
172
|
-
iframe.style.display = 'none'
|
|
172
|
+
iframe.style.display = 'none'
|
|
173
173
|
iframe.style.width = '100%'
|
|
174
174
|
iframe.style.height = '100%'
|
|
175
175
|
|
|
176
|
-
//
|
|
176
|
+
// set iframe id
|
|
177
177
|
const spatialId = this.generateUUID()
|
|
178
178
|
iframe.spatialId = spatialId
|
|
179
179
|
iframe.id = `spatial-iframe-${spatialId}`
|
|
180
180
|
|
|
181
|
-
//
|
|
181
|
+
// parse features parameter
|
|
182
182
|
const featuresObj = this.parseFeatures(features || '')
|
|
183
183
|
|
|
184
|
-
//
|
|
184
|
+
// set iframe styles based on features
|
|
185
185
|
if (featuresObj.width) {
|
|
186
186
|
iframe.style.width = featuresObj.width
|
|
187
187
|
}
|
|
@@ -197,36 +197,36 @@ export class PuppeteerPlatform implements PlatformAbility {
|
|
|
197
197
|
iframe.style.position = 'absolute'
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
//
|
|
200
|
+
// add iframe to DOM
|
|
201
201
|
document.body.appendChild(iframe)
|
|
202
202
|
|
|
203
|
-
//
|
|
203
|
+
// create enhanced windowProxy object
|
|
204
204
|
const windowProxy = this.createEnhancedWindowProxy(iframe, url, spatialId)
|
|
205
205
|
|
|
206
|
-
//
|
|
206
|
+
// set iframe src
|
|
207
207
|
iframe.src = 'about:blank'
|
|
208
208
|
|
|
209
209
|
console.log(
|
|
210
210
|
`PuppeteerPlatform created iframe window with spatialId: ${spatialId}, URL: ${url}`,
|
|
211
211
|
)
|
|
212
212
|
|
|
213
|
-
//
|
|
213
|
+
// initialize iframe content
|
|
214
214
|
this.initializeIframeContent(iframe, url, spatialId)
|
|
215
215
|
|
|
216
216
|
return { spatialId, iframe, windowProxy }
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
/**
|
|
220
|
-
*
|
|
220
|
+
* create enhanced windowProxy object
|
|
221
221
|
*/
|
|
222
222
|
private createEnhancedWindowProxy(
|
|
223
223
|
iframe: HTMLIFrameElement,
|
|
224
224
|
url: string,
|
|
225
225
|
spatialId: string,
|
|
226
226
|
) {
|
|
227
|
-
//
|
|
227
|
+
// create enhanced windowProxy object
|
|
228
228
|
return {
|
|
229
|
-
//
|
|
229
|
+
// basic properties
|
|
230
230
|
location: {
|
|
231
231
|
href: url,
|
|
232
232
|
toString: () => url,
|
|
@@ -240,25 +240,25 @@ export class PuppeteerPlatform implements PlatformAbility {
|
|
|
240
240
|
userAgent: `Mozilla/5.0 (WebKit) SpatialId/${spatialId}`,
|
|
241
241
|
},
|
|
242
242
|
|
|
243
|
-
//
|
|
243
|
+
// methods
|
|
244
244
|
close: () => {
|
|
245
245
|
console.log(`Closing iframe with spatialId: ${spatialId}`)
|
|
246
246
|
iframe.remove()
|
|
247
247
|
this.iframeRegistry.delete(spatialId)
|
|
248
248
|
},
|
|
249
249
|
|
|
250
|
-
//
|
|
250
|
+
// document access
|
|
251
251
|
document: iframe.contentDocument || ({} as Document),
|
|
252
252
|
contentWindow: iframe.contentWindow || ({} as Window),
|
|
253
253
|
|
|
254
|
-
//
|
|
254
|
+
// add message communication method
|
|
255
255
|
postMessage: (message: any, targetOrigin?: string) => {
|
|
256
256
|
if (iframe.contentWindow) {
|
|
257
257
|
iframe.contentWindow.postMessage(message, targetOrigin || '*')
|
|
258
258
|
}
|
|
259
259
|
},
|
|
260
260
|
|
|
261
|
-
//
|
|
261
|
+
// add event listener method
|
|
262
262
|
addEventListener: (
|
|
263
263
|
type: string,
|
|
264
264
|
listener: EventListenerOrEventListenerObject,
|
|
@@ -277,11 +277,11 @@ export class PuppeteerPlatform implements PlatformAbility {
|
|
|
277
277
|
}
|
|
278
278
|
},
|
|
279
279
|
|
|
280
|
-
//
|
|
280
|
+
// execute JavaScript
|
|
281
281
|
executeScript: (code: string): any => {
|
|
282
282
|
if (iframe.contentWindow) {
|
|
283
283
|
try {
|
|
284
|
-
//
|
|
284
|
+
// use type assertion and safer way to execute script
|
|
285
285
|
const win = iframe.contentWindow as any
|
|
286
286
|
return win.eval(code)
|
|
287
287
|
} catch (error) {
|
|
@@ -295,16 +295,16 @@ export class PuppeteerPlatform implements PlatformAbility {
|
|
|
295
295
|
return null
|
|
296
296
|
},
|
|
297
297
|
|
|
298
|
-
//
|
|
298
|
+
// get iframe reference
|
|
299
299
|
getIframe: () => iframe,
|
|
300
300
|
|
|
301
|
-
//
|
|
301
|
+
// get spatialId
|
|
302
302
|
getSpatialId: () => spatialId,
|
|
303
303
|
}
|
|
304
304
|
}
|
|
305
305
|
|
|
306
306
|
/**
|
|
307
|
-
*
|
|
307
|
+
* initialize iframe content
|
|
308
308
|
*/
|
|
309
309
|
private initializeIframeContent(
|
|
310
310
|
iframe: HTMLIFrameElement,
|
|
@@ -312,20 +312,20 @@ export class PuppeteerPlatform implements PlatformAbility {
|
|
|
312
312
|
spatialId: string,
|
|
313
313
|
): void {
|
|
314
314
|
try {
|
|
315
|
-
//
|
|
315
|
+
// wait for iframe to load
|
|
316
316
|
iframe.onload = () => {
|
|
317
317
|
try {
|
|
318
|
-
//
|
|
318
|
+
// set iframe content
|
|
319
319
|
const iframeContent = `
|
|
320
|
-
//
|
|
320
|
+
// inject communication script
|
|
321
321
|
window.webSpatialId = '${spatialId}';
|
|
322
322
|
window.SpatialId = '${spatialId}';
|
|
323
323
|
|
|
324
|
-
//
|
|
324
|
+
// override window.open to support webspatial protocol
|
|
325
325
|
const originalOpen = window.open;
|
|
326
326
|
window.open = function(url, target, features) {
|
|
327
327
|
if (url && url.startsWith('webspatial://')) {
|
|
328
|
-
//
|
|
328
|
+
// handle webspatial protocol through windowProxy
|
|
329
329
|
const windowProxy = new Proxy({}, {
|
|
330
330
|
get: function(target, prop) {
|
|
331
331
|
if (prop === 'toString') {
|
|
@@ -339,33 +339,33 @@ export class PuppeteerPlatform implements PlatformAbility {
|
|
|
339
339
|
return originalOpen.call(window, url, target, features);
|
|
340
340
|
};
|
|
341
341
|
|
|
342
|
-
//
|
|
342
|
+
// set navigator.userAgent to identify webspatial environment
|
|
343
343
|
Object.defineProperty(navigator, 'userAgent', {
|
|
344
344
|
value: 'WebSpatial/1.0 ' + navigator.userAgent,
|
|
345
345
|
configurable: true
|
|
346
346
|
});
|
|
347
347
|
|
|
348
|
-
//
|
|
348
|
+
// send loaded message
|
|
349
349
|
window.parent.postMessage({
|
|
350
350
|
type: 'iframe_loaded',
|
|
351
351
|
spatialId: '${spatialId}',
|
|
352
352
|
url: '${url}'
|
|
353
353
|
}, '${window.location.origin}');
|
|
354
354
|
|
|
355
|
-
//
|
|
355
|
+
// set message handler
|
|
356
356
|
window.addEventListener('message', (event) => {
|
|
357
357
|
if (event.origin !== window.parent.location.origin) return;
|
|
358
358
|
|
|
359
359
|
const data = event.data;
|
|
360
360
|
if (data && data.type === 'webspatial_command') {
|
|
361
|
-
//
|
|
361
|
+
// handle command from parent window
|
|
362
362
|
console.log('Received command in iframe from parent:', data.command);
|
|
363
|
-
//
|
|
363
|
+
// add command handling logic here
|
|
364
364
|
}
|
|
365
365
|
});
|
|
366
366
|
`
|
|
367
367
|
|
|
368
|
-
//
|
|
368
|
+
// use document.write instead of eval for security and type compliance
|
|
369
369
|
const doc = iframe.contentDocument
|
|
370
370
|
if (doc) {
|
|
371
371
|
doc.open()
|
|
@@ -400,7 +400,7 @@ export class PuppeteerPlatform implements PlatformAbility {
|
|
|
400
400
|
}
|
|
401
401
|
|
|
402
402
|
/**
|
|
403
|
-
*
|
|
403
|
+
* parse features string to object
|
|
404
404
|
*/
|
|
405
405
|
private parseFeatures(features: string): Record<string, string> {
|
|
406
406
|
const result: Record<string, string> = {}
|
|
@@ -417,7 +417,7 @@ export class PuppeteerPlatform implements PlatformAbility {
|
|
|
417
417
|
}
|
|
418
418
|
|
|
419
419
|
/**
|
|
420
|
-
*
|
|
420
|
+
* send message to iframe with specified spatialId
|
|
421
421
|
*/
|
|
422
422
|
public sendMessageToIframe(spatialId: string, message: any): boolean {
|
|
423
423
|
const iframe = this.iframeRegistry.get(spatialId)
|
|
@@ -429,7 +429,7 @@ export class PuppeteerPlatform implements PlatformAbility {
|
|
|
429
429
|
}
|
|
430
430
|
|
|
431
431
|
/**
|
|
432
|
-
*
|
|
432
|
+
* get all active iframes
|
|
433
433
|
*/
|
|
434
434
|
public getAllActiveIframes(): Array<{
|
|
435
435
|
spatialId: string
|
|
@@ -445,10 +445,10 @@ export class PuppeteerPlatform implements PlatformAbility {
|
|
|
445
445
|
}
|
|
446
446
|
|
|
447
447
|
/**
|
|
448
|
-
*
|
|
448
|
+
* dispose all active iframes
|
|
449
449
|
*/
|
|
450
450
|
public dispose(): void {
|
|
451
|
-
//
|
|
451
|
+
// close all iframes
|
|
452
452
|
this.iframeRegistry.forEach((iframe, spatialId) => {
|
|
453
453
|
console.log(`Disposing iframe with spatialId: ${spatialId}`)
|
|
454
454
|
iframe.remove()
|
|
@@ -456,7 +456,7 @@ export class PuppeteerPlatform implements PlatformAbility {
|
|
|
456
456
|
this.iframeRegistry.clear()
|
|
457
457
|
}
|
|
458
458
|
|
|
459
|
-
//
|
|
459
|
+
// generate UUID function
|
|
460
460
|
private generateUUID(): string {
|
|
461
461
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
|
|
462
462
|
/[xy]/g,
|
|
@@ -2,6 +2,7 @@ import { SpatialObject } from '../SpatialObject'
|
|
|
2
2
|
import {
|
|
3
3
|
CreateAttachmentEntityCommand,
|
|
4
4
|
UpdateAttachmentEntityCommand,
|
|
5
|
+
InitializeAttachmentCommand,
|
|
5
6
|
} from '../JSBCommand'
|
|
6
7
|
import {
|
|
7
8
|
AttachmentEntityOptions,
|
|
@@ -41,5 +42,6 @@ export async function createAttachmentEntity(
|
|
|
41
42
|
throw new Error('createAttachmentEntity failed: ' + result?.errorMessage)
|
|
42
43
|
}
|
|
43
44
|
const { id, windowProxy } = result.data!
|
|
45
|
+
await new InitializeAttachmentCommand(id, options).execute()
|
|
44
46
|
return new Attachment(id, windowProxy, options)
|
|
45
47
|
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
AddComponentToEntityCommand,
|
|
15
15
|
AddEntityToEntityCommand,
|
|
16
|
+
RemoveComponentFromEntityCommand,
|
|
16
17
|
RemoveEntityFromParentCommand,
|
|
17
18
|
UpdateEntityEventCommand,
|
|
18
19
|
UpdateEntityPropertiesCommand,
|
|
@@ -23,16 +24,16 @@ import { SpatialComponent } from '../component/SpatialComponent'
|
|
|
23
24
|
import { SpatialWebEvent } from '../../SpatialWebEvent'
|
|
24
25
|
import { createSpatialEvent } from '../../SpatialWebEventCreator'
|
|
25
26
|
import {
|
|
26
|
-
CubeInfoMsg,
|
|
27
27
|
ObjectDestroyMsg,
|
|
28
28
|
SpatialDragEndMsg,
|
|
29
29
|
SpatialDragMsg,
|
|
30
|
+
SpatialDragStartMsg,
|
|
31
|
+
SpatialMagnifyEndMsg,
|
|
30
32
|
SpatialMagnifyMsg,
|
|
31
33
|
SpatialRotateEndMsg,
|
|
32
34
|
SpatialRotateMsg,
|
|
33
35
|
SpatialTapMsg,
|
|
34
36
|
SpatialWebMsgType,
|
|
35
|
-
TransformMsg,
|
|
36
37
|
} from '../../WebMsgCommand'
|
|
37
38
|
|
|
38
39
|
export class SpatialEntity extends SpatialObject {
|
|
@@ -43,6 +44,31 @@ export class SpatialEntity extends SpatialObject {
|
|
|
43
44
|
events: Record<string, (data: any) => void> = {}
|
|
44
45
|
children: SpatialEntity[] = []
|
|
45
46
|
parent: SpatialEntityOrReality | null = null
|
|
47
|
+
private _enableInput: boolean = false
|
|
48
|
+
|
|
49
|
+
get enableInput(): boolean {
|
|
50
|
+
return this._enableInput
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
set enableInput(value: boolean) {
|
|
54
|
+
// Why enabling only 'spatialtap' makes the entity interactive:
|
|
55
|
+
// - On the native (Swift/RealityKit) side, SpatialEntity.updateGesture(type, isEnable)
|
|
56
|
+
// toggles per-gesture flags. Then enableInteractive = enableTap || enableRotate || enableDrag || enableMagnify.
|
|
57
|
+
// - As soon as any gesture (e.g., 'spatialtap') is enabled, enableInteractive becomes true and
|
|
58
|
+
// InputTargetComponent is attached, making the entity targetable by targetedToAnyEntity().
|
|
59
|
+
// - The view layer forwards hit gestures to the web, so enabling 'spatialtap' is sufficient to
|
|
60
|
+
// make the entity targetable; enable additional gestures only when needed.
|
|
61
|
+
if (this._enableInput === value) return
|
|
62
|
+
this._enableInput = value
|
|
63
|
+
void this.updateEntityEvent('spatialtap', value).catch(err => {
|
|
64
|
+
console.error('enableInput updateEntityEvent failed', 'spatialtap', err)
|
|
65
|
+
// Roll back local flag if the native toggle fails to keep web/native states consistent.
|
|
66
|
+
// Otherwise, the web side would think the entity is interactive while RealityKit is not.
|
|
67
|
+
if (this._enableInput === value) {
|
|
68
|
+
this._enableInput = !value
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
}
|
|
46
72
|
constructor(
|
|
47
73
|
id: string,
|
|
48
74
|
public userData?: SpatialEntityUserData,
|
|
@@ -54,6 +80,9 @@ export class SpatialEntity extends SpatialObject {
|
|
|
54
80
|
async addComponent(component: SpatialComponent) {
|
|
55
81
|
return new AddComponentToEntityCommand(this, component).execute()
|
|
56
82
|
}
|
|
83
|
+
async removeComponent(component: SpatialComponent) {
|
|
84
|
+
return new RemoveComponentFromEntityCommand(this, component).execute()
|
|
85
|
+
}
|
|
57
86
|
async setPosition(position: Vec3) {
|
|
58
87
|
return this.updateTransform({ position })
|
|
59
88
|
}
|
|
@@ -123,13 +152,15 @@ export class SpatialEntity extends SpatialObject {
|
|
|
123
152
|
return new UpdateEntityEventCommand(this, eventName, isEnable).execute()
|
|
124
153
|
}
|
|
125
154
|
private onReceiveEvent = (
|
|
126
|
-
data:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
155
|
+
data:
|
|
156
|
+
| SpatialTapMsg
|
|
157
|
+
| SpatialDragStartMsg
|
|
158
|
+
| SpatialDragMsg
|
|
159
|
+
| SpatialDragEndMsg
|
|
160
|
+
| SpatialMagnifyMsg
|
|
161
|
+
| SpatialMagnifyEndMsg
|
|
162
|
+
| SpatialRotateMsg
|
|
163
|
+
| SpatialRotateEndMsg
|
|
133
164
|
| ObjectDestroyMsg,
|
|
134
165
|
) => {
|
|
135
166
|
// console.log('SpatialEntityEvent', data)
|
|
@@ -139,27 +170,21 @@ export class SpatialEntity extends SpatialObject {
|
|
|
139
170
|
}
|
|
140
171
|
// tap
|
|
141
172
|
else if (type === SpatialWebMsgType.spatialtap) {
|
|
142
|
-
const evt = createSpatialEvent(
|
|
143
|
-
SpatialWebMsgType.spatialtap,
|
|
144
|
-
(data as SpatialTapMsg).detail,
|
|
145
|
-
)
|
|
173
|
+
const evt = createSpatialEvent(SpatialWebMsgType.spatialtap, data.detail)
|
|
146
174
|
this.dispatchEvent(evt)
|
|
147
175
|
} else if (type === SpatialWebMsgType.spatialdragstart) {
|
|
148
176
|
const evt = createSpatialEvent(
|
|
149
177
|
SpatialWebMsgType.spatialdragstart,
|
|
150
|
-
|
|
178
|
+
data.detail,
|
|
151
179
|
)
|
|
152
180
|
this.dispatchEvent(evt)
|
|
153
181
|
} else if (type === SpatialWebMsgType.spatialdrag) {
|
|
154
|
-
const evt = createSpatialEvent(
|
|
155
|
-
SpatialWebMsgType.spatialdrag,
|
|
156
|
-
(data as SpatialDragMsg).detail,
|
|
157
|
-
)
|
|
182
|
+
const evt = createSpatialEvent(SpatialWebMsgType.spatialdrag, data.detail)
|
|
158
183
|
this.dispatchEvent(evt)
|
|
159
184
|
} else if (type === SpatialWebMsgType.spatialdragend) {
|
|
160
185
|
const evt = createSpatialEvent(
|
|
161
186
|
SpatialWebMsgType.spatialdragend,
|
|
162
|
-
|
|
187
|
+
data.detail,
|
|
163
188
|
)
|
|
164
189
|
this.dispatchEvent(evt)
|
|
165
190
|
}
|
|
@@ -167,13 +192,13 @@ export class SpatialEntity extends SpatialObject {
|
|
|
167
192
|
else if (type === SpatialWebMsgType.spatialrotate) {
|
|
168
193
|
const evt = createSpatialEvent(
|
|
169
194
|
SpatialWebMsgType.spatialrotate,
|
|
170
|
-
|
|
195
|
+
data.detail,
|
|
171
196
|
)
|
|
172
197
|
this.dispatchEvent(evt)
|
|
173
198
|
} else if (type === SpatialWebMsgType.spatialrotateend) {
|
|
174
199
|
const evt = createSpatialEvent(
|
|
175
200
|
SpatialWebMsgType.spatialrotateend,
|
|
176
|
-
|
|
201
|
+
data.detail,
|
|
177
202
|
)
|
|
178
203
|
this.dispatchEvent(evt)
|
|
179
204
|
}
|
|
@@ -181,13 +206,13 @@ export class SpatialEntity extends SpatialObject {
|
|
|
181
206
|
else if (type === SpatialWebMsgType.spatialmagnify) {
|
|
182
207
|
const evt = createSpatialEvent(
|
|
183
208
|
SpatialWebMsgType.spatialmagnify,
|
|
184
|
-
|
|
209
|
+
data.detail,
|
|
185
210
|
)
|
|
186
211
|
this.dispatchEvent(evt)
|
|
187
212
|
} else if (type === SpatialWebMsgType.spatialmagnifyend) {
|
|
188
213
|
const evt = createSpatialEvent(
|
|
189
214
|
SpatialWebMsgType.spatialmagnifyend,
|
|
190
|
-
|
|
215
|
+
data.detail,
|
|
191
216
|
)
|
|
192
217
|
this.dispatchEvent(evt)
|
|
193
218
|
}
|
|
@@ -200,7 +225,7 @@ export class SpatialEntity extends SpatialObject {
|
|
|
200
225
|
}
|
|
201
226
|
this.events[evt.type]?.(evt)
|
|
202
227
|
if (evt.bubbles && !evt.cancelBubble) {
|
|
203
|
-
if (this.parent
|
|
228
|
+
if (this.parent) {
|
|
204
229
|
this.parent.dispatchEvent(evt)
|
|
205
230
|
}
|
|
206
231
|
}
|