@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
package/package.json
CHANGED
package/src/JSBCommand.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { createPlatform } from './platform-adapter'
|
|
|
2
2
|
import { WebSpatialProtocolResult } from './platform-adapter/interface'
|
|
3
3
|
import { SpatialComponent } from './reality/component/SpatialComponent'
|
|
4
4
|
import { SpatialEntity } from './reality/entity/SpatialEntity'
|
|
5
|
+
import { SpatialMaterial } from './reality/material/SpatialMaterial'
|
|
5
6
|
import { SpatializedDynamic3DElement } from './SpatializedDynamic3DElement'
|
|
6
7
|
import { SpatializedElement } from './SpatializedElement'
|
|
7
8
|
import { SpatialObject } from './SpatialObject'
|
|
@@ -386,6 +387,38 @@ export class AddComponentToEntityCommand extends JSBCommand {
|
|
|
386
387
|
commandType = 'AddComponentToEntity'
|
|
387
388
|
}
|
|
388
389
|
|
|
390
|
+
export class RemoveComponentFromEntityCommand extends JSBCommand {
|
|
391
|
+
constructor(
|
|
392
|
+
public entity: SpatialEntity,
|
|
393
|
+
public comp: SpatialComponent,
|
|
394
|
+
) {
|
|
395
|
+
super()
|
|
396
|
+
}
|
|
397
|
+
protected getParams(): Record<string, any> | undefined {
|
|
398
|
+
return {
|
|
399
|
+
entityId: this.entity.id,
|
|
400
|
+
componentId: this.comp.id,
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
commandType = 'RemoveComponentFromEntity'
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export class SetMaterialsOnEntityCommand extends JSBCommand {
|
|
407
|
+
constructor(
|
|
408
|
+
public entityId: string,
|
|
409
|
+
public materials: SpatialMaterial[],
|
|
410
|
+
) {
|
|
411
|
+
super()
|
|
412
|
+
}
|
|
413
|
+
protected getParams(): Record<string, any> | undefined {
|
|
414
|
+
return {
|
|
415
|
+
entityId: this.entityId,
|
|
416
|
+
materialIds: this.materials.map(m => m.id),
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
commandType = 'SetMaterialsOnEntity'
|
|
420
|
+
}
|
|
421
|
+
|
|
389
422
|
export class AddEntityToDynamic3DCommand extends JSBCommand {
|
|
390
423
|
constructor(
|
|
391
424
|
public d3dEle: SpatializedDynamic3DElement,
|
|
@@ -502,6 +535,24 @@ export class ConvertFromSceneToEntityCommand extends JSBCommand {
|
|
|
502
535
|
commandType = 'ConvertFromSceneToEntity'
|
|
503
536
|
}
|
|
504
537
|
|
|
538
|
+
export class ConvertCoordinateCommand extends JSBCommand {
|
|
539
|
+
constructor(
|
|
540
|
+
public position: Vec3,
|
|
541
|
+
public fromId: string,
|
|
542
|
+
public toId: string,
|
|
543
|
+
) {
|
|
544
|
+
super()
|
|
545
|
+
}
|
|
546
|
+
protected getParams(): Record<string, any> | undefined {
|
|
547
|
+
return {
|
|
548
|
+
position: this.position,
|
|
549
|
+
fromId: this.fromId,
|
|
550
|
+
toId: this.toId,
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
commandType = 'ConvertCoordinate'
|
|
554
|
+
}
|
|
555
|
+
|
|
505
556
|
export class CreateTextureResourceCommand extends JSBCommand {
|
|
506
557
|
constructor(private url: string) {
|
|
507
558
|
super()
|
|
@@ -627,11 +678,26 @@ export class CreateAttachmentEntityCommand extends WebSpatialProtocolCommand {
|
|
|
627
678
|
constructor(private options: AttachmentEntityOptions) {
|
|
628
679
|
super()
|
|
629
680
|
}
|
|
681
|
+
protected getParams() {
|
|
682
|
+
return {} // No metadata — just trigger engine/webview creation
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
export class InitializeAttachmentCommand extends JSBCommand {
|
|
687
|
+
commandType = 'InitializeAttachment'
|
|
688
|
+
constructor(
|
|
689
|
+
private attachmentId: string,
|
|
690
|
+
private options: AttachmentEntityOptions,
|
|
691
|
+
) {
|
|
692
|
+
super()
|
|
693
|
+
}
|
|
630
694
|
protected getParams() {
|
|
631
695
|
return {
|
|
696
|
+
id: this.attachmentId,
|
|
632
697
|
parentEntityId: this.options.parentEntityId,
|
|
633
698
|
position: this.options.position ?? [0, 0, 0],
|
|
634
699
|
size: this.options.size,
|
|
700
|
+
ownerViewId: this.options.ownerViewId,
|
|
635
701
|
}
|
|
636
702
|
}
|
|
637
703
|
}
|
package/src/Spatial.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { SpatialWebEvent } from './SpatialWebEvent'
|
|
|
6
6
|
* This is the main entry point for the WebSpatial SDK, providing access to spatial capabilities.
|
|
7
7
|
*/
|
|
8
8
|
export class Spatial {
|
|
9
|
+
private wsAppShellVersionFromUA: string | null | undefined
|
|
10
|
+
|
|
9
11
|
/**
|
|
10
12
|
* Requests a spatial session object from the browser.
|
|
11
13
|
* This is the primary method to initialize spatial functionality.
|
|
@@ -33,6 +35,25 @@ export class Spatial {
|
|
|
33
35
|
return false
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
getShellVersionFromUA(): string | null {
|
|
39
|
+
if (this.wsAppShellVersionFromUA !== undefined) {
|
|
40
|
+
return this.wsAppShellVersionFromUA
|
|
41
|
+
}
|
|
42
|
+
if (
|
|
43
|
+
typeof navigator === 'undefined' ||
|
|
44
|
+
typeof navigator.userAgent !== 'string'
|
|
45
|
+
) {
|
|
46
|
+
this.wsAppShellVersionFromUA = null
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const match = navigator.userAgent.match(
|
|
51
|
+
/WSAppShell\/(\d+(?:\.\d+){2}(?:[-+][0-9A-Za-z.-]+)*)/,
|
|
52
|
+
)
|
|
53
|
+
this.wsAppShellVersionFromUA = match ? match[1] : '1.3.0'
|
|
54
|
+
return this.wsAppShellVersionFromUA
|
|
55
|
+
}
|
|
56
|
+
|
|
36
57
|
/** @deprecated
|
|
37
58
|
* Checks if WebSpatial is supported in the current environment.
|
|
38
59
|
* Verifies compatibility between native and client versions.
|
|
@@ -51,7 +72,7 @@ export class Spatial {
|
|
|
51
72
|
if (window.__WebSpatialData && window.__WebSpatialData.getNativeVersion) {
|
|
52
73
|
return window.__WebSpatialData.getNativeVersion()
|
|
53
74
|
}
|
|
54
|
-
return window.WebSpatailNativeVersion === '
|
|
75
|
+
return window.WebSpatailNativeVersion === 'WS_SHELL_VERSION'
|
|
55
76
|
? this.getClientVersion()
|
|
56
77
|
: window.WebSpatailNativeVersion
|
|
57
78
|
}
|
package/src/SpatialScene.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SpatialSceneCreationOptions,
|
|
3
3
|
SpatialSceneProperties,
|
|
4
|
+
Vec3,
|
|
4
5
|
} from './types/types'
|
|
5
6
|
import { SpatialSceneCreationOptionsInternal } from './types/internal'
|
|
6
7
|
import {
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
UpdateSceneConfig,
|
|
10
11
|
UpdateSpatialSceneProperties,
|
|
11
12
|
} from './JSBCommand'
|
|
13
|
+
import { ConvertCoordinateCommand } from './JSBCommand'
|
|
12
14
|
|
|
13
15
|
import { SpatializedElement } from './SpatializedElement'
|
|
14
16
|
import { SpatialObject } from './SpatialObject'
|
|
@@ -34,6 +36,24 @@ export class SpatialScene extends SpatialObject {
|
|
|
34
36
|
return instance
|
|
35
37
|
}
|
|
36
38
|
|
|
39
|
+
async convertCoordinate(
|
|
40
|
+
position: Vec3,
|
|
41
|
+
fromId: string,
|
|
42
|
+
toId: string,
|
|
43
|
+
): Promise<Vec3> {
|
|
44
|
+
try {
|
|
45
|
+
const ret = await new ConvertCoordinateCommand(
|
|
46
|
+
position,
|
|
47
|
+
fromId,
|
|
48
|
+
toId,
|
|
49
|
+
).execute()
|
|
50
|
+
return (ret as any)?.data ?? position
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.warn('SpatialScene.convertCoordinate error:', error)
|
|
53
|
+
throw error
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
37
57
|
/**
|
|
38
58
|
* Updates the properties of the spatial scene.
|
|
39
59
|
* This can include background settings, lighting, and other scene-wide properties.
|
|
@@ -6,11 +6,15 @@ import {
|
|
|
6
6
|
import { SpatialEntity } from './reality'
|
|
7
7
|
import { SpatializedElement } from './SpatializedElement'
|
|
8
8
|
import {
|
|
9
|
+
SpatialEntityEventType,
|
|
9
10
|
SpatialEntityOrReality,
|
|
10
11
|
SpatializedElementProperties,
|
|
11
12
|
} from './types/types'
|
|
13
|
+
|
|
12
14
|
export class SpatializedDynamic3DElement extends SpatializedElement {
|
|
13
15
|
children: SpatialEntityOrReality[] = []
|
|
16
|
+
events: Record<string, (data: any) => void> = {}
|
|
17
|
+
|
|
14
18
|
constructor(id: string) {
|
|
15
19
|
super(id)
|
|
16
20
|
}
|
|
@@ -21,6 +25,21 @@ export class SpatializedDynamic3DElement extends SpatializedElement {
|
|
|
21
25
|
entity.parent = this
|
|
22
26
|
return ans
|
|
23
27
|
}
|
|
28
|
+
|
|
29
|
+
addEvent(type: SpatialEntityEventType, callback: (data: any) => void) {
|
|
30
|
+
this.events[type] = callback
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
removeEvent(eventName: SpatialEntityEventType) {
|
|
34
|
+
if (this.events[eventName]) {
|
|
35
|
+
delete this.events[eventName]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
dispatchEvent(evt: CustomEvent) {
|
|
40
|
+
this.events[evt.type]?.(evt)
|
|
41
|
+
}
|
|
42
|
+
|
|
24
43
|
async updateProperties(properties: Partial<SpatializedElementProperties>) {
|
|
25
44
|
return new UpdateSpatializedDynamic3DElementProperties(
|
|
26
45
|
this,
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
SpatialTapEvent,
|
|
17
17
|
} from './types/types'
|
|
18
18
|
import {
|
|
19
|
-
CubeInfoMsg,
|
|
20
19
|
ObjectDestroyMsg,
|
|
21
20
|
SpatialDragEndMsg,
|
|
22
21
|
SpatialDragMsg,
|
|
@@ -27,7 +26,6 @@ import {
|
|
|
27
26
|
SpatialRotateMsg,
|
|
28
27
|
SpatialTapMsg,
|
|
29
28
|
SpatialWebMsgType,
|
|
30
|
-
TransformMsg,
|
|
31
29
|
} from './WebMsgCommand'
|
|
32
30
|
|
|
33
31
|
/**
|
|
@@ -115,8 +113,6 @@ export abstract class SpatializedElement extends SpatialObject {
|
|
|
115
113
|
*/
|
|
116
114
|
protected onReceiveEvent(
|
|
117
115
|
data:
|
|
118
|
-
| CubeInfoMsg
|
|
119
|
-
| TransformMsg
|
|
120
116
|
| SpatialTapMsg
|
|
121
117
|
| SpatialDragStartMsg
|
|
122
118
|
| SpatialDragMsg
|
|
@@ -128,31 +124,6 @@ export abstract class SpatializedElement extends SpatialObject {
|
|
|
128
124
|
const { type } = data
|
|
129
125
|
if (type === SpatialWebMsgType.objectdestroy) {
|
|
130
126
|
this.isDestroyed = true
|
|
131
|
-
} else if (type === SpatialWebMsgType.cubeInfo) {
|
|
132
|
-
// Handle cube info updates (bounding box information)
|
|
133
|
-
const cubeInfoMsg = data as CubeInfoMsg
|
|
134
|
-
this._cubeInfo = new CubeInfo(cubeInfoMsg.size, cubeInfoMsg.origin)
|
|
135
|
-
} else if (type === SpatialWebMsgType.transform) {
|
|
136
|
-
// Handle transformation matrix updates
|
|
137
|
-
this._transform = new DOMMatrix([
|
|
138
|
-
data.detail.column0[0],
|
|
139
|
-
data.detail.column0[1],
|
|
140
|
-
data.detail.column0[2],
|
|
141
|
-
0,
|
|
142
|
-
data.detail.column1[0],
|
|
143
|
-
data.detail.column1[1],
|
|
144
|
-
data.detail.column1[2],
|
|
145
|
-
0,
|
|
146
|
-
data.detail.column2[0],
|
|
147
|
-
data.detail.column2[1],
|
|
148
|
-
data.detail.column2[2],
|
|
149
|
-
0,
|
|
150
|
-
data.detail.column3[0],
|
|
151
|
-
data.detail.column3[1],
|
|
152
|
-
data.detail.column3[2],
|
|
153
|
-
1,
|
|
154
|
-
])
|
|
155
|
-
this._transformInv = this._transform.inverse()
|
|
156
127
|
} else if (type === SpatialWebMsgType.spatialtap) {
|
|
157
128
|
// Handle tap gestures
|
|
158
129
|
const event = createSpatialEvent(
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { SpatialWebMsgType } from './WebMsgCommand'
|
|
3
|
+
import { SpatializedStatic3DElement } from './SpatializedStatic3DElement'
|
|
4
|
+
|
|
5
|
+
// Single mock for the native bridge layer — everything else runs as-is
|
|
6
|
+
vi.mock('./JSBCommand', () => {
|
|
7
|
+
class OkCommand {
|
|
8
|
+
execute() {
|
|
9
|
+
return Promise.resolve({
|
|
10
|
+
success: true,
|
|
11
|
+
data: undefined,
|
|
12
|
+
errorCode: '',
|
|
13
|
+
errorMessage: '',
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return { UpdateSpatializedStatic3DElementProperties: OkCommand }
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
describe('SpatializedStatic3DElement', () => {
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
vi.clearAllMocks()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('ready starts as a pending promise', () => {
|
|
27
|
+
const el = new SpatializedStatic3DElement('s1', 'model.glb')
|
|
28
|
+
expect(el.ready).toBeInstanceOf(Promise)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('ready resolves to true on modelloaded event', async () => {
|
|
32
|
+
const el = new SpatializedStatic3DElement('s2', 'model.glb')
|
|
33
|
+
const p = el.ready
|
|
34
|
+
|
|
35
|
+
el.onReceiveEvent({ type: SpatialWebMsgType.modelloaded })
|
|
36
|
+
await expect(p).resolves.toBe(true)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('ready resolves to false on modelloadfailed event', async () => {
|
|
40
|
+
const el = new SpatializedStatic3DElement('s3', 'model.glb')
|
|
41
|
+
const p = el.ready
|
|
42
|
+
|
|
43
|
+
el.onReceiveEvent({ type: SpatialWebMsgType.modelloadfailed })
|
|
44
|
+
await expect(p).resolves.toBe(false)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('fires onLoadCallback on modelloaded', () => {
|
|
48
|
+
const el = new SpatializedStatic3DElement('s4', 'model.glb')
|
|
49
|
+
const cb = vi.fn()
|
|
50
|
+
el.onLoadCallback = cb
|
|
51
|
+
|
|
52
|
+
el.onReceiveEvent({ type: SpatialWebMsgType.modelloaded })
|
|
53
|
+
expect(cb).toHaveBeenCalledTimes(1)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('fires onLoadFailureCallback on modelloadfailed', () => {
|
|
57
|
+
const el = new SpatializedStatic3DElement('s5', 'model.glb')
|
|
58
|
+
const cb = vi.fn()
|
|
59
|
+
el.onLoadFailureCallback = cb
|
|
60
|
+
|
|
61
|
+
el.onReceiveEvent({ type: SpatialWebMsgType.modelloadfailed })
|
|
62
|
+
expect(cb).toHaveBeenCalledTimes(1)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('does not fire callbacks when they are not set', () => {
|
|
66
|
+
const el = new SpatializedStatic3DElement('s6', 'model.glb')
|
|
67
|
+
// Should not throw when no callbacks are registered
|
|
68
|
+
expect(() =>
|
|
69
|
+
el.onReceiveEvent({ type: SpatialWebMsgType.modelloaded }),
|
|
70
|
+
).not.toThrow()
|
|
71
|
+
expect(() =>
|
|
72
|
+
el.onReceiveEvent({ type: SpatialWebMsgType.modelloadfailed }),
|
|
73
|
+
).not.toThrow()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('resets ready when modelURL changes', async () => {
|
|
77
|
+
const el = new SpatializedStatic3DElement('s7', 'a.glb')
|
|
78
|
+
const first = el.ready
|
|
79
|
+
|
|
80
|
+
await el.updateProperties({ modelURL: 'b.glb' })
|
|
81
|
+
expect(el.ready).not.toBe(first)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('does not reset ready when modelURL stays the same', async () => {
|
|
85
|
+
const el = new SpatializedStatic3DElement('s8', 'a.glb')
|
|
86
|
+
const first = el.ready
|
|
87
|
+
|
|
88
|
+
await el.updateProperties({ modelURL: 'a.glb' })
|
|
89
|
+
expect(el.ready).toBe(first)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('cancels old ready promise with false when modelURL changes', async () => {
|
|
93
|
+
const el = new SpatializedStatic3DElement('s9', 'a.glb')
|
|
94
|
+
const first = el.ready
|
|
95
|
+
|
|
96
|
+
await el.updateProperties({ modelURL: 'b.glb' })
|
|
97
|
+
await expect(first).resolves.toBe(false)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('new ready promise works after URL change', async () => {
|
|
101
|
+
const el = new SpatializedStatic3DElement('s10', 'a.glb')
|
|
102
|
+
|
|
103
|
+
await el.updateProperties({ modelURL: 'b.glb' })
|
|
104
|
+
const second = el.ready
|
|
105
|
+
|
|
106
|
+
el.onReceiveEvent({ type: SpatialWebMsgType.modelloaded })
|
|
107
|
+
await expect(second).resolves.toBe(true)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('handles multiple URL changes in sequence', async () => {
|
|
111
|
+
const el = new SpatializedStatic3DElement('s11', 'a.glb')
|
|
112
|
+
const p1 = el.ready
|
|
113
|
+
|
|
114
|
+
await el.updateProperties({ modelURL: 'b.glb' })
|
|
115
|
+
await expect(p1).resolves.toBe(false)
|
|
116
|
+
|
|
117
|
+
const p2 = el.ready
|
|
118
|
+
await el.updateProperties({ modelURL: 'c.glb' })
|
|
119
|
+
await expect(p2).resolves.toBe(false)
|
|
120
|
+
|
|
121
|
+
const p3 = el.ready
|
|
122
|
+
el.onReceiveEvent({ type: SpatialWebMsgType.modelloaded })
|
|
123
|
+
await expect(p3).resolves.toBe(true)
|
|
124
|
+
})
|
|
125
|
+
})
|
|
@@ -37,6 +37,8 @@ export class SpatializedStatic3DElement extends SpatializedElement {
|
|
|
37
37
|
* @returns Promise that resolves when the model is loaded (true) or fails to load (false)
|
|
38
38
|
*/
|
|
39
39
|
private createReadyPromise() {
|
|
40
|
+
// If there's an existing promise reject it before it's replaced
|
|
41
|
+
this._readyResolve?.(false)
|
|
40
42
|
return new Promise<boolean>(resolve => {
|
|
41
43
|
this._readyResolve = resolve
|
|
42
44
|
})
|
package/src/WebMsgCommand.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
Vec3,
|
|
3
|
-
Size3D,
|
|
4
2
|
SpatialDragEventDetail,
|
|
5
3
|
SpatialTapEventDetail,
|
|
6
4
|
SpatialRotateEventDetail,
|
|
@@ -10,8 +8,6 @@ import {
|
|
|
10
8
|
} from './types/types'
|
|
11
9
|
|
|
12
10
|
export enum SpatialWebMsgType {
|
|
13
|
-
cubeInfo = 'cubeInfo',
|
|
14
|
-
transform = 'transform',
|
|
15
11
|
modelloaded = 'modelloaded',
|
|
16
12
|
modelloadfailed = 'modelloadfailed',
|
|
17
13
|
spatialtap = 'spatialtap',
|
|
@@ -30,28 +26,6 @@ export interface ObjectDestroyMsg {
|
|
|
30
26
|
type: SpatialWebMsgType.objectdestroy
|
|
31
27
|
}
|
|
32
28
|
|
|
33
|
-
export interface CubeInfoMsg {
|
|
34
|
-
type: SpatialWebMsgType.cubeInfo
|
|
35
|
-
origin: Vec3
|
|
36
|
-
size: Size3D
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface CubeInfoMsg {
|
|
40
|
-
type: SpatialWebMsgType.cubeInfo
|
|
41
|
-
origin: Vec3
|
|
42
|
-
size: Size3D
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface TransformMsg {
|
|
46
|
-
type: SpatialWebMsgType.transform
|
|
47
|
-
detail: {
|
|
48
|
-
column0: [number, number, number]
|
|
49
|
-
column1: [number, number, number]
|
|
50
|
-
column2: [number, number, number]
|
|
51
|
-
column3: [number, number, number]
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
29
|
export interface SpatialTapMsg {
|
|
56
30
|
type: SpatialWebMsgType.spatialtap
|
|
57
31
|
detail: SpatialTapEventDetail
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,8 @@ export { SpatializedElement } from './SpatializedElement'
|
|
|
6
6
|
export { Spatialized2DElement } from './Spatialized2DElement'
|
|
7
7
|
export { SpatializedStatic3DElement } from './SpatializedStatic3DElement'
|
|
8
8
|
export { SpatializedDynamic3DElement } from './SpatializedDynamic3DElement'
|
|
9
|
+
export * as PhysicalMetrics from './physicalMetrics'
|
|
10
|
+
|
|
9
11
|
export * from './reality'
|
|
10
12
|
export * from './types/types'
|
|
11
13
|
export * from './types/global.d'
|
|
@@ -434,49 +434,6 @@ describe('SpatializedElement', () => {
|
|
|
434
434
|
})
|
|
435
435
|
})
|
|
436
436
|
|
|
437
|
-
it('handles transform and cubeInfo events and updates internal state', async () => {
|
|
438
|
-
const { SpatialWebEvent } = await import('./SpatialWebEvent')
|
|
439
|
-
const { SpatialWebMsgType } = await import('./WebMsgCommand')
|
|
440
|
-
const { SpatializedElement } = await import('./SpatializedElement')
|
|
441
|
-
|
|
442
|
-
SpatialWebEvent.init()
|
|
443
|
-
|
|
444
|
-
class TestElement extends SpatializedElement {
|
|
445
|
-
updateProperties = vi.fn().mockResolvedValue({
|
|
446
|
-
success: true,
|
|
447
|
-
data: undefined,
|
|
448
|
-
errorCode: '',
|
|
449
|
-
errorMessage: '',
|
|
450
|
-
})
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
const e = new TestElement('el2')
|
|
454
|
-
window.__SpatialWebEvent({
|
|
455
|
-
id: 'el2',
|
|
456
|
-
data: {
|
|
457
|
-
type: SpatialWebMsgType.cubeInfo,
|
|
458
|
-
size: { width: 1, height: 2, depth: 3 },
|
|
459
|
-
origin: { x: 4, y: 5, z: 6 },
|
|
460
|
-
},
|
|
461
|
-
})
|
|
462
|
-
expect(e.cubeInfo?.front).toBe(9)
|
|
463
|
-
|
|
464
|
-
window.__SpatialWebEvent({
|
|
465
|
-
id: 'el2',
|
|
466
|
-
data: {
|
|
467
|
-
type: SpatialWebMsgType.transform,
|
|
468
|
-
detail: {
|
|
469
|
-
column0: [1, 0, 0],
|
|
470
|
-
column1: [0, 1, 0],
|
|
471
|
-
column2: [0, 0, 1],
|
|
472
|
-
column3: [10, 20, 30],
|
|
473
|
-
},
|
|
474
|
-
},
|
|
475
|
-
})
|
|
476
|
-
expect(e.transform).toBeDefined()
|
|
477
|
-
expect(e.transformInv).toBeDefined()
|
|
478
|
-
})
|
|
479
|
-
|
|
480
437
|
it('updates flags via gesture handler setters and updates transform via JSB', async () => {
|
|
481
438
|
const { SpatialWebEvent } = await import('./SpatialWebEvent')
|
|
482
439
|
const { SpatializedElement } = await import('./SpatializedElement')
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
async function loadModule() {
|
|
4
|
+
vi.resetModules()
|
|
5
|
+
return await import('./physicalMetrics')
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
describe('physicalMetrics', () => {
|
|
9
|
+
test('default scaled conversion', async () => {
|
|
10
|
+
const { pointToPhysical, physicalToPoint, getValue } = await loadModule()
|
|
11
|
+
const v = getValue()
|
|
12
|
+
expect(v.meterToPtScaled).toBe(1360)
|
|
13
|
+
expect(v.meterToPtUnscaled).toBe(1360)
|
|
14
|
+
expect(pointToPhysical(1360)).toBe(1)
|
|
15
|
+
expect(physicalToPoint(1)).toBe(1360)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('unscaled compensation uses unscaled metrics', async () => {
|
|
19
|
+
const { pointToPhysical, physicalToPoint } = await loadModule()
|
|
20
|
+
expect(
|
|
21
|
+
pointToPhysical(1360, { worldScalingCompensation: 'unscaled' }),
|
|
22
|
+
).toBe(1)
|
|
23
|
+
expect(physicalToPoint(1, { worldScalingCompensation: 'unscaled' })).toBe(
|
|
24
|
+
1360,
|
|
25
|
+
)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('updateValue applies window metrics', async () => {
|
|
29
|
+
const m = await loadModule()
|
|
30
|
+
const { SpatialWebEvent } = await import('./SpatialWebEvent')
|
|
31
|
+
SpatialWebEvent.init()
|
|
32
|
+
;(window as any).__webspatialsdk__ = {
|
|
33
|
+
physicalMetrics: {
|
|
34
|
+
meterToPtScaled: 2000,
|
|
35
|
+
meterToPtUnscaled: 1500,
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
const unsubscribe = m.subscribe(() => {})
|
|
39
|
+
window.__SpatialWebEvent({ id: 'window', data: {} })
|
|
40
|
+
const v = m.getValue()
|
|
41
|
+
expect(v.meterToPtScaled).toBe(2000)
|
|
42
|
+
expect(v.meterToPtUnscaled).toBe(1500)
|
|
43
|
+
expect(m.pointToPhysical(2000)).toBe(1)
|
|
44
|
+
expect(
|
|
45
|
+
m.pointToPhysical(1500, { worldScalingCompensation: 'unscaled' }),
|
|
46
|
+
).toBe(1)
|
|
47
|
+
unsubscribe()
|
|
48
|
+
;(window as any).__webspatialsdk__ = undefined
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('updateValue applies partial metrics', async () => {
|
|
52
|
+
const m = await loadModule()
|
|
53
|
+
const { SpatialWebEvent } = await import('./SpatialWebEvent')
|
|
54
|
+
SpatialWebEvent.init()
|
|
55
|
+
;(window as any).__webspatialsdk__ = {
|
|
56
|
+
physicalMetrics: { meterToPtScaled: 1000 },
|
|
57
|
+
}
|
|
58
|
+
const unsubscribe = m.subscribe(() => {})
|
|
59
|
+
window.__SpatialWebEvent({ id: 'window', data: {} })
|
|
60
|
+
expect(m.getValue().meterToPtScaled).toBe(1000)
|
|
61
|
+
expect(m.getValue().meterToPtUnscaled).toBe(1360)
|
|
62
|
+
expect(m.pointToPhysical(1000)).toBe(1)
|
|
63
|
+
expect(m.physicalToPoint(1, { worldScalingCompensation: 'unscaled' })).toBe(
|
|
64
|
+
1360,
|
|
65
|
+
)
|
|
66
|
+
unsubscribe()
|
|
67
|
+
;(window as any).__webspatialsdk__ = undefined
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('subscribe listens to event and supports unsubscribe', async () => {
|
|
71
|
+
const m = await loadModule()
|
|
72
|
+
const { SpatialWebEvent } = await import('./SpatialWebEvent')
|
|
73
|
+
SpatialWebEvent.init()
|
|
74
|
+
const cb = vi.fn()
|
|
75
|
+
const unsubscribe = m.subscribe(cb)
|
|
76
|
+
;(window as any).__webspatialsdk__ = {
|
|
77
|
+
physicalMetrics: {
|
|
78
|
+
meterToPtScaled: 900,
|
|
79
|
+
meterToPtUnscaled: 800,
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
window.__SpatialWebEvent({ id: 'window', data: {} })
|
|
83
|
+
expect(cb).toHaveBeenCalledTimes(1)
|
|
84
|
+
expect(m.getValue().meterToPtScaled).toBe(900)
|
|
85
|
+
expect(m.getValue().meterToPtUnscaled).toBe(800)
|
|
86
|
+
;(window as any).__webspatialsdk__ = {
|
|
87
|
+
physicalMetrics: {
|
|
88
|
+
meterToPtScaled: 700,
|
|
89
|
+
meterToPtUnscaled: 600,
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
window.__SpatialWebEvent({ id: 'window', data: {} })
|
|
93
|
+
expect(cb).toHaveBeenCalledTimes(2)
|
|
94
|
+
expect(m.getValue().meterToPtScaled).toBe(700)
|
|
95
|
+
expect(m.getValue().meterToPtUnscaled).toBe(600)
|
|
96
|
+
|
|
97
|
+
unsubscribe()
|
|
98
|
+
;(window as any).__webspatialsdk__ = {
|
|
99
|
+
physicalMetrics: {
|
|
100
|
+
meterToPtScaled: 500,
|
|
101
|
+
meterToPtUnscaled: 400,
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
window.__SpatialWebEvent({ id: 'window', data: {} })
|
|
105
|
+
expect(cb).toHaveBeenCalledTimes(2)
|
|
106
|
+
expect(m.getValue().meterToPtScaled).toBe(500)
|
|
107
|
+
expect(m.getValue().meterToPtUnscaled).toBe(400)
|
|
108
|
+
;(window as any).__webspatialsdk__ = undefined
|
|
109
|
+
})
|
|
110
|
+
})
|