@found-in-space/skykit 0.2.0-alpha.1 → 0.2.0-alpha.20260528
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/README.md +142 -11
- package/examples/custom-object-layer/custom-object-layer.js +1 -24
- package/examples/xr-free-roam/index.html +62 -4
- package/examples/xr-free-roam/xr-free-roam.css +249 -18
- package/examples/xr-free-roam/xr-free-roam.js +675 -274
- package/package.json +22 -6
- package/src/__tests__/skykit-anchored-images.test.js +32 -4
- package/src/__tests__/skykit-browser.test.js +267 -0
- package/src/__tests__/skykit-data.test.js +131 -0
- package/src/__tests__/skykit-parallax.test.js +4 -4
- package/src/__tests__/skykit-touch-os.test.js +71 -0
- package/src/__tests__/skykit-xr.test.js +179 -2
- package/src/__tests__/skykit.test.js +142 -506
- package/src/actions.js +0 -8
- package/src/anchored-images.js +14 -15
- package/src/browser-addons.d.ts +16 -0
- package/src/browser-addons.js +155 -0
- package/src/browser-constellations.d.ts +13 -0
- package/src/browser-constellations.js +387 -0
- package/src/browser.d.ts +81 -0
- package/src/browser.js +192 -13
- package/src/data.d.ts +133 -0
- package/src/data.js +447 -0
- package/src/embed.d.ts +5 -0
- package/src/embed.js +53 -2
- package/src/hr-diagram.js +23 -5
- package/src/index.d.ts +21 -73
- package/src/index.js +0 -1
- package/src/plugins.js +22 -708
- package/src/three-shim.d.ts +32 -0
- package/src/touch-os.d.ts +70 -0
- package/src/touch-os.js +275 -0
- package/src/utils.js +96 -6
- package/src/viewer-entry.d.ts +10 -0
- package/src/viewer-entry.js +4 -0
- package/src/viewer.js +110 -12
- package/src/xr/plugins.js +298 -13
- package/src/xr/session.js +60 -14
- package/src/xr.d.ts +40 -0
- package/src/xr.js +2 -0
package/src/three-shim.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ declare module 'three' {
|
|
|
17
17
|
updateMatrixWorld(force?: boolean): void;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export type ColorRepresentation = number | string;
|
|
21
|
+
|
|
20
22
|
export class Group extends Object3D {}
|
|
21
23
|
export class Scene extends Object3D {}
|
|
22
24
|
export class Camera extends Object3D {
|
|
@@ -105,10 +107,40 @@ declare module 'three' {
|
|
|
105
107
|
|
|
106
108
|
export class ShaderMaterial extends Material {}
|
|
107
109
|
|
|
110
|
+
export class LineBasicMaterial extends Material {
|
|
111
|
+
constructor(parameters?: {
|
|
112
|
+
color?: ColorRepresentation;
|
|
113
|
+
transparent?: boolean;
|
|
114
|
+
opacity?: number;
|
|
115
|
+
depthTest?: boolean;
|
|
116
|
+
depthWrite?: boolean;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export class BufferAttribute {
|
|
121
|
+
array: ArrayLike<number>;
|
|
122
|
+
itemSize: number;
|
|
123
|
+
needsUpdate: boolean;
|
|
124
|
+
count: number;
|
|
125
|
+
constructor(array: ArrayLike<number>, itemSize: number, normalized?: boolean);
|
|
126
|
+
}
|
|
127
|
+
|
|
108
128
|
export class BufferGeometry {
|
|
129
|
+
attributes: Record<string, BufferAttribute>;
|
|
130
|
+
setAttribute(name: string, attribute: BufferAttribute): this;
|
|
131
|
+
getAttribute(name: string): BufferAttribute | undefined;
|
|
132
|
+
computeBoundingSphere(): void;
|
|
109
133
|
dispose(): void;
|
|
110
134
|
}
|
|
111
135
|
|
|
136
|
+
export class Line extends Object3D {
|
|
137
|
+
geometry: BufferGeometry;
|
|
138
|
+
material: Material | Material[];
|
|
139
|
+
frustumCulled: boolean;
|
|
140
|
+
renderOrder: number;
|
|
141
|
+
constructor(geometry?: BufferGeometry, material?: Material | Material[]);
|
|
142
|
+
}
|
|
143
|
+
|
|
112
144
|
export class Mesh extends Object3D {
|
|
113
145
|
readonly isMesh: boolean;
|
|
114
146
|
geometry: BufferGeometry;
|
package/src/touch-os.d.ts
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
AppShellChange,
|
|
3
|
+
AppShellPresentation,
|
|
4
|
+
AppShellSession,
|
|
5
|
+
AppShellSessionSeed,
|
|
2
6
|
DisplayNode,
|
|
3
7
|
DisplayRuntime,
|
|
8
|
+
EmbeddedSurfaceService,
|
|
4
9
|
RuntimeOutput,
|
|
5
10
|
RuntimeOptions,
|
|
6
11
|
SurfaceMetrics,
|
|
12
|
+
TabletHomeLauncherLayoutOptions,
|
|
13
|
+
TouchAppCapability,
|
|
14
|
+
TouchAppContext,
|
|
15
|
+
TouchAppEvent,
|
|
16
|
+
TouchAppModule,
|
|
17
|
+
TouchAppPreferredWindow,
|
|
18
|
+
TouchAppRegistry,
|
|
19
|
+
TouchAppStorage,
|
|
20
|
+
TouchIconDescriptor,
|
|
21
|
+
WindowManagerAppHostMode,
|
|
7
22
|
} from '@found-in-space/touch-os';
|
|
8
23
|
import type {
|
|
9
24
|
HudPanelDriverOptions,
|
|
@@ -97,6 +112,55 @@ export interface TouchOsHudPlugin extends SkykitPlugin {
|
|
|
97
112
|
|
|
98
113
|
export type TouchOsPanelDriverKind = 'hud' | 'pose-anchored' | 'scene';
|
|
99
114
|
|
|
115
|
+
export interface SkykitTabletRootOptions {
|
|
116
|
+
id?: string;
|
|
117
|
+
apps?: readonly TouchAppModule<unknown>[];
|
|
118
|
+
registry?: TouchAppRegistry;
|
|
119
|
+
presentation?: AppShellPresentation;
|
|
120
|
+
appHostMode?: WindowManagerAppHostMode;
|
|
121
|
+
homeKey?: boolean;
|
|
122
|
+
keepAlive?: boolean;
|
|
123
|
+
initialSessions?: readonly AppShellSessionSeed[];
|
|
124
|
+
appStates?: Readonly<Record<string, unknown>>;
|
|
125
|
+
getAppState?: (session: AppShellSession) => unknown;
|
|
126
|
+
forwardAppOutputs?: boolean;
|
|
127
|
+
storage?: TouchAppStorage;
|
|
128
|
+
surfaces?: EmbeddedSurfaceService;
|
|
129
|
+
onAppEvent?: (event: TouchAppEvent) => void;
|
|
130
|
+
onShellChange?: (change: AppShellChange) => void;
|
|
131
|
+
homeControl?: 'button' | 'bar' | 'none';
|
|
132
|
+
taskSwitcher?: 'cards' | 'list' | 'none';
|
|
133
|
+
taskCloseControl?: 'button' | 'none';
|
|
134
|
+
launcherLayout?: TabletHomeLauncherLayoutOptions;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface SkykitSurfaceAppRenderContext<TState = unknown> {
|
|
138
|
+
context: TouchAppContext;
|
|
139
|
+
state: TState;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface SkykitSurfaceAppOutputContext {
|
|
143
|
+
context: TouchAppContext;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface SkykitSurfaceAppOptions<TState = unknown> {
|
|
147
|
+
id: string;
|
|
148
|
+
name: string;
|
|
149
|
+
version?: string;
|
|
150
|
+
icon?: TouchIconDescriptor;
|
|
151
|
+
capabilities?: readonly TouchAppCapability[];
|
|
152
|
+
preferredWindow?: TouchAppPreferredWindow;
|
|
153
|
+
rootId?: string;
|
|
154
|
+
node:
|
|
155
|
+
| DisplayNode
|
|
156
|
+
| ((context: SkykitSurfaceAppRenderContext<TState>) => DisplayNode | null | undefined);
|
|
157
|
+
padding?: number;
|
|
158
|
+
pointerOpaque?: boolean;
|
|
159
|
+
backgroundColor?: string;
|
|
160
|
+
emptyLabel?: string;
|
|
161
|
+
onOutput?: (output: RuntimeOutput, context: SkykitSurfaceAppOutputContext) => void;
|
|
162
|
+
}
|
|
163
|
+
|
|
100
164
|
export interface TouchOsPanelRootContext {
|
|
101
165
|
id: string;
|
|
102
166
|
context: SkykitPluginContext;
|
|
@@ -212,6 +276,12 @@ export interface CreateTouchOsHostFrameOptions {
|
|
|
212
276
|
events?: readonly ThreePanelHostInputEvent[];
|
|
213
277
|
}
|
|
214
278
|
|
|
279
|
+
export declare function createSkykitTabletRoot(options?: SkykitTabletRootOptions): DisplayNode;
|
|
280
|
+
|
|
281
|
+
export declare function createSkykitSurfaceApp<TState = unknown>(
|
|
282
|
+
options: SkykitSurfaceAppOptions<TState>
|
|
283
|
+
): TouchAppModule<TState>;
|
|
284
|
+
|
|
215
285
|
export declare function createTouchOsHudPlugin(options: TouchOsHudPluginOptions): TouchOsHudPlugin;
|
|
216
286
|
export declare function createTouchOsPanelPlugin(options: TouchOsPanelPluginOptions): TouchOsPanelPlugin;
|
|
217
287
|
|
package/src/touch-os.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
|
+
createAppShell,
|
|
2
3
|
createButton,
|
|
3
4
|
createColumn,
|
|
4
5
|
createDPad,
|
|
5
6
|
createDockLayout,
|
|
6
7
|
createHoldButton,
|
|
8
|
+
createNode,
|
|
9
|
+
createRect,
|
|
7
10
|
createRuntime,
|
|
11
|
+
createTabletHomePresentation,
|
|
8
12
|
createTextLabel,
|
|
13
|
+
createTouchAppRegistry,
|
|
9
14
|
createValueReadout,
|
|
15
|
+
defineTouchApp,
|
|
16
|
+
rectContainsPoint,
|
|
10
17
|
} from '@found-in-space/touch-os';
|
|
11
18
|
import {
|
|
12
19
|
createHudPanelDriver,
|
|
@@ -31,6 +38,185 @@ const DEFAULT_PANEL_DRIVER_OPTIONS = Object.freeze({
|
|
|
31
38
|
pointerClaimPolicy: 'block-on-hit',
|
|
32
39
|
transparent: true,
|
|
33
40
|
});
|
|
41
|
+
const DEFAULT_TABLET_LAUNCHER_LAYOUT = Object.freeze({
|
|
42
|
+
tileWidth: 84,
|
|
43
|
+
tileHeight: 88,
|
|
44
|
+
gap: 10,
|
|
45
|
+
bodyPadding: 10,
|
|
46
|
+
iconMinSize: 38,
|
|
47
|
+
iconMaxSize: 46,
|
|
48
|
+
iconScale: 0.55,
|
|
49
|
+
iconTop: 2,
|
|
50
|
+
labelGap: 5,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const SkykitSurfaceAppFrameComponent = {
|
|
54
|
+
kind: 'skykit-surface-app-frame',
|
|
55
|
+
getChildren(ctx) {
|
|
56
|
+
return ctx.props.child ? [ctx.props.child] : [];
|
|
57
|
+
},
|
|
58
|
+
measure(ctx) {
|
|
59
|
+
const padding = positiveFinite(ctx.props.padding, 0);
|
|
60
|
+
const width = Math.max(0, ctx.constraints.maxWidth - padding * 2);
|
|
61
|
+
const height = Math.max(0, ctx.constraints.maxHeight - padding * 2);
|
|
62
|
+
if (ctx.props.child) {
|
|
63
|
+
ctx.measureChild(ctx.props.child.id, {
|
|
64
|
+
minWidth: 0,
|
|
65
|
+
minHeight: 0,
|
|
66
|
+
maxWidth: width,
|
|
67
|
+
maxHeight: height,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
width: ctx.constraints.maxWidth,
|
|
72
|
+
height: ctx.constraints.maxHeight,
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
layout(ctx) {
|
|
76
|
+
const padding = positiveFinite(ctx.props.padding, 0);
|
|
77
|
+
const content = createRect(
|
|
78
|
+
ctx.bounds.x + padding,
|
|
79
|
+
ctx.bounds.y + padding,
|
|
80
|
+
Math.max(0, ctx.bounds.width - padding * 2),
|
|
81
|
+
Math.max(0, ctx.bounds.height - padding * 2),
|
|
82
|
+
);
|
|
83
|
+
if (ctx.props.child) {
|
|
84
|
+
ctx.setChildBounds(ctx.props.child.id, content);
|
|
85
|
+
}
|
|
86
|
+
ctx.setContentBounds(content);
|
|
87
|
+
},
|
|
88
|
+
render(ctx) {
|
|
89
|
+
const theme = ctx.services.theme.getTokens();
|
|
90
|
+
return [{
|
|
91
|
+
type: 'rect',
|
|
92
|
+
componentId: ctx.id,
|
|
93
|
+
role: 'skykit-surface-app-frame',
|
|
94
|
+
rect: ctx.bounds,
|
|
95
|
+
fill: ctx.props.backgroundColor ?? theme.backgroundColor,
|
|
96
|
+
strokeWidth: 0,
|
|
97
|
+
radius: 0,
|
|
98
|
+
}];
|
|
99
|
+
},
|
|
100
|
+
hitTest(ctx) {
|
|
101
|
+
if (ctx.props.pointerOpaque === false || !rectContainsPoint(ctx.bounds, ctx.point)) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
targetId: `${ctx.id}:background`,
|
|
106
|
+
role: 'surface-app-background',
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Build a SkyKit-flavored touch-os tablet shell from ordinary touch apps.
|
|
113
|
+
*
|
|
114
|
+
* @param {import('./touch-os.d.ts').SkykitTabletRootOptions} [options]
|
|
115
|
+
* @returns {import('@found-in-space/touch-os').DisplayNode}
|
|
116
|
+
*/
|
|
117
|
+
export function createSkykitTabletRoot(options = {}) {
|
|
118
|
+
if (!options || typeof options !== 'object') {
|
|
119
|
+
throw new TypeError('createSkykitTabletRoot requires an options object.');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const id = options.id ?? 'skykit-tablet';
|
|
123
|
+
const registry = options.registry ?? createTouchAppRegistry(options.apps ?? []);
|
|
124
|
+
const presentation = options.presentation ?? createTabletHomePresentation({
|
|
125
|
+
homeControl: options.homeControl ?? 'button',
|
|
126
|
+
taskSwitcher: options.taskSwitcher ?? 'cards',
|
|
127
|
+
taskCloseControl: options.taskCloseControl ?? 'button',
|
|
128
|
+
launcherLayout: {
|
|
129
|
+
...DEFAULT_TABLET_LAUNCHER_LAYOUT,
|
|
130
|
+
...(options.launcherLayout ?? {}),
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return createAppShell(id, {
|
|
135
|
+
registry,
|
|
136
|
+
presentation,
|
|
137
|
+
appHostMode: options.appHostMode ?? 'same-runtime',
|
|
138
|
+
homeKey: options.homeKey ?? true,
|
|
139
|
+
keepAlive: options.keepAlive ?? true,
|
|
140
|
+
...(options.initialSessions === undefined ? {} : { initialSessions: options.initialSessions }),
|
|
141
|
+
...(options.appStates === undefined ? {} : { appStates: options.appStates }),
|
|
142
|
+
...(options.getAppState === undefined ? {} : { getAppState: options.getAppState }),
|
|
143
|
+
...(options.forwardAppOutputs === undefined ? {} : { forwardAppOutputs: options.forwardAppOutputs }),
|
|
144
|
+
...(options.storage === undefined ? {} : { storage: options.storage }),
|
|
145
|
+
...(options.surfaces === undefined ? {} : { surfaces: options.surfaces }),
|
|
146
|
+
...(options.onAppEvent === undefined ? {} : { onAppEvent: options.onAppEvent }),
|
|
147
|
+
...(options.onShellChange === undefined ? {} : { onShellChange: options.onShellChange }),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Wrap a display node as a full-screen tablet app. This is useful for surface
|
|
153
|
+
* consumers such as HR diagrams, camera mirrors, or other panel-hosted views.
|
|
154
|
+
*
|
|
155
|
+
* @template TState
|
|
156
|
+
* @param {import('./touch-os.d.ts').SkykitSurfaceAppOptions<TState>} options
|
|
157
|
+
* @returns {import('@found-in-space/touch-os').TouchAppModule<TState>}
|
|
158
|
+
*/
|
|
159
|
+
export function createSkykitSurfaceApp(options) {
|
|
160
|
+
if (!options || typeof options !== 'object') {
|
|
161
|
+
throw new TypeError('createSkykitSurfaceApp requires options.');
|
|
162
|
+
}
|
|
163
|
+
const id = requiredString(options.id, 'createSkykitSurfaceApp id');
|
|
164
|
+
const name = requiredString(options.name, 'createSkykitSurfaceApp name');
|
|
165
|
+
const rootId = options.rootId ?? `${id}:root`;
|
|
166
|
+
|
|
167
|
+
return defineTouchApp({
|
|
168
|
+
manifest: {
|
|
169
|
+
id,
|
|
170
|
+
name,
|
|
171
|
+
version: options.version ?? '1.0.0',
|
|
172
|
+
icon: options.icon ?? createSymbolIcon(name),
|
|
173
|
+
capabilities: options.capabilities ?? ['surfaces'],
|
|
174
|
+
preferredWindow: {
|
|
175
|
+
width: options.preferredWindow?.width ?? 360,
|
|
176
|
+
height: options.preferredWindow?.height ?? 300,
|
|
177
|
+
minWidth: options.preferredWindow?.minWidth ?? 260,
|
|
178
|
+
minHeight: options.preferredWindow?.minHeight ?? 180,
|
|
179
|
+
resizable: options.preferredWindow?.resizable ?? false,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
createApp(ctx) {
|
|
183
|
+
return {
|
|
184
|
+
render(state) {
|
|
185
|
+
const child = resolveSurfaceAppNode(options.node, {
|
|
186
|
+
context: ctx,
|
|
187
|
+
state,
|
|
188
|
+
});
|
|
189
|
+
if (!child) {
|
|
190
|
+
return createTextLabel(`${rootId}:empty`, {
|
|
191
|
+
text: options.emptyLabel ?? `${name} unavailable`,
|
|
192
|
+
tone: 'muted',
|
|
193
|
+
align: 'center',
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return createSkykitSurfaceAppFrame(rootId, {
|
|
197
|
+
child,
|
|
198
|
+
padding: options.padding ?? 0,
|
|
199
|
+
pointerOpaque: options.pointerOpaque !== false,
|
|
200
|
+
backgroundColor: options.backgroundColor,
|
|
201
|
+
});
|
|
202
|
+
},
|
|
203
|
+
handleOutput(output) {
|
|
204
|
+
options.onOutput?.(output, { context: ctx });
|
|
205
|
+
emitSkykitSurfaceAppOutput(ctx, output);
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* @param {string} id
|
|
214
|
+
* @param {{ child: import('@found-in-space/touch-os').DisplayNode; padding?: number; pointerOpaque?: boolean; backgroundColor?: string }} props
|
|
215
|
+
* @returns {import('@found-in-space/touch-os').DisplayNode}
|
|
216
|
+
*/
|
|
217
|
+
function createSkykitSurfaceAppFrame(id, props) {
|
|
218
|
+
return createNode(id, SkykitSurfaceAppFrameComponent, props);
|
|
219
|
+
}
|
|
34
220
|
|
|
35
221
|
/**
|
|
36
222
|
* Create a SkyKit plugin that mounts a touch-os HUD and routes action outputs
|
|
@@ -901,6 +1087,95 @@ function finiteNumber(value, fallback) {
|
|
|
901
1087
|
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
902
1088
|
}
|
|
903
1089
|
|
|
1090
|
+
/**
|
|
1091
|
+
* @param {unknown} value
|
|
1092
|
+
* @param {string} context
|
|
1093
|
+
* @returns {string}
|
|
1094
|
+
*/
|
|
1095
|
+
function requiredString(value, context) {
|
|
1096
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
1097
|
+
throw new TypeError(`${context} must be a non-empty string.`);
|
|
1098
|
+
}
|
|
1099
|
+
return value;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
/**
|
|
1103
|
+
* @param {import('./touch-os.d.ts').SkykitSurfaceAppOptions['node']} node
|
|
1104
|
+
* @param {import('./touch-os.d.ts').SkykitSurfaceAppRenderContext} context
|
|
1105
|
+
* @returns {import('@found-in-space/touch-os').DisplayNode | null}
|
|
1106
|
+
*/
|
|
1107
|
+
function resolveSurfaceAppNode(node, context) {
|
|
1108
|
+
const resolved = typeof node === 'function' ? node(context) : node;
|
|
1109
|
+
if (resolved == null) return null;
|
|
1110
|
+
if (!resolved || typeof resolved !== 'object' || typeof resolved.id !== 'string') {
|
|
1111
|
+
throw new TypeError('SkyKit surface app nodes must be display nodes.');
|
|
1112
|
+
}
|
|
1113
|
+
return resolved;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* @param {import('@found-in-space/touch-os').TouchAppContext} context
|
|
1118
|
+
* @param {unknown} output
|
|
1119
|
+
*/
|
|
1120
|
+
function emitSkykitSurfaceAppOutput(context, output) {
|
|
1121
|
+
if (isTouchOsActionOutput(output)) {
|
|
1122
|
+
context.actions.emit({
|
|
1123
|
+
type: 'app-action',
|
|
1124
|
+
appId: context.appId,
|
|
1125
|
+
instanceId: context.instanceId,
|
|
1126
|
+
windowId: context.windowId,
|
|
1127
|
+
name: output.actionId,
|
|
1128
|
+
...(output.payload === undefined ? {} : { payload: output.payload }),
|
|
1129
|
+
componentId: output.componentId,
|
|
1130
|
+
});
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
if (isTouchOsChangeOutput(output)) {
|
|
1134
|
+
context.actions.emit({
|
|
1135
|
+
type: 'app-change',
|
|
1136
|
+
appId: context.appId,
|
|
1137
|
+
instanceId: context.instanceId,
|
|
1138
|
+
windowId: context.windowId,
|
|
1139
|
+
name: `${output.field}.change`,
|
|
1140
|
+
payload: {
|
|
1141
|
+
field: output.field,
|
|
1142
|
+
value: output.value,
|
|
1143
|
+
},
|
|
1144
|
+
componentId: output.componentId,
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
/**
|
|
1150
|
+
* @param {unknown} output
|
|
1151
|
+
* @returns {output is { type: 'change-request'; componentId: string; field: string; value: unknown }}
|
|
1152
|
+
*/
|
|
1153
|
+
function isTouchOsChangeOutput(output) {
|
|
1154
|
+
return Boolean(output)
|
|
1155
|
+
&& typeof output === 'object'
|
|
1156
|
+
&& output.type === 'change-request'
|
|
1157
|
+
&& typeof output.componentId === 'string'
|
|
1158
|
+
&& typeof output.field === 'string';
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
/**
|
|
1162
|
+
* @param {string} name
|
|
1163
|
+
* @returns {{ kind: 'symbol'; value: string }}
|
|
1164
|
+
*/
|
|
1165
|
+
function createSymbolIcon(name) {
|
|
1166
|
+
const letters = name
|
|
1167
|
+
.split(/\s+/)
|
|
1168
|
+
.filter(Boolean)
|
|
1169
|
+
.map((part) => part[0])
|
|
1170
|
+
.join('')
|
|
1171
|
+
.slice(0, 2)
|
|
1172
|
+
.toUpperCase();
|
|
1173
|
+
return {
|
|
1174
|
+
kind: 'symbol',
|
|
1175
|
+
value: letters || 'SK',
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
|
|
904
1179
|
function now() {
|
|
905
1180
|
return typeof performance !== 'undefined' && typeof performance.now === 'function'
|
|
906
1181
|
? performance.now()
|
package/src/utils.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
|
+
import {
|
|
3
|
+
resolveSpatialLookAt,
|
|
4
|
+
} from '@found-in-space/spatial';
|
|
2
5
|
|
|
3
6
|
export const DEFAULT_MAG_LIMIT = 6.5;
|
|
4
7
|
export const IDENTITY_QUATERNION = Object.freeze({ x: 0, y: 0, z: 0, w: 1 });
|
|
@@ -86,16 +89,17 @@ export function resolveAnchorRoot(roots, anchorMode, scaleBandId) {
|
|
|
86
89
|
* @param {number} revision
|
|
87
90
|
* @returns {SkykitViewState}
|
|
88
91
|
*/
|
|
89
|
-
export function normalizeViewState(input = {}, revision = 0) {
|
|
92
|
+
export function normalizeViewState(input = {}, revision = 0, options = {}) {
|
|
90
93
|
const observerPc = normalizeVector3(input.observerPc, { x: 0, y: 0, z: 0 });
|
|
91
94
|
const coordinateUnitsPerParsec = positiveFinite(input.coordinateUnitsPerParsec, 1);
|
|
95
|
+
const look = resolveViewLook(input, observerPc, options);
|
|
92
96
|
return {
|
|
93
97
|
revision,
|
|
94
98
|
observerPc,
|
|
95
99
|
renderObserverPosition: normalizeVector3(input.renderObserverPosition, observerPc),
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
orientationIcrs:
|
|
100
|
+
lookAt: look.lookAt,
|
|
101
|
+
targetPc: look.targetPc,
|
|
102
|
+
orientationIcrs: look.orientationIcrs,
|
|
99
103
|
limitingMagnitude: finiteNumber(input.limitingMagnitude, DEFAULT_MAG_LIMIT),
|
|
100
104
|
...(input.verticalFovDeg !== undefined ? { verticalFovDeg: positiveFinite(input.verticalFovDeg, 40) } : {}),
|
|
101
105
|
...(input.aspectRatio !== undefined ? { aspectRatio: positiveFinite(input.aspectRatio, 1) } : {}),
|
|
@@ -110,8 +114,8 @@ export function cloneViewState(view) {
|
|
|
110
114
|
...view,
|
|
111
115
|
observerPc: cloneVector3(view.observerPc),
|
|
112
116
|
renderObserverPosition: cloneVector3(view.renderObserverPosition),
|
|
117
|
+
lookAt: cloneLookAt(view.lookAt),
|
|
113
118
|
targetPc: view.targetPc ? cloneVector3(view.targetPc) : null,
|
|
114
|
-
directionIcrs: view.directionIcrs ? cloneVector3(view.directionIcrs) : null,
|
|
115
119
|
orientationIcrs: view.orientationIcrs ? cloneQuaternion(view.orientationIcrs) : null,
|
|
116
120
|
motion: view.motion ? cloneMotion(view.motion) : null,
|
|
117
121
|
};
|
|
@@ -125,7 +129,6 @@ export function toStarOctreeViewPatch(view) {
|
|
|
125
129
|
limitingMagnitude: view.limitingMagnitude,
|
|
126
130
|
mDesired: view.limitingMagnitude,
|
|
127
131
|
...(view.targetPc ? { targetPc: view.targetPc } : {}),
|
|
128
|
-
...(view.directionIcrs ? { directionIcrs: view.directionIcrs } : {}),
|
|
129
132
|
...(view.orientationIcrs ? { orientationIcrs: view.orientationIcrs } : {}),
|
|
130
133
|
...(view.verticalFovDeg !== undefined ? { verticalFovDeg: view.verticalFovDeg } : {}),
|
|
131
134
|
...(view.aspectRatio !== undefined ? { aspectRatio: view.aspectRatio } : {}),
|
|
@@ -134,6 +137,93 @@ export function toStarOctreeViewPatch(view) {
|
|
|
134
137
|
return patch;
|
|
135
138
|
}
|
|
136
139
|
|
|
140
|
+
/**
|
|
141
|
+
* @param {Partial<SkykitViewState>} input
|
|
142
|
+
* @param {Record<string, unknown>} [options]
|
|
143
|
+
* @returns {Promise<Partial<SkykitViewState>>}
|
|
144
|
+
*/
|
|
145
|
+
export async function resolveViewLookAtInput(input = {}, options = {}) {
|
|
146
|
+
const observerPc = normalizeVector3(
|
|
147
|
+
input.observerPc ?? /** @type {{ observerPc?: unknown }} */ (options).observerPc,
|
|
148
|
+
{ x: 0, y: 0, z: 0 },
|
|
149
|
+
);
|
|
150
|
+
const lookInput = input.lookAt
|
|
151
|
+
?? (input.orientationIcrs ? { orientationIcrs: input.orientationIcrs } : null);
|
|
152
|
+
if (!lookInput) return input;
|
|
153
|
+
const resolved = await resolveSpatialLookAt(lookInput, {
|
|
154
|
+
observerPc,
|
|
155
|
+
resolveStar: typeof options.resolveStar === 'function'
|
|
156
|
+
? /** @type {import('@found-in-space/spatial').ResolveSpatialLookAtOptions['resolveStar']} */ (options.resolveStar)
|
|
157
|
+
: undefined,
|
|
158
|
+
resolveBookmark: typeof options.resolveBookmark === 'function'
|
|
159
|
+
? /** @type {import('@found-in-space/spatial').ResolveSpatialLookAtOptions['resolveBookmark']} */ (options.resolveBookmark)
|
|
160
|
+
: undefined,
|
|
161
|
+
});
|
|
162
|
+
const resolvedLook = /** @type {import('@found-in-space/spatial').SpatialResolvedLookAt} */ (resolved);
|
|
163
|
+
return {
|
|
164
|
+
...input,
|
|
165
|
+
lookAt: /** @type {import('./index.d.ts').SkykitLookAtInput | null} */ (resolvedLook.lookAt),
|
|
166
|
+
targetPc: resolvedLook.targetPc,
|
|
167
|
+
orientationIcrs: resolvedLook.orientationIcrs,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @param {Partial<SkykitViewState>} input
|
|
173
|
+
* @param {Vector3Like} observerPc
|
|
174
|
+
* @param {Record<string, unknown>} [options]
|
|
175
|
+
* @returns {{ lookAt: import('./index.d.ts').SkykitLookAtInput | null; targetPc: Vector3Like | null; orientationIcrs: QuaternionLike | null }}
|
|
176
|
+
*/
|
|
177
|
+
function resolveViewLook(input, observerPc, options = {}) {
|
|
178
|
+
const lookInput = input.lookAt
|
|
179
|
+
?? (input.orientationIcrs ? { orientationIcrs: input.orientationIcrs } : null);
|
|
180
|
+
if (!lookInput) {
|
|
181
|
+
return {
|
|
182
|
+
lookAt: null,
|
|
183
|
+
targetPc: null,
|
|
184
|
+
orientationIcrs: null,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
const resolved = resolveSpatialLookAt(lookInput, {
|
|
188
|
+
observerPc,
|
|
189
|
+
resolveStar: typeof options.resolveStar === 'function'
|
|
190
|
+
? /** @type {import('@found-in-space/spatial').ResolveSpatialLookAtOptions['resolveStar']} */ (options.resolveStar)
|
|
191
|
+
: undefined,
|
|
192
|
+
resolveBookmark: typeof options.resolveBookmark === 'function'
|
|
193
|
+
? /** @type {import('@found-in-space/spatial').ResolveSpatialLookAtOptions['resolveBookmark']} */ (options.resolveBookmark)
|
|
194
|
+
: undefined,
|
|
195
|
+
});
|
|
196
|
+
if (resolved && typeof /** @type {Promise<unknown>} */ (resolved).then === 'function') {
|
|
197
|
+
throw new TypeError('normalizeViewState() received an async lookAt resolver result.');
|
|
198
|
+
}
|
|
199
|
+
const resolvedLook = /** @type {import('@found-in-space/spatial').SpatialResolvedLookAt} */ (resolved);
|
|
200
|
+
return {
|
|
201
|
+
lookAt: /** @type {import('./index.d.ts').SkykitLookAtInput | null} */ (cloneLookAt(resolvedLook.lookAt)),
|
|
202
|
+
targetPc: resolvedLook.targetPc
|
|
203
|
+
? cloneVector3(resolvedLook.targetPc)
|
|
204
|
+
: (input.targetPc == null ? null : normalizeVector3(input.targetPc, { x: 0, y: 0, z: 0 })),
|
|
205
|
+
orientationIcrs: resolvedLook.orientationIcrs ? cloneQuaternion(resolvedLook.orientationIcrs) : null,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* @param {unknown} lookAt
|
|
211
|
+
* @returns {import('./index.d.ts').SkykitLookAtInput | null}
|
|
212
|
+
*/
|
|
213
|
+
function cloneLookAt(lookAt) {
|
|
214
|
+
if (!lookAt || typeof lookAt !== 'object') return null;
|
|
215
|
+
const source = /** @type {Record<string, unknown>} */ (lookAt);
|
|
216
|
+
return {
|
|
217
|
+
...source,
|
|
218
|
+
...(source.targetPc && typeof source.targetPc === 'object'
|
|
219
|
+
? { targetPc: cloneVector3(/** @type {Vector3Like} */ (source.targetPc)) }
|
|
220
|
+
: {}),
|
|
221
|
+
...(source.orientationIcrs && typeof source.orientationIcrs === 'object'
|
|
222
|
+
? { orientationIcrs: cloneQuaternion(/** @type {QuaternionLike} */ (source.orientationIcrs)) }
|
|
223
|
+
: {}),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
137
227
|
/**
|
|
138
228
|
* @param {SkykitPluginInput} plugin
|
|
139
229
|
* @param {SkykitThreePluginContext} context
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type * as ThreeNamespace from 'three';
|
|
2
|
+
|
|
3
|
+
export declare const THREE: typeof ThreeNamespace;
|
|
4
|
+
export {
|
|
5
|
+
createSkykitBrowser,
|
|
6
|
+
type SkykitBrowser,
|
|
7
|
+
type SkykitBrowserObjectHandle,
|
|
8
|
+
type SkykitBrowserObjectOptions,
|
|
9
|
+
type SkykitBrowserOptions,
|
|
10
|
+
} from './browser.js';
|