@diffenderfer-games/hub 2.0.0-next.6 → 2.0.0-next.8
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 +58 -27
- package/dist/auto.d.ts +2 -2
- package/dist/auto.js +2 -2
- 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/index.d.ts +2 -2
- package/dist/index.js +2 -2
- 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 +88 -11
- package/dist/shared/index.d.ts.map +1 -1
- 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/src.js +172 -28
- package/dist/shared/src.js.map +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/dist/turns-rules.d.ts +2 -0
- package/dist/turns-rules.js +1 -0
- package/dist/turns.d.ts +23 -1
- package/dist/turns.d.ts.map +1 -1
- package/dist/turns.js +79 -13
- package/dist/turns.js.map +1 -1
- package/package.json +7 -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/turns/hub-turns.ts +25 -4
- package/src/core/turns/turn-game.ts +143 -21
- package/src/core/turns/your-turn-buzz.ts +69 -0
- package/src/core/version/client-version.gen.ts +1 -1
- package/src/index.ts +68 -67
- package/src/overlay/overlay.ts +3 -3
- package/src/turns-kit/check-rules.ts +116 -17
- package/src/turns-rules.ts +12 -0
- package/src/turns.ts +4 -3
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* y down, the head centre near (0, −96) (§14.2). Layers go back to front as
|
|
4
4
|
* in §3.2, with facial hair split into beard and moustache layers.
|
|
5
5
|
*/
|
|
6
|
-
import { partName, type AvatarLook } from '@diffenderfer-games/hub-contract/avatar';
|
|
6
|
+
import { partName, resolveColour, type AvatarLook } from '@diffenderfer-games/hub-contract/avatar';
|
|
7
7
|
import type { ArtSet } from '../art';
|
|
8
|
-
import type { HeadwearPart } from '../art/shape';
|
|
8
|
+
import type { HeadwearPart, Shape } from '../art/shape';
|
|
9
9
|
import { currentArt } from '../art-store';
|
|
10
10
|
import { EXPRESSIONS } from '../face';
|
|
11
11
|
import { n, type Warp } from '../geom';
|
|
@@ -13,13 +13,16 @@ import { g, path, rect, rot, sc, tr, type Clip, type SceneNode } from '../scene'
|
|
|
13
13
|
import { bodyLayout, groundShadow } from './body';
|
|
14
14
|
import { accessoryColour, lookColours, tones, type Colours } from './colours';
|
|
15
15
|
import { HEAD_PATH, LINE, paintShapes, SKULL_R, turnWarp, type Ctx } from './context';
|
|
16
|
-
import { glasses, goggles, gogglesStrapBack } from './eyewear';
|
|
16
|
+
import { FRAME_BLACK, glasses, goggles, gogglesStrapBack, type LensGlass } from './eyewear';
|
|
17
17
|
import { brow, eye } from './eyes';
|
|
18
18
|
import { cheeks, ears, head, type EarExtra } from './head';
|
|
19
19
|
import { extras, mouth, nose } from './mouth';
|
|
20
20
|
import { crown, handProp, headset } from './props';
|
|
21
21
|
import type { Anchor, Facing, Point, Pose, RenderOptions } from './types';
|
|
22
22
|
|
|
23
|
+
/** The lens tint drawn with a mirror finish. */
|
|
24
|
+
const MIRROR_TINT = 'p:lens-mirror';
|
|
25
|
+
|
|
23
26
|
/** The head centre's height above the feet. */
|
|
24
27
|
export const HEAD_Y = -96;
|
|
25
28
|
|
|
@@ -49,6 +52,7 @@ interface Parts {
|
|
|
49
52
|
readonly beard: ArtSet['beard'][string] | undefined;
|
|
50
53
|
readonly glasses: ArtSet['glasses'][string] | undefined;
|
|
51
54
|
readonly glassesColour: string;
|
|
55
|
+
readonly lensGlass: LensGlass;
|
|
52
56
|
readonly freckles: boolean;
|
|
53
57
|
readonly rosy: boolean;
|
|
54
58
|
readonly earExtra: EarExtra | undefined;
|
|
@@ -68,7 +72,8 @@ function resolveParts(look: AvatarLook, art: ArtSet): Parts {
|
|
|
68
72
|
hatColour: accessoryColour(look, hatAcc),
|
|
69
73
|
beard: art.beard[partName(look.facialHair.style)],
|
|
70
74
|
glasses: glassAcc ? art.glasses[partName(glassAcc.id)] : undefined,
|
|
71
|
-
glassesColour: glassAcc?.colour === undefined ?
|
|
75
|
+
glassesColour: glassAcc?.colour === undefined ? FRAME_BLACK : accessoryColour(look, glassAcc),
|
|
76
|
+
lensGlass: glassAcc?.tint === undefined ? {} : { tint: resolveColour(glassAcc.tint), ...(glassAcc.tint === MIRROR_TINT ? { isMirror: true } : {}) },
|
|
72
77
|
freckles: face?.id === 'face.freckles',
|
|
73
78
|
rosy: face?.id === 'face.rosy',
|
|
74
79
|
earExtra: earAcc ? { kind: earAcc.id === 'ears.hoops' ? 'hoops' : 'studs', colour: earAcc.colour === undefined ? '#f5c542' : accessoryColour(look, earAcc) } : undefined,
|
|
@@ -89,8 +94,41 @@ function backOfHead(phi: number): { readonly crescent: Clip; readonly half: Clip
|
|
|
89
94
|
};
|
|
90
95
|
}
|
|
91
96
|
|
|
97
|
+
/**
|
|
98
|
+
* The shoulder line in head space: hair below it hangs behind the body and
|
|
99
|
+
* shoulders, hair above it (or over the face) stays over the head (§14.6
|
|
100
|
+
* "Long hair"). The two halves overlap by a unit so the seam never shows.
|
|
101
|
+
*/
|
|
102
|
+
export const SHOULDER_Y = 36;
|
|
103
|
+
const ABOVE_SHOULDERS: Clip = { shapes: [rect(-200, -200, 400, 200 + SHOULDER_Y + 1, {}), path(HEAD_PATH.d(), {})] };
|
|
104
|
+
const BELOW_SHOULDERS: Clip = { shapes: [rect(-200, SHOULDER_Y - 1, 400, 300, {})] };
|
|
105
|
+
|
|
106
|
+
/** True when a layer reaches below the shoulder line. */
|
|
107
|
+
function hangs(shapes: readonly Shape[] | undefined): boolean {
|
|
108
|
+
return shapes?.some((s) => s.d.bottom() > SHOULDER_Y) === true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** A head's drawing: what goes behind the body (hanging hair) and the head itself. */
|
|
112
|
+
interface HeadNodes {
|
|
113
|
+
readonly behindBody: SceneNode[];
|
|
114
|
+
readonly head: SceneNode[];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Splits a hair layer at the shoulders: the part above stays in `over`, the part below goes in `behind`. */
|
|
118
|
+
function splitAtShoulders(nodes: readonly SceneNode[], doesHang: boolean, over: SceneNode[], behind: SceneNode[]): void {
|
|
119
|
+
if (nodes.length === 0) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (!doesHang) {
|
|
123
|
+
over.push(...nodes);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
over.push(g(nodes, { clip: ABOVE_SHOULDERS }));
|
|
127
|
+
behind.push(g(nodes, { clip: BELOW_SHOULDERS }));
|
|
128
|
+
}
|
|
129
|
+
|
|
92
130
|
/** The front and side views of the head. */
|
|
93
|
-
function frontHead(ctx: Ctx, p: Parts, isPlaying: boolean, status: Pose['status'], angle: number, gazeX: number):
|
|
131
|
+
function frontHead(ctx: Ctx, p: Parts, isPlaying: boolean, status: Pose['status'], angle: number, gazeX: number): HeadNodes {
|
|
94
132
|
const hatCut: Clip | undefined = p.hat && typeof p.hat.hides === 'number' ? { shapes: [rect(-120, p.hat.hides, 240, 260, {})] } : undefined;
|
|
95
133
|
const isHairShown = !(p.hat && p.hat.hides === 'all');
|
|
96
134
|
const cut = (nodes: SceneNode[]): SceneNode[] => (hatCut && nodes.length > 0 ? [g(nodes, { clip: hatCut })] : nodes);
|
|
@@ -98,13 +136,15 @@ function frontHead(ctx: Ctx, p: Parts, isPlaying: boolean, status: Pose['status'
|
|
|
98
136
|
const hatTones = tones(p.hatColour);
|
|
99
137
|
const warpFront = turnWarp((angle + gazeX * 6) * DEG);
|
|
100
138
|
const warpBack: Warp | undefined = ctx.phi === 0 ? undefined : (px, py) => [px - Math.sin(ctx.phi) * 8, py];
|
|
139
|
+
const behindBody: SceneNode[] = [];
|
|
101
140
|
const out: SceneNode[] = [];
|
|
141
|
+
if (isHairShown) {
|
|
142
|
+
// The hair behind the head is behind the body too: it hangs down the back.
|
|
143
|
+
behindBody.push(...cut(paintShapes(ctx.c, hairTie, p.hair?.back, warpBack)));
|
|
144
|
+
}
|
|
102
145
|
if (p.hat?.back) {
|
|
103
146
|
out.push(...paintShapes(ctx.c, hatTones, p.hat.back, warpBack));
|
|
104
147
|
}
|
|
105
|
-
if (isHairShown) {
|
|
106
|
-
out.push(...cut(paintShapes(ctx.c, hairTie, p.hair?.back, warpBack)));
|
|
107
|
-
}
|
|
108
148
|
if (!p.hat?.hidesEars) {
|
|
109
149
|
out.push(...ears(ctx, 'behind', p.earExtra));
|
|
110
150
|
}
|
|
@@ -114,13 +154,13 @@ function frontHead(ctx: Ctx, p: Parts, isPlaying: boolean, status: Pose['status'
|
|
|
114
154
|
// A turned head shows the back of the hair (the rear art) on the far side (§14.2).
|
|
115
155
|
const co = Math.cos(ctx.phi);
|
|
116
156
|
const shift: Warp = (px, py) => [px * (0.92 + 0.08 * co) - turnS * 5, py];
|
|
117
|
-
const rear = [
|
|
118
|
-
...(isHairShown ? cut(paintShapes(ctx.c, hairTie, p.hair?.rear, shift)) : []),
|
|
119
|
-
...(p.hat ? paintShapes(ctx.c, hatTones, p.hat.rear, shift) : []),
|
|
120
|
-
];
|
|
121
157
|
const clips = backOfHead(ctx.phi);
|
|
122
|
-
|
|
123
|
-
|
|
158
|
+
const onFarSide = (nodes: SceneNode[]): SceneNode[] => (nodes.length > 0 ? [g([g(nodes, { clip: clips.crescent })], { clip: clips.half })] : []);
|
|
159
|
+
if (isHairShown) {
|
|
160
|
+
splitAtShoulders(onFarSide(cut(paintShapes(ctx.c, hairTie, p.hair?.rear, shift))), hangs(p.hair?.rear), out, behindBody);
|
|
161
|
+
}
|
|
162
|
+
if (p.hat) {
|
|
163
|
+
out.push(...onFarSide(paintShapes(ctx.c, hatTones, p.hat.rear, shift)));
|
|
124
164
|
}
|
|
125
165
|
}
|
|
126
166
|
if (!p.hat?.hidesEars) {
|
|
@@ -139,7 +179,7 @@ function frontHead(ctx: Ctx, p: Parts, isPlaying: boolean, status: Pose['status'
|
|
|
139
179
|
out.push(...nose(ctx), ...mouth(ctx));
|
|
140
180
|
out.push(...paintShapes(ctx.c, hairTie, p.beard?.moustache, faceWarp));
|
|
141
181
|
if (isHairShown) {
|
|
142
|
-
|
|
182
|
+
splitAtShoulders(cut(paintShapes(ctx.c, hairTie, p.hair?.front, warpFront)), hangs(p.hair?.front), out, behindBody);
|
|
143
183
|
if (p.hair?.front?.some((s) => s.clip === 'head')) {
|
|
144
184
|
out.push(path(HEAD_PATH.d(), { fill: 'none', stroke: ctx.c.skinInk, sw: LINE }));
|
|
145
185
|
}
|
|
@@ -147,7 +187,7 @@ function frontHead(ctx: Ctx, p: Parts, isPlaying: boolean, status: Pose['status'
|
|
|
147
187
|
if (isPlaying) {
|
|
148
188
|
out.push(...goggles(ctx));
|
|
149
189
|
} else if (p.glasses) {
|
|
150
|
-
out.push(...glasses(ctx, p.glasses, p.glassesColour));
|
|
190
|
+
out.push(...glasses(ctx, p.glasses, p.glassesColour, p.lensGlass));
|
|
151
191
|
}
|
|
152
192
|
if (p.hat) {
|
|
153
193
|
out.push(...paintShapes(ctx.c, hatTones, p.hat.front, warpFront));
|
|
@@ -156,11 +196,11 @@ function frontHead(ctx: Ctx, p: Parts, isPlaying: boolean, status: Pose['status'
|
|
|
156
196
|
out.push(...headset(ctx, false));
|
|
157
197
|
}
|
|
158
198
|
out.push(...extras(ctx));
|
|
159
|
-
return out;
|
|
199
|
+
return { behindBody, head: out };
|
|
160
200
|
}
|
|
161
201
|
|
|
162
202
|
/** The back views (N, NE): the rear art wrapped past 180°. */
|
|
163
|
-
function backHead(ctx: Ctx, p: Parts, isPlaying: boolean, status: Pose['status'], angle: number):
|
|
203
|
+
function backHead(ctx: Ctx, p: Parts, isPlaying: boolean, status: Pose['status'], angle: number): HeadNodes {
|
|
164
204
|
const phi = (angle - 180) * DEG;
|
|
165
205
|
const warp = turnWarp(phi);
|
|
166
206
|
const isHairShown = !(p.hat && p.hat.hides === 'all');
|
|
@@ -186,7 +226,8 @@ function backHead(ctx: Ctx, p: Parts, isPlaying: boolean, status: Pose['status']
|
|
|
186
226
|
if (status === 'headset') {
|
|
187
227
|
out.push(...headset(ctx, true));
|
|
188
228
|
}
|
|
189
|
-
|
|
229
|
+
// From behind, long hair falls down the back, over the body.
|
|
230
|
+
return { behindBody: [], head: out };
|
|
190
231
|
}
|
|
191
232
|
|
|
192
233
|
/** The grey silhouette: a malformed code, a hidden player, or before the art loads. */
|
|
@@ -236,15 +277,19 @@ export function composeAvatar(look: AvatarLook | null, opts: RenderOptions = {})
|
|
|
236
277
|
const headY = HEAD_Y + b.headDy + (pose.bob?.y ?? 0);
|
|
237
278
|
const tilt = face.headTilt + b.tilt + (pose.tilt ?? 0);
|
|
238
279
|
const sq = face.squash + (pose.squash ?? 0);
|
|
239
|
-
const
|
|
280
|
+
const drawn = isBack ? backHead(ctx, parts, isPlaying, status, angle) : frontHead(ctx, parts, isPlaying, status, angle, gaze.x);
|
|
281
|
+
const headNodes = drawn.head;
|
|
240
282
|
const top = headY - 50 - Math.max(parts.hair?.top ?? 0, parts.hat?.top ?? 0);
|
|
241
283
|
if (status === 'crown') {
|
|
242
284
|
headNodes.push(...crown(Math.max(parts.hair?.top ?? 0, parts.hat?.top ?? 0)));
|
|
243
285
|
}
|
|
244
286
|
const handProps = status && !isBack ? handProp(status, b.prop.x, b.prop.y, parts.c.body, parts.c.bodyInk) : [];
|
|
287
|
+
const headTf = [tr(headX, headY), rot(tilt), sc(1 + sq, 1 - sq)];
|
|
245
288
|
const character: SceneNode[] = [
|
|
246
|
-
...b.behind,
|
|
247
|
-
g(
|
|
289
|
+
...b.behind,
|
|
290
|
+
...(drawn.behindBody.length > 0 ? [g(drawn.behindBody, { tf: headTf })] : []),
|
|
291
|
+
...b.body,
|
|
292
|
+
g(headNodes, { tf: headTf }),
|
|
248
293
|
...b.front, ...handProps,
|
|
249
294
|
];
|
|
250
295
|
const lifted = b.lift > 0 ? [g(character, { tf: [tr(0, -b.lift)] })] : character;
|
|
@@ -1,31 +1,93 @@
|
|
|
1
1
|
/** Glasses and the `goggles` status prop, both following the head turn. */
|
|
2
2
|
import type { GlassesPart } from '../art/shape';
|
|
3
|
-
import { ink } from '../colour';
|
|
3
|
+
import { deltaE, ink, lightness, outline } from '../colour';
|
|
4
4
|
import { blob, Path } from '../geom';
|
|
5
|
-
import { g, line, path, sc, tr, type SceneNode } from '../scene';
|
|
5
|
+
import { g, line, path, rect, sc, tr, type SceneNode } from '../scene';
|
|
6
6
|
import { feat, LINE, turnWarp, type Ctx } from './context';
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
export
|
|
8
|
+
/** What fills a lens: clear glass, a tint, or a mirror finish. */
|
|
9
|
+
export interface LensGlass {
|
|
10
|
+
/** The tint colour; absent for clear glass. */
|
|
11
|
+
readonly tint?: string;
|
|
12
|
+
readonly isMirror?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** The default frame colour. */
|
|
16
|
+
export const FRAME_BLACK = '#1d1a1e';
|
|
17
|
+
|
|
18
|
+
const SHINE = 'M-6 -4L-2 -7';
|
|
19
|
+
const MIRROR_STREAKS = 'M-9 7L3 -11M-1 10L9 -4';
|
|
20
|
+
|
|
21
|
+
/** The frame's colour and line width, and the dark edge a frame gets when it is light or close to the skin. */
|
|
22
|
+
interface FrameInk {
|
|
23
|
+
readonly colour: string;
|
|
24
|
+
readonly sw: number;
|
|
25
|
+
readonly edge: string | undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A frame stroke along a path, over its edge when it has one. */
|
|
29
|
+
function frameLine(d: string, frame: FrameInk, sw = frame.sw): SceneNode[] {
|
|
30
|
+
return [...(frame.edge === undefined ? [] : [path(d, line(frame.edge, sw + 1.6))]), path(d, line(frame.colour, sw))];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** One lens (or visor) in its own coordinates: the glass, then the frame. */
|
|
34
|
+
function lensNodes(d: string, glass: LensGlass, frame: FrameInk, shines: readonly (readonly [number, number])[]): SceneNode[] {
|
|
35
|
+
const out: SceneNode[] = [];
|
|
36
|
+
const clip = { shapes: [path(d, {})] };
|
|
37
|
+
if (glass.tint === undefined) {
|
|
38
|
+
out.push(path(d, { fill: '#ffffff', fillOpacity: 0.16 }));
|
|
39
|
+
} else if (glass.isMirror === true) {
|
|
40
|
+
out.push(path(d, { fill: glass.tint }), g([
|
|
41
|
+
rect(-60, 2, 120, 40, { fill: outline(glass.tint), opacity: 0.75 }),
|
|
42
|
+
...shines.map(([px, py]) => path(MIRROR_STREAKS, line('#ffffff', 2.6, 'round', 0.85), [tr(px, py)])),
|
|
43
|
+
], { clip }));
|
|
44
|
+
} else {
|
|
45
|
+
// Dark enough to read as sunglasses, light enough that winks show through.
|
|
46
|
+
out.push(path(d, { fill: glass.tint, fillOpacity: 0.86 }), g([rect(-60, 3, 120, 40, { fill: '#ffffff', opacity: 0.12 })], { clip }));
|
|
47
|
+
}
|
|
48
|
+
out.push(...frameLine(d, frame));
|
|
49
|
+
out.push(...shines.map(([px, py]) => path(SHINE, line('#ffffff', 1.8, 'round', glass.tint === undefined ? 0.7 : 0.9), [tr(px, py)])));
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Glasses: arms toward the ears, a lens at each eye (or one visor), and the bridge. */
|
|
54
|
+
export function glasses(ctx: Ctx, part: GlassesPart, colour: string, glass: LensGlass): SceneNode[] {
|
|
10
55
|
const out: SceneNode[] = [];
|
|
11
56
|
const warp = turnWarp(ctx.phi, ctx.dy);
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
57
|
+
const isEdged = lightness(colour) > 0.7 || deltaE(colour, ctx.c.skin) < 24;
|
|
58
|
+
const frame: FrameInk = { colour, sw: part.frame ?? 3.2, edge: isEdged ? ink(colour) : undefined };
|
|
59
|
+
const tinted: LensGlass = part.tint === undefined ? {} : { tint: glass.tint ?? part.tint, ...(glass.isMirror === true ? { isMirror: true } : {}) };
|
|
60
|
+
if (part.isSingle !== true) {
|
|
61
|
+
for (const side of [-1, 1] as const) {
|
|
62
|
+
if (feat(ctx, 30 * side).vis) {
|
|
63
|
+
out.push(...frameLine(new Path().m(30 * side, 2).l(45 * side, 0).d(warp), frame, 2.6));
|
|
64
|
+
}
|
|
15
65
|
}
|
|
16
66
|
}
|
|
67
|
+
if (part.visor) {
|
|
68
|
+
const shines = [feat(ctx, -17), feat(ctx, 17)].filter((f) => f.vis).map((f) => [f.x, 4 + ctx.dy] as const);
|
|
69
|
+
out.push(...lensNodes(part.visor.d(warp), tinted, frame, shines));
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
const lens = part.lens;
|
|
17
73
|
for (const side of [-1, 1] as const) {
|
|
18
74
|
const f = feat(ctx, 17 * side);
|
|
19
|
-
if (!f.vis) {
|
|
75
|
+
if (!lens || !f.vis || (part.isSingle === true && side < 0)) {
|
|
20
76
|
continue;
|
|
21
77
|
}
|
|
78
|
+
const flip = part.isMirrored === true && side < 0 ? -1 : 1;
|
|
22
79
|
out.push(g([
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
], { tf: [tr(f.x, 4 + ctx.dy), sc(f.sx, 1)] }));
|
|
80
|
+
...lensNodes(lens.d(), tinted, frame, [[0, 0]]),
|
|
81
|
+
...(part.bar ? frameLine(part.bar.d(), frame, 4.4) : []),
|
|
82
|
+
], { tf: [tr(f.x, 4 + ctx.dy), sc(f.sx * flip, 1)] }));
|
|
83
|
+
}
|
|
84
|
+
if (part.chain && feat(ctx, 30).vis) {
|
|
85
|
+
out.push(path(part.chain.d(warp), line(colour, 1.6, 'round', 0.9)));
|
|
86
|
+
}
|
|
87
|
+
if (part.bridge) {
|
|
88
|
+
const fb = feat(ctx, 0);
|
|
89
|
+
out.push(g(frameLine(part.bridge.d(), frame, 2.8), { tf: [tr(fb.x, 4 + ctx.dy), sc(fb.sx, 1)] }));
|
|
26
90
|
}
|
|
27
|
-
const fb = feat(ctx, 0);
|
|
28
|
-
out.push(g([path(part.bridge.d(), line(frame, 2.8))], { tf: [tr(fb.x, 4 + ctx.dy), sc(fb.sx, 1)] }));
|
|
29
91
|
return out;
|
|
30
92
|
}
|
|
31
93
|
|
package/src/avatar-three.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `@diffenderfer-games/hub/avatar/three`: avatars in three.js, as a
|
|
3
|
-
* `CanvasTexture` or a billboard `Sprite`. Pass your THREE namespace (the hub
|
|
4
|
-
* has no three dependency):
|
|
5
|
-
*
|
|
6
|
-
* ```ts
|
|
7
|
-
* import * as THREE from 'three';
|
|
8
|
-
* import { ready } from '@diffenderfer-games/hub/avatar';
|
|
9
|
-
* import { avatarSprite } from '@diffenderfer-games/hub/avatar/three';
|
|
10
|
-
*
|
|
11
|
-
* await ready();
|
|
12
|
-
* const { sprite } = avatarSprite(THREE, me.look, { frame: 'head', size: 128, height: 0.4 });
|
|
13
|
-
* helmet.add(sprite);
|
|
14
|
-
* ```
|
|
15
|
-
*
|
|
16
|
-
* @packageDocumentation
|
|
17
|
-
*/
|
|
18
|
-
export { avatarCanvasTexture, avatarSprite, type ThreeAvatarOptions, type ThreeAvatarTexture, type ThreeNs, type ThreeTextureNs } from './avatar/adapters/three';
|
|
1
|
+
/**
|
|
2
|
+
* `@diffenderfer-games/hub/avatar/three`: avatars in three.js, as a
|
|
3
|
+
* `CanvasTexture` or a billboard `Sprite`. Pass your THREE namespace (the hub
|
|
4
|
+
* has no three dependency):
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import * as THREE from 'three';
|
|
8
|
+
* import { ready } from '@diffenderfer-games/hub/avatar';
|
|
9
|
+
* import { avatarSprite } from '@diffenderfer-games/hub/avatar/three';
|
|
10
|
+
*
|
|
11
|
+
* await ready();
|
|
12
|
+
* const { sprite } = avatarSprite(THREE, me.look, { frame: 'head', size: 128, height: 0.4 });
|
|
13
|
+
* helmet.add(sprite);
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @packageDocumentation
|
|
17
|
+
*/
|
|
18
|
+
export { avatarCanvasTexture, avatarSprite, type ThreeAvatarOptions, type ThreeAvatarTexture, type ThreeNs, type ThreeTextureNs } from './avatar/adapters/three';
|
package/src/avatar.ts
CHANGED
|
@@ -24,8 +24,8 @@ import { renderElement } from './avatar/outputs/element';
|
|
|
24
24
|
import { renderSvg } from './avatar/outputs/svg';
|
|
25
25
|
|
|
26
26
|
export {
|
|
27
|
-
ACCESSORY_SLOTS, CATALOG, CATALOG_REVISION, DEFAULT_GEN, LOOK_CODE_PREFIX, PALETTES,
|
|
28
|
-
canonicalLook, decodeLook, defaultLook, distinctLooks, encodeLook, isLookCode, isValidLook, lookProblem,
|
|
27
|
+
ACCESSORY_SLOTS, CATALOG, CATALOG_REVISION, DEFAULT_GEN, FRAME_PRESETS, LOOK_CODE_PREFIX, PALETTES, SECOND_COLOUR_CODE,
|
|
28
|
+
canonicalLook, decodeLook, defaultLook, distinctLooks, encodeLook, isLookCode, isTinted, isValidLook, lookProblem,
|
|
29
29
|
normaliseHex, partById, partName, partsOf, randomLook, resolveColour, sameLook, seededRng,
|
|
30
30
|
type AccessorySlot, type AvatarLook, type CatalogEntry, type DecodedLook, type LookAccessory, type LookColour,
|
|
31
31
|
type LookPart, type LookProblem, type PaletteName, type PartCategory, type Preset, type RandomLookOptions, type Rng,
|