@worldmesh/runtime 0.1.0 → 0.2.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/README.md +21 -2
- package/dist/avatar/descriptor.d.ts +27 -0
- package/dist/avatar/descriptor.d.ts.map +1 -0
- package/dist/avatar/descriptor.js +41 -0
- package/dist/avatar/descriptor.js.map +1 -0
- package/dist/avatar/handoff.d.ts +29 -0
- package/dist/avatar/handoff.d.ts.map +1 -0
- package/dist/avatar/handoff.js +92 -0
- package/dist/avatar/handoff.js.map +1 -0
- package/dist/avatar/loader.d.ts +26 -0
- package/dist/avatar/loader.d.ts.map +1 -0
- package/dist/avatar/loader.js +177 -0
- package/dist/avatar/loader.js.map +1 -0
- package/dist/controls/input.d.ts +25 -0
- package/dist/controls/input.d.ts.map +1 -1
- package/dist/controls/input.js +60 -2
- package/dist/controls/input.js.map +1 -1
- package/dist/controls/touch.d.ts.map +1 -1
- package/dist/controls/touch.js +5 -0
- package/dist/controls/touch.js.map +1 -1
- package/dist/core/worldmesh.d.ts.map +1 -1
- package/dist/core/worldmesh.js +114 -8
- package/dist/core/worldmesh.js.map +1 -1
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/movement/controller.d.ts +10 -1
- package/dist/movement/controller.d.ts.map +1 -1
- package/dist/movement/controller.js +61 -4
- package/dist/movement/controller.js.map +1 -1
- package/dist/net/adapter.d.ts +3 -14
- package/dist/net/adapter.d.ts.map +1 -1
- package/dist/net/adapter.js.map +1 -1
- package/dist/net/presence.d.ts +68 -0
- package/dist/net/presence.d.ts.map +1 -0
- package/dist/net/presence.js +425 -0
- package/dist/net/presence.js.map +1 -0
- package/dist/player/avatar.d.ts +4 -0
- package/dist/player/avatar.d.ts.map +1 -1
- package/dist/player/avatar.js +97 -4
- package/dist/player/avatar.js.map +1 -1
- package/dist/player/player.d.ts +13 -0
- package/dist/player/player.d.ts.map +1 -1
- package/dist/player/player.js +44 -5
- package/dist/player/player.js.map +1 -1
- package/dist/portals/portals.d.ts +4 -1
- package/dist/portals/portals.d.ts.map +1 -1
- package/dist/portals/portals.js +6 -2
- package/dist/portals/portals.js.map +1 -1
- package/dist/types.d.ts +78 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/overlay.d.ts +7 -0
- package/dist/ui/overlay.d.ts.map +1 -1
- package/dist/ui/overlay.js +62 -7
- package/dist/ui/overlay.js.map +1 -1
- package/package.json +4 -1
- package/src/avatar/descriptor.ts +56 -0
- package/src/avatar/handoff.ts +91 -0
- package/src/avatar/loader.ts +184 -0
- package/src/controls/input.ts +61 -2
- package/src/controls/touch.ts +5 -0
- package/src/core/worldmesh.ts +110 -8
- package/src/index.ts +21 -1
- package/src/movement/controller.ts +59 -5
- package/src/net/adapter.ts +3 -14
- package/src/net/presence.ts +498 -0
- package/src/player/avatar.ts +109 -4
- package/src/player/player.ts +42 -3
- package/src/portals/portals.ts +6 -2
- package/src/types.ts +77 -4
- package/src/ui/overlay.ts +60 -7
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/ui/overlay.d.ts
CHANGED
|
@@ -7,12 +7,19 @@ import type { UiOptions } from '../types.js';
|
|
|
7
7
|
export declare class Overlay {
|
|
8
8
|
readonly root: HTMLDivElement;
|
|
9
9
|
private prompt;
|
|
10
|
+
private lock;
|
|
10
11
|
private onEnter;
|
|
12
|
+
private deferLockPanel;
|
|
11
13
|
constructor(options: UiOptions & {
|
|
12
14
|
onEnter: (touch?: boolean) => void;
|
|
13
15
|
onInteract?: () => void;
|
|
14
16
|
});
|
|
17
|
+
/** The pause screen, with the control list, is on screen. */
|
|
18
|
+
isPaused(): boolean;
|
|
15
19
|
setLocked(locked: boolean): void;
|
|
20
|
+
/** Keep the full-screen click target; strip the dimmer and copy. */
|
|
21
|
+
private hideLockChrome;
|
|
22
|
+
private showLockChrome;
|
|
16
23
|
setPrompt(text: string | null): void;
|
|
17
24
|
dispose(): void;
|
|
18
25
|
}
|
package/dist/ui/overlay.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../src/ui/overlay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../src/ui/overlay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AA+Q7C;;;;GAIG;AACH,qBAAa,OAAO;IAClB,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,IAAI,CAAiB;IAC7B,OAAO,CAAC,OAAO,CAA4B;IAC3C,OAAO,CAAC,cAAc,CAAU;gBAG9B,OAAO,EAAE,SAAS,GAAG;QACnB,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;QACnC,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;KACzB;IAiHH,6DAA6D;IAC7D,QAAQ,IAAI,OAAO;IAInB,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAQhC,oEAAoE;IACpE,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,cAAc;IAQtB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IASpC,OAAO,IAAI,IAAI;CAGhB"}
|
package/dist/ui/overlay.js
CHANGED
|
@@ -44,6 +44,14 @@ const CSS = `
|
|
|
44
44
|
-webkit-tap-highlight-color: transparent;
|
|
45
45
|
}
|
|
46
46
|
.wm-overlay[data-locked="true"] .wm-lock { display: none; }
|
|
47
|
+
/* First visit with deferLockPanel: keep the click target, hide the chrome. */
|
|
48
|
+
.wm-overlay[data-defer-lock="true"]:not([data-lock-ready="true"]) .wm-lock {
|
|
49
|
+
background: transparent;
|
|
50
|
+
backdrop-filter: none;
|
|
51
|
+
}
|
|
52
|
+
.wm-overlay[data-defer-lock="true"]:not([data-lock-ready="true"]) .wm-lock-content {
|
|
53
|
+
visibility: hidden;
|
|
54
|
+
}
|
|
47
55
|
.wm-lock-content {
|
|
48
56
|
margin: auto 0;
|
|
49
57
|
width: 100%;
|
|
@@ -56,10 +64,14 @@ const CSS = `
|
|
|
56
64
|
box-sizing: border-box;
|
|
57
65
|
}
|
|
58
66
|
.wm-lock-title {
|
|
67
|
+
width: 100%;
|
|
68
|
+
margin: 0;
|
|
69
|
+
padding: 0;
|
|
70
|
+
display: block;
|
|
71
|
+
text-align: center;
|
|
59
72
|
font-size: clamp(22px, 5vw, 34px);
|
|
60
73
|
font-weight: 600;
|
|
61
74
|
letter-spacing: .01em;
|
|
62
|
-
margin: 0;
|
|
63
75
|
}
|
|
64
76
|
.wm-lock-cta {
|
|
65
77
|
font-size: 13.5px;
|
|
@@ -260,14 +272,20 @@ const KEY_LEGEND = [
|
|
|
260
272
|
export class Overlay {
|
|
261
273
|
root;
|
|
262
274
|
prompt;
|
|
275
|
+
lock;
|
|
263
276
|
onEnter;
|
|
277
|
+
deferLockPanel;
|
|
264
278
|
constructor(options) {
|
|
265
279
|
injectStyles();
|
|
266
280
|
this.onEnter = options.onEnter;
|
|
281
|
+
this.deferLockPanel = options.deferLockPanel === true;
|
|
267
282
|
const isTouch = isTouchDevice();
|
|
268
283
|
this.root = document.createElement('div');
|
|
269
284
|
this.root.className = 'wm-overlay';
|
|
270
|
-
|
|
285
|
+
// Already walking: no click-to-enter layer. Esc is what brings the pause screen up.
|
|
286
|
+
this.root.dataset.locked = options.moveBeforeLock ? 'true' : 'false';
|
|
287
|
+
if (this.deferLockPanel)
|
|
288
|
+
this.root.dataset.deferLock = 'true';
|
|
271
289
|
if (options.crosshair !== false) {
|
|
272
290
|
const crosshair = document.createElement('div');
|
|
273
291
|
crosshair.className = 'wm-crosshair';
|
|
@@ -275,6 +293,7 @@ export class Overlay {
|
|
|
275
293
|
}
|
|
276
294
|
const lock = document.createElement('div');
|
|
277
295
|
lock.className = 'wm-lock';
|
|
296
|
+
this.lock = lock;
|
|
278
297
|
const content = document.createElement('div');
|
|
279
298
|
content.className = 'wm-lock-content';
|
|
280
299
|
const title = document.createElement('h1');
|
|
@@ -284,19 +303,28 @@ export class Overlay {
|
|
|
284
303
|
const cta = document.createElement('button');
|
|
285
304
|
cta.type = 'button';
|
|
286
305
|
cta.className = 'wm-lock-cta';
|
|
287
|
-
cta.textContent =
|
|
306
|
+
cta.textContent = 'Continue';
|
|
288
307
|
content.appendChild(cta);
|
|
289
308
|
const handleEnter = (e) => {
|
|
290
|
-
e.preventDefault();
|
|
291
309
|
e.stopPropagation();
|
|
292
310
|
const touchInitiated = ('pointerType' in e && e.pointerType === 'touch') ||
|
|
293
311
|
e.type === 'touchend' ||
|
|
294
312
|
isTouch;
|
|
295
313
|
this.onEnter(touchInitiated);
|
|
296
314
|
};
|
|
315
|
+
// A click or tap on the button resumes. The Enter key does not: that key talks.
|
|
316
|
+
cta.addEventListener('keydown', (event) => {
|
|
317
|
+
if (event.key === 'Enter' || event.key === ' ')
|
|
318
|
+
event.preventDefault();
|
|
319
|
+
});
|
|
297
320
|
cta.addEventListener('click', handleEnter);
|
|
298
|
-
|
|
299
|
-
|
|
321
|
+
lock.addEventListener('click', (event) => {
|
|
322
|
+
const target = event.target;
|
|
323
|
+
if (target instanceof Element && target.closest('button, a, input, textarea, select, label, .walk-pause-actions')) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
handleEnter(event);
|
|
327
|
+
});
|
|
300
328
|
if (options.controlsHint !== false) {
|
|
301
329
|
const keys = document.createElement('div');
|
|
302
330
|
keys.className = 'wm-keys';
|
|
@@ -319,6 +347,8 @@ export class Overlay {
|
|
|
319
347
|
}
|
|
320
348
|
lock.appendChild(content);
|
|
321
349
|
this.root.appendChild(lock);
|
|
350
|
+
if (this.deferLockPanel)
|
|
351
|
+
this.hideLockChrome();
|
|
322
352
|
this.prompt = document.createElement('div');
|
|
323
353
|
this.prompt.className = 'wm-prompt';
|
|
324
354
|
this.prompt.dataset.visible = 'false';
|
|
@@ -340,12 +370,37 @@ export class Overlay {
|
|
|
340
370
|
}
|
|
341
371
|
const hint = document.createElement('div');
|
|
342
372
|
hint.className = 'wm-hint';
|
|
343
|
-
hint.textContent = isTouch ? 'Tap ⏸ to
|
|
373
|
+
hint.textContent = isTouch ? 'Tap ⏸ to open the menu' : 'Esc to open the menu';
|
|
344
374
|
this.root.appendChild(hint);
|
|
345
375
|
document.body.appendChild(this.root);
|
|
346
376
|
}
|
|
377
|
+
/** The pause screen, with the control list, is on screen. */
|
|
378
|
+
isPaused() {
|
|
379
|
+
return this.root.dataset.lockReady === 'true' && this.root.dataset.locked !== 'true';
|
|
380
|
+
}
|
|
347
381
|
setLocked(locked) {
|
|
348
382
|
this.root.dataset.locked = String(locked);
|
|
383
|
+
if (!locked && this.deferLockPanel) {
|
|
384
|
+
this.root.dataset.lockReady = 'true';
|
|
385
|
+
this.showLockChrome();
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
/** Keep the full-screen click target; strip the dimmer and copy. */
|
|
389
|
+
hideLockChrome() {
|
|
390
|
+
this.lock.style.background = 'transparent';
|
|
391
|
+
this.lock.style.backdropFilter = 'none';
|
|
392
|
+
this.lock.style.setProperty('-webkit-backdrop-filter', 'none');
|
|
393
|
+
const content = this.lock.querySelector('.wm-lock-content');
|
|
394
|
+
if (content instanceof HTMLElement)
|
|
395
|
+
content.style.visibility = 'hidden';
|
|
396
|
+
}
|
|
397
|
+
showLockChrome() {
|
|
398
|
+
this.lock.style.background = '';
|
|
399
|
+
this.lock.style.backdropFilter = '';
|
|
400
|
+
this.lock.style.removeProperty('-webkit-backdrop-filter');
|
|
401
|
+
const content = this.lock.querySelector('.wm-lock-content');
|
|
402
|
+
if (content instanceof HTMLElement)
|
|
403
|
+
content.style.visibility = '';
|
|
349
404
|
}
|
|
350
405
|
setPrompt(text) {
|
|
351
406
|
if (!text) {
|
package/dist/ui/overlay.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overlay.js","sourceRoot":"","sources":["../../src/ui/overlay.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,QAAQ,GAAG,yBAAyB,CAAC;AAE3C,MAAM,GAAG,GAAG
|
|
1
|
+
{"version":3,"file":"overlay.js","sourceRoot":"","sources":["../../src/ui/overlay.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@worldmesh/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The shared WorldMesh navigation runtime: controls, movement, camera, player, abilities and portals.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"worldmesh",
|
|
@@ -53,6 +53,9 @@
|
|
|
53
53
|
"typecheck": "tsc -p tsconfig.json",
|
|
54
54
|
"prepare": "npm run build"
|
|
55
55
|
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@pixiv/three-vrm": "^3.5.5"
|
|
58
|
+
},
|
|
56
59
|
"peerDependencies": {
|
|
57
60
|
"three": ">=0.160.0"
|
|
58
61
|
},
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The only thing the runtime knows about external avatars: a normalized
|
|
3
|
+
* descriptor pointing at a model file the avatar's platform hosts. How it was
|
|
4
|
+
* found (which platform, which account, which OAuth flow) stays with whoever
|
|
5
|
+
* produced it — the WorldMesh hub's Avatar Wallet, or the world itself.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type AvatarFormat = 'vrm' | 'glb' | 'gltf';
|
|
9
|
+
|
|
10
|
+
export interface AvatarDescriptor {
|
|
11
|
+
/** Where the avatar lives, e.g. 'vroid' or 'atproto'. Informational. */
|
|
12
|
+
provider: string;
|
|
13
|
+
/** The platform's id for the avatar. Informational. */
|
|
14
|
+
avatarId: string;
|
|
15
|
+
name: string | null;
|
|
16
|
+
thumbnail: string | null;
|
|
17
|
+
format: AvatarFormat;
|
|
18
|
+
/** The model file, on the platform's own storage. May be short-lived. */
|
|
19
|
+
modelUrl: string;
|
|
20
|
+
/** When `modelUrl` stops working (ISO 8601), or null. */
|
|
21
|
+
expiresAt: string | null;
|
|
22
|
+
/** The avatar's public page, for credit where its license asks for it. */
|
|
23
|
+
sourceUrl: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const FORMATS: readonly string[] = ['vrm', 'glb', 'gltf'];
|
|
27
|
+
|
|
28
|
+
/** Model URLs must be https, except on a local development machine. */
|
|
29
|
+
export function isLoadableUrl(raw: unknown): raw is string {
|
|
30
|
+
if (typeof raw !== 'string') return false;
|
|
31
|
+
try {
|
|
32
|
+
const url = new URL(raw);
|
|
33
|
+
if (url.protocol === 'https:') return true;
|
|
34
|
+
return url.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname);
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Accepts a descriptor from the network only if it has the right shape. */
|
|
41
|
+
export function parseAvatarDescriptor(value: unknown): AvatarDescriptor | null {
|
|
42
|
+
if (!value || typeof value !== 'object') return null;
|
|
43
|
+
const d = value as Record<string, unknown>;
|
|
44
|
+
if (typeof d.format !== 'string' || !FORMATS.includes(d.format) || !isLoadableUrl(d.modelUrl)) return null;
|
|
45
|
+
const text = (v: unknown) => (typeof v === 'string' ? v : null);
|
|
46
|
+
return {
|
|
47
|
+
provider: text(d.provider) ?? 'unknown',
|
|
48
|
+
avatarId: text(d.avatarId) ?? '',
|
|
49
|
+
name: text(d.name),
|
|
50
|
+
thumbnail: isLoadableUrl(d.thumbnail) ? d.thumbnail : null,
|
|
51
|
+
format: d.format as AvatarFormat,
|
|
52
|
+
modelUrl: d.modelUrl,
|
|
53
|
+
expiresAt: text(d.expiresAt),
|
|
54
|
+
sourceUrl: isLoadableUrl(d.sourceUrl) ? d.sourceUrl : null,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { parseAvatarDescriptor, type AvatarDescriptor } from './descriptor.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Carrying a visitor's chosen avatar between worlds.
|
|
5
|
+
*
|
|
6
|
+
* The WorldMesh hub appends an opaque, short-lived ticket to a world's URL as
|
|
7
|
+
* a fragment (`#wm-avatar=…`): fragments are never sent to servers or in
|
|
8
|
+
* Referer headers. The runtime takes it out of the address bar (so it is not
|
|
9
|
+
* bookmarked or shared), keeps it in sessionStorage for this tab, and adds it
|
|
10
|
+
* again to portal destinations so the avatar follows the visitor.
|
|
11
|
+
*
|
|
12
|
+
* A ticket only ever resolves to an avatar descriptor. It carries no session,
|
|
13
|
+
* no account and no platform credentials.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const AVATAR_TICKET_PARAM = 'wm-avatar';
|
|
17
|
+
/** sessionStorage key. The hub writes the same key. */
|
|
18
|
+
export const AVATAR_TICKET_STORAGE_KEY = 'worldmesh.avatarTicket';
|
|
19
|
+
const TICKET_PATTERN = /^[A-Za-z0-9_-]{16,256}\.[A-Za-z0-9_-]{16,512}$/;
|
|
20
|
+
|
|
21
|
+
/** Moves a ticket from this page's URL fragment into storage, and returns the current one. */
|
|
22
|
+
export function takeAvatarTicket(): string | null {
|
|
23
|
+
const hash = window.location.hash.slice(1);
|
|
24
|
+
if (hash) {
|
|
25
|
+
const params = new URLSearchParams(hash);
|
|
26
|
+
const ticket = params.get(AVATAR_TICKET_PARAM);
|
|
27
|
+
if (ticket !== null) {
|
|
28
|
+
params.delete(AVATAR_TICKET_PARAM);
|
|
29
|
+
const rest = params.toString();
|
|
30
|
+
history.replaceState(history.state, '', `${window.location.pathname}${window.location.search}${rest ? `#${rest}` : ''}`);
|
|
31
|
+
if (TICKET_PATTERN.test(ticket)) setAvatarTicket(ticket);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return getAvatarTicket();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getAvatarTicket(): string | null {
|
|
38
|
+
try {
|
|
39
|
+
const ticket = sessionStorage.getItem(AVATAR_TICKET_STORAGE_KEY);
|
|
40
|
+
return ticket && TICKET_PATTERN.test(ticket) ? ticket : null;
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function setAvatarTicket(ticket: string | null): void {
|
|
47
|
+
try {
|
|
48
|
+
if (ticket) sessionStorage.setItem(AVATAR_TICKET_STORAGE_KEY, ticket);
|
|
49
|
+
else sessionStorage.removeItem(AVATAR_TICKET_STORAGE_KEY);
|
|
50
|
+
} catch {
|
|
51
|
+
// Storage blocked: the avatar simply does not follow to the next world.
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Adds the current ticket to a destination URL's fragment, unless it already has one. */
|
|
56
|
+
export function withAvatarTicket(url: URL): URL {
|
|
57
|
+
const ticket = getAvatarTicket();
|
|
58
|
+
if (ticket && !url.hash && /^https?:$/.test(url.protocol)) {
|
|
59
|
+
url.hash = `${AVATAR_TICKET_PARAM}=${ticket}`;
|
|
60
|
+
}
|
|
61
|
+
return url;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Exchanges a ticket at the hub for a descriptor. Sends no cookies and no
|
|
66
|
+
* credentials. Null when the visitor has not chosen an avatar, the ticket has
|
|
67
|
+
* expired, or the hub cannot be reached.
|
|
68
|
+
*/
|
|
69
|
+
export async function resolveWorldMeshAvatar(hubUrl: string, ticket: string, timeoutMs = 10_000): Promise<AvatarDescriptor | null> {
|
|
70
|
+
const controller = new AbortController();
|
|
71
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
72
|
+
try {
|
|
73
|
+
const response = await fetch(new URL('/api/account/avatar/resolve', hubUrl), {
|
|
74
|
+
method: 'POST',
|
|
75
|
+
mode: 'cors',
|
|
76
|
+
credentials: 'omit',
|
|
77
|
+
headers: { 'Content-Type': 'application/json' },
|
|
78
|
+
body: JSON.stringify({ ticket }),
|
|
79
|
+
signal: controller.signal,
|
|
80
|
+
});
|
|
81
|
+
if (response.status === 401) {
|
|
82
|
+
setAvatarTicket(null);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
if (!response.ok) return null;
|
|
86
|
+
const body = (await response.json()) as { avatar?: unknown };
|
|
87
|
+
return parseAvatarDescriptor(body.avatar);
|
|
88
|
+
} finally {
|
|
89
|
+
clearTimeout(timer);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { AnimationMixer, Box3, Group, MathUtils, Mesh, Texture, type AnimationAction, type Object3D } from 'three';
|
|
2
|
+
import type { AvatarMotion } from '../player/avatar.js';
|
|
3
|
+
import type { AvatarDescriptor } from './descriptor.js';
|
|
4
|
+
|
|
5
|
+
/** A loaded external avatar, normalised to the WorldMesh body convention. */
|
|
6
|
+
export interface LoadedAvatar {
|
|
7
|
+
/** Feet at y = 0, facing -Z, scaled to the player's height. */
|
|
8
|
+
object: Object3D;
|
|
9
|
+
/** Call once per frame with how the player moves. */
|
|
10
|
+
animate(motion: AvatarMotion): void;
|
|
11
|
+
dispose(): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface LoadAvatarOptions {
|
|
15
|
+
/** Standing height to scale the model to, in metres. */
|
|
16
|
+
height: number;
|
|
17
|
+
/** Refuse models larger than this. Defaults to 40 MB. */
|
|
18
|
+
maxBytes?: number;
|
|
19
|
+
signal?: AbortSignal;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Downloads a VRM / glTF model straight from the URL in the descriptor (the
|
|
24
|
+
* avatar platform's own storage) and turns it into a player body. The loaders
|
|
25
|
+
* are imported on first use, so worlds that never show an external avatar
|
|
26
|
+
* never download them.
|
|
27
|
+
*/
|
|
28
|
+
export async function loadAvatarModel(descriptor: AvatarDescriptor, options: LoadAvatarOptions): Promise<LoadedAvatar> {
|
|
29
|
+
const buffer = await download(descriptor.modelUrl, options.maxBytes ?? 40 * 1024 * 1024, options.signal);
|
|
30
|
+
const [{ GLTFLoader }, vrmModule] = await Promise.all([
|
|
31
|
+
import('three/examples/jsm/loaders/GLTFLoader.js'),
|
|
32
|
+
descriptor.format === 'vrm' ? import('@pixiv/three-vrm') : Promise.resolve(null),
|
|
33
|
+
]);
|
|
34
|
+
const loader = new GLTFLoader();
|
|
35
|
+
if (vrmModule) loader.register((parser) => new vrmModule.VRMLoaderPlugin(parser));
|
|
36
|
+
const gltf = await loader.parseAsync(buffer, '');
|
|
37
|
+
|
|
38
|
+
const vrm = vrmModule ? (gltf.userData.vrm as import('@pixiv/three-vrm').VRM | undefined) : undefined;
|
|
39
|
+
if (descriptor.format === 'vrm' && (!vrm || !vrmModule)) throw new Error('Not a VRM model');
|
|
40
|
+
const model = vrm ? vrm.scene : gltf.scene;
|
|
41
|
+
if (vrm && vrmModule) {
|
|
42
|
+
vrmModule.VRMUtils.removeUnnecessaryVertices(model);
|
|
43
|
+
vrmModule.VRMUtils.combineSkeletons(model);
|
|
44
|
+
// VRM 0.x faces -Z; this makes it face +Z like VRM 1.0 and plain glTF.
|
|
45
|
+
vrmModule.VRMUtils.rotateVRM0(vrm);
|
|
46
|
+
}
|
|
47
|
+
model.traverse((child) => {
|
|
48
|
+
// Skinned meshes are bounded by their rest pose; animated limbs would pop out of view.
|
|
49
|
+
child.frustumCulled = false;
|
|
50
|
+
if ((child as Mesh).isMesh) child.castShadow = true;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// glTF and VRM face +Z; WorldMesh bodies face -Z. Scale to the player and
|
|
54
|
+
// stand the feet on y = 0.
|
|
55
|
+
const root = new Group();
|
|
56
|
+
root.name = 'worldmesh:external-avatar';
|
|
57
|
+
const turn = new Group();
|
|
58
|
+
turn.rotation.y = Math.PI;
|
|
59
|
+
turn.add(model);
|
|
60
|
+
root.add(turn);
|
|
61
|
+
root.updateMatrixWorld(true);
|
|
62
|
+
const box = new Box3().setFromObject(root);
|
|
63
|
+
const modelHeight = box.max.y - box.min.y;
|
|
64
|
+
if (!(modelHeight > 0.05 && modelHeight < 100)) throw new Error('Model has no usable size');
|
|
65
|
+
const scale = options.height / modelHeight;
|
|
66
|
+
turn.scale.setScalar(scale);
|
|
67
|
+
turn.position.y = -box.min.y * scale;
|
|
68
|
+
|
|
69
|
+
const animate = vrm ? vrmAnimator(vrm) : clipAnimator(model, gltf.animations);
|
|
70
|
+
return {
|
|
71
|
+
object: root,
|
|
72
|
+
animate,
|
|
73
|
+
dispose() {
|
|
74
|
+
if (vrm && vrmModule) vrmModule.VRMUtils.deepDispose(model);
|
|
75
|
+
else disposeTree(model);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function download(url: string, maxBytes: number, signal?: AbortSignal): Promise<ArrayBuffer> {
|
|
81
|
+
const response = await fetch(url, { mode: 'cors', credentials: 'omit', referrerPolicy: 'no-referrer', signal });
|
|
82
|
+
if (!response.ok) throw new Error(`Model download failed (${response.status})`);
|
|
83
|
+
if (Number(response.headers.get('Content-Length') ?? '0') > maxBytes) throw new Error('Model is too large');
|
|
84
|
+
if (!response.body) return response.arrayBuffer();
|
|
85
|
+
const reader = response.body.getReader();
|
|
86
|
+
const chunks: Uint8Array[] = [];
|
|
87
|
+
let size = 0;
|
|
88
|
+
for (;;) {
|
|
89
|
+
const { done, value } = await reader.read();
|
|
90
|
+
if (done) break;
|
|
91
|
+
size += value.byteLength;
|
|
92
|
+
if (size > maxBytes) {
|
|
93
|
+
await reader.cancel();
|
|
94
|
+
throw new Error('Model is too large');
|
|
95
|
+
}
|
|
96
|
+
chunks.push(value);
|
|
97
|
+
}
|
|
98
|
+
const bytes = new Uint8Array(size);
|
|
99
|
+
let offset = 0;
|
|
100
|
+
for (const chunk of chunks) {
|
|
101
|
+
bytes.set(chunk, offset);
|
|
102
|
+
offset += chunk.byteLength;
|
|
103
|
+
}
|
|
104
|
+
return bytes.buffer;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* VRMs arrive in a T-pose with no animation. Lower the arms and swing arms
|
|
109
|
+
* and legs with speed, through the normalised humanoid rig so it works for
|
|
110
|
+
* any VRM, then let spring bones (hair, clothes) follow.
|
|
111
|
+
*/
|
|
112
|
+
function vrmAnimator(vrm: import('@pixiv/three-vrm').VRM): (motion: AvatarMotion) => void {
|
|
113
|
+
const bone = (name: Parameters<NonNullable<typeof vrm.humanoid>['getNormalizedBoneNode']>[0]) =>
|
|
114
|
+
vrm.humanoid?.getNormalizedBoneNode(name) ?? null;
|
|
115
|
+
const armL = bone('leftUpperArm');
|
|
116
|
+
const armR = bone('rightUpperArm');
|
|
117
|
+
const foreArmL = bone('leftLowerArm');
|
|
118
|
+
const foreArmR = bone('rightLowerArm');
|
|
119
|
+
const legL = bone('leftUpperLeg');
|
|
120
|
+
const legR = bone('rightUpperLeg');
|
|
121
|
+
const kneeL = bone('leftLowerLeg');
|
|
122
|
+
const kneeR = bone('rightLowerLeg');
|
|
123
|
+
// VRM 0.x rigs are mirrored on X and Z relative to VRM 1.0.
|
|
124
|
+
const mirror = vrm.meta?.metaVersion === '0' ? -1 : 1;
|
|
125
|
+
let phase = 0;
|
|
126
|
+
let stride = 0;
|
|
127
|
+
let air = 0;
|
|
128
|
+
|
|
129
|
+
return (motion) => {
|
|
130
|
+
if (motion.dt <= 0) return;
|
|
131
|
+
const blend = 1 - Math.exp(-motion.dt * 10);
|
|
132
|
+
const walking = motion.grounded ? MathUtils.clamp(motion.speed / 4.5, 0, 1) : 0;
|
|
133
|
+
stride += (walking - stride) * blend;
|
|
134
|
+
air += ((motion.grounded ? 0 : 1) - air) * blend;
|
|
135
|
+
phase += motion.dt * (4 + motion.speed * 1.2) * (stride > 0.01 ? 1 : 0);
|
|
136
|
+
const swing = Math.sin(phase) * 0.6 * stride;
|
|
137
|
+
|
|
138
|
+
const armDown = 1.2 - air * 0.5;
|
|
139
|
+
if (armL) armL.rotation.set(mirror * swing * 0.7, 0, mirror * -armDown);
|
|
140
|
+
if (armR) armR.rotation.set(mirror * -swing * 0.7, 0, mirror * armDown);
|
|
141
|
+
if (foreArmL) foreArmL.rotation.set(0, 0, mirror * -0.15);
|
|
142
|
+
if (foreArmR) foreArmR.rotation.set(0, 0, mirror * 0.15);
|
|
143
|
+
if (legL) legL.rotation.set(mirror * (-swing - air * 0.3), 0, 0);
|
|
144
|
+
if (legR) legR.rotation.set(mirror * (swing - air * 0.1), 0, 0);
|
|
145
|
+
if (kneeL) kneeL.rotation.set(mirror * (Math.max(0, swing) * 0.8 + air * 0.4), 0, 0);
|
|
146
|
+
if (kneeR) kneeR.rotation.set(mirror * (Math.max(0, -swing) * 0.8 + air * 0.2), 0, 0);
|
|
147
|
+
vrm.update(motion.dt);
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Plain glTF bodies: play an idle clip, and a walk/run clip while moving, if the file has them. */
|
|
152
|
+
function clipAnimator(model: Object3D, clips: import('three').AnimationClip[]): (motion: AvatarMotion) => void {
|
|
153
|
+
if (!clips.length) return () => {};
|
|
154
|
+
const mixer = new AnimationMixer(model);
|
|
155
|
+
const find = (pattern: RegExp) => clips.find((clip) => pattern.test(clip.name));
|
|
156
|
+
const idleClip = find(/idle|stand|breath/i) ?? clips[0];
|
|
157
|
+
const walkClip = find(/walk|run|jog/i);
|
|
158
|
+
const idle = mixer.clipAction(idleClip).play();
|
|
159
|
+
const walk: AnimationAction | null = walkClip && walkClip !== idleClip ? mixer.clipAction(walkClip) : null;
|
|
160
|
+
let moving = false;
|
|
161
|
+
return (motion) => {
|
|
162
|
+
const nowMoving = motion.grounded && motion.speed > 0.5;
|
|
163
|
+
if (walk && nowMoving !== moving) {
|
|
164
|
+
moving = nowMoving;
|
|
165
|
+
const [from, to] = moving ? [idle, walk] : [walk, idle];
|
|
166
|
+
to.reset().play();
|
|
167
|
+
from.crossFadeTo(to, 0.2, false);
|
|
168
|
+
}
|
|
169
|
+
mixer.update(motion.dt);
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function disposeTree(root: Object3D): void {
|
|
174
|
+
root.traverse((child) => {
|
|
175
|
+
if (!(child instanceof Mesh)) return;
|
|
176
|
+
child.geometry.dispose();
|
|
177
|
+
for (const material of Array.isArray(child.material) ? child.material : [child.material]) {
|
|
178
|
+
for (const value of Object.values(material)) {
|
|
179
|
+
if (value instanceof Texture) value.dispose();
|
|
180
|
+
}
|
|
181
|
+
material.dispose();
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|