@nadicodeai/ui 6.0.0 → 6.0.2
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/AGENTS.md +1 -1
- package/dist/components/forma-avatar.d.ts +23 -0
- package/dist/components/forma-avatar.d.ts.map +1 -0
- package/dist/components/forma-avatar.js +95 -0
- package/dist/hooks/use-confirm-action.d.ts +0 -6
- package/dist/hooks/use-confirm-action.d.ts.map +1 -1
- package/dist/hooks/use-confirm-action.js +5 -12
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/internal/forma/frame.d.ts +10 -0
- package/dist/internal/forma/frame.d.ts.map +1 -0
- package/dist/internal/forma/frame.js +211 -0
- package/dist/internal/forma/geometry.d.ts +41 -0
- package/dist/internal/forma/geometry.d.ts.map +1 -0
- package/dist/internal/forma/geometry.js +235 -0
- package/dist/internal/forma/hallmark.d.ts +5 -0
- package/dist/internal/forma/hallmark.d.ts.map +1 -0
- package/dist/internal/forma/hallmark.js +4 -0
- package/dist/internal/forma/model.d.ts +53 -0
- package/dist/internal/forma/model.d.ts.map +1 -0
- package/dist/internal/forma/model.js +70 -0
- package/dist/internal/forma/motion.d.ts +42 -0
- package/dist/internal/forma/motion.d.ts.map +1 -0
- package/dist/internal/forma/motion.js +194 -0
- package/dist/internal/forma/rig.d.ts +10 -0
- package/dist/internal/forma/rig.d.ts.map +1 -0
- package/dist/internal/forma/rig.js +211 -0
- package/dist/internal/forma/still.d.ts +4 -0
- package/dist/internal/forma/still.d.ts.map +1 -0
- package/dist/internal/forma/still.js +8 -0
- package/dist/lib/use-copy-to-clipboard.d.ts +0 -41
- package/dist/lib/use-copy-to-clipboard.d.ts.map +1 -1
- package/dist/lib/use-copy-to-clipboard.js +6 -15
- package/docs/contract.md +2 -2
- package/docs/forma-avatar.md +92 -0
- package/package.json +2 -2
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
export const TAU = Math.PI * 2;
|
|
2
|
+
const CAMERA_DISTANCE = 5.2;
|
|
3
|
+
export const clamp = (value, low, high) => Math.max(low, Math.min(high, value));
|
|
4
|
+
export const mix = (a, b, t) => a + (b - a) * t;
|
|
5
|
+
export const smooth = (t) => {
|
|
6
|
+
const u = clamp(t, 0, 1);
|
|
7
|
+
return u * u * (3 - 2 * u);
|
|
8
|
+
};
|
|
9
|
+
const signedPow = (value, power) => Math.sign(value) * Math.pow(Math.abs(value), power);
|
|
10
|
+
export const f3 = (value) => (Math.round(value * 1000) / 1000).toString();
|
|
11
|
+
function lut(pts) {
|
|
12
|
+
const table = pts
|
|
13
|
+
.filter((p) => p[0] >= -0.001)
|
|
14
|
+
.map((p) => [Math.abs(p[0]), p[1]])
|
|
15
|
+
.sort((a, b) => a[1] - b[1]);
|
|
16
|
+
const height = Math.max(...pts.map((p) => Math.abs(p[1])));
|
|
17
|
+
const width = Math.max(...pts.map((p) => Math.abs(p[0])));
|
|
18
|
+
const first = table[0];
|
|
19
|
+
const last = table[table.length - 1];
|
|
20
|
+
const hw = (y) => {
|
|
21
|
+
if (!first || !last || y <= first[1] || y >= last[1])
|
|
22
|
+
return 0.02;
|
|
23
|
+
let best = 0.02;
|
|
24
|
+
for (let i = 1; i < table.length; i++) {
|
|
25
|
+
const b = table[i];
|
|
26
|
+
const a = table[i - 1];
|
|
27
|
+
if (b && a && b[1] >= y) {
|
|
28
|
+
const u = (y - a[1]) / Math.max(1e-6, b[1] - a[1]);
|
|
29
|
+
best = Math.max(best, mix(a[0], b[0], u));
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return best;
|
|
34
|
+
};
|
|
35
|
+
return { height, width, hw, outline: pts };
|
|
36
|
+
}
|
|
37
|
+
function superBody(spec) {
|
|
38
|
+
const { power, width, height, taper = 0 } = spec;
|
|
39
|
+
const radial = (y) => Math.sqrt(Math.max(0.03, 1 - (taper * y) / height));
|
|
40
|
+
const hw = (y) => {
|
|
41
|
+
const v = clamp(Math.abs(y / height), 0, 1);
|
|
42
|
+
return width * radial(y) * Math.pow(Math.max(0, 1 - Math.pow(v, power)), 1 / power);
|
|
43
|
+
};
|
|
44
|
+
const outline = [];
|
|
45
|
+
for (let i = 0; i < 96; i++) {
|
|
46
|
+
const t = (i / 96) * TAU;
|
|
47
|
+
const y = height * signedPow(Math.sin(t), 2 / power);
|
|
48
|
+
outline.push([width * radial(y) * signedPow(Math.cos(t), 2 / power), y]);
|
|
49
|
+
}
|
|
50
|
+
return { height, width, hw, outline };
|
|
51
|
+
}
|
|
52
|
+
function polyBody(vertices, rounds, cut) {
|
|
53
|
+
let pts = [...vertices];
|
|
54
|
+
for (let r = 0; r < rounds; r++) {
|
|
55
|
+
const next = [];
|
|
56
|
+
for (let i = 0; i < pts.length; i++) {
|
|
57
|
+
const a = pts[i];
|
|
58
|
+
const b = pts[(i + 1) % pts.length];
|
|
59
|
+
next.push([mix(a[0], b[0], cut), mix(a[1], b[1], cut)], [mix(a[0], b[0], 1 - cut), mix(a[1], b[1], 1 - cut)]);
|
|
60
|
+
}
|
|
61
|
+
pts = next;
|
|
62
|
+
}
|
|
63
|
+
return lut(pts);
|
|
64
|
+
}
|
|
65
|
+
function cloudBody(circles, bottom = -Infinity) {
|
|
66
|
+
const pts = [];
|
|
67
|
+
for (let i = 0; i < 144; i++) {
|
|
68
|
+
const a = (i / 144) * TAU;
|
|
69
|
+
const dx = Math.cos(a);
|
|
70
|
+
const dy = Math.sin(a);
|
|
71
|
+
let r = 0;
|
|
72
|
+
for (const [cx, cy, cr] of circles) {
|
|
73
|
+
const b = -2 * (dx * cx + dy * cy);
|
|
74
|
+
const c = cx * cx + cy * cy - cr * cr;
|
|
75
|
+
const disc = b * b - 4 * c;
|
|
76
|
+
if (disc >= 0)
|
|
77
|
+
r = Math.max(r, (-b + Math.sqrt(disc)) / 2);
|
|
78
|
+
}
|
|
79
|
+
if (dy < 0 && bottom > -Infinity)
|
|
80
|
+
r = Math.min(r, bottom / dy);
|
|
81
|
+
pts.push([dx * r, dy * r]);
|
|
82
|
+
}
|
|
83
|
+
return lut(pts);
|
|
84
|
+
}
|
|
85
|
+
function stadiumBody(halfLength, r) {
|
|
86
|
+
const pts = [];
|
|
87
|
+
for (let i = 0; i <= 40; i++) {
|
|
88
|
+
const a = -Math.PI / 2 + (i / 40) * Math.PI;
|
|
89
|
+
pts.push([halfLength + r * Math.cos(a), r * Math.sin(a)]);
|
|
90
|
+
}
|
|
91
|
+
for (let i = 0; i <= 40; i++) {
|
|
92
|
+
const a = Math.PI / 2 + (i / 40) * Math.PI;
|
|
93
|
+
pts.push([-halfLength + r * Math.cos(a), r * Math.sin(a)]);
|
|
94
|
+
}
|
|
95
|
+
return lut(pts);
|
|
96
|
+
}
|
|
97
|
+
const withCore = (body, core) => ({ ...body, core });
|
|
98
|
+
export const bodies = {
|
|
99
|
+
nadia: withCore(superBody({ power: 2.3, width: 1, height: 0.965 }), { cy: 0, r: 1 }),
|
|
100
|
+
goccia: withCore(superBody({ power: 2, width: 1, height: 1.16, taper: 0.94 }), {
|
|
101
|
+
cy: -0.18,
|
|
102
|
+
r: 0.92,
|
|
103
|
+
}),
|
|
104
|
+
nuvola: withCore(cloudBody([
|
|
105
|
+
[0, -0.08, 0.8],
|
|
106
|
+
[-0.46, 0.34, 0.5],
|
|
107
|
+
[0.4, 0.42, 0.56],
|
|
108
|
+
[-0.72, -0.1, 0.42],
|
|
109
|
+
[0.74, -0.14, 0.4],
|
|
110
|
+
], -0.86), { cy: -0.06, r: 0.84 }),
|
|
111
|
+
triangolo: withCore(polyBody([
|
|
112
|
+
[0, 1.42],
|
|
113
|
+
[1.28, -0.86],
|
|
114
|
+
[-1.28, -0.86],
|
|
115
|
+
], 4, 0.22), { cy: -0.26, r: 0.8 }),
|
|
116
|
+
esagono: withCore(polyBody([0, 1, 2, 3, 4, 5].map((k) => {
|
|
117
|
+
const a = Math.PI / 2 + (k * Math.PI) / 3;
|
|
118
|
+
return [Math.cos(a) * 1.02, Math.sin(a) * 1.12];
|
|
119
|
+
}), 3, 0.16), { cy: 0, r: 0.84 }),
|
|
120
|
+
quadrato: withCore(superBody({ power: 3.6, width: 0.92, height: 0.97 }), { cy: 0, r: 0.9 }),
|
|
121
|
+
pillola: withCore(stadiumBody(0.4, 0.68), { cy: 0, r: 0.68 }),
|
|
122
|
+
uovo: withCore(superBody({ power: 2.2, width: 0.84, height: 1.06, taper: 0.5 }), {
|
|
123
|
+
cy: -0.16,
|
|
124
|
+
r: 0.84,
|
|
125
|
+
}),
|
|
126
|
+
};
|
|
127
|
+
const visorMask = { width: 0.86, height: 0.505, y: 0.055, power: 4.6 };
|
|
128
|
+
export const eyeProfiles = {
|
|
129
|
+
nadia: { power: 2, width: 0.065, height: 0.151, spacing: 0.247, y: 0.069, signature: true },
|
|
130
|
+
goccia: { power: 4, width: 0.108, height: 0.054, spacing: 0.23, y: 0.015 },
|
|
131
|
+
nuvola: { power: 2, width: 0.108, height: 0.108, spacing: 0.23, y: 0.068 },
|
|
132
|
+
triangolo: { power: 3.8, width: 0.069, height: 0.12, spacing: 0.247, y: 0.054 },
|
|
133
|
+
esagono: { power: 2.6, width: 0.077, height: 0.116, spacing: 0.22, y: 0.068 },
|
|
134
|
+
quadrato: { power: 1.6, width: 0.095, height: 0.135, spacing: 0.245, y: 0.08 },
|
|
135
|
+
pillola: { power: 2, width: 0.108, height: 0.108, spacing: 0.23, y: 0.068 },
|
|
136
|
+
uovo: { power: 3.8, width: 0.069, height: 0.12, spacing: 0.247, y: 0.054 },
|
|
137
|
+
};
|
|
138
|
+
export function superellipse(hw, hh, power, n) {
|
|
139
|
+
const pts = [];
|
|
140
|
+
for (let i = 0; i < n; i++) {
|
|
141
|
+
const t = (i / n) * TAU;
|
|
142
|
+
pts.push([hw * signedPow(Math.cos(t), 2 / power), hh * signedPow(Math.sin(t), 2 / power)]);
|
|
143
|
+
}
|
|
144
|
+
return pts;
|
|
145
|
+
}
|
|
146
|
+
export const visorPoints = superellipse(visorMask.width, visorMask.height, visorMask.power, 48).map(([x, y]) => [x, y + visorMask.y]);
|
|
147
|
+
export function closedPath(pts) {
|
|
148
|
+
const n = pts.length;
|
|
149
|
+
let d = "";
|
|
150
|
+
for (let i = 0; i < n; i++) {
|
|
151
|
+
const p0 = pts[(i + n - 1) % n];
|
|
152
|
+
const p1 = pts[i];
|
|
153
|
+
const p2 = pts[(i + 1) % n];
|
|
154
|
+
const p3 = pts[(i + 2) % n];
|
|
155
|
+
if (i === 0)
|
|
156
|
+
d += `M${f3(p1[0])} ${f3(p1[1])}`;
|
|
157
|
+
d += `C${f3(p1[0] + (p2[0] - p0[0]) / 6)} ${f3(p1[1] + (p2[1] - p0[1]) / 6)} ${f3(p2[0] - (p3[0] - p1[0]) / 6)} ${f3(p2[1] - (p3[1] - p1[1]) / 6)} ${f3(p2[0])} ${f3(p2[1])}`;
|
|
158
|
+
}
|
|
159
|
+
return `${d}Z`;
|
|
160
|
+
}
|
|
161
|
+
export function project(body, xs, ys, yaw, pitch) {
|
|
162
|
+
const { core } = body;
|
|
163
|
+
let lon = Math.asin(clamp(xs, -1, 1)) + yaw;
|
|
164
|
+
let lat = Math.asin(clamp(ys, -1, 1)) - pitch;
|
|
165
|
+
lon = Math.atan2(Math.sin(lon), Math.cos(lon)); // a full turn brings the face home
|
|
166
|
+
const front = Math.cos(lon) > 0 && Math.cos(lat) > 0;
|
|
167
|
+
lon = clamp(lon, -Math.PI / 2, Math.PI / 2);
|
|
168
|
+
lat = clamp(lat, -Math.PI / 2, Math.PI / 2);
|
|
169
|
+
const depth = Math.cos(lon) * Math.cos(lat) * core.r;
|
|
170
|
+
const perspective = CAMERA_DISTANCE / (CAMERA_DISTANCE - depth);
|
|
171
|
+
const foreshorten = Math.sqrt(perspective);
|
|
172
|
+
return {
|
|
173
|
+
x: Math.sin(lon) * Math.cos(lat) * core.r * perspective,
|
|
174
|
+
y: core.cy + Math.sin(lat) * core.r * 0.965 * perspective,
|
|
175
|
+
sx: Math.max(0.02, Math.cos(lon)) * core.r * foreshorten,
|
|
176
|
+
sy: Math.max(0.02, Math.cos(lat)) * core.r * foreshorten,
|
|
177
|
+
front,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const RING_OUTER = 1;
|
|
181
|
+
const RING_INNER = 0.64;
|
|
182
|
+
const RING_SAMPLES = 96;
|
|
183
|
+
const radialProfiles = new WeakMap();
|
|
184
|
+
function radialProfile(body) {
|
|
185
|
+
let profile = radialProfiles.get(body);
|
|
186
|
+
if (profile)
|
|
187
|
+
return profile;
|
|
188
|
+
const polar = body.outline
|
|
189
|
+
.map(([x, y]) => ({ angle: Math.atan2(y, x), radius: Math.hypot(x, y) }))
|
|
190
|
+
.sort((a, b) => a.angle - b.angle);
|
|
191
|
+
profile = [];
|
|
192
|
+
for (let i = 0; i < RING_SAMPLES; i++) {
|
|
193
|
+
let angle = (i / RING_SAMPLES) * TAU;
|
|
194
|
+
if (angle > Math.PI)
|
|
195
|
+
angle -= TAU;
|
|
196
|
+
let next = polar.findIndex((p) => p.angle >= angle);
|
|
197
|
+
if (next === -1)
|
|
198
|
+
next = 0;
|
|
199
|
+
const b = polar[next];
|
|
200
|
+
const a = polar[(next + polar.length - 1) % polar.length];
|
|
201
|
+
let span = b.angle - a.angle;
|
|
202
|
+
let offset = angle - a.angle;
|
|
203
|
+
if (span <= 0)
|
|
204
|
+
span += TAU;
|
|
205
|
+
if (offset < 0)
|
|
206
|
+
offset += TAU;
|
|
207
|
+
profile.push(mix(a.radius, b.radius, span > 0 ? offset / span : 0));
|
|
208
|
+
}
|
|
209
|
+
radialProfiles.set(body, profile);
|
|
210
|
+
return profile;
|
|
211
|
+
}
|
|
212
|
+
export function shellPath(body, morph) {
|
|
213
|
+
if (morph <= 0)
|
|
214
|
+
return closedPath(body.outline);
|
|
215
|
+
const profile = radialProfile(body);
|
|
216
|
+
const outer = profile.map((radius, i) => {
|
|
217
|
+
const angle = (i / RING_SAMPLES) * TAU;
|
|
218
|
+
const r = mix(radius, RING_OUTER, morph);
|
|
219
|
+
return [Math.cos(angle) * r, Math.sin(angle) * r];
|
|
220
|
+
});
|
|
221
|
+
const inner = outer.map(([x, y]) => [x * RING_INNER * morph, y * RING_INNER * morph]);
|
|
222
|
+
return closedPath(outer) + closedPath(inner);
|
|
223
|
+
}
|
|
224
|
+
export function stripePath(body, yaw, a, b) {
|
|
225
|
+
let d = "";
|
|
226
|
+
const base = Math.atan2(Math.sin(a + yaw), Math.cos(a + yaw));
|
|
227
|
+
const length = b - a;
|
|
228
|
+
for (const k of [-1, 0, 1]) {
|
|
229
|
+
const low = Math.max(base + k * TAU, -Math.PI / 2);
|
|
230
|
+
const high = Math.min(base + k * TAU + length, Math.PI / 2);
|
|
231
|
+
if (high > low)
|
|
232
|
+
d += `M${f3(Math.sin(low) * body.width)} -1.3H${f3(Math.sin(high) * body.width)}V1.3H${f3(Math.sin(low) * body.width)}Z`;
|
|
233
|
+
}
|
|
234
|
+
return d || "M0 0";
|
|
235
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const ITALY_PATH = "M188.4,46.1L207.3,52.6L210.9,43.9L241.6,36L248.6,51.8L293.1,63.5L289.7,85.7L297.1,104.7L272.4,98.2L247.1,114L248.8,135.9L245,148.4L255.2,170.5L284.4,192.2L300,227.5L334.6,261.4L359,261.1L366.6,270.4L357.8,278.7L385.7,293.6L408.5,306.1L435.2,327.5L438.4,335.1L432.6,349.6L415.4,330.7L388.3,324L375.2,350.3L397.7,365.2L394,386.2L381,388.5L364.4,422.6L351.5,425.6L351.6,413.5L357.9,392.2L364.7,383.7L352.5,360.4L343,340L330.1,334.9L321,317.3L301,309.9L287.5,293.3L264.5,290.6L240.2,271.9L211.7,244.6L190.6,220.2L180.9,177.9L165.4,172.9L140.1,158.6L125.8,164.4L107.8,184.5L94.9,187.7L98.4,168.9L81.6,163.4L73.6,129.6L84.3,116.1L75.2,99.5L76.5,86.9L89.9,96.4L104.9,94.3L122.3,79.1L127.7,86.2L142.5,84.8L149.2,66.7L172.3,72.3L186,64.7ZM322.8,416.3L346.4,412.9L335.2,443.9L339.8,456L333.3,476L309.5,461.4L293.7,457.2L250.3,437.3L254.6,417L291,420.6ZM134.5,305.2L150.1,292.4L168.7,321.6L164.4,375.2L150.2,372.6L137.5,386L125.8,375.4L124.5,326.6L117.4,303.1Z";
|
|
2
|
+
export declare const ITALY_BOX = 512;
|
|
3
|
+
export declare const HALLMARK_SIZE = 0.39;
|
|
4
|
+
export declare const HALLMARK_LONGITUDE = 1.24;
|
|
5
|
+
//# sourceMappingURL=hallmark.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hallmark.d.ts","sourceRoot":"","sources":["../../../src/internal/forma/hallmark.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,UAAU,07BACk6B,CAAC;AAE17B,eAAO,MAAM,SAAS,MAAM,CAAC;AAC7B,eAAO,MAAM,aAAa,OAAO,CAAC;AAClC,eAAO,MAAM,kBAAkB,OAAO,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export const ITALY_PATH = "M188.4,46.1L207.3,52.6L210.9,43.9L241.6,36L248.6,51.8L293.1,63.5L289.7,85.7L297.1,104.7L272.4,98.2L247.1,114L248.8,135.9L245,148.4L255.2,170.5L284.4,192.2L300,227.5L334.6,261.4L359,261.1L366.6,270.4L357.8,278.7L385.7,293.6L408.5,306.1L435.2,327.5L438.4,335.1L432.6,349.6L415.4,330.7L388.3,324L375.2,350.3L397.7,365.2L394,386.2L381,388.5L364.4,422.6L351.5,425.6L351.6,413.5L357.9,392.2L364.7,383.7L352.5,360.4L343,340L330.1,334.9L321,317.3L301,309.9L287.5,293.3L264.5,290.6L240.2,271.9L211.7,244.6L190.6,220.2L180.9,177.9L165.4,172.9L140.1,158.6L125.8,164.4L107.8,184.5L94.9,187.7L98.4,168.9L81.6,163.4L73.6,129.6L84.3,116.1L75.2,99.5L76.5,86.9L89.9,96.4L104.9,94.3L122.3,79.1L127.7,86.2L142.5,84.8L149.2,66.7L172.3,72.3L186,64.7ZM322.8,416.3L346.4,412.9L335.2,443.9L339.8,456L333.3,476L309.5,461.4L293.7,457.2L250.3,437.3L254.6,417L291,420.6ZM134.5,305.2L150.1,292.4L168.7,321.6L164.4,375.2L150.2,372.6L137.5,386L125.8,375.4L124.5,326.6L117.4,303.1Z";
|
|
2
|
+
export const ITALY_BOX = 512;
|
|
3
|
+
export const HALLMARK_SIZE = 0.39;
|
|
4
|
+
export const HALLMARK_LONGITUDE = 1.24;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export declare const formaForms: readonly ["nadia", "goccia", "nuvola", "triangolo", "esagono", "quadrato", "pillola", "uovo"];
|
|
2
|
+
export type FormaForm = (typeof formaForms)[number];
|
|
3
|
+
/** The forms a customer may choose; Nadia's is reserved for the root Agent. */
|
|
4
|
+
export declare const formaChoices: ("goccia" | "nuvola" | "triangolo" | "esagono" | "quadrato" | "pillola" | "uovo")[];
|
|
5
|
+
export type FormaChoice = (typeof formaChoices)[number];
|
|
6
|
+
export declare const formaStates: readonly ["idle", "working", "thinking", "waiting", "blocked", "done"];
|
|
7
|
+
export type FormaState = (typeof formaStates)[number];
|
|
8
|
+
export type FormaView = "front" | "side";
|
|
9
|
+
export declare const formaColors: readonly ["verde", "cobalto", "rosso", "arancio", "giallo", "avorio"];
|
|
10
|
+
export type FormaColor = (typeof formaColors)[number];
|
|
11
|
+
/** The CSS custom property that carries each shell colour. */
|
|
12
|
+
export declare const formaColorVariable: Record<FormaColor, string>;
|
|
13
|
+
/** The three pigments that sweep a completion, the shell's own leading. */
|
|
14
|
+
export declare const celebrationColors: Record<FormaColor, readonly [FormaColor, FormaColor, FormaColor]>;
|
|
15
|
+
export type FormaVisor = "black" | "ivory";
|
|
16
|
+
export type FormaEyeTone = "light" | "ink";
|
|
17
|
+
export interface FormDetail {
|
|
18
|
+
name: string;
|
|
19
|
+
/** The shell colour a form wears until the customer picks one. */
|
|
20
|
+
pigment: FormaColor;
|
|
21
|
+
/** The visor fill, and the tone the eyes take against it. */
|
|
22
|
+
visor: FormaVisor;
|
|
23
|
+
eyes: FormaEyeTone;
|
|
24
|
+
}
|
|
25
|
+
export declare const formDetails: Record<FormaForm, FormDetail>;
|
|
26
|
+
/** The form an Agent wears until a customer chooses one. */
|
|
27
|
+
export declare const defaultForm: FormaChoice;
|
|
28
|
+
export interface FormaOptions {
|
|
29
|
+
form: FormaForm;
|
|
30
|
+
/** Shell colour. Nadia always keeps her tricolour. */
|
|
31
|
+
color?: FormaColor;
|
|
32
|
+
state?: FormaState;
|
|
33
|
+
/** CSS pixels, 12 through 512. Default 32. */
|
|
34
|
+
size?: number;
|
|
35
|
+
/** Hold a deterministic still. Default false. */
|
|
36
|
+
paused?: boolean;
|
|
37
|
+
/** Turn to show the flank. Waiting and blocked always face forward. */
|
|
38
|
+
view?: FormaView;
|
|
39
|
+
/** Changing this key replays a gesture even if the state is unchanged. */
|
|
40
|
+
motionKey?: string | number;
|
|
41
|
+
}
|
|
42
|
+
export interface ResolvedFormaOptions {
|
|
43
|
+
form: FormaForm;
|
|
44
|
+
color?: FormaColor;
|
|
45
|
+
state: FormaState;
|
|
46
|
+
size: number;
|
|
47
|
+
paused: boolean;
|
|
48
|
+
view: FormaView;
|
|
49
|
+
motionKey?: string | number;
|
|
50
|
+
}
|
|
51
|
+
export declare function resolveFormaOptions(options: FormaOptions): ResolvedFormaOptions;
|
|
52
|
+
export declare function shellColor(options: Pick<ResolvedFormaOptions, "form" | "color">): FormaColor;
|
|
53
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/internal/forma/model.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,UAAU,+FASb,CAAC;AACX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpD,+EAA+E;AAC/E,eAAO,MAAM,YAAY,qFAExB,CAAC;AACF,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAExD,eAAO,MAAM,WAAW,wEAAyE,CAAC;AAClG,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AACtD,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAEzC,eAAO,MAAM,WAAW,uEAAwE,CAAC;AACjG,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtD,8DAA8D;AAC9D,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAOzD,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,CAAC,CAQ7F,CAAC;AAEJ,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC;AAC3C,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,KAAK,CAAC;AAE3C,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,OAAO,EAAE,UAAU,CAAC;IACpB,6DAA6D;IAC7D,KAAK,EAAE,UAAU,CAAC;IAClB,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,SAAS,EAAE,UAAU,CASrD,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,WAAW,EAAE,WAAoB,CAAC;AAE/C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,SAAS,CAAC;IAChB,sDAAsD;IACtD,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uEAAuE;IACvE,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,UAAU,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,SAAS,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7B;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,GAAG,oBAAoB,CAoB/E;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,oBAAoB,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,UAAU,CAE5F"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export const formaForms = [
|
|
2
|
+
"nadia",
|
|
3
|
+
"goccia",
|
|
4
|
+
"nuvola",
|
|
5
|
+
"triangolo",
|
|
6
|
+
"esagono",
|
|
7
|
+
"quadrato",
|
|
8
|
+
"pillola",
|
|
9
|
+
"uovo",
|
|
10
|
+
];
|
|
11
|
+
/** The forms a customer may choose; Nadia's is reserved for the root Agent. */
|
|
12
|
+
export const formaChoices = formaForms.filter((form) => form !== "nadia");
|
|
13
|
+
export const formaStates = ["idle", "working", "thinking", "waiting", "blocked", "done"];
|
|
14
|
+
export const formaColors = ["verde", "cobalto", "rosso", "arancio", "giallo", "avorio"];
|
|
15
|
+
/** The CSS custom property that carries each shell colour. */
|
|
16
|
+
export const formaColorVariable = {
|
|
17
|
+
verde: "--nc-identity-verde",
|
|
18
|
+
cobalto: "--nc-identity-cobalto",
|
|
19
|
+
rosso: "--nc-identity-rosso",
|
|
20
|
+
arancio: "--nc-identity-arancio",
|
|
21
|
+
giallo: "--nc-identity-giallo",
|
|
22
|
+
avorio: "--nc-forma-avorio",
|
|
23
|
+
};
|
|
24
|
+
/** The three pigments that sweep a completion, the shell's own leading. */
|
|
25
|
+
export const celebrationColors = {
|
|
26
|
+
verde: ["verde", "cobalto", "giallo"],
|
|
27
|
+
cobalto: ["cobalto", "verde", "giallo"],
|
|
28
|
+
rosso: ["rosso", "arancio", "giallo"],
|
|
29
|
+
arancio: ["arancio", "rosso", "giallo"],
|
|
30
|
+
giallo: ["giallo", "verde", "cobalto"],
|
|
31
|
+
avorio: ["avorio", "cobalto", "verde"],
|
|
32
|
+
};
|
|
33
|
+
export const formDetails = {
|
|
34
|
+
nadia: { name: "Nadia", pigment: "verde", visor: "black", eyes: "light" },
|
|
35
|
+
goccia: { name: "Goccia", pigment: "rosso", visor: "black", eyes: "light" },
|
|
36
|
+
nuvola: { name: "Nuvola", pigment: "cobalto", visor: "ivory", eyes: "ink" },
|
|
37
|
+
triangolo: { name: "Triangolo", pigment: "verde", visor: "ivory", eyes: "ink" },
|
|
38
|
+
esagono: { name: "Esagono", pigment: "giallo", visor: "black", eyes: "light" },
|
|
39
|
+
quadrato: { name: "Quadrato", pigment: "arancio", visor: "ivory", eyes: "ink" },
|
|
40
|
+
pillola: { name: "Pillola", pigment: "verde", visor: "black", eyes: "light" },
|
|
41
|
+
uovo: { name: "Uovo", pigment: "cobalto", visor: "black", eyes: "light" },
|
|
42
|
+
};
|
|
43
|
+
/** The form an Agent wears until a customer chooses one. */
|
|
44
|
+
export const defaultForm = "uovo";
|
|
45
|
+
export function resolveFormaOptions(options) {
|
|
46
|
+
if (!formaForms.includes(options.form))
|
|
47
|
+
throw new TypeError("Unknown Forma avatar form");
|
|
48
|
+
if (options.color !== undefined && !formaColors.includes(options.color))
|
|
49
|
+
throw new TypeError("Unknown Forma avatar colour");
|
|
50
|
+
const state = options.state ?? "idle";
|
|
51
|
+
if (!formaStates.includes(state))
|
|
52
|
+
throw new TypeError("Unknown Forma avatar state");
|
|
53
|
+
const size = options.size ?? 32;
|
|
54
|
+
if (!Number.isFinite(size) || size < 12 || size > 512)
|
|
55
|
+
throw new RangeError("Forma avatar size must be 12–512 CSS pixels");
|
|
56
|
+
if (options.view !== undefined && options.view !== "front" && options.view !== "side")
|
|
57
|
+
throw new TypeError("Unknown Forma avatar view");
|
|
58
|
+
return {
|
|
59
|
+
form: options.form,
|
|
60
|
+
color: options.form === "nadia" ? undefined : options.color,
|
|
61
|
+
state,
|
|
62
|
+
size,
|
|
63
|
+
paused: options.paused ?? false,
|
|
64
|
+
view: state === "waiting" || state === "blocked" ? "front" : (options.view ?? "front"),
|
|
65
|
+
motionKey: options.motionKey,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export function shellColor(options) {
|
|
69
|
+
return options.form === "nadia" ? "verde" : (options.color ?? formDetails[options.form].pigment);
|
|
70
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ResolvedFormaOptions } from "./model.js";
|
|
2
|
+
export declare const easeOut: (x: number) => number;
|
|
3
|
+
export declare const easeInOut: (x: number) => number;
|
|
4
|
+
declare function makePose(): {
|
|
5
|
+
yaw: number;
|
|
6
|
+
pitch: number;
|
|
7
|
+
roll: number;
|
|
8
|
+
lift: number;
|
|
9
|
+
stretch: number;
|
|
10
|
+
open: number;
|
|
11
|
+
gazeX: number;
|
|
12
|
+
gazeY: number;
|
|
13
|
+
morph: number;
|
|
14
|
+
working: number;
|
|
15
|
+
waiting: number;
|
|
16
|
+
thinking: number;
|
|
17
|
+
done: number;
|
|
18
|
+
};
|
|
19
|
+
export type Pose = ReturnType<typeof makePose>;
|
|
20
|
+
type SpringChannel = "pitch" | "roll" | "lift" | "stretch";
|
|
21
|
+
export interface FormaMotion extends ResolvedFormaOptions {
|
|
22
|
+
pose: Pose;
|
|
23
|
+
velocity: Record<SpringChannel, number>;
|
|
24
|
+
pointer: {
|
|
25
|
+
x: number;
|
|
26
|
+
y: number;
|
|
27
|
+
};
|
|
28
|
+
started: number;
|
|
29
|
+
entered: number;
|
|
30
|
+
lastTime: number;
|
|
31
|
+
celebratedAt: number;
|
|
32
|
+
skipEntry: boolean;
|
|
33
|
+
snap: boolean;
|
|
34
|
+
still: boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare function createMotion(options: ResolvedFormaOptions, now: number): FormaMotion;
|
|
37
|
+
export declare function updateMotion(motion: FormaMotion, options: ResolvedFormaOptions, now: number): void;
|
|
38
|
+
export declare function needsMotion(motion: FormaMotion, now: number): boolean;
|
|
39
|
+
export declare function settled(motion: FormaMotion, now: number): boolean;
|
|
40
|
+
export declare function stepPose(motion: FormaMotion, now: number, dt: number): Pose;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=motion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"motion.d.ts","sourceRoot":"","sources":["../../../src/internal/forma/motion.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAqBpD,eAAO,MAAM,OAAO,MAhBP,MAAM,KAAG,MAgByB,CAAC;AAChD,eAAO,MAAM,SAAS,MAjBT,MAAM,KAAG,MAiB4B,CAAC;AAEnD,iBAAS,QAAQ;;;;;;;;;;;;;;EAgBhB;AACD,MAAM,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC/C,KAAK,aAAa,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAE3D,MAAM,WAAW,WAAY,SAAQ,oBAAoB;IACvD,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IACxC,OAAO,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW,CAcpF;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,oBAAoB,EAC7B,GAAG,EAAE,MAAM,GACV,IAAI,CAYN;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAOrE;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAQjE;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAsG3E"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { TAU, clamp, mix } from "./geometry.js";
|
|
2
|
+
function bezier(x1, y1, x2, y2) {
|
|
3
|
+
const coordinate = (t, a, b) => 3 * (1 - t) * (1 - t) * t * a + 3 * (1 - t) * t * t * b + t * t * t;
|
|
4
|
+
return (x) => {
|
|
5
|
+
const target = clamp(x, 0, 1);
|
|
6
|
+
let low = 0;
|
|
7
|
+
let high = 1;
|
|
8
|
+
let t = target;
|
|
9
|
+
for (let i = 0; i < 12; i++) {
|
|
10
|
+
if (coordinate(t, x1, x2) < target)
|
|
11
|
+
low = t;
|
|
12
|
+
else
|
|
13
|
+
high = t;
|
|
14
|
+
t = (low + high) / 2;
|
|
15
|
+
}
|
|
16
|
+
return coordinate(t, y1, y2);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const SPRING_STIFFNESS = 100;
|
|
20
|
+
const SPRING_DAMPING = 10;
|
|
21
|
+
export const easeOut = bezier(0.23, 1, 0.32, 1);
|
|
22
|
+
export const easeInOut = bezier(0.77, 0, 0.175, 1);
|
|
23
|
+
function makePose() {
|
|
24
|
+
return {
|
|
25
|
+
yaw: -0.16,
|
|
26
|
+
pitch: 0.02,
|
|
27
|
+
roll: 0,
|
|
28
|
+
lift: 0,
|
|
29
|
+
stretch: 1,
|
|
30
|
+
open: 1,
|
|
31
|
+
gazeX: 0,
|
|
32
|
+
gazeY: 0,
|
|
33
|
+
morph: 0,
|
|
34
|
+
working: 0,
|
|
35
|
+
waiting: 0,
|
|
36
|
+
thinking: 0,
|
|
37
|
+
done: 0,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function createMotion(options, now) {
|
|
41
|
+
return {
|
|
42
|
+
...options,
|
|
43
|
+
pose: makePose(),
|
|
44
|
+
velocity: { pitch: 0, roll: 0, lift: 0, stretch: 0 },
|
|
45
|
+
pointer: { x: 0, y: 0 },
|
|
46
|
+
started: now,
|
|
47
|
+
entered: now,
|
|
48
|
+
lastTime: now,
|
|
49
|
+
celebratedAt: -Infinity,
|
|
50
|
+
skipEntry: true,
|
|
51
|
+
snap: true,
|
|
52
|
+
still: options.paused,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function updateMotion(motion, options, now) {
|
|
56
|
+
const formChanged = options.form !== motion.form;
|
|
57
|
+
const event = options.state !== motion.state || options.motionKey !== motion.motionKey;
|
|
58
|
+
if (formChanged)
|
|
59
|
+
Object.assign(motion, createMotion(options, now));
|
|
60
|
+
else if (event) {
|
|
61
|
+
motion.entered = now;
|
|
62
|
+
motion.skipEntry = options.paused;
|
|
63
|
+
motion.celebratedAt = options.state === "done" && !options.paused ? now : -Infinity;
|
|
64
|
+
}
|
|
65
|
+
if (options.paused !== motion.paused || options.view !== motion.view)
|
|
66
|
+
motion.snap = options.paused;
|
|
67
|
+
Object.assign(motion, options);
|
|
68
|
+
}
|
|
69
|
+
export function needsMotion(motion, now) {
|
|
70
|
+
if (motion.still)
|
|
71
|
+
return false;
|
|
72
|
+
const age = now - motion.entered;
|
|
73
|
+
if (motion.state === "waiting")
|
|
74
|
+
return age < 900;
|
|
75
|
+
if (motion.state === "blocked")
|
|
76
|
+
return age < 1000;
|
|
77
|
+
if (motion.state === "done")
|
|
78
|
+
return !motion.skipEntry && age < 2500;
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
export function settled(motion, now) {
|
|
82
|
+
const t = (now - motion.entered) / 1000;
|
|
83
|
+
return (motion.still ||
|
|
84
|
+
(motion.state === "done" && (motion.skipEntry || t >= 2.45)) ||
|
|
85
|
+
(motion.state === "waiting" && t >= 0.85) ||
|
|
86
|
+
(motion.state === "blocked" && t >= 1));
|
|
87
|
+
}
|
|
88
|
+
export function stepPose(motion, now, dt) {
|
|
89
|
+
const { pose, velocity, state: current, still } = motion;
|
|
90
|
+
const t = (now - motion.entered) / 1000;
|
|
91
|
+
const clock = (now - motion.started) / 1000;
|
|
92
|
+
const point = motion.pointer;
|
|
93
|
+
const instant = still ||
|
|
94
|
+
motion.snap ||
|
|
95
|
+
(current === "waiting" && t > 0.85) ||
|
|
96
|
+
(current === "blocked" && t >= 1) ||
|
|
97
|
+
(current === "done" && t >= 2.45);
|
|
98
|
+
const blend = instant ? 1 : 1 - Math.exp(-12 * dt);
|
|
99
|
+
motion.snap = false;
|
|
100
|
+
const target = makePose();
|
|
101
|
+
if (current === "working")
|
|
102
|
+
Object.assign(target, { yaw: 0, pitch: 0.16, open: 0.76, gazeY: -0.04, working: 1 });
|
|
103
|
+
if (current === "waiting")
|
|
104
|
+
Object.assign(target, { yaw: 0, pitch: -0.025, open: 1.1, gazeY: 0, waiting: 1 });
|
|
105
|
+
if (current === "thinking")
|
|
106
|
+
Object.assign(target, {
|
|
107
|
+
yaw: 0.15,
|
|
108
|
+
pitch: -0.17,
|
|
109
|
+
roll: -0.09,
|
|
110
|
+
lift: 0.025,
|
|
111
|
+
open: 0.96,
|
|
112
|
+
gazeX: 0.065,
|
|
113
|
+
gazeY: 0.075,
|
|
114
|
+
thinking: 1,
|
|
115
|
+
});
|
|
116
|
+
if (current === "done")
|
|
117
|
+
Object.assign(target, { yaw: 0, pitch: -0.015, open: 0.24, gazeY: 0.018, done: 1 });
|
|
118
|
+
if (current === "blocked")
|
|
119
|
+
Object.assign(target, { yaw: 0, pitch: 0, morph: 1, open: 0.4 });
|
|
120
|
+
if (!still) {
|
|
121
|
+
if (current === "idle") {
|
|
122
|
+
target.yaw += Math.sin(t * 0.7) * 0.018 + point.x * 0.9;
|
|
123
|
+
target.pitch += point.y * 0.5;
|
|
124
|
+
target.gazeX = point.x * 0.11;
|
|
125
|
+
target.gazeY = -point.y * 0.07;
|
|
126
|
+
target.lift = Math.sin(t * 1.1) * 0.009;
|
|
127
|
+
}
|
|
128
|
+
if (current === "working") {
|
|
129
|
+
const scan = Math.sin((clock * TAU) / 3.2);
|
|
130
|
+
const beat = Math.sin((clock * TAU) / 1.05);
|
|
131
|
+
target.gazeX = scan * 0.085;
|
|
132
|
+
target.yaw = scan * 0.15;
|
|
133
|
+
target.roll = -scan * 0.036;
|
|
134
|
+
target.pitch += beat * 0.033;
|
|
135
|
+
target.lift = 0.015 * beat;
|
|
136
|
+
target.open += beat * 0.08;
|
|
137
|
+
if (!motion.skipEntry && t < 0.5) {
|
|
138
|
+
const acknowledge = Math.sin((t / 0.5) * Math.PI);
|
|
139
|
+
target.pitch += 0.17 * acknowledge;
|
|
140
|
+
target.lift -= 0.065 * acknowledge;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (current === "thinking") {
|
|
144
|
+
const consider = Math.sin((clock * TAU) / 4.6);
|
|
145
|
+
target.yaw += consider * 0.07;
|
|
146
|
+
target.roll += consider * 0.035;
|
|
147
|
+
target.gazeX += consider * 0.019;
|
|
148
|
+
target.lift += consider * 0.015;
|
|
149
|
+
}
|
|
150
|
+
if (current === "done" && !motion.skipEntry && t < 1.65) {
|
|
151
|
+
const windup = 1 - easeOut((t - 0.2) / 0.16);
|
|
152
|
+
target.yaw = -0.16 * windup + TAU * easeInOut((t - 0.2) / 0.78);
|
|
153
|
+
target.pitch = 0.13 * windup - 0.07 * Math.sin(clamp((t - 0.2) / 0.78, 0, 1) * Math.PI);
|
|
154
|
+
target.roll = -0.11 * windup + 0.12 * Math.sin(clamp((t - 0.2) / 0.88, 0, 1) * TAU);
|
|
155
|
+
if (t < 0.2)
|
|
156
|
+
target.lift = -0.07 * easeOut(t / 0.2);
|
|
157
|
+
else if (t < 0.53)
|
|
158
|
+
target.lift = mix(-0.07, 0.24, easeOut((t - 0.2) / 0.33));
|
|
159
|
+
else if (t < 0.98)
|
|
160
|
+
target.lift = 0.24 * (1 - easeInOut((t - 0.53) / 0.45));
|
|
161
|
+
else
|
|
162
|
+
target.lift = 0.042 * Math.sin(clamp((t - 0.98) / 0.4, 0, 1) * Math.PI);
|
|
163
|
+
target.stretch =
|
|
164
|
+
1 - 0.032 * windup + 0.024 * Math.sin(clamp((t - 0.2) / 0.78, 0, 1) * Math.PI);
|
|
165
|
+
}
|
|
166
|
+
if (current === "working" || current === "thinking") {
|
|
167
|
+
target.yaw += point.x * 0.45;
|
|
168
|
+
target.pitch += point.y * 0.2;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (motion.view === "side")
|
|
172
|
+
Object.assign(target, { yaw: -1.2, pitch: 0.02, roll: 0, gazeX: 0, gazeY: 0 });
|
|
173
|
+
target.yaw =
|
|
174
|
+
pose.yaw + Math.atan2(Math.sin(target.yaw - pose.yaw), Math.cos(target.yaw - pose.yaw));
|
|
175
|
+
for (const key of Object.keys(pose)) {
|
|
176
|
+
if (key in velocity && !instant) {
|
|
177
|
+
const channel = key;
|
|
178
|
+
const steps = Math.max(1, Math.ceil(dt * 120));
|
|
179
|
+
const step = dt / steps;
|
|
180
|
+
for (let i = 0; i < steps; i++) {
|
|
181
|
+
velocity[channel] +=
|
|
182
|
+
(SPRING_STIFFNESS * (target[key] - pose[key]) - SPRING_DAMPING * velocity[channel]) *
|
|
183
|
+
step;
|
|
184
|
+
pose[key] += velocity[channel] * step;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
pose[key] = mix(pose[key], target[key], blend);
|
|
189
|
+
if (key in velocity)
|
|
190
|
+
velocity[key] = 0;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return pose;
|
|
194
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ResolvedFormaOptions } from "./model.js";
|
|
2
|
+
export interface FormaHandle {
|
|
3
|
+
update(options: ResolvedFormaOptions): void;
|
|
4
|
+
dispose(): void;
|
|
5
|
+
}
|
|
6
|
+
export interface FormaMountCallbacks {
|
|
7
|
+
onModeChange?: (mode: "live" | "still") => void;
|
|
8
|
+
}
|
|
9
|
+
export declare function mountForma(svg: SVGSVGElement, options: ResolvedFormaOptions, callbacks?: FormaMountCallbacks): FormaHandle;
|
|
10
|
+
//# sourceMappingURL=rig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rig.d.ts","sourceRoot":"","sources":["../../../src/internal/forma/rig.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAUpD,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC5C,OAAO,IAAI,IAAI,CAAC;CACjB;AACD,MAAM,WAAW,mBAAmB;IAClC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,KAAK,IAAI,CAAC;CACjD;AAyLD,wBAAgB,UAAU,CACxB,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,oBAAoB,EAC7B,SAAS,GAAE,mBAAwB,GAClC,WAAW,CAwCb"}
|