@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
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
2
3
|
import test from 'node:test';
|
|
3
4
|
import * as THREE from 'three';
|
|
4
5
|
|
|
5
6
|
import {
|
|
6
7
|
applySkykitXrDepthRange,
|
|
7
8
|
computeSkykitXrDepthRange,
|
|
9
|
+
createSkykitXrBodyPlugin,
|
|
8
10
|
createSkykitXrBodyTracker,
|
|
9
11
|
createSkykitXrControlBindings,
|
|
10
12
|
createSkykitXrNavigationPlugin,
|
|
11
13
|
createSkykitXrObserverRig,
|
|
12
14
|
createSkykitXrPickRouter,
|
|
13
15
|
createSkykitXrRaySource,
|
|
16
|
+
createSkykitXrRayVisualPlugin,
|
|
14
17
|
createSkykitXrRig,
|
|
15
18
|
createSkykitXrSessionPlugin,
|
|
16
19
|
createSkykitXrStarPickingPlugin,
|
|
@@ -20,6 +23,38 @@ import {
|
|
|
20
23
|
} from '../xr.js';
|
|
21
24
|
import { createSkykitActionRegistry } from '../index.js';
|
|
22
25
|
|
|
26
|
+
test('xr free-roam demo uses restored alpha XR regressions defaults', () => {
|
|
27
|
+
const source = readFileSync(new URL('../../examples/xr-free-roam/xr-free-roam.js', import.meta.url), 'utf8');
|
|
28
|
+
|
|
29
|
+
assert.match(source, /createDefaultThreeStarFieldMaterialProfile/);
|
|
30
|
+
assert.doesNotMatch(source, /createVrThreeStarFieldMaterialProfile/);
|
|
31
|
+
assert.match(source, /createSkykitXrRayVisualPlugin/);
|
|
32
|
+
assert.match(source, /createSkykitXrBodyPlugin/);
|
|
33
|
+
assert.match(source, /createSurfaceShell/);
|
|
34
|
+
assert.match(source, /createMetaSidecarProviderService/);
|
|
35
|
+
assert.match(source, /deriveMetaSidecarUrlFromRenderUrl/);
|
|
36
|
+
assert.match(source, /metaSidecarEntryDisplayFields/);
|
|
37
|
+
assert.match(source, /datasetId:\s*DATASET_ID_c56103/);
|
|
38
|
+
assert.match(source, /attributes:\s*\[\s*'objectRef'\s*,\s*'pickMeta'\s*\]/);
|
|
39
|
+
assert.match(source, /selectSun:\s*'xr-demo:selected\.sun'/);
|
|
40
|
+
assert.match(source, /primaryActionId:\s*XR_DEMO_ACTIONS\.goSelected/);
|
|
41
|
+
assert.match(source, /primaryActionLabel:\s*'Fly to'/);
|
|
42
|
+
assert.match(source, /homeControl:\s*'button'/);
|
|
43
|
+
assert.match(source, /pointerType:\s*'ray'/);
|
|
44
|
+
assert.match(source, /return xrRig\.leftHandRoot/);
|
|
45
|
+
assert.match(source, /latestPanelFrame = rootContext\?\.frame \?\? latestPanelFrame/);
|
|
46
|
+
assert.doesNotMatch(source, /dragThreshold/);
|
|
47
|
+
assert.doesNotMatch(source, /driver:\s*'pose-anchored'/);
|
|
48
|
+
assert.doesNotMatch(source, /anchorPose/);
|
|
49
|
+
assert.doesNotMatch(source, /latestPanelFrame = frame/);
|
|
50
|
+
assert.doesNotMatch(source, /createChoiceGroup/);
|
|
51
|
+
assert.doesNotMatch(source, /createSlider/);
|
|
52
|
+
assert.doesNotMatch(source, /createToggle/);
|
|
53
|
+
assert.doesNotMatch(source, /createStack/);
|
|
54
|
+
assert.doesNotMatch(source, /waypointPrefix/);
|
|
55
|
+
assert.doesNotMatch(source, /panelState\.page/);
|
|
56
|
+
});
|
|
57
|
+
|
|
23
58
|
test('skykit/xr rig builds multi-root hierarchy', () => {
|
|
24
59
|
const camera = new THREE.PerspectiveCamera();
|
|
25
60
|
const rig = createSkykitXrRig({
|
|
@@ -84,6 +119,55 @@ test('skykit/xr body, rays, and pick router compose generic route results', () =
|
|
|
84
119
|
assert.equal(route.hit.object, 'target');
|
|
85
120
|
});
|
|
86
121
|
|
|
122
|
+
test('skykit/xr body plugin drives tracked hand roots before dependent parts', () => {
|
|
123
|
+
const rig = createSkykitXrRig();
|
|
124
|
+
const leftGripSpace = {};
|
|
125
|
+
const referenceSpace = {};
|
|
126
|
+
const bodyUpdates = [];
|
|
127
|
+
let part = null;
|
|
128
|
+
const plugin = createSkykitXrBodyPlugin({
|
|
129
|
+
rig,
|
|
130
|
+
onBody(body) {
|
|
131
|
+
bodyUpdates.push(body);
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
plugin.setup(createPluginContext({
|
|
135
|
+
addPart(nextPart) {
|
|
136
|
+
part = nextPart;
|
|
137
|
+
},
|
|
138
|
+
}));
|
|
139
|
+
|
|
140
|
+
part.update(createXrFrame({
|
|
141
|
+
referenceSpace,
|
|
142
|
+
inputSources: [{
|
|
143
|
+
handedness: 'left',
|
|
144
|
+
gripSpace: leftGripSpace,
|
|
145
|
+
gamepad: {
|
|
146
|
+
buttons: [{ pressed: false, touched: false, value: 0 }],
|
|
147
|
+
axes: [0, 0],
|
|
148
|
+
},
|
|
149
|
+
}],
|
|
150
|
+
xrFrame: {
|
|
151
|
+
getPose(space, ref) {
|
|
152
|
+
assert.equal(ref, referenceSpace);
|
|
153
|
+
if (space !== leftGripSpace) return null;
|
|
154
|
+
return {
|
|
155
|
+
transform: {
|
|
156
|
+
position: { x: 0.1, y: 0.2, z: 0.3 },
|
|
157
|
+
orientation: { x: 0, y: 0, z: 0, w: 1 },
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
}));
|
|
163
|
+
|
|
164
|
+
assert.deepEqual(rig.leftHandRoot.position.toArray(), [0.1, 0.2, 0.3]);
|
|
165
|
+
assert.equal(rig.leftHandRoot.visible, true);
|
|
166
|
+
assert.equal(rig.rightHandRoot.visible, false);
|
|
167
|
+
assert.equal(plugin.getBody().leftHand?.buttons, 1);
|
|
168
|
+
assert.equal(bodyUpdates.length, 1);
|
|
169
|
+
});
|
|
170
|
+
|
|
87
171
|
test('skykit/xr depth helpers compute and apply render state', () => {
|
|
88
172
|
const range = computeSkykitXrDepthRange({
|
|
89
173
|
visibleBounds: { min: { x: -1, y: -1, z: -20 }, max: { x: 1, y: 1, z: -10 } },
|
|
@@ -102,6 +186,32 @@ test('skykit/xr depth helpers compute and apply render state', () => {
|
|
|
102
186
|
assert.deepEqual(state, { depthNear: range.depthNear, depthFar: range.depthFar });
|
|
103
187
|
});
|
|
104
188
|
|
|
189
|
+
test('skykit/xr depth helpers include distant visible star bounds', () => {
|
|
190
|
+
const range = computeSkykitXrDepthRange({
|
|
191
|
+
observer: { x: 0, y: 0, z: 0 },
|
|
192
|
+
visibleBounds: {
|
|
193
|
+
min: { x: 62, y: 602, z: -13 },
|
|
194
|
+
max: { x: 64, y: 604, z: -11 },
|
|
195
|
+
},
|
|
196
|
+
observerCentricSpheres: [{ radiusNavigationUnits: 16 }],
|
|
197
|
+
scale: {
|
|
198
|
+
navigationUnits: 'pc',
|
|
199
|
+
metersPerNavigationUnit: 1,
|
|
200
|
+
worldUnitsPerNavigationUnit: 1,
|
|
201
|
+
},
|
|
202
|
+
policy: {
|
|
203
|
+
near: 0.03,
|
|
204
|
+
minFar: 100,
|
|
205
|
+
maxFar: 2000000,
|
|
206
|
+
marginFactor: 1.2,
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
assert.ok(range.far > 720);
|
|
211
|
+
assert.ok(range.telemetry.farthestVisibleBoundsDistance > 600);
|
|
212
|
+
assert.equal(range.telemetry.farthestObserverCentricSphereDistance, 16);
|
|
213
|
+
});
|
|
214
|
+
|
|
105
215
|
test('skykit/xr session helpers use injected navigator', async () => {
|
|
106
216
|
let ended = false;
|
|
107
217
|
const session = {
|
|
@@ -118,7 +228,8 @@ test('skykit/xr session helpers use injected navigator', async () => {
|
|
|
118
228
|
async isSessionSupported(mode) {
|
|
119
229
|
return mode === 'immersive-vr';
|
|
120
230
|
},
|
|
121
|
-
async requestSession() {
|
|
231
|
+
async requestSession(_mode, init) {
|
|
232
|
+
this.lastInit = init;
|
|
122
233
|
return session;
|
|
123
234
|
},
|
|
124
235
|
},
|
|
@@ -126,6 +237,7 @@ test('skykit/xr session helpers use injected navigator', async () => {
|
|
|
126
237
|
assert.equal(await isSkykitXrModeSupported('immersive-vr', { navigator }), true);
|
|
127
238
|
const handle = await enterSkykitXrSession({ navigator, mode: 'immersive-vr' });
|
|
128
239
|
assert.equal(handle.presenting, true);
|
|
240
|
+
assert.deepEqual(navigator.xr.lastInit.optionalFeatures, ['local-floor']);
|
|
129
241
|
await exitSkykitXrSession(handle);
|
|
130
242
|
assert.equal(ended, true);
|
|
131
243
|
});
|
|
@@ -147,15 +259,20 @@ test('skykit/xr observer rig bridges viewer state to an XR rig without camera re
|
|
|
147
259
|
|
|
148
260
|
test('skykit/xr session plugin registers enter/exit actions and syncs snapshot state', async () => {
|
|
149
261
|
let activeSession = null;
|
|
262
|
+
let requestedReferenceSpaceCount = 0;
|
|
150
263
|
const session = {
|
|
151
264
|
async requestReferenceSpace(type) {
|
|
265
|
+
requestedReferenceSpaceCount += 1;
|
|
152
266
|
return { type };
|
|
153
267
|
},
|
|
154
268
|
async end() {
|
|
155
269
|
this.ended = true;
|
|
270
|
+
activeSession = null;
|
|
271
|
+
renderer.xr.isPresenting = false;
|
|
156
272
|
},
|
|
157
273
|
addEventListener() {},
|
|
158
274
|
};
|
|
275
|
+
let rendererReferenceSpaceType = null;
|
|
159
276
|
const renderer = {
|
|
160
277
|
xr: {
|
|
161
278
|
enabled: false,
|
|
@@ -163,6 +280,9 @@ test('skykit/xr session plugin registers enter/exit actions and syncs snapshot s
|
|
|
163
280
|
getSession() {
|
|
164
281
|
return activeSession;
|
|
165
282
|
},
|
|
283
|
+
setReferenceSpaceType(type) {
|
|
284
|
+
rendererReferenceSpaceType = type;
|
|
285
|
+
},
|
|
166
286
|
async setSession(nextSession) {
|
|
167
287
|
activeSession = nextSession;
|
|
168
288
|
this.isPresenting = Boolean(nextSession);
|
|
@@ -174,7 +294,8 @@ test('skykit/xr session plugin registers enter/exit actions and syncs snapshot s
|
|
|
174
294
|
async isSessionSupported() {
|
|
175
295
|
return true;
|
|
176
296
|
},
|
|
177
|
-
async requestSession() {
|
|
297
|
+
async requestSession(_mode, init) {
|
|
298
|
+
this.lastInit = init;
|
|
178
299
|
return session;
|
|
179
300
|
},
|
|
180
301
|
},
|
|
@@ -195,8 +316,12 @@ test('skykit/xr session plugin registers enter/exit actions and syncs snapshot s
|
|
|
195
316
|
|
|
196
317
|
await actions.invoke('skykit:xr.enter');
|
|
197
318
|
assert.equal(renderer.xr.enabled, true);
|
|
319
|
+
assert.equal(rendererReferenceSpaceType, 'local-floor');
|
|
320
|
+
assert.deepEqual(navigator.xr.lastInit.optionalFeatures, ['local-floor']);
|
|
321
|
+
assert.equal(requestedReferenceSpaceCount, 0);
|
|
198
322
|
assert.equal(activeSession, session);
|
|
199
323
|
assert.equal(plugin.getSnapshot().presenting, true);
|
|
324
|
+
assert.equal(plugin.getSnapshot().enterStage, 'presenting');
|
|
200
325
|
|
|
201
326
|
const frame = createXrFrame({ renderer });
|
|
202
327
|
part.update(frame);
|
|
@@ -240,6 +365,58 @@ test('skykit/xr navigation plugin updates viewer state from controller axes', ()
|
|
|
240
365
|
assert.deepEqual(actions.getControlValue('skykit:ship.control.move'), { x: 0, y: 0, z: -10 });
|
|
241
366
|
});
|
|
242
367
|
|
|
368
|
+
test('skykit/xr ray visual shows the controller ray and shortens at blockers', () => {
|
|
369
|
+
let part = null;
|
|
370
|
+
let disposedRaySource = false;
|
|
371
|
+
const plugin = createSkykitXrRayVisualPlugin({
|
|
372
|
+
raySource: {
|
|
373
|
+
getRay() {
|
|
374
|
+
return {
|
|
375
|
+
id: 'ray',
|
|
376
|
+
kind: 'target-ray',
|
|
377
|
+
handedness: 'right',
|
|
378
|
+
origin: { x: 1, y: 2, z: 3 },
|
|
379
|
+
direction: { x: 0, y: 0, z: -1 },
|
|
380
|
+
length: 10,
|
|
381
|
+
};
|
|
382
|
+
},
|
|
383
|
+
getSnapshot() {
|
|
384
|
+
return { id: 'ray-source' };
|
|
385
|
+
},
|
|
386
|
+
dispose() {
|
|
387
|
+
disposedRaySource = true;
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
blockers: [{
|
|
391
|
+
blockRay() {
|
|
392
|
+
return { blocked: true, distance: 3, hit: { componentId: 'panel' } };
|
|
393
|
+
},
|
|
394
|
+
}],
|
|
395
|
+
});
|
|
396
|
+
const context = createPluginContext({
|
|
397
|
+
addPart(nextPart) {
|
|
398
|
+
part = nextPart;
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
plugin.setup(context);
|
|
402
|
+
part.attach(context);
|
|
403
|
+
|
|
404
|
+
part.update(createXrFrame());
|
|
405
|
+
|
|
406
|
+
assert.equal(part.object3d.visible, true);
|
|
407
|
+
assert.equal(plugin.getSnapshot().blocked, true);
|
|
408
|
+
assert.equal(plugin.getSnapshot().lastLength, 3);
|
|
409
|
+
assert.deepEqual(
|
|
410
|
+
Array.from(part.object3d.children[0].geometry.getAttribute('position').array),
|
|
411
|
+
[1, 2, 3, 1, 2, 0],
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
part.update({ ...createXrFrame(), xr: { presenting: false } });
|
|
415
|
+
assert.equal(part.object3d.visible, false);
|
|
416
|
+
part.dispose();
|
|
417
|
+
assert.equal(disposedRaySource, true);
|
|
418
|
+
});
|
|
419
|
+
|
|
243
420
|
test('skykit/xr star picking fires only on trigger edge and registers attribute-only demand', () => {
|
|
244
421
|
const actions = createSkykitActionRegistry();
|
|
245
422
|
let part = null;
|