@lemoncat7/dsh-theme-xiaohei 0.4.6 → 0.5.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 +15 -9
- package/THIRD_PARTY_NOTICES.md +13 -1
- package/docs/dsh-021-compatibility.md +23 -0
- package/docs/releases/0.5.0.md +24 -0
- package/docs/rift-interaction.md +35 -0
- package/docs/rift-preview.md +39 -0
- package/lib/client.companion.js +3 -4
- package/lib/client.decorations.js +224 -51
- package/lib/client.js +532 -2405
- package/lib/index.d.ts +1 -2
- package/lib/index.js +393 -11
- package/lib/rift/boot.d.ts +6 -0
- package/package.json +29 -19
- package/lib/appearance.d.ts +0 -12
- package/lib/appearance.js +0 -42
- package/lib/blink.d.ts +0 -4
- package/lib/blink.js +0 -110
- package/lib/bloub-heixiu/ambient.d.ts +0 -3
- package/lib/bloub-heixiu/ambient.js +0 -12
- package/lib/bloub-heixiu/character.d.ts +0 -33
- package/lib/bloub-heixiu/character.js +0 -124
- package/lib/bloub-heixiu/effects.d.ts +0 -4
- package/lib/bloub-heixiu/effects.js +0 -91
- package/lib/bloub-heixiu/flight.d.ts +0 -15
- package/lib/bloub-heixiu/flight.js +0 -66
- package/lib/bloub-heixiu/follow.d.ts +0 -13
- package/lib/bloub-heixiu/follow.js +0 -23
- package/lib/bloub-heixiu/layout.d.ts +0 -34
- package/lib/bloub-heixiu/layout.js +0 -26
- package/lib/bloub-heixiu/renderer.d.ts +0 -16
- package/lib/bloub-heixiu/renderer.js +0 -94
- package/lib/bloub-heixiu/runtime.d.ts +0 -6
- package/lib/bloub-heixiu/runtime.js +0 -440
- package/lib/bloub-heixiu/signals.d.ts +0 -17
- package/lib/bloub-heixiu/signals.js +0 -55
- package/lib/bloub-heixiu/styles.d.ts +0 -2
- package/lib/bloub-heixiu/styles.js +0 -98
- package/lib/boot-appearance.d.ts +0 -10
- package/lib/boot-appearance.js +0 -47
- package/lib/boot-loader.d.ts +0 -5
- package/lib/boot-loader.js +0 -158
- package/lib/chrome/accessibility.d.ts +0 -3
- package/lib/chrome/accessibility.js +0 -69
- package/lib/chrome/blink.d.ts +0 -3
- package/lib/chrome/blink.js +0 -15
- package/lib/chrome/composer-avatar.d.ts +0 -10
- package/lib/chrome/composer-avatar.js +0 -599
- package/lib/chrome/composer-send-heixiu.d.ts +0 -3
- package/lib/chrome/composer-send-heixiu.js +0 -143
- package/lib/chrome/composer.d.ts +0 -3
- package/lib/chrome/composer.js +0 -135
- package/lib/chrome/controls.d.ts +0 -8
- package/lib/chrome/controls.js +0 -348
- package/lib/chrome/conversation-messages.d.ts +0 -3
- package/lib/chrome/conversation-messages.js +0 -329
- package/lib/chrome/conversation-submissions.d.ts +0 -3
- package/lib/chrome/conversation-submissions.js +0 -68
- package/lib/chrome/conversation.d.ts +0 -3
- package/lib/chrome/conversation.js +0 -67
- package/lib/chrome/frames.d.ts +0 -6
- package/lib/chrome/frames.js +0 -134
- package/lib/chrome/gaze.d.ts +0 -3
- package/lib/chrome/gaze.js +0 -103
- package/lib/chrome/identity.d.ts +0 -6
- package/lib/chrome/identity.js +0 -47
- package/lib/chrome/overlays.d.ts +0 -3
- package/lib/chrome/overlays.js +0 -104
- package/lib/chrome/portal.d.ts +0 -6
- package/lib/chrome/portal.js +0 -169
- package/lib/chrome/reactions.d.ts +0 -3
- package/lib/chrome/reactions.js +0 -85
- package/lib/chrome/sidebar.d.ts +0 -3
- package/lib/chrome/sidebar.js +0 -313
- package/lib/chrome/tokens.d.ts +0 -3
- package/lib/chrome/tokens.js +0 -224
- package/lib/chrome/workspace.d.ts +0 -3
- package/lib/chrome/workspace.js +0 -94
- package/lib/chrome.d.ts +0 -10
- package/lib/chrome.js +0 -46
- package/lib/client-entry.d.ts +0 -3
- package/lib/client-entry.js +0 -3
- package/lib/client.d.ts +0 -10
- package/lib/client.pose-chin-dark.js +0 -4
- package/lib/client.pose-chin-light.js +0 -4
- package/lib/client.pose-doze-dark.js +0 -4
- package/lib/client.pose-doze-light.js +0 -4
- package/lib/client.pose-halfpeek-dark.js +0 -4
- package/lib/client.pose-halfpeek-light.js +0 -4
- package/lib/client.pose-peek-dark.js +0 -4
- package/lib/client.pose-peek-light.js +0 -4
- package/lib/client.pose-seated-dark.js +0 -4
- package/lib/client.pose-seated-light.js +0 -4
- package/lib/client.rig-chin-dark.js +0 -4
- package/lib/client.rig-chin-light.js +0 -4
- package/lib/client.rig-halfpeek-dark.js +0 -4
- package/lib/client.rig-halfpeek-light.js +0 -4
- package/lib/client.rig-peek-dark.js +0 -4
- package/lib/client.rig-peek-light.js +0 -4
- package/lib/client.rig-seated-dark.js +0 -4
- package/lib/client.rig-seated-light.js +0 -4
- package/lib/companion-entry.d.ts +0 -3
- package/lib/companion-entry.js +0 -3
- package/lib/composer-avatar.d.ts +0 -18
- package/lib/composer-avatar.js +0 -441
- package/lib/composer-draft-preview.d.ts +0 -4
- package/lib/composer-draft-preview.js +0 -8
- package/lib/composer-send-heixiu.d.ts +0 -8
- package/lib/composer-send-heixiu.js +0 -133
- package/lib/current-session.d.ts +0 -6
- package/lib/current-session.js +0 -5
- package/lib/decorations-entry.d.ts +0 -3
- package/lib/decorations-entry.js +0 -18
- package/lib/deferred-companion.d.ts +0 -5
- package/lib/deferred-companion.js +0 -9
- package/lib/deferred-decorations.d.ts +0 -3
- package/lib/deferred-decorations.js +0 -51
- package/lib/gaze.d.ts +0 -4
- package/lib/gaze.js +0 -230
- package/lib/generated-backgrounds.d.ts +0 -14
- package/lib/generated-backgrounds.js +0 -14
- package/lib/generated-character-motion.d.ts +0 -4
- package/lib/generated-character-motion.js +0 -2
- package/lib/generated-identity.d.ts +0 -6
- package/lib/generated-identity.js +0 -6
- package/lib/generated-keyart.d.ts +0 -34
- package/lib/generated-keyart.js +0 -34
- package/lib/generated-loading-assets.d.ts +0 -4
- package/lib/generated-loading-assets.js +0 -4
- package/lib/heixiu-interactions.d.ts +0 -10
- package/lib/heixiu-interactions.js +0 -161
- package/lib/host-contract.d.ts +0 -9
- package/lib/host-contract.js +0 -13
- package/lib/host-dom.d.ts +0 -11
- package/lib/host-dom.js +0 -72
- package/lib/loading-heixiu.d.ts +0 -8
- package/lib/loading-heixiu.js +0 -287
- package/lib/metallic-brand-mark.d.ts +0 -10
- package/lib/metallic-brand-mark.js +0 -296
- package/lib/plugin.d.ts +0 -6
- package/lib/plugin.js +0 -60
- package/lib/portal.d.ts +0 -40
- package/lib/portal.js +0 -225
- package/lib/reactions.d.ts +0 -20
- package/lib/reactions.js +0 -380
- package/lib/scene/avatar-model.d.ts +0 -6
- package/lib/scene/avatar-model.js +0 -646
- package/lib/scene/avatar-motion.d.ts +0 -3
- package/lib/scene/avatar-motion.js +0 -799
- package/lib/scene/character-assets.d.ts +0 -13
- package/lib/scene/character-assets.js +0 -26
- package/lib/scene/character-expression.d.ts +0 -4
- package/lib/scene/character-expression.js +0 -15
- package/lib/scene/character-eye-material.d.ts +0 -13
- package/lib/scene/character-eye-material.js +0 -88
- package/lib/scene/character-eye-renderer.d.ts +0 -5
- package/lib/scene/character-eye-renderer.js +0 -80
- package/lib/scene/character-eye-rig.d.ts +0 -20
- package/lib/scene/character-eye-rig.js +0 -35
- package/lib/scene/character-gaze.d.ts +0 -16
- package/lib/scene/character-gaze.js +0 -36
- package/lib/scene/character-idle.d.ts +0 -17
- package/lib/scene/character-idle.js +0 -65
- package/lib/scene/character-layout.d.ts +0 -24
- package/lib/scene/character-layout.js +0 -51
- package/lib/scene/character-motion-clock.d.ts +0 -18
- package/lib/scene/character-motion-clock.js +0 -70
- package/lib/scene/character-motion-types.d.ts +0 -12
- package/lib/scene/character-motion-types.js +0 -2
- package/lib/scene/character-motion.d.ts +0 -6
- package/lib/scene/character-motion.js +0 -187
- package/lib/scene/character-poses.d.ts +0 -26
- package/lib/scene/character-poses.js +0 -11
- package/lib/scene/character-rig-renderer.d.ts +0 -5
- package/lib/scene/character-rig-renderer.js +0 -95
- package/lib/scene/character-rig.d.ts +0 -22
- package/lib/scene/character-rig.js +0 -61
- package/lib/scene/heixiu-feedback.d.ts +0 -3
- package/lib/scene/heixiu-feedback.js +0 -64
- package/lib/scene/heixiu-interactions.d.ts +0 -3
- package/lib/scene/heixiu-interactions.js +0 -92
- package/lib/scene/ids.d.ts +0 -5
- package/lib/scene/ids.js +0 -5
- package/lib/scene/pointer-bridge.d.ts +0 -21
- package/lib/scene/pointer-bridge.js +0 -155
- package/lib/scene/runtime.d.ts +0 -12
- package/lib/scene/runtime.js +0 -74
- package/lib/scene/state-transitions.d.ts +0 -3
- package/lib/scene/state-transitions.js +0 -37
- package/lib/scene/styles.d.ts +0 -5
- package/lib/scene/styles.js +0 -69
- package/lib/scene/sylva-performance.d.ts +0 -11
- package/lib/scene/sylva-performance.js +0 -58
- package/lib/scene/wallpaper-character-styles.d.ts +0 -3
- package/lib/scene/wallpaper-character-styles.js +0 -49
- package/lib/scene/wallpaper-character.d.ts +0 -5
- package/lib/scene/wallpaper-character.js +0 -222
- package/lib/scene.d.ts +0 -8
- package/lib/scene.js +0 -8
- package/lib/sidebar-glass.d.ts +0 -16
- package/lib/sidebar-glass.js +0 -168
- package/lib/sidebar-heixiu-escape.d.ts +0 -44
- package/lib/sidebar-heixiu-escape.js +0 -340
- package/lib/sidebar-heixiu-roaming.d.ts +0 -27
- package/lib/sidebar-heixiu-roaming.js +0 -310
- package/lib/sidebar-reveal.d.ts +0 -8
- package/lib/sidebar-reveal.js +0 -59
- package/lib/state.d.ts +0 -21
- package/lib/state.js +0 -123
- package/lib/theme.d.ts +0 -19
- package/lib/theme.js +0 -188
- package/lib/vendor/bloub/decor.d.ts +0 -114
- package/lib/vendor/bloub/decor.js +0 -198
- package/lib/vendor/bloub/engine.d.ts +0 -213
- package/lib/vendor/bloub/engine.js +0 -452
- package/lib/vendor/bloub/expressions.d.ts +0 -35
- package/lib/vendor/bloub/expressions.js +0 -143
- package/lib/vendor/bloub/eyefit.d.ts +0 -75
- package/lib/vendor/bloub/eyefit.js +0 -393
- package/lib/vendor/bloub/face.d.ts +0 -63
- package/lib/vendor/bloub/face.js +0 -106
- package/lib/vendor/bloub/math.d.ts +0 -22
- package/lib/vendor/bloub/math.js +0 -36
- package/lib/vendor/bloub/profiles.d.ts +0 -8
- package/lib/vendor/bloub/profiles.js +0 -20
- package/lib/vendor/bloub/repere.d.ts +0 -30
- package/lib/vendor/bloub/repere.js +0 -32
- package/lib/vendor/bloub/shape.d.ts +0 -80
- package/lib/vendor/bloub/shape.js +0 -268
- package/lib/vendor/bloub/skins.d.ts +0 -36
- package/lib/vendor/bloub/skins.js +0 -74
- package/lib/vendor/bloub/states.d.ts +0 -87
- package/lib/vendor/bloub/states.js +0 -468
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { CharacterRig } from './character-motion-types.js';
|
|
2
|
-
export declare const CHARACTER_POSE_NAMES: readonly ["seated", "peek", "chin", "doze", "halfpeek"];
|
|
3
|
-
export type CharacterPose = typeof CHARACTER_POSE_NAMES[number];
|
|
4
|
-
export type CharacterAppearance = 'light' | 'dark';
|
|
5
|
-
export declare function createCharacterAssetLoader(importChunk: (path: string) => Promise<unknown>): {
|
|
6
|
-
pose(pose: CharacterPose, appearance: CharacterAppearance): Promise<string>;
|
|
7
|
-
rig(pose: string, appearance: CharacterAppearance): Promise<CharacterRig | undefined>;
|
|
8
|
-
};
|
|
9
|
-
export declare const characterAssets: {
|
|
10
|
-
pose(pose: CharacterPose, appearance: CharacterAppearance): Promise<string>;
|
|
11
|
-
rig(pose: string, appearance: CharacterAppearance): Promise<CharacterRig | undefined>;
|
|
12
|
-
};
|
|
13
|
-
//# sourceMappingURL=character-assets.d.ts.map
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export const CHARACTER_POSE_NAMES = ['seated', 'peek', 'chin', 'doze', 'halfpeek'];
|
|
2
|
-
export function createCharacterAssetLoader(importChunk) {
|
|
3
|
-
const cache = new Map();
|
|
4
|
-
function load(path) {
|
|
5
|
-
let promise = cache.get(path);
|
|
6
|
-
if (!promise) {
|
|
7
|
-
promise = Promise.resolve().then(() => importChunk(path)).catch(error => { cache.delete(path); throw error; });
|
|
8
|
-
cache.set(path, promise);
|
|
9
|
-
}
|
|
10
|
-
return promise;
|
|
11
|
-
}
|
|
12
|
-
return {
|
|
13
|
-
async pose(pose, appearance) {
|
|
14
|
-
const module = await load(`./client.pose-${pose}-${appearance}.js`);
|
|
15
|
-
return module.image;
|
|
16
|
-
},
|
|
17
|
-
async rig(pose, appearance) {
|
|
18
|
-
if (pose === 'doze' || !CHARACTER_POSE_NAMES.includes(pose))
|
|
19
|
-
return undefined;
|
|
20
|
-
const module = await load(`./client.rig-${pose}-${appearance}.js`);
|
|
21
|
-
return module.rig;
|
|
22
|
-
},
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
export const characterAssets = createCharacterAssetLoader(path => require.async(path));
|
|
26
|
-
//# sourceMappingURL=character-assets.js.map
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
const smooth = (t) => { const x = Math.max(0, Math.min(1, t)); return x * x * (3 - 2 * x); };
|
|
2
|
-
/** Fast closure, short hold, relaxed opening; zero velocity at each join. */
|
|
3
|
-
export function blinkClosure(progress) {
|
|
4
|
-
if (progress <= 0 || progress >= 1)
|
|
5
|
-
return 0;
|
|
6
|
-
if (progress < .3)
|
|
7
|
-
return smooth(progress / .3);
|
|
8
|
-
if (progress < .4)
|
|
9
|
-
return 1;
|
|
10
|
-
return 1 - smooth((progress - .4) / .6);
|
|
11
|
-
}
|
|
12
|
-
export function attentionBlink(progress) {
|
|
13
|
-
return blinkClosure((progress - .12) / .16);
|
|
14
|
-
}
|
|
15
|
-
//# sourceMappingURL=character-expression.js.map
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { EyeRig } from './character-eye-rig.js';
|
|
2
|
-
/** Quadratic lid ordinate at a horizontal art-space coordinate. */
|
|
3
|
-
export declare function lidY(eye: EyeRig, control: readonly [number, number], x: number): number;
|
|
4
|
-
/** Cache original ink and a locally textured skin plate, never read pixels per frame. */
|
|
5
|
-
export declare function prepareEyeMaterial(doc: Document, source: CanvasRenderingContext2D, eye: EyeRig): {
|
|
6
|
-
ink: HTMLCanvasElement;
|
|
7
|
-
skin: HTMLCanvasElement;
|
|
8
|
-
left: number;
|
|
9
|
-
top: number;
|
|
10
|
-
width: number;
|
|
11
|
-
height: number;
|
|
12
|
-
};
|
|
13
|
-
//# sourceMappingURL=character-eye-material.d.ts.map
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/** Quadratic lid ordinate at a horizontal art-space coordinate. */
|
|
2
|
-
export function lidY(eye, control, x) {
|
|
3
|
-
let low = 0, high = 1;
|
|
4
|
-
for (let i = 0; i < 14; i++) {
|
|
5
|
-
const t = (low + high) / 2, s = 1 - t;
|
|
6
|
-
if (s * s * eye.left[0] + 2 * s * t * control[0] + t * t * eye.right[0] < x)
|
|
7
|
-
low = t;
|
|
8
|
-
else
|
|
9
|
-
high = t;
|
|
10
|
-
}
|
|
11
|
-
const t = (low + high) / 2, s = 1 - t;
|
|
12
|
-
return s * s * eye.left[1] + 2 * s * t * control[1] + t * t * eye.right[1];
|
|
13
|
-
}
|
|
14
|
-
/** Cache original ink and a locally textured skin plate, never read pixels per frame. */
|
|
15
|
-
export function prepareEyeMaterial(doc, source, eye) {
|
|
16
|
-
const left = Math.floor(eye.left[0] - 9), right = Math.ceil(eye.right[0] + 9);
|
|
17
|
-
const top = Math.floor(Math.min(eye.upper[1], eye.left[1], eye.right[1]) - 5);
|
|
18
|
-
const bottom = Math.ceil(Math.max(eye.lower[1], eye.left[1], eye.right[1]) + 5);
|
|
19
|
-
const width = right - left, height = bottom - top;
|
|
20
|
-
const canvas = () => { const c = doc.createElement('canvas'); c.width = width; c.height = height; return c; };
|
|
21
|
-
const ink = canvas(), skin = canvas(), mask = canvas();
|
|
22
|
-
const ic = ink.getContext('2d'), sc = skin.getContext('2d'), mc = mask.getContext('2d');
|
|
23
|
-
const pixels = source.getImageData(0, 0, source.canvas.width, source.canvas.height);
|
|
24
|
-
const pixel = (x, y) => {
|
|
25
|
-
const offset = (Math.max(0, Math.min(pixels.height - 1, Math.round(y / 2))) * pixels.width + Math.max(0, Math.min(pixels.width - 1, Math.round(x / 2)))) * 4;
|
|
26
|
-
return [pixels.data[offset], pixels.data[offset + 1], pixels.data[offset + 2]];
|
|
27
|
-
};
|
|
28
|
-
const ref = pixel(...eye.skin), output = ic.createImageData(width, height), plate = sc.createImageData(width, height);
|
|
29
|
-
const isSkin = (c) => c.every((v, i) => Math.abs(v - ref[i]) < 25);
|
|
30
|
-
const skinAt = (x, y, c) => {
|
|
31
|
-
if (isSkin(c))
|
|
32
|
-
return c;
|
|
33
|
-
const sum = [ref[0] * .05, ref[1] * .05, ref[2] * .05];
|
|
34
|
-
let weight = .05;
|
|
35
|
-
// Inpaint from nearby matching face pixels, preserving local lighting.
|
|
36
|
-
// Source hair, green iris and dark ink cannot bleed into the skin plate.
|
|
37
|
-
for (const radius of [6, 12, 20, 30])
|
|
38
|
-
for (let i = 0; i < 8; i++) {
|
|
39
|
-
const p = pixel(x + Math.cos(i * Math.PI / 4) * radius, y + Math.sin(i * Math.PI / 4) * radius);
|
|
40
|
-
if (!isSkin(p))
|
|
41
|
-
continue;
|
|
42
|
-
const w = 1 / radius;
|
|
43
|
-
weight += w;
|
|
44
|
-
for (let k = 0; k < 3; k++)
|
|
45
|
-
sum[k] += p[k] * w;
|
|
46
|
-
}
|
|
47
|
-
return sum.map(v => Math.round(v / weight));
|
|
48
|
-
};
|
|
49
|
-
for (let x = 0; x < width; x++) {
|
|
50
|
-
const ax = left + x, upper = lidY(eye, eye.upper, ax);
|
|
51
|
-
// Restrict extraction to the upper lash: exclude eyebrow, pupil and hair.
|
|
52
|
-
for (let y = 0; y < height; y++) {
|
|
53
|
-
const ay = top + y, c = pixel(ax, ay), index = (y * width + x) * 4;
|
|
54
|
-
const darkness = 1 - (c[0] + c[1] + c[2]) / Math.max(1, ref[0] + ref[1] + ref[2]);
|
|
55
|
-
const green = c[1] > c[0] * 1.15 && c[1] > c[2] * 1.12;
|
|
56
|
-
const inBand = ay >= upper - 12 && ay <= upper + 3;
|
|
57
|
-
const alpha = inBand && !green ? Math.max(0, Math.min(1, (darkness - .3) / .35)) : 0;
|
|
58
|
-
output.data.set([...c, Math.round(alpha * 255)], index);
|
|
59
|
-
plate.data.set([...skinAt(ax, ay, c), 255], index);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
ic.putImageData(output, 0, 0);
|
|
63
|
-
sc.putImageData(plate, 0, 0);
|
|
64
|
-
// Opaque coverage of extracted ink, including its antialiased fringe. Using
|
|
65
|
-
// the ink's original alpha here leaves a second, stationary lash behind.
|
|
66
|
-
const inkMask = mc.createImageData(width, height);
|
|
67
|
-
for (let i = 0; i < output.data.length; i += 4)
|
|
68
|
-
inkMask.data[i + 3] = output.data[i + 3] > 4 ? 255 : 0;
|
|
69
|
-
mc.putImageData(inkMask, 0, 0);
|
|
70
|
-
const expanded = canvas(), ec = expanded.getContext('2d');
|
|
71
|
-
for (const [dx, dy] of [[0, 0], [-1, 0], [1, 0], [0, -1], [0, 1]])
|
|
72
|
-
ec.drawImage(mask, dx, dy);
|
|
73
|
-
mc.drawImage(expanded, 0, 0);
|
|
74
|
-
mc.translate(-left, -top);
|
|
75
|
-
mc.beginPath();
|
|
76
|
-
mc.moveTo(eye.left[0] - 7, eye.left[1]);
|
|
77
|
-
mc.quadraticCurveTo(eye.upper[0], eye.upper[1] - 4, eye.right[0] + 7, eye.right[1]);
|
|
78
|
-
mc.quadraticCurveTo(eye.lower[0], eye.lower[1] + 20, eye.left[0] - 7, eye.left[1]);
|
|
79
|
-
mc.closePath();
|
|
80
|
-
mc.fill();
|
|
81
|
-
// Feather only a subpixel edge, not the eye or the whole character.
|
|
82
|
-
sc.globalCompositeOperation = 'destination-in';
|
|
83
|
-
sc.filter = 'blur(0.65px)';
|
|
84
|
-
sc.drawImage(mask, 0, 0);
|
|
85
|
-
sc.filter = 'none';
|
|
86
|
-
return { ink, skin, left, top, width, height };
|
|
87
|
-
}
|
|
88
|
-
//# sourceMappingURL=character-eye-material.js.map
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { type EyeRig } from './character-eye-rig.js';
|
|
2
|
-
import type { Point } from './character-motion-types.js';
|
|
3
|
-
/** Prepare tiny iris surfaces once. Never shift a rectangular face/eye patch. */
|
|
4
|
-
export declare function createEyeRenderer(ctx: CanvasRenderingContext2D, body: HTMLImageElement, eyes: readonly EyeRig[]): (gaze: Point, closure: number) => void;
|
|
5
|
-
//# sourceMappingURL=character-eye-renderer.d.ts.map
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { clampGaze, eyelidControls } from './character-eye-rig.js';
|
|
2
|
-
import { lidY, prepareEyeMaterial } from './character-eye-material.js';
|
|
3
|
-
/** Prepare tiny iris surfaces once. Never shift a rectangular face/eye patch. */
|
|
4
|
-
export function createEyeRenderer(ctx, body, eyes) {
|
|
5
|
-
const doc = ctx.canvas.ownerDocument;
|
|
6
|
-
const source = doc.createElement('canvas');
|
|
7
|
-
source.width = body.width;
|
|
8
|
-
source.height = body.height;
|
|
9
|
-
const sample = source.getContext('2d', { willReadFrequently: true });
|
|
10
|
-
sample.drawImage(body, 0, 0);
|
|
11
|
-
const color = (p) => {
|
|
12
|
-
const data = sample.getImageData(Math.round(p[0] / 2), Math.round(p[1] / 2), 1, 1).data;
|
|
13
|
-
return [data[0], data[1], data[2]];
|
|
14
|
-
};
|
|
15
|
-
const rgb = (c) => `rgb(${c.map(v => Math.round(v)).join(' ')})`;
|
|
16
|
-
const prepared = eyes.map(eye => {
|
|
17
|
-
const [cx, cy, rx, ry, angle] = eye.iris;
|
|
18
|
-
// Full-size local surface preserves source texels when moved fractionally.
|
|
19
|
-
const iris = doc.createElement('canvas');
|
|
20
|
-
iris.width = Math.ceil(rx * 2 + 4);
|
|
21
|
-
iris.height = Math.ceil(ry * 2 + 4);
|
|
22
|
-
const ic = iris.getContext('2d');
|
|
23
|
-
ic.translate(iris.width / 2, iris.height / 2);
|
|
24
|
-
ic.rotate(angle);
|
|
25
|
-
ic.beginPath();
|
|
26
|
-
ic.ellipse(0, 0, rx, ry, 0, 0, Math.PI * 2);
|
|
27
|
-
ic.clip();
|
|
28
|
-
ic.rotate(-angle);
|
|
29
|
-
ic.drawImage(body, 0, 0, body.width, body.height, -cx, -cy, body.width * 2, body.height * 2);
|
|
30
|
-
return { eye, iris, skin: color(eye.skin), material: prepareEyeMaterial(doc, sample, eye) };
|
|
31
|
-
});
|
|
32
|
-
function aperture(eye, closure) {
|
|
33
|
-
const { upper, lower, left, right } = eyelidControls(eye, closure);
|
|
34
|
-
ctx.beginPath();
|
|
35
|
-
ctx.moveTo(...left);
|
|
36
|
-
ctx.quadraticCurveTo(...upper, ...right);
|
|
37
|
-
ctx.quadraticCurveTo(...lower, ...left);
|
|
38
|
-
ctx.closePath();
|
|
39
|
-
}
|
|
40
|
-
return (gaze, closure) => {
|
|
41
|
-
const [gx, gy] = clampGaze(...gaze);
|
|
42
|
-
ctx.save();
|
|
43
|
-
ctx.scale(.5, .5);
|
|
44
|
-
for (const { eye, iris, skin, material } of prepared) {
|
|
45
|
-
const [cx, cy, rx, ry] = eye.iris;
|
|
46
|
-
ctx.save();
|
|
47
|
-
ctx.drawImage(material.skin, material.left, material.top);
|
|
48
|
-
ctx.save();
|
|
49
|
-
aperture(eye, 0);
|
|
50
|
-
ctx.clip();
|
|
51
|
-
if (closure < .999) {
|
|
52
|
-
ctx.save();
|
|
53
|
-
aperture(eye, closure);
|
|
54
|
-
ctx.clip();
|
|
55
|
-
const shade = ctx.createLinearGradient(0, cy - ry, 0, cy + ry);
|
|
56
|
-
const brightness = (skin[0] + skin[1] + skin[2]) / 765;
|
|
57
|
-
const white = brightness > .65 ? [255, 253, 241] : [166, 185, 193];
|
|
58
|
-
shade.addColorStop(0, rgb(white.map(c => c * .83)));
|
|
59
|
-
shade.addColorStop(.5, rgb(white));
|
|
60
|
-
ctx.fillStyle = shade;
|
|
61
|
-
ctx.fillRect(cx - 70, cy - 70, 140, 140);
|
|
62
|
-
const dx = gx * rx * .26, dy = gy * ry * .13;
|
|
63
|
-
ctx.drawImage(iris, cx - iris.width / 2 + dx, cy - iris.height / 2 + dy);
|
|
64
|
-
ctx.restore();
|
|
65
|
-
}
|
|
66
|
-
ctx.restore();
|
|
67
|
-
const lids = eyelidControls(eye, closure);
|
|
68
|
-
// Move narrow strips of the original brushwork, preserving lash thickness.
|
|
69
|
-
// Neither a replacement stroke nor an opacity swap is used for blinking.
|
|
70
|
-
for (let x = 0; x < material.width; x++) {
|
|
71
|
-
const ax = material.left + x;
|
|
72
|
-
const dy = lidY({ ...eye, left: lids.left, right: lids.right }, lids.upper, ax) - lidY(eye, eye.upper, ax);
|
|
73
|
-
ctx.drawImage(material.ink, x, 0, 1, material.height, ax, material.top + dy, 1, material.height);
|
|
74
|
-
}
|
|
75
|
-
ctx.restore();
|
|
76
|
-
}
|
|
77
|
-
ctx.restore();
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
//# sourceMappingURL=character-eye-renderer.js.map
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { Point } from './character-motion-types.js';
|
|
2
|
-
/** Landmarks in the approved full-resolution artwork, not screen coordinates. */
|
|
3
|
-
export interface EyeRig {
|
|
4
|
-
left: Point;
|
|
5
|
-
right: Point;
|
|
6
|
-
upper: Point;
|
|
7
|
-
lower: Point;
|
|
8
|
-
iris: readonly [number, number, number, number, number];
|
|
9
|
-
skin: Point;
|
|
10
|
-
}
|
|
11
|
-
export declare const EYE_RIGS: Readonly<Record<string, readonly EyeRig[]>>;
|
|
12
|
-
export declare function clampGaze(x: number, y: number): Point;
|
|
13
|
-
/** Both lids meet on the same gently curved lash line, never a rectangular wipe. */
|
|
14
|
-
export declare function eyelidControls(eye: EyeRig, closure: number): {
|
|
15
|
-
upper: Point;
|
|
16
|
-
lower: Point;
|
|
17
|
-
left: Point;
|
|
18
|
-
right: Point;
|
|
19
|
-
};
|
|
20
|
-
//# sourceMappingURL=character-eye-rig.d.ts.map
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
export const EYE_RIGS = {
|
|
2
|
-
seated: [
|
|
3
|
-
{ left: [285, 223], right: [326, 224], upper: [302, 188], lower: [310, 276], iris: [309, 230, 13, 20, 0], skin: [315, 258] },
|
|
4
|
-
{ left: [373, 221], right: [421, 226], upper: [387, 186], lower: [393, 277], iris: [393, 228, 15, 22, 0], skin: [391, 261] },
|
|
5
|
-
],
|
|
6
|
-
peek: [
|
|
7
|
-
{ left: [80, 214], right: [119, 221], upper: [103, 186], lower: [91, 267], iris: [102, 225, 14, 20, .25], skin: [110, 253] },
|
|
8
|
-
{ left: [171, 252], right: [210, 265], upper: [195, 221], lower: [175, 300], iris: [186, 260, 15, 20, .4], skin: [186, 288] },
|
|
9
|
-
],
|
|
10
|
-
chin: [
|
|
11
|
-
{ left: [252, 252], right: [290, 247], upper: [266, 223], lower: [287, 297], iris: [278, 258, 14, 19, -.3], skin: [291, 285] },
|
|
12
|
-
{ left: [339, 234], right: [387, 227], upper: [358, 196], lower: [365, 280], iris: [361, 235, 15, 21, -.25], skin: [367, 269] },
|
|
13
|
-
],
|
|
14
|
-
halfpeek: [
|
|
15
|
-
{ left: [89, 390], right: [159, 395], upper: [116, 337], lower: [110, 471], iris: [116, 401, 23, 30, .22], skin: [117, 445] },
|
|
16
|
-
],
|
|
17
|
-
};
|
|
18
|
-
export function clampGaze(x, y) {
|
|
19
|
-
if (!Number.isFinite(x) || !Number.isFinite(y))
|
|
20
|
-
return [0, 0];
|
|
21
|
-
const length = Math.max(1, Math.hypot(x, y));
|
|
22
|
-
return [x / length, y / length];
|
|
23
|
-
}
|
|
24
|
-
/** Both lids meet on the same gently curved lash line, never a rectangular wipe. */
|
|
25
|
-
export function eyelidControls(eye, closure) {
|
|
26
|
-
const t = Math.max(0, Math.min(1, closure));
|
|
27
|
-
// Upper lid does most of the travel. Closing at the eye's centre looked like
|
|
28
|
-
// a squint; the resting lash sits near the lower rim instead.
|
|
29
|
-
const cornerY = (eye.left[1] + eye.right[1]) / 2;
|
|
30
|
-
const drop = (eye.lower[1] - cornerY) * .24;
|
|
31
|
-
const middle = [(eye.left[0] + eye.right[0]) / 2, cornerY + drop + (eye.right[0] - eye.left[0]) * .18];
|
|
32
|
-
const lerp = (p) => [p[0] + (middle[0] - p[0]) * t, p[1] + (middle[1] - p[1]) * t];
|
|
33
|
-
return { upper: lerp(eye.upper), lower: lerp(eye.lower), left: [eye.left[0], eye.left[1] + drop * t], right: [eye.right[0], eye.right[1] + drop * t] };
|
|
34
|
-
}
|
|
35
|
-
//# sourceMappingURL=character-eye-rig.js.map
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { Point } from './character-motion-types.js';
|
|
2
|
-
interface GazeClock {
|
|
3
|
-
requestAnimationFrame(callback: (time: number) => void): number;
|
|
4
|
-
cancelAnimationFrame(handle: number): void;
|
|
5
|
-
performance: {
|
|
6
|
-
now(): number;
|
|
7
|
-
};
|
|
8
|
-
}
|
|
9
|
-
/** Event-driven damping. At rest there are no queued frames or polling. */
|
|
10
|
-
export declare function createCharacterGaze(clock: GazeClock, changed: (point: Point) => void): {
|
|
11
|
-
target(x: number, y: number): void;
|
|
12
|
-
reset(): void;
|
|
13
|
-
dispose(): void;
|
|
14
|
-
};
|
|
15
|
-
export {};
|
|
16
|
-
//# sourceMappingURL=character-gaze.d.ts.map
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { clampGaze } from './character-eye-rig.js';
|
|
2
|
-
/** Event-driven damping. At rest there are no queued frames or polling. */
|
|
3
|
-
export function createCharacterGaze(clock, changed) {
|
|
4
|
-
let value = [0, 0], target = [0, 0], frame, last = 0, disposed = false;
|
|
5
|
-
function step(now) {
|
|
6
|
-
frame = undefined;
|
|
7
|
-
if (disposed)
|
|
8
|
-
return;
|
|
9
|
-
const dt = Math.min(64, Math.max(0, now - last));
|
|
10
|
-
last = now;
|
|
11
|
-
const amount = 1 - Math.exp(-dt / 95);
|
|
12
|
-
value = [value[0] + (target[0] - value[0]) * amount, value[1] + (target[1] - value[1]) * amount];
|
|
13
|
-
const settled = Math.hypot(value[0] - target[0], value[1] - target[1]) < .001;
|
|
14
|
-
if (settled)
|
|
15
|
-
value = target;
|
|
16
|
-
changed(value);
|
|
17
|
-
if (!settled)
|
|
18
|
-
frame = clock.requestAnimationFrame(step);
|
|
19
|
-
}
|
|
20
|
-
function stop() { if (frame !== undefined)
|
|
21
|
-
clock.cancelAnimationFrame(frame); frame = undefined; }
|
|
22
|
-
return {
|
|
23
|
-
target(x, y) {
|
|
24
|
-
if (disposed)
|
|
25
|
-
return;
|
|
26
|
-
target = clampGaze(x, y);
|
|
27
|
-
if (frame === undefined && Math.hypot(value[0] - target[0], value[1] - target[1]) >= .001) {
|
|
28
|
-
last = clock.performance.now();
|
|
29
|
-
frame = clock.requestAnimationFrame(step);
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
reset() { stop(); target = value = [0, 0]; changed(value); },
|
|
33
|
-
dispose() { disposed = true; stop(); },
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
//# sourceMappingURL=character-gaze.js.map
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export type CharacterIdlePose = 'seated' | 'chin' | 'doze';
|
|
2
|
-
export declare const CHARACTER_CHIN_DELAY = 30000;
|
|
3
|
-
export declare const CHARACTER_DOZE_DELAY = 180000;
|
|
4
|
-
interface IdleClock {
|
|
5
|
-
now(): number;
|
|
6
|
-
setTimeout(callback: () => void, delay: number): number;
|
|
7
|
-
clearTimeout(handle: number): void;
|
|
8
|
-
}
|
|
9
|
-
/** One pending deadline, no polling, no model/session state or persisted user data. */
|
|
10
|
-
export declare function createCharacterIdleController(clock: IdleClock, changed: (pose: CharacterIdlePose) => void): {
|
|
11
|
-
readonly pose: CharacterIdlePose;
|
|
12
|
-
activity(): void;
|
|
13
|
-
setPaused(value: boolean): void;
|
|
14
|
-
dispose(): void;
|
|
15
|
-
};
|
|
16
|
-
export {};
|
|
17
|
-
//# sourceMappingURL=character-idle.d.ts.map
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
export const CHARACTER_CHIN_DELAY = 30_000;
|
|
2
|
-
export const CHARACTER_DOZE_DELAY = 180_000;
|
|
3
|
-
/** One pending deadline, no polling, no model/session state or persisted user data. */
|
|
4
|
-
export function createCharacterIdleController(clock, changed) {
|
|
5
|
-
let pose = 'seated';
|
|
6
|
-
let lastActivity = clock.now();
|
|
7
|
-
let timer;
|
|
8
|
-
let paused = true;
|
|
9
|
-
let disposed = false;
|
|
10
|
-
const clear = () => {
|
|
11
|
-
if (timer !== undefined)
|
|
12
|
-
clock.clearTimeout(timer);
|
|
13
|
-
timer = undefined;
|
|
14
|
-
};
|
|
15
|
-
const update = (next) => {
|
|
16
|
-
if (next !== pose) {
|
|
17
|
-
pose = next;
|
|
18
|
-
changed(next);
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
const schedule = () => {
|
|
22
|
-
clear();
|
|
23
|
-
if (paused || disposed || pose === 'doze')
|
|
24
|
-
return;
|
|
25
|
-
const deadline = pose === 'seated' ? CHARACTER_CHIN_DELAY : CHARACTER_DOZE_DELAY;
|
|
26
|
-
timer = clock.setTimeout(tick, Math.max(1, lastActivity + deadline - clock.now()));
|
|
27
|
-
};
|
|
28
|
-
function tick() {
|
|
29
|
-
timer = undefined;
|
|
30
|
-
if (paused || disposed)
|
|
31
|
-
return;
|
|
32
|
-
const elapsed = clock.now() - lastActivity;
|
|
33
|
-
update(elapsed >= CHARACTER_DOZE_DELAY ? 'doze' : elapsed >= CHARACTER_CHIN_DELAY ? 'chin' : 'seated');
|
|
34
|
-
schedule();
|
|
35
|
-
}
|
|
36
|
-
return {
|
|
37
|
-
get pose() { return pose; },
|
|
38
|
-
activity() {
|
|
39
|
-
if (paused || disposed)
|
|
40
|
-
return;
|
|
41
|
-
const now = clock.now();
|
|
42
|
-
// Pointer events can fire at 120 Hz. Update a timestamp at most once per
|
|
43
|
-
// second; never reset a timeout on each pointer movement or keystroke.
|
|
44
|
-
if (pose === 'seated' && now - lastActivity < 1000)
|
|
45
|
-
return;
|
|
46
|
-
lastActivity = now;
|
|
47
|
-
if (pose !== 'seated') {
|
|
48
|
-
update('seated');
|
|
49
|
-
schedule();
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
setPaused(value) {
|
|
53
|
-
if (disposed || value === paused)
|
|
54
|
-
return;
|
|
55
|
-
paused = value;
|
|
56
|
-
clear();
|
|
57
|
-
lastActivity = clock.now();
|
|
58
|
-
update('seated');
|
|
59
|
-
if (!paused)
|
|
60
|
-
schedule();
|
|
61
|
-
},
|
|
62
|
-
dispose() { disposed = true; clear(); },
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
//# sourceMappingURL=character-idle.js.map
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/** Pure placement policy: decoration may only occupy space outside message bounds. */
|
|
2
|
-
export interface CharacterRect {
|
|
3
|
-
left: number;
|
|
4
|
-
top: number;
|
|
5
|
-
right: number;
|
|
6
|
-
bottom: number;
|
|
7
|
-
}
|
|
8
|
-
export interface CharacterLayoutInput {
|
|
9
|
-
sidebar: CharacterRect;
|
|
10
|
-
scroll: CharacterRect;
|
|
11
|
-
flow: CharacterRect;
|
|
12
|
-
viewportWidth: number;
|
|
13
|
-
viewportHeight: number;
|
|
14
|
-
controls: CharacterRect[];
|
|
15
|
-
}
|
|
16
|
-
export interface CharacterPlacement {
|
|
17
|
-
pose: 'seated' | 'peek' | 'halfpeek' | 'hidden';
|
|
18
|
-
left: number;
|
|
19
|
-
top: number;
|
|
20
|
-
width: number;
|
|
21
|
-
height: number;
|
|
22
|
-
}
|
|
23
|
-
export declare function resolveCharacterPlacement(input: CharacterLayoutInput): CharacterPlacement;
|
|
24
|
-
//# sourceMappingURL=character-layout.d.ts.map
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
const hidden = { pose: 'hidden', left: 0, top: 0, width: 0, height: 0 };
|
|
2
|
-
const SEATED_RATIO = 0.71;
|
|
3
|
-
const PEEK_RATIO = 0.63;
|
|
4
|
-
const HALF_PEEK_RATIO = 0.44;
|
|
5
|
-
export function resolveCharacterPlacement(input) {
|
|
6
|
-
const { sidebar, scroll, flow, viewportWidth, viewportHeight, controls } = input;
|
|
7
|
-
// Wallpaper belongs to the viewport, not the welcome/composer dock. Reserve
|
|
8
|
-
// the same top/bottom chrome space before and after the first message.
|
|
9
|
-
const bottom = Math.min(sidebar.bottom, viewportHeight) - 148;
|
|
10
|
-
const top = Math.max(sidebar.top + 24, 96);
|
|
11
|
-
if (bottom - top < 130 || sidebar.right > flow.left)
|
|
12
|
-
return hidden;
|
|
13
|
-
const rightStart = flow.right + 56;
|
|
14
|
-
const seatedWidth = Math.min(250, scroll.right - rightStart - 20, (bottom - top) * SEATED_RATIO, 340 * SEATED_RATIO);
|
|
15
|
-
if (seatedWidth >= 190) {
|
|
16
|
-
const height = seatedWidth / SEATED_RATIO;
|
|
17
|
-
return { pose: 'seated', left: scroll.right - seatedWidth - 16,
|
|
18
|
-
top: top + (bottom - top - height) * 0.6, width: seatedWidth, height };
|
|
19
|
-
}
|
|
20
|
-
const avatarInset = viewportWidth > 760 ? 52 : 0;
|
|
21
|
-
const freeGap = flow.left - avatarInset - sidebar.right;
|
|
22
|
-
if (freeGap >= 66) {
|
|
23
|
-
const width = Math.min(104, freeGap - 12, (bottom - top) * PEEK_RATIO);
|
|
24
|
-
const height = width / PEEK_RATIO;
|
|
25
|
-
return { pose: 'peek', left: sidebar.right - 6,
|
|
26
|
-
top: top + Math.min(160, (bottom - top - height) * 0.5), width, height };
|
|
27
|
-
}
|
|
28
|
-
// A collapsed rail has a large blank region between navigation and Settings.
|
|
29
|
-
// Use that region only after excluding every real interactive control.
|
|
30
|
-
if (sidebar.right - sidebar.left > 100)
|
|
31
|
-
return hidden;
|
|
32
|
-
const left = sidebar.left + 7;
|
|
33
|
-
const width = Math.min(60, flow.left - avatarInset - left - 10);
|
|
34
|
-
if (width < 40)
|
|
35
|
-
return hidden;
|
|
36
|
-
const height = width / HALF_PEEK_RATIO;
|
|
37
|
-
const blocked = controls.filter(r => r.right > left && r.left < left + width)
|
|
38
|
-
.map(r => ({ start: Math.max(top, r.top - 12), end: Math.min(bottom, r.bottom + 12) }))
|
|
39
|
-
.filter(r => r.end > r.start).sort((a, b) => a.start - b.start);
|
|
40
|
-
let cursor = top;
|
|
41
|
-
let best = { start: top, end: top };
|
|
42
|
-
for (const block of [...blocked, { start: bottom, end: bottom }]) {
|
|
43
|
-
if (block.start - cursor > best.end - best.start)
|
|
44
|
-
best = { start: cursor, end: block.start };
|
|
45
|
-
cursor = Math.max(cursor, block.end);
|
|
46
|
-
}
|
|
47
|
-
if (best.end - best.start < height + 12)
|
|
48
|
-
return hidden;
|
|
49
|
-
return { pose: 'halfpeek', left, top: best.start + (best.end - best.start - height) * 0.45, width, height };
|
|
50
|
-
}
|
|
51
|
-
//# sourceMappingURL=character-layout.js.map
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
interface Clock {
|
|
2
|
-
setTimeout(callback: () => void, delay: number): number;
|
|
3
|
-
clearTimeout(handle: number): void;
|
|
4
|
-
performance: {
|
|
5
|
-
now(): number;
|
|
6
|
-
};
|
|
7
|
-
}
|
|
8
|
-
export interface CharacterMotionClip {
|
|
9
|
-
duration: number;
|
|
10
|
-
}
|
|
11
|
-
/** One deadline; 60 fps only during a short gesture, no idle frame loop. */
|
|
12
|
-
export declare function createCharacterMotionClock(clock: Clock, changed: (action: string | undefined, progress: number) => void, random?: () => number): {
|
|
13
|
-
setClips(next: Readonly<Record<string, CharacterMotionClip>> | undefined): void;
|
|
14
|
-
attention(): void;
|
|
15
|
-
dispose(): void;
|
|
16
|
-
};
|
|
17
|
-
export {};
|
|
18
|
-
//# sourceMappingURL=character-motion-clock.d.ts.map
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/** One deadline; 60 fps only during a short gesture, no idle frame loop. */
|
|
2
|
-
export function createCharacterMotionClock(clock, changed, random = Math.random) {
|
|
3
|
-
let timer, disposed = false, active = '';
|
|
4
|
-
let clips;
|
|
5
|
-
let blinksSinceEar = 0, nextEar = 0;
|
|
6
|
-
const clear = () => { if (timer !== undefined)
|
|
7
|
-
clock.clearTimeout(timer); timer = undefined; };
|
|
8
|
-
function rest() {
|
|
9
|
-
active = '';
|
|
10
|
-
changed(undefined, 0);
|
|
11
|
-
if (clips && !disposed)
|
|
12
|
-
timer = clock.setTimeout(start, 2800 + random() * 2800);
|
|
13
|
-
}
|
|
14
|
-
function play(action) {
|
|
15
|
-
const clip = clips?.[action];
|
|
16
|
-
if (!clip || disposed)
|
|
17
|
-
return;
|
|
18
|
-
clear();
|
|
19
|
-
active = action;
|
|
20
|
-
const started = clock.performance.now();
|
|
21
|
-
const step = () => {
|
|
22
|
-
timer = undefined;
|
|
23
|
-
if (!clips || disposed)
|
|
24
|
-
return;
|
|
25
|
-
const t = Math.min(1, (clock.performance.now() - started) / clip.duration);
|
|
26
|
-
changed(active, t);
|
|
27
|
-
if (t >= 1) {
|
|
28
|
-
rest();
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
timer = clock.setTimeout(step, 1000 / 60);
|
|
32
|
-
};
|
|
33
|
-
step();
|
|
34
|
-
}
|
|
35
|
-
function start() {
|
|
36
|
-
timer = undefined;
|
|
37
|
-
if (!clips || disposed)
|
|
38
|
-
return;
|
|
39
|
-
const gestures = Object.keys(clips).filter(name => name !== 'blink' && name !== 'attention');
|
|
40
|
-
const ears = gestures.filter(name => name.startsWith('ear'));
|
|
41
|
-
// Random selection alone can starve ear motion indefinitely. Keep a quiet,
|
|
42
|
-
// bounded cadence, alternating ears instead of twitching both in lockstep.
|
|
43
|
-
const action = ears.length && blinksSinceEar >= 2 ? ears[nextEar++ % ears.length]
|
|
44
|
-
: (random() < .72 && clips.blink) || !gestures.length ? 'blink'
|
|
45
|
-
: gestures[Math.floor(random() * gestures.length)];
|
|
46
|
-
if (action.startsWith('ear'))
|
|
47
|
-
blinksSinceEar = 0;
|
|
48
|
-
else
|
|
49
|
-
blinksSinceEar++;
|
|
50
|
-
play(action);
|
|
51
|
-
}
|
|
52
|
-
return {
|
|
53
|
-
setClips(next) {
|
|
54
|
-
if (disposed || next === clips)
|
|
55
|
-
return;
|
|
56
|
-
clear();
|
|
57
|
-
clips = next;
|
|
58
|
-
active = '';
|
|
59
|
-
blinksSinceEar = 0;
|
|
60
|
-
nextEar = 0;
|
|
61
|
-
changed(undefined, 0);
|
|
62
|
-
if (clips)
|
|
63
|
-
timer = clock.setTimeout(start, 1200 + random() * 800);
|
|
64
|
-
},
|
|
65
|
-
attention() { if (!active)
|
|
66
|
-
play('attention'); },
|
|
67
|
-
dispose() { disposed = true; clear(); clips = undefined; changed(undefined, 0); },
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
//# sourceMappingURL=character-motion-clock.js.map
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export type Point = readonly [number, number];
|
|
2
|
-
export interface CharacterLayer {
|
|
3
|
-
readonly src: string;
|
|
4
|
-
readonly box: readonly [number, number, number, number];
|
|
5
|
-
}
|
|
6
|
-
export interface CharacterRig {
|
|
7
|
-
readonly layers: Readonly<Record<string, CharacterLayer>>;
|
|
8
|
-
readonly ears: readonly Point[];
|
|
9
|
-
readonly size: Point;
|
|
10
|
-
}
|
|
11
|
-
export type CharacterMotionCatalog = Readonly<Record<string, Readonly<Record<'light' | 'dark', CharacterRig>>>>;
|
|
12
|
-
//# sourceMappingURL=character-motion-types.d.ts.map
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/** Owns loading and lifecycle only. Rig math and painting stay independent. */
|
|
2
|
-
export declare function createCharacterMotion(doc: Document): {
|
|
3
|
-
setPose(nextPart: HTMLElement | undefined, nextPose: string): void;
|
|
4
|
-
dispose(): void;
|
|
5
|
-
};
|
|
6
|
-
//# sourceMappingURL=character-motion.d.ts.map
|