@diffenderfer-games/hub 2.0.0-next.6 → 2.0.0-next.7
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 +39 -28
- package/dist/avatar-pixi.js +1 -1
- package/dist/avatar-svg.js +1 -1
- package/dist/avatar.d.ts +2 -2
- package/dist/avatar.js +3 -3
- package/dist/doctor/cli.mjs +1 -1
- package/dist/shared/art.js +145 -3
- package/dist/shared/art.js.map +1 -1
- package/dist/shared/avatar.d.ts +3 -3
- package/dist/shared/client-version.gen.js +1 -1
- package/dist/shared/daily.js +1 -1
- package/dist/shared/describe.d.ts.map +1 -1
- package/dist/shared/describe.js +9 -1
- package/dist/shared/describe.js.map +1 -1
- package/dist/shared/index.d.ts +2 -2
- package/dist/shared/live-canvas.js +1 -1
- package/dist/shared/overlays.js +1 -1
- package/dist/shared/pose-key.js +1 -1
- package/dist/shared/svg-markup.js +112 -27
- package/dist/shared/svg-markup.js.map +1 -1
- package/dist/shared/svg.js +1 -1
- package/package.json +2 -2
- package/src/avatar/adapters/pixi-crowd.ts +302 -302
- package/src/avatar/adapters/pixi-types.ts +84 -84
- package/src/avatar/adapters/three.ts +126 -126
- package/src/avatar/art/glasses.ts +61 -3
- package/src/avatar/art/shape.ts +22 -3
- package/src/avatar/describe.ts +4 -1
- package/src/avatar/geom.ts +5 -0
- package/src/avatar/render/compose.ts +67 -22
- package/src/avatar/render/eyewear.ts +75 -13
- package/src/avatar-three.ts +18 -18
- package/src/avatar.ts +2 -2
- package/src/core/avatar/hub-avatar.ts +308 -308
- package/src/core/hub-progress.ts +269 -269
- package/src/core/page-account.ts +121 -121
- package/src/core/version/client-version.gen.ts +1 -1
- package/src/overlay/overlay.ts +3 -3
|
@@ -1,84 +1,84 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The slice of PixiJS the adapters use, typed structurally so the hub has no
|
|
3
|
-
* dependency on pixi. Pixi 8 is the target; Pixi 7 works through the same
|
|
4
|
-
* calls where they exist (`Texture.from(canvas)`, `texture.update()`).
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/** A canvas-like source Pixi can upload. */
|
|
8
|
-
export type PixiSource = HTMLCanvasElement | OffscreenCanvas;
|
|
9
|
-
|
|
10
|
-
/** A texture's uploadable source (Pixi 8). */
|
|
11
|
-
export interface PixiTextureSource {
|
|
12
|
-
update(): void;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** A texture (Pixi 8: `source.update()`; Pixi 7: `update()`). */
|
|
16
|
-
export interface PixiTexture {
|
|
17
|
-
readonly source?: PixiTextureSource;
|
|
18
|
-
update?(): void;
|
|
19
|
-
destroy(destroySource?: boolean): void;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** A display object's shared fields. */
|
|
23
|
-
export interface PixiDisplay {
|
|
24
|
-
x: number;
|
|
25
|
-
y: number;
|
|
26
|
-
visible: boolean;
|
|
27
|
-
alpha: number;
|
|
28
|
-
zIndex: number;
|
|
29
|
-
readonly scale: { x: number; y: number; set(x: number, y?: number): void };
|
|
30
|
-
destroy(options?: { children?: boolean }): void;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** A sprite showing a `TTexture`. */
|
|
34
|
-
export interface PixiSprite<TTexture extends PixiTexture = PixiTexture> extends PixiDisplay {
|
|
35
|
-
texture: TTexture;
|
|
36
|
-
readonly anchor: { set(x: number, y?: number): void };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** A container that takes `TChild` sprites. */
|
|
40
|
-
export interface PixiContainer<TChild extends PixiDisplay = PixiSprite> extends PixiDisplay {
|
|
41
|
-
sortableChildren: boolean;
|
|
42
|
-
addChild(child: TChild): void;
|
|
43
|
-
removeChild(child: TChild): void;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** A frame rectangle. */
|
|
47
|
-
export interface PixiRectangle {
|
|
48
|
-
readonly x: number;
|
|
49
|
-
readonly y: number;
|
|
50
|
-
readonly width: number;
|
|
51
|
-
readonly height: number;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* The PIXI namespace, as passed in by the game (`import * as PIXI from
|
|
56
|
-
* 'pixi.js'`, or an object of the named imports). It is generic over the
|
|
57
|
-
* game's own classes, inferred from what is passed, so the sprites and
|
|
58
|
-
* textures that come back are pixi's own types and go straight into a
|
|
59
|
-
* `Container` with no cast.
|
|
60
|
-
*/
|
|
61
|
-
export interface PixiNs<
|
|
62
|
-
TTexture extends PixiTexture = PixiTexture,
|
|
63
|
-
TSprite extends PixiSprite<TTexture> = PixiSprite<TTexture>,
|
|
64
|
-
TRectangle extends PixiRectangle = PixiRectangle,
|
|
65
|
-
> {
|
|
66
|
-
readonly Texture: {
|
|
67
|
-
from(source: PixiSource): TTexture;
|
|
68
|
-
new (options: { source: NonNullable<TTexture['source']>; frame: TRectangle }): TTexture;
|
|
69
|
-
};
|
|
70
|
-
readonly Sprite: new (texture?: TTexture) => TSprite;
|
|
71
|
-
/** Not used by the adapters; accepted so an object of the named imports can include it. */
|
|
72
|
-
readonly Container?: new () => PixiDisplay;
|
|
73
|
-
readonly Rectangle: new (x: number, y: number, width: number, height: number) => TRectangle;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** Re-uploads a texture after its canvas changed (Pixi 8 and 7). */
|
|
77
|
-
export function refresh(texture: PixiTexture): void {
|
|
78
|
-
if (texture.source) {
|
|
79
|
-
texture.source.update();
|
|
80
|
-
} else {
|
|
81
|
-
texture.update?.();
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The slice of PixiJS the adapters use, typed structurally so the hub has no
|
|
3
|
+
* dependency on pixi. Pixi 8 is the target; Pixi 7 works through the same
|
|
4
|
+
* calls where they exist (`Texture.from(canvas)`, `texture.update()`).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** A canvas-like source Pixi can upload. */
|
|
8
|
+
export type PixiSource = HTMLCanvasElement | OffscreenCanvas;
|
|
9
|
+
|
|
10
|
+
/** A texture's uploadable source (Pixi 8). */
|
|
11
|
+
export interface PixiTextureSource {
|
|
12
|
+
update(): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** A texture (Pixi 8: `source.update()`; Pixi 7: `update()`). */
|
|
16
|
+
export interface PixiTexture {
|
|
17
|
+
readonly source?: PixiTextureSource;
|
|
18
|
+
update?(): void;
|
|
19
|
+
destroy(destroySource?: boolean): void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** A display object's shared fields. */
|
|
23
|
+
export interface PixiDisplay {
|
|
24
|
+
x: number;
|
|
25
|
+
y: number;
|
|
26
|
+
visible: boolean;
|
|
27
|
+
alpha: number;
|
|
28
|
+
zIndex: number;
|
|
29
|
+
readonly scale: { x: number; y: number; set(x: number, y?: number): void };
|
|
30
|
+
destroy(options?: { children?: boolean }): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A sprite showing a `TTexture`. */
|
|
34
|
+
export interface PixiSprite<TTexture extends PixiTexture = PixiTexture> extends PixiDisplay {
|
|
35
|
+
texture: TTexture;
|
|
36
|
+
readonly anchor: { set(x: number, y?: number): void };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A container that takes `TChild` sprites. */
|
|
40
|
+
export interface PixiContainer<TChild extends PixiDisplay = PixiSprite> extends PixiDisplay {
|
|
41
|
+
sortableChildren: boolean;
|
|
42
|
+
addChild(child: TChild): void;
|
|
43
|
+
removeChild(child: TChild): void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A frame rectangle. */
|
|
47
|
+
export interface PixiRectangle {
|
|
48
|
+
readonly x: number;
|
|
49
|
+
readonly y: number;
|
|
50
|
+
readonly width: number;
|
|
51
|
+
readonly height: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The PIXI namespace, as passed in by the game (`import * as PIXI from
|
|
56
|
+
* 'pixi.js'`, or an object of the named imports). It is generic over the
|
|
57
|
+
* game's own classes, inferred from what is passed, so the sprites and
|
|
58
|
+
* textures that come back are pixi's own types and go straight into a
|
|
59
|
+
* `Container` with no cast.
|
|
60
|
+
*/
|
|
61
|
+
export interface PixiNs<
|
|
62
|
+
TTexture extends PixiTexture = PixiTexture,
|
|
63
|
+
TSprite extends PixiSprite<TTexture> = PixiSprite<TTexture>,
|
|
64
|
+
TRectangle extends PixiRectangle = PixiRectangle,
|
|
65
|
+
> {
|
|
66
|
+
readonly Texture: {
|
|
67
|
+
from(source: PixiSource): TTexture;
|
|
68
|
+
new (options: { source: NonNullable<TTexture['source']>; frame: TRectangle }): TTexture;
|
|
69
|
+
};
|
|
70
|
+
readonly Sprite: new (texture?: TTexture) => TSprite;
|
|
71
|
+
/** Not used by the adapters; accepted so an object of the named imports can include it. */
|
|
72
|
+
readonly Container?: new () => PixiDisplay;
|
|
73
|
+
readonly Rectangle: new (x: number, y: number, width: number, height: number) => TRectangle;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Re-uploads a texture after its canvas changed (Pixi 8 and 7). */
|
|
77
|
+
export function refresh(texture: PixiTexture): void {
|
|
78
|
+
if (texture.source) {
|
|
79
|
+
texture.source.update();
|
|
80
|
+
} else {
|
|
81
|
+
texture.update?.();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
@@ -1,126 +1,126 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* three.js helpers (§3.8): an avatar on a `CanvasTexture` (redrawn only when
|
|
3
|
-
* the pose changes, at most 30 times a second) and a ready-made `Sprite`.
|
|
4
|
-
* Pass the game's three namespace; the hub never imports three. A 3D head is
|
|
5
|
-
* out of scope (§14.4, Q11).
|
|
6
|
-
*/
|
|
7
|
-
import type { Animator } from '../animator/animator';
|
|
8
|
-
import { liveCanvas } from '../outputs/live-canvas';
|
|
9
|
-
import type { LookLike, Pose, RenderOptions } from '../render/types';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* The slice of three.js {@link avatarCanvasTexture} uses: `import * as THREE
|
|
13
|
-
* from 'three'`, or an object of the named imports. Generic over the game's
|
|
14
|
-
* own `CanvasTexture`, inferred from what is passed, so the texture that comes
|
|
15
|
-
* back is three's own type.
|
|
16
|
-
*/
|
|
17
|
-
export interface ThreeTextureNs<TTexture extends ThreeTexture = ThreeTexture> {
|
|
18
|
-
readonly CanvasTexture: new (canvas: HTMLCanvasElement | OffscreenCanvas) => TTexture;
|
|
19
|
-
readonly SRGBColorSpace?: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* The slice of three.js {@link avatarSprite} uses (the namespace, or the named
|
|
24
|
-
* imports). The sprite that comes back is three's own `Sprite`, so
|
|
25
|
-
* `scene.add(sprite)` needs no cast. `TMap` is what the game's
|
|
26
|
-
* `SpriteMaterial` takes as its `map` (three's `Texture`); it is inferred on
|
|
27
|
-
* its own so an inline object of the named imports infers too.
|
|
28
|
-
*/
|
|
29
|
-
export interface ThreeNs<
|
|
30
|
-
TTexture extends ThreeTexture = ThreeTexture,
|
|
31
|
-
TMaterial extends ThreeMaterial = ThreeMaterial,
|
|
32
|
-
TSprite extends ThreeSprite = ThreeSprite,
|
|
33
|
-
TMap = TTexture,
|
|
34
|
-
> extends ThreeTextureNs<TTexture> {
|
|
35
|
-
readonly SpriteMaterial: new (params: { map: TMap; transparent: boolean }) => TMaterial;
|
|
36
|
-
readonly Sprite: new (material: TMaterial) => TSprite;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** A texture. */
|
|
40
|
-
export interface ThreeTexture {
|
|
41
|
-
needsUpdate: boolean;
|
|
42
|
-
colorSpace?: string;
|
|
43
|
-
dispose(): void;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** A material. */
|
|
47
|
-
export interface ThreeMaterial {
|
|
48
|
-
dispose(): void;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** A sprite. */
|
|
52
|
-
export interface ThreeSprite {
|
|
53
|
-
readonly scale: { set(x: number, y: number, z: number): void };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** Options for the three.js helpers. */
|
|
57
|
-
export interface ThreeAvatarOptions extends RenderOptions {
|
|
58
|
-
readonly animator?: Animator;
|
|
59
|
-
/** Texture pixels per `size` pixel (default 2). */
|
|
60
|
-
readonly resolution?: number;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** A live avatar texture; `texture` is the game's own `CanvasTexture`. */
|
|
64
|
-
export interface ThreeAvatarTexture<TTexture extends ThreeTexture = ThreeTexture> {
|
|
65
|
-
readonly texture: TTexture;
|
|
66
|
-
/** Redraws for the animator's pose at `t` (or a given pose), at most 30 times a second. */
|
|
67
|
-
update(t: number | Pose): void;
|
|
68
|
-
setLook(like: LookLike): void;
|
|
69
|
-
dispose(): void;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const MIN_FRAME_MS = 1000 / 30;
|
|
73
|
-
|
|
74
|
-
/** A `CanvasTexture` showing the avatar. */
|
|
75
|
-
export function avatarCanvasTexture<TTexture extends ThreeTexture>(
|
|
76
|
-
three: ThreeTextureNs<TTexture>,
|
|
77
|
-
like: LookLike,
|
|
78
|
-
opts: ThreeAvatarOptions = {},
|
|
79
|
-
): ThreeAvatarTexture<TTexture> {
|
|
80
|
-
const live = liveCanvas(like, opts, opts.resolution ?? 2);
|
|
81
|
-
live.draw(opts.pose);
|
|
82
|
-
const texture = new three.CanvasTexture(live.canvas);
|
|
83
|
-
if (three.SRGBColorSpace) {
|
|
84
|
-
texture.colorSpace = three.SRGBColorSpace;
|
|
85
|
-
}
|
|
86
|
-
let last = -Infinity;
|
|
87
|
-
return {
|
|
88
|
-
texture,
|
|
89
|
-
update(t) {
|
|
90
|
-
if (typeof t === 'number') {
|
|
91
|
-
if (t - last < MIN_FRAME_MS) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
last = t;
|
|
95
|
-
}
|
|
96
|
-
const pose = typeof t === 'number' ? opts.animator?.pose(t) : t;
|
|
97
|
-
if (live.draw(pose ?? opts.pose)) {
|
|
98
|
-
texture.needsUpdate = true;
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
setLook(next) {
|
|
102
|
-
live.setLook(next);
|
|
103
|
-
opts.animator?.setLook(next);
|
|
104
|
-
if (live.draw(opts.pose)) {
|
|
105
|
-
texture.needsUpdate = true;
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
dispose() {
|
|
109
|
-
texture.dispose();
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** A billboard `Sprite` of the avatar, `height` world units tall (default 1). */
|
|
115
|
-
export function avatarSprite<TTexture extends ThreeTexture & TMap, TMaterial extends ThreeMaterial, TSprite extends ThreeSprite, TMap>(
|
|
116
|
-
three: ThreeNs<TTexture, TMaterial, TSprite, TMap>,
|
|
117
|
-
like: LookLike,
|
|
118
|
-
opts: ThreeAvatarOptions & { readonly height?: number } = {},
|
|
119
|
-
): { readonly sprite: TSprite; readonly avatar: ThreeAvatarTexture<TTexture> } {
|
|
120
|
-
const avatar = avatarCanvasTexture(three, like, opts);
|
|
121
|
-
const sprite = new three.Sprite(new three.SpriteMaterial({ map: avatar.texture, transparent: true }));
|
|
122
|
-
const h = opts.height ?? 1;
|
|
123
|
-
const aspect = opts.frame === 'full' ? 172 / 206 : 1;
|
|
124
|
-
sprite.scale.set(h * aspect, h, 1);
|
|
125
|
-
return { sprite, avatar };
|
|
126
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* three.js helpers (§3.8): an avatar on a `CanvasTexture` (redrawn only when
|
|
3
|
+
* the pose changes, at most 30 times a second) and a ready-made `Sprite`.
|
|
4
|
+
* Pass the game's three namespace; the hub never imports three. A 3D head is
|
|
5
|
+
* out of scope (§14.4, Q11).
|
|
6
|
+
*/
|
|
7
|
+
import type { Animator } from '../animator/animator';
|
|
8
|
+
import { liveCanvas } from '../outputs/live-canvas';
|
|
9
|
+
import type { LookLike, Pose, RenderOptions } from '../render/types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The slice of three.js {@link avatarCanvasTexture} uses: `import * as THREE
|
|
13
|
+
* from 'three'`, or an object of the named imports. Generic over the game's
|
|
14
|
+
* own `CanvasTexture`, inferred from what is passed, so the texture that comes
|
|
15
|
+
* back is three's own type.
|
|
16
|
+
*/
|
|
17
|
+
export interface ThreeTextureNs<TTexture extends ThreeTexture = ThreeTexture> {
|
|
18
|
+
readonly CanvasTexture: new (canvas: HTMLCanvasElement | OffscreenCanvas) => TTexture;
|
|
19
|
+
readonly SRGBColorSpace?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The slice of three.js {@link avatarSprite} uses (the namespace, or the named
|
|
24
|
+
* imports). The sprite that comes back is three's own `Sprite`, so
|
|
25
|
+
* `scene.add(sprite)` needs no cast. `TMap` is what the game's
|
|
26
|
+
* `SpriteMaterial` takes as its `map` (three's `Texture`); it is inferred on
|
|
27
|
+
* its own so an inline object of the named imports infers too.
|
|
28
|
+
*/
|
|
29
|
+
export interface ThreeNs<
|
|
30
|
+
TTexture extends ThreeTexture = ThreeTexture,
|
|
31
|
+
TMaterial extends ThreeMaterial = ThreeMaterial,
|
|
32
|
+
TSprite extends ThreeSprite = ThreeSprite,
|
|
33
|
+
TMap = TTexture,
|
|
34
|
+
> extends ThreeTextureNs<TTexture> {
|
|
35
|
+
readonly SpriteMaterial: new (params: { map: TMap; transparent: boolean }) => TMaterial;
|
|
36
|
+
readonly Sprite: new (material: TMaterial) => TSprite;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A texture. */
|
|
40
|
+
export interface ThreeTexture {
|
|
41
|
+
needsUpdate: boolean;
|
|
42
|
+
colorSpace?: string;
|
|
43
|
+
dispose(): void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A material. */
|
|
47
|
+
export interface ThreeMaterial {
|
|
48
|
+
dispose(): void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** A sprite. */
|
|
52
|
+
export interface ThreeSprite {
|
|
53
|
+
readonly scale: { set(x: number, y: number, z: number): void };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Options for the three.js helpers. */
|
|
57
|
+
export interface ThreeAvatarOptions extends RenderOptions {
|
|
58
|
+
readonly animator?: Animator;
|
|
59
|
+
/** Texture pixels per `size` pixel (default 2). */
|
|
60
|
+
readonly resolution?: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** A live avatar texture; `texture` is the game's own `CanvasTexture`. */
|
|
64
|
+
export interface ThreeAvatarTexture<TTexture extends ThreeTexture = ThreeTexture> {
|
|
65
|
+
readonly texture: TTexture;
|
|
66
|
+
/** Redraws for the animator's pose at `t` (or a given pose), at most 30 times a second. */
|
|
67
|
+
update(t: number | Pose): void;
|
|
68
|
+
setLook(like: LookLike): void;
|
|
69
|
+
dispose(): void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const MIN_FRAME_MS = 1000 / 30;
|
|
73
|
+
|
|
74
|
+
/** A `CanvasTexture` showing the avatar. */
|
|
75
|
+
export function avatarCanvasTexture<TTexture extends ThreeTexture>(
|
|
76
|
+
three: ThreeTextureNs<TTexture>,
|
|
77
|
+
like: LookLike,
|
|
78
|
+
opts: ThreeAvatarOptions = {},
|
|
79
|
+
): ThreeAvatarTexture<TTexture> {
|
|
80
|
+
const live = liveCanvas(like, opts, opts.resolution ?? 2);
|
|
81
|
+
live.draw(opts.pose);
|
|
82
|
+
const texture = new three.CanvasTexture(live.canvas);
|
|
83
|
+
if (three.SRGBColorSpace) {
|
|
84
|
+
texture.colorSpace = three.SRGBColorSpace;
|
|
85
|
+
}
|
|
86
|
+
let last = -Infinity;
|
|
87
|
+
return {
|
|
88
|
+
texture,
|
|
89
|
+
update(t) {
|
|
90
|
+
if (typeof t === 'number') {
|
|
91
|
+
if (t - last < MIN_FRAME_MS) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
last = t;
|
|
95
|
+
}
|
|
96
|
+
const pose = typeof t === 'number' ? opts.animator?.pose(t) : t;
|
|
97
|
+
if (live.draw(pose ?? opts.pose)) {
|
|
98
|
+
texture.needsUpdate = true;
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
setLook(next) {
|
|
102
|
+
live.setLook(next);
|
|
103
|
+
opts.animator?.setLook(next);
|
|
104
|
+
if (live.draw(opts.pose)) {
|
|
105
|
+
texture.needsUpdate = true;
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
dispose() {
|
|
109
|
+
texture.dispose();
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** A billboard `Sprite` of the avatar, `height` world units tall (default 1). */
|
|
115
|
+
export function avatarSprite<TTexture extends ThreeTexture & TMap, TMaterial extends ThreeMaterial, TSprite extends ThreeSprite, TMap>(
|
|
116
|
+
three: ThreeNs<TTexture, TMaterial, TSprite, TMap>,
|
|
117
|
+
like: LookLike,
|
|
118
|
+
opts: ThreeAvatarOptions & { readonly height?: number } = {},
|
|
119
|
+
): { readonly sprite: TSprite; readonly avatar: ThreeAvatarTexture<TTexture> } {
|
|
120
|
+
const avatar = avatarCanvasTexture(three, like, opts);
|
|
121
|
+
const sprite = new three.Sprite(new three.SpriteMaterial({ map: avatar.texture, transparent: true }));
|
|
122
|
+
const h = opts.height ?? 1;
|
|
123
|
+
const aspect = opts.frame === 'full' ? 172 / 206 : 1;
|
|
124
|
+
sprite.scale.set(h * aspect, h, 1);
|
|
125
|
+
return { sprite, avatar };
|
|
126
|
+
}
|
|
@@ -1,9 +1,67 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Glasses (catalog `glasses.*`). A lens is drawn at each eye (centred on it,
|
|
3
|
+
* authored for the viewer's right eye); `sport` is one visor across both
|
|
4
|
+
* eyes and the monocle is one lens. Lenses are big enough to cover the eye
|
|
5
|
+
* whites (about 21 × 25), so winks and sleepy eyes show through.
|
|
6
|
+
*/
|
|
7
|
+
import { arc, blob, Path, roundedRect } from '../geom';
|
|
3
8
|
import type { GlassesPart } from './shape';
|
|
4
9
|
|
|
10
|
+
const ring = (r: number): Path => blob(arc(0, 0, r, r, 0, 330, 11));
|
|
11
|
+
|
|
5
12
|
/** Every glasses part by name. */
|
|
6
13
|
export const GLASSES_ART: Readonly<Record<string, GlassesPart>> = {
|
|
7
|
-
round: { lens:
|
|
14
|
+
round: { lens: ring(13), bridge: blob([[-4, 0], [0, -3], [4, 0]], false) },
|
|
8
15
|
square: { lens: roundedRect(-13.5, -10.5, 27, 21, 5), bridge: blob([[-3.5, -2], [0, -4], [3.5, -2]], false) },
|
|
16
|
+
|
|
17
|
+
owl: { lens: ring(16.5), bridge: blob([[-1.5, -1], [0, -2.5], [1.5, -1]], false), frame: 4.4 },
|
|
18
|
+
|
|
19
|
+
'cat-eye': {
|
|
20
|
+
lens: blob([[-13, -8], [-4, -12], [9, -12.5], [18, -17, 'c'], [15.5, 1], [9, 11], [-3, 13], [-11.5, 8]]),
|
|
21
|
+
bridge: blob([[-4, -3], [0, -5], [4, -3]], false),
|
|
22
|
+
isMirrored: true,
|
|
23
|
+
frame: 3.6,
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
'half-rim': {
|
|
27
|
+
lens: roundedRect(-14, -11, 28, 22, 6),
|
|
28
|
+
bar: blob([[-14.6, -4], [-13.4, -11.2], [13.4, -11.2], [14.6, -4]], false),
|
|
29
|
+
bridge: new Path().m(-4, -6).l(4, -6),
|
|
30
|
+
frame: 1.5,
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
aviator: {
|
|
34
|
+
lens: blob([[-12, -11], [13, -11.5], [15.5, -3], [11.5, 9], [3, 14], [-6, 12.5], [-12.5, 3]]),
|
|
35
|
+
bridge: new Path().m(-5, -9.5).l(5, -9.5).m(-4.5, -2).q(0, -5, 4.5, -2),
|
|
36
|
+
isMirrored: true,
|
|
37
|
+
frame: 2.6,
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
sunglasses: {
|
|
41
|
+
lens: blob([[-14.5, -12.5, 'c'], [15.5, -13.5, 'c'], [15, 1], [10.5, 11.5], [-8.5, 11.5], [-14, 3]]),
|
|
42
|
+
bridge: blob([[-3.5, -6], [0, -8], [3.5, -6]], false),
|
|
43
|
+
isMirrored: true,
|
|
44
|
+
frame: 4.2,
|
|
45
|
+
tint: '#262a33',
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
sport: {
|
|
49
|
+
visor: blob([[-40, -8], [-20, -12], [0, -10], [20, -12], [40, -8], [38, 6], [26, 15], [9, 14], [0, 9], [-9, 14], [-26, 15], [-38, 6]]),
|
|
50
|
+
frame: 3,
|
|
51
|
+
tint: '#2d6fd6',
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
hearts: {
|
|
55
|
+
lens: blob([[0, -6, 'c'], [6, -13], [13, -12.5], [16.5, -4], [11.5, 6], [0, 15.5, 'c'], [-11.5, 6], [-16.5, -4], [-13, -12.5], [-6, -13]]),
|
|
56
|
+
bridge: blob([[-1.5, -4], [0, -6], [1.5, -4]], false),
|
|
57
|
+
frame: 3,
|
|
58
|
+
tint: '#e0708f',
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
monocle: {
|
|
62
|
+
lens: ring(13.5),
|
|
63
|
+
isSingle: true,
|
|
64
|
+
chain: new Path().m(26, 16).c(32, 30, 38, 38, 46, 44),
|
|
65
|
+
frame: 2.8,
|
|
66
|
+
},
|
|
9
67
|
};
|
package/src/avatar/art/shape.ts
CHANGED
|
@@ -48,10 +48,29 @@ export interface FacialHairPart {
|
|
|
48
48
|
readonly moustache?: readonly Shape[];
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Glasses, drawn per lens so they follow the eyes through a head turn. Lens
|
|
53
|
+
* paths are centred on the eye and drawn for the viewer's right eye.
|
|
54
|
+
*/
|
|
52
55
|
export interface GlassesPart {
|
|
53
|
-
|
|
54
|
-
readonly
|
|
56
|
+
/** One lens (absent for a visor). */
|
|
57
|
+
readonly lens?: Path;
|
|
58
|
+
/** The bridge over the nose, centred between the eyes. */
|
|
59
|
+
readonly bridge?: Path;
|
|
60
|
+
/** The frame's line width (default 3.2). */
|
|
61
|
+
readonly frame?: number;
|
|
62
|
+
/** A heavier bar along each lens's top (half-rims). */
|
|
63
|
+
readonly bar?: Path;
|
|
64
|
+
/** Mirror the lens for the other eye (shapes with an outer and inner side). */
|
|
65
|
+
readonly isMirrored?: true;
|
|
66
|
+
/** One visor across both eyes in head space instead of lenses (sport). */
|
|
67
|
+
readonly visor?: Path;
|
|
68
|
+
/** Only the right-hand lens, with no arms or bridge (the monocle). */
|
|
69
|
+
readonly isSingle?: true;
|
|
70
|
+
/** The monocle's chain, in head space. */
|
|
71
|
+
readonly chain?: Path;
|
|
72
|
+
/** The lens tint when the look has none (tinted glasses only). */
|
|
73
|
+
readonly tint?: string;
|
|
55
74
|
}
|
|
56
75
|
|
|
57
76
|
/**
|
package/src/avatar/describe.ts
CHANGED
|
@@ -19,7 +19,10 @@ const BEARD_WORDS: Readonly<Record<string, string>> = {
|
|
|
19
19
|
chinstrap: 'a chinstrap beard', short: 'a short beard', full: 'a beard', long: 'a long beard',
|
|
20
20
|
};
|
|
21
21
|
const EXTRA_WORDS: Readonly<Record<string, string>> = {
|
|
22
|
-
'glasses.round': 'round glasses', 'glasses.square': 'square glasses', '
|
|
22
|
+
'glasses.round': 'round glasses', 'glasses.square': 'square glasses', 'glasses.owl': 'big round glasses',
|
|
23
|
+
'glasses.cat-eye': 'cat-eye glasses', 'glasses.half-rim': 'half-rim glasses', 'glasses.aviator': 'aviator glasses',
|
|
24
|
+
'glasses.sunglasses': 'sunglasses', 'glasses.sport': 'sport sunglasses', 'glasses.hearts': 'heart glasses', 'glasses.monocle': 'a monocle',
|
|
25
|
+
'headwear.hijab': 'a hijab',
|
|
23
26
|
'headwear.headwrap': 'a headwrap', 'headwear.beanie': 'a beanie', 'headwear.cap': 'a cap',
|
|
24
27
|
'face.freckles': 'freckles', 'face.rosy': 'rosy cheeks', 'ears.studs': 'earrings', 'ears.hoops': 'hoop earrings',
|
|
25
28
|
};
|
package/src/avatar/geom.ts
CHANGED
|
@@ -56,6 +56,11 @@ export class Path {
|
|
|
56
56
|
return this;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/** The lowest y of any point (control points included, so it never under-reports). */
|
|
60
|
+
bottom(): number {
|
|
61
|
+
return this.cmds.reduce((low, { p }) => p.reduce((lo, v, i) => (i % 2 === 1 ? Math.max(lo, v) : lo), low), -Infinity);
|
|
62
|
+
}
|
|
63
|
+
|
|
59
64
|
/** The SVG `d` string, optionally warping every point. */
|
|
60
65
|
d(warp?: Warp): string {
|
|
61
66
|
return this.cmds.map(({ c, p }) => {
|