wobnimo 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/AGENTS.md +18 -4
  2. package/README.md +38 -7
  3. package/dist/characters/bao.d.ts +6 -0
  4. package/dist/characters/bao.js +1 -0
  5. package/dist/characters/berry.d.ts +6 -0
  6. package/dist/characters/berry.js +1 -0
  7. package/dist/characters/boo.js +1 -1
  8. package/dist/characters/bumble.d.ts +6 -0
  9. package/dist/characters/bumble.js +1 -0
  10. package/dist/characters/cacti.d.ts +6 -0
  11. package/dist/characters/cacti.js +1 -0
  12. package/dist/characters/cap.d.ts +6 -0
  13. package/dist/characters/cap.js +1 -0
  14. package/dist/characters/drift.d.ts +6 -0
  15. package/dist/characters/drift.js +1 -0
  16. package/dist/characters/ember.d.ts +6 -0
  17. package/dist/characters/ember.js +1 -0
  18. package/dist/characters/hop.d.ts +6 -0
  19. package/dist/characters/hop.js +1 -0
  20. package/dist/characters/index.d.ts +11 -1
  21. package/dist/characters/index.js +11 -11
  22. package/dist/characters/inky.js +1 -1
  23. package/dist/characters/latte.d.ts +6 -0
  24. package/dist/characters/latte.js +1 -0
  25. package/dist/characters/microduck.js +1 -1
  26. package/dist/characters/miso.js +1 -1
  27. package/dist/characters/mochi.js +1 -1
  28. package/dist/characters/pebble.js +1 -1
  29. package/dist/characters/pip.js +1 -1
  30. package/dist/characters/rex.js +1 -1
  31. package/dist/characters/swirl.d.ts +6 -0
  32. package/dist/characters/swirl.js +1 -0
  33. package/dist/characters/tad.js +1 -1
  34. package/dist/characters/volt.js +1 -1
  35. package/dist/cli.js +1888 -0
  36. package/dist/element.d.ts +1 -1
  37. package/dist/element.js +9 -9
  38. package/dist/index-Cm5wWKoT.d.ts +12 -0
  39. package/dist/index.d.ts +4 -0
  40. package/dist/index.js +6 -6
  41. package/dist/react.js +16 -16
  42. package/dist/wobnimo.js +12 -12
  43. package/package.json +14 -4
  44. package/skill/SKILL.md +719 -0
  45. package/skill/examples/bao.ts +73 -0
  46. package/skill/examples/berry.ts +97 -0
  47. package/skill/examples/boo.ts +87 -0
  48. package/skill/examples/bumble.ts +70 -0
  49. package/skill/examples/cacti.ts +79 -0
  50. package/skill/examples/cap.ts +59 -0
  51. package/skill/examples/drift.ts +74 -0
  52. package/skill/examples/ember.ts +78 -0
  53. package/skill/examples/hop.ts +66 -0
  54. package/skill/examples/inky.ts +168 -0
  55. package/skill/examples/latte.ts +73 -0
  56. package/skill/examples/microduck.ts +222 -0
  57. package/skill/examples/miso.ts +182 -0
  58. package/skill/examples/mochi.ts +78 -0
  59. package/skill/examples/pebble.ts +174 -0
  60. package/skill/examples/pip.ts +170 -0
  61. package/skill/examples/rex.ts +193 -0
  62. package/skill/examples/swirl.ts +69 -0
  63. package/skill/examples/tad.ts +117 -0
  64. package/skill/examples/volt.ts +214 -0
  65. package/skill/reference/recipes.md +551 -0
  66. package/skill/reference/spec.md +258 -0
  67. package/skill/templates/starter.ts +73 -0
  68. package/dist/index-DRDEWSGV.d.ts +0 -8
@@ -0,0 +1,551 @@
1
+ # Recipes
2
+
3
+ Ready-made features. Each recipe is a fragment of a spec: merge each list
4
+ into the list of the same name in your file (`bones` into `bones`, `parts`
5
+ into `parts`, and so on), keeping the order shown.
6
+
7
+ **Every number here fits the starter template** (`npx wobnimo new`):
8
+
9
+ ```
10
+ body bone pivot [0, 0, 0] body ellipsoid at [0, 0.19, 0] radii [0.25, 0.19, 0.23]
11
+ head bone pivot [0, 0.3, 0] head ellipsoid at [0, 0.2, 0] radii [0.31, 0.27, 0.28]
12
+ (head space: top of head y 0.47, front z 0.28, sides x ±0.31)
13
+ ```
14
+
15
+ If your body or head is a different size or in a different place, move the
16
+ recipe with it: shift `at`, `from`, `to` and `pivot` by the same amount your
17
+ head or body moved, and scale sizes by how much bigger or smaller it is.
18
+ Then run `npx wobnimo check` and apply its fixes.
19
+
20
+ Materials named `skin` in the recipes mean your main material. Recipes that
21
+ add a material give its full preset.
22
+
23
+ ## Finding a point on a surface
24
+
25
+ You often need a point **on** the surface: where an ear sits, where a spot
26
+ goes, where a paint belongs. For an ellipsoid with centre `c` and radii
27
+ `[rx, ry, rz]`, pick a direction `d = [dx, dy, dz]` from the centre (it does
28
+ not need to be unit length) and compute:
29
+
30
+ ```
31
+ k = 1 / sqrt((dx/rx)² + (dy/ry)² + (dz/rz)²)
32
+ point = [c.x + dx·k, c.y + dy·k, c.z + dz·k]
33
+ ```
34
+
35
+ For a sphere of radius `r`, `k = r / sqrt(dx² + dy² + dz²)`.
36
+
37
+ Example: a spot on the starter head, up and to the right and forward,
38
+ direction `[0.5, 0.5, 0.7]`:
39
+ `k = 1 / sqrt((0.5/0.31)² + (0.5/0.27)² + (0.7/0.28)²) = 1 / sqrt(2.60 + 3.43 + 6.25) = 0.285`
40
+ → point `[0.143, 0.2 + 0.143, 0.2] = [0.143, 0.343, 0.2]` in head space.
41
+
42
+ To push something **into** the surface (an ear base, a horn root), move it a
43
+ little toward the centre. To sit **on** the surface (a paint, a spot), use the
44
+ point itself.
45
+
46
+ ---
47
+
48
+ ## Ears
49
+
50
+ ### Round ears (bear, panda, mouse, hamster)
51
+
52
+ ```ts
53
+ materials: {
54
+ ear: { color: '#ffb35c', shadeOf: 'skin', f0: 0.04, spec: 0.8, shininess: 60, rim: 0.8, sss: 0.2, sssColor: '#ffd9a8' },
55
+ },
56
+ parts: [
57
+ { bone: 'head', shape: 'sphere', at: [-0.2, 0.42, -0.03], radius: 0.09, blend: 0.03, material: 'ear' },
58
+ { bone: 'head', shape: 'sphere', at: [0.2, 0.42, -0.03], radius: 0.09, blend: 0.03, material: 'ear' },
59
+ ],
60
+ paints: [
61
+ /* inner ear: painted only on the 'ear' material, so it cannot spill onto the head */
62
+ { bone: 'head', on: ['ear'], kind: 'sphere', at: [-0.21, 0.44, 0.055], radius: 0.05, color: '#ff9f9f', soft: 0.4 },
63
+ { bone: 'head', on: ['ear'], kind: 'sphere', at: [0.21, 0.44, 0.055], radius: 0.05, color: '#ff9f9f', soft: 0.4 },
64
+ ],
65
+ ```
66
+
67
+ Mouse: radius 0.13, at `[±0.24, 0.42, -0.04]`. Panda: give the ears a dark
68
+ material instead of the inner paint.
69
+
70
+ ### Pointed ears that spring (cat, fox, wolf)
71
+
72
+ ```ts
73
+ materials: {
74
+ ear: { color: '#ffb35c', shadeOf: 'skin', f0: 0.04, spec: 0.8, shininess: 60, rim: 0.8, sss: 0.2, sssColor: '#ffd9a8' },
75
+ },
76
+ bones: [
77
+ { name: 'earL', parent: 'head', pivot: [-0.15, 0.4, 0], rest: { rz: 0.3 }, jiggle: { axis: [0, 1, 0], length: 0.16, freq: 3.5, damping: 0.2, maxAngle: 0.4 } },
78
+ { name: 'earR', parent: 'head', pivot: [0.15, 0.4, 0], rest: { rz: -0.3 }, jiggle: { axis: [0, 1, 0], length: 0.16, freq: 3.5, damping: 0.2, maxAngle: 0.4 } },
79
+ ],
80
+ parts: [
81
+ { bone: 'earL', shape: 'roundCone', from: [0, 0, 0], to: [0, 0.18, 0], r1: 0.08, r2: 0.015, material: 'ear' },
82
+ { bone: 'earR', shape: 'roundCone', from: [0, 0, 0], to: [0, 0.18, 0], r1: 0.08, r2: 0.015, material: 'ear' },
83
+ ],
84
+ paints: [
85
+ { bone: 'earL', on: ['ear'], kind: 'ellipse', at: [0, 0.07, 0.04], radii: [0.035, 0.07], color: '#ffd9c2', soft: 0.4 },
86
+ { bone: 'earR', on: ['ear'], kind: 'ellipse', at: [0, 0.07, 0.04], radii: [0.035, 0.07], color: '#ffd9c2', soft: 0.4 },
87
+ ],
88
+ acting: {
89
+ listening: { earL: { rx: -0.3 }, earR: { rx: -0.3 } },
90
+ sad: { earL: { rz: 1, rx: 0.4 }, earR: { rz: -1, rx: 0.4 } },
91
+ error: { earL: { rz: 1.1 }, earR: { rz: -1.1 } },
92
+ },
93
+ ```
94
+
95
+ Dark ear tips (fox): add a sphere paint at the tip,
96
+ `{ bone: 'earL', on: ['ear'], kind: 'sphere', at: [0, 0.2, 0], radius: 0.07, color: '#3a2318', soft: 0.3 }`.
97
+
98
+ ### Long floppy ears (bunny)
99
+
100
+ ```ts
101
+ materials: {
102
+ ear: { color: '#fbf8f4', shadeOf: 'skin', f0: 0.03, spec: 0.4, shininess: 20, rim: 1.2, sheen: 0.7, sss: 0.35, sssColor: '#ffc4d4' },
103
+ },
104
+ bones: [
105
+ { name: 'earL', parent: 'head', pivot: [-0.11, 0.42, -0.02], rest: { rz: 0.3, rx: 0.15 }, jiggle: { axis: [0, 1, 0], length: 0.32, freq: 2, damping: 0.14, maxAngle: 0.8 } },
106
+ { name: 'earR', parent: 'head', pivot: [0.11, 0.42, -0.02], rest: { rz: -0.3, rx: 0.15 }, jiggle: { axis: [0, 1, 0], length: 0.32, freq: 2, damping: 0.14, maxAngle: 0.8 } },
107
+ ],
108
+ parts: [
109
+ { bone: 'earL', shape: 'ellipsoid', at: [0, 0.2, 0], radii: [0.065, 0.22, 0.035], material: 'ear' },
110
+ { bone: 'earR', shape: 'ellipsoid', at: [0, 0.2, 0], radii: [0.065, 0.22, 0.035], material: 'ear' },
111
+ ],
112
+ paints: [
113
+ { bone: 'earL', on: ['ear'], kind: 'ellipse', at: [0, 0.2, 0.01], radii: [0.035, 0.16], color: '#ffc2d3', soft: 0.4 },
114
+ { bone: 'earR', on: ['ear'], kind: 'ellipse', at: [0, 0.2, 0.01], radii: [0.035, 0.16], color: '#ffc2d3', soft: 0.4 },
115
+ ],
116
+ acting: {
117
+ listening: { earL: { rz: 0, rx: -0.2 }, earR: { rz: 0, rx: -0.2 } },
118
+ sleeping: { earL: { rz: 1.2, rx: 0.5 }, earR: { rz: -1.2, rx: 0.5 } },
119
+ sad: { earL: { rz: 1.4, rx: 0.6 }, earR: { rz: -1.4, rx: 0.6 } },
120
+ },
121
+ ```
122
+
123
+ These ears make the character about 0.4 taller: run the check and copy the
124
+ new `frame` and `bound` it suggests.
125
+
126
+ ---
127
+
128
+ ## Arms, legs and feet
129
+
130
+ ### Stubby arms that wave (driven by `flap`)
131
+
132
+ ```ts
133
+ bones: [
134
+ { name: 'armL', parent: 'body', pivot: [-0.21, 0.27, 0.04], rest: { rz: -0.35 }, drive: { rz: [['flap', -1.2]] } },
135
+ { name: 'armR', parent: 'body', pivot: [0.21, 0.27, 0.04], rest: { rz: 0.35 }, drive: { rz: [['flap', 1.2]] } },
136
+ ],
137
+ parts: [
138
+ { bone: 'armL', shape: 'capsule', from: [0, 0, 0], to: [0, -0.12, 0.02], radius: 0.05, blend: 0.03, material: 'skin' },
139
+ { bone: 'armR', shape: 'capsule', from: [0, 0, 0], to: [0, -0.12, 0.02], radius: 0.05, blend: 0.03, material: 'skin' },
140
+ ],
141
+ ```
142
+
143
+ The arm hangs down from the shoulder pivot. The engine raises both arms when
144
+ celebrating and surprised. Acting for arms (the right arm takes the opposite
145
+ sign of the left):
146
+
147
+ ```ts
148
+ acting: {
149
+ /* a wave with the right arm: rz > 0 swings a right arm out and up */
150
+ celebrating: { armR: { rz: [1.8, 0.4, 3, 0] } },
151
+ /* droop: swing both hands in against the body */
152
+ sad: { armL: { rz: 0.3 }, armR: { rz: -0.3 } },
153
+ sleeping: { armL: { rz: 0.3 }, armR: { rz: -0.3 } },
154
+ },
155
+ ```
156
+
157
+ ### Feet (already in the starter)
158
+
159
+ ```ts
160
+ parts: [
161
+ { bone: 'body', shape: 'ellipsoid', at: [-0.15, 0.04, 0.08], radii: [0.1, 0.05, 0.12], blend: 0.04, material: 'foot' },
162
+ { bone: 'body', shape: 'ellipsoid', at: [0.15, 0.04, 0.08], radii: [0.1, 0.05, 0.12], blend: 0.04, material: 'foot' },
163
+ ],
164
+ ```
165
+
166
+ Feet sit with their bottom on the floor: `at.y` equals the y radius.
167
+
168
+ ### Short legs with feet
169
+
170
+ Raise the body first: change the body ellipsoid's `at` to `[0, 0.32, 0]` and
171
+ the head bone's pivot to `[0, 0.43, 0]`, and remove the starter's two feet.
172
+ Then:
173
+
174
+ ```ts
175
+ parts: [
176
+ { bone: 'body', shape: 'capsule', from: [-0.11, 0.22, 0], to: [-0.12, 0.07, 0.02], radius: 0.055, blend: 0.04, material: 'skin' },
177
+ { bone: 'body', shape: 'capsule', from: [0.11, 0.22, 0], to: [0.12, 0.07, 0.02], radius: 0.055, blend: 0.04, material: 'skin' },
178
+ { bone: 'body', shape: 'ellipsoid', at: [-0.12, 0.04, 0.05], radii: [0.08, 0.045, 0.11], blend: 0.03, material: 'foot' },
179
+ { bone: 'body', shape: 'ellipsoid', at: [0.12, 0.04, 0.05], radii: [0.08, 0.045, 0.11], blend: 0.03, material: 'foot' },
180
+ ],
181
+ ```
182
+
183
+ ---
184
+
185
+ ## Wings, tails, horns and antennae
186
+
187
+ ### Small wings (bird, bee, bat, angel)
188
+
189
+ ```ts
190
+ materials: {
191
+ wing: { color: '#e8f6ff', f0: 0.1, spec: 1.8, shininess: 200, rim: 1.4, sss: 0.6, sssColor: '#ffffff', gloss: 0.9 },
192
+ },
193
+ bones: [
194
+ { name: 'wingL', parent: 'body', pivot: [-0.16, 0.3, -0.16], drive: { rz: [['flap', -1.4]] }, wave: { rz: [0.08, 0.8, 0] } },
195
+ { name: 'wingR', parent: 'body', pivot: [0.16, 0.3, -0.16], drive: { rz: [['flap', 1.4]] }, wave: { rz: [-0.08, 0.8, 1.6] } },
196
+ ],
197
+ parts: [
198
+ { bone: 'wingL', shape: 'ellipsoid', at: [-0.1, 0.1, 0], rot: [0, 0, 0.5], radii: [0.12, 0.17, 0.014], material: 'wing' },
199
+ { bone: 'wingR', shape: 'ellipsoid', at: [0.1, 0.1, 0], rot: [0, 0, -0.5], radii: [0.12, 0.17, 0.014], material: 'wing' },
200
+ ],
201
+ acting: {
202
+ celebrating: { wingL: { rz: [0, 0.5, 9, 0] }, wingR: { rz: [0, 0.5, 9, Math.PI] } },
203
+ },
204
+ ```
205
+
206
+ Feathered wings (bird): use the body's feather material, make them thicker
207
+ (`radii: [0.1, 0.16, 0.05]`) and hang them at the sides like arms.
208
+
209
+ ### A tail that wags
210
+
211
+ ```ts
212
+ bones: [
213
+ { name: 'tail', parent: 'body', pivot: [0, 0.14, -0.2], rest: { rx: -0.5 }, wave: { ry: [0.25, 1.2, 0] },
214
+ jiggle: { axis: [0, 0, -1], length: 0.2, freq: 2.5, damping: 0.15 } },
215
+ ],
216
+ parts: [
217
+ { bone: 'tail', shape: 'roundCone', from: [0, 0, 0], to: [0, 0.04, -0.22], r1: 0.06, r2: 0.035, blend: 0.03, material: 'skin' },
218
+ ],
219
+ acting: {
220
+ celebrating: { tail: { ry: [0, 0.7, 4, 0] } },
221
+ happy: { tail: { ry: [0, 0.7, 5, 0] } },
222
+ sad: { tail: { rx: 0.6 } },
223
+ },
224
+ ```
225
+
226
+ Pom-pom tail (bunny, bear): a `sphere` of radius 0.07 at `[0, 0, -0.03]` on the
227
+ tail bone instead. Big fluffy tail (fox, squirrel): see `ember`.
228
+
229
+ ### Horns
230
+
231
+ ```ts
232
+ materials: {
233
+ horn: { color: '#fff1d6', f0: 0.05, spec: 1, shininess: 80, rim: 0.4, gloss: 0.6 },
234
+ },
235
+ parts: [
236
+ { bone: 'head', shape: 'roundCone', from: [-0.13, 0.4, 0], to: [-0.18, 0.56, -0.02], r1: 0.045, r2: 0.012, blend: 0.02, material: 'horn' },
237
+ { bone: 'head', shape: 'roundCone', from: [0.13, 0.4, 0], to: [0.18, 0.56, -0.02], r1: 0.045, r2: 0.012, blend: 0.02, material: 'horn' },
238
+ ],
239
+ ```
240
+
241
+ One unicorn horn: `from: [0, 0.42, 0.06], to: [0, 0.64, 0.1], r1: 0.05, r2: 0.01`.
242
+
243
+ ### Antenna with a glowing bulb
244
+
245
+ The bulb takes the colour of the current state (`glows: true`).
246
+
247
+ ```ts
248
+ materials: {
249
+ stalk: { color: '#3a3f4c', f0: 0.04, spec: 0.8, shininess: 60, rim: 0.5 },
250
+ bulb: { color: '#ffffff', f0: 0.05, spec: 1, shininess: 120, rim: 0, glows: true, emitStrength: 1.4 },
251
+ },
252
+ bones: [
253
+ { name: 'antenna', parent: 'head', pivot: [0, 0.45, 0], jiggle: { axis: [0, 1, 0], length: 0.16, freq: 2.6, damping: 0.1 } },
254
+ ],
255
+ parts: [
256
+ { bone: 'antenna', shape: 'capsule', from: [0, -0.02, 0], to: [0, 0.13, 0], radius: 0.012, material: 'stalk' },
257
+ { bone: 'antenna', shape: 'sphere', at: [0, 0.16, 0], radius: 0.035, material: 'bulb' },
258
+ ],
259
+ ```
260
+
261
+ Move the `top` anchor above the bulb: `top: { bone: 'antenna', at: [0, 0.22, 0] }`.
262
+
263
+ ---
264
+
265
+ ## Things to wear
266
+
267
+ ### Party hat with a pom-pom
268
+
269
+ ```ts
270
+ materials: {
271
+ hat: { color: '#7c5cff', f0: 0.06, spec: 1.6, shininess: 160, rim: 0.5, gloss: 0.8 },
272
+ pom: { color: '#ffd84d', f0: 0.03, spec: 0.4, shininess: 20, rim: 1, sheen: 0.7 },
273
+ },
274
+ parts: [
275
+ { bone: 'head', shape: 'roundCone', at: [0.05, 0.44, 0], rot: [0, 0, -0.25], from: [0, 0, 0], to: [0, 0.2, 0], r1: 0.1, r2: 0.012, material: 'hat' },
276
+ { bone: 'head', shape: 'sphere', at: [0.1, 0.64, 0], radius: 0.035, material: 'pom' },
277
+ ],
278
+ ```
279
+
280
+ Stripes on the hat: sphere paints along it (see "Stripes" below).
281
+
282
+ ### Beanie
283
+
284
+ ```ts
285
+ materials: {
286
+ knit: { color: '#e8433f', f0: 0.02, spec: 0.15, shininess: 10, rim: 0.7, sheen: 0.7, surface: { kind: 'scales', scale: 42, strength: 0.75, color: '#a8242a', bone: 'head' } },
287
+ },
288
+ parts: [
289
+ /* the top half of an ellipsoid a little larger than the head */
290
+ { bone: 'head', shape: 'ellipsoid', at: [0, 0.27, -0.01], radii: [0.32, 0.23, 0.29], clip: { normal: [0, -1, 0] }, material: 'knit' },
291
+ { bone: 'head', shape: 'cylinder', at: [0, 0.29, -0.01], radius: 0.315, half: 0.035, round: 0.03, material: 'knit' },
292
+ { bone: 'head', shape: 'sphere', at: [0, 0.52, -0.01], radius: 0.06, material: 'knit' },
293
+ ],
294
+ ```
295
+
296
+ Keep the eyes below the beanie's rim (eye y under 0.24).
297
+
298
+ ### Headphones
299
+
300
+ ```ts
301
+ materials: {
302
+ band: { color: '#20253a', f0: 0.06, spec: 1.4, shininess: 120, rim: 0.4, gloss: 0.7 },
303
+ cup: { color: '#4f7cff', f0: 0.08, spec: 1.6, shininess: 160, rim: 0.5, gloss: 0.8 },
304
+ },
305
+ parts: [
306
+ /* the band: a ring around the head's outline, facing the camera, cut to its top half */
307
+ { bone: 'head', shape: 'frame', at: [0, 0.2, 0], half: [0.33, 0.29], corner: 0.29, width: 0.015, depth: 0.025, clip: { normal: [0, -1, 0] }, material: 'band' },
308
+ /* the cups: cylinders turned on their side (rz 90°) */
309
+ { bone: 'head', shape: 'cylinder', at: [-0.33, 0.18, 0], rot: [0, 0, 1.5708], radius: 0.1, half: 0.045, round: 0.02, material: 'cup' },
310
+ { bone: 'head', shape: 'cylinder', at: [0.33, 0.18, 0], rot: [0, 0, 1.5708], radius: 0.1, half: 0.045, round: 0.02, material: 'cup' },
311
+ ],
312
+ anchors: {
313
+ ear: { bone: 'head', at: [-0.38, 0.18, 0] },
314
+ },
315
+ ```
316
+
317
+ The band's `half` is the head's x and y radii plus 0.02, so it hugs the head
318
+ without touching it. Move the `ear` anchor onto a cup, as above.
319
+
320
+ ### Round glasses
321
+
322
+ ```ts
323
+ materials: {
324
+ rims: { color: '#2b2b33', f0: 0.06, spec: 1.6, shininess: 160, rim: 0.4, gloss: 0.8 },
325
+ },
326
+ parts: [
327
+ /* a frame shape faces the camera; one ring per eye, in front of it */
328
+ { bone: 'head', shape: 'frame', at: [-0.11, 0.2, 0.275], half: [0.075, 0.075], corner: 0.075, width: 0.01, depth: 0.008, material: 'rims' },
329
+ { bone: 'head', shape: 'frame', at: [0.11, 0.2, 0.275], half: [0.075, 0.075], corner: 0.075, width: 0.01, depth: 0.008, material: 'rims' },
330
+ { bone: 'head', shape: 'capsule', from: [-0.035, 0.21, 0.285], to: [0.035, 0.21, 0.285], radius: 0.009, material: 'rims' },
331
+ ],
332
+ ```
333
+
334
+ Square glasses: a smaller `corner` (0.02). Put the rings just in front of the
335
+ eyes: their z is the face's surface plus about 0.005.
336
+
337
+ ### Bow
338
+
339
+ ```ts
340
+ materials: {
341
+ bow: { color: '#ff5d8f', f0: 0.06, spec: 1.6, shininess: 160, rim: 0.5, gloss: 0.8 },
342
+ },
343
+ parts: [
344
+ { bone: 'head', shape: 'ellipsoid', at: [0.2, 0.43, 0.02], rot: [0, 0, 0.5], radii: [0.075, 0.05, 0.03], material: 'bow' },
345
+ { bone: 'head', shape: 'ellipsoid', at: [0.31, 0.39, 0.02], rot: [0, 0, -0.3], radii: [0.075, 0.05, 0.03], material: 'bow' },
346
+ { bone: 'head', shape: 'sphere', at: [0.26, 0.41, 0.03], radius: 0.035, material: 'bow' },
347
+ ],
348
+ ```
349
+
350
+ ---
351
+
352
+ ## Faces
353
+
354
+ ### Snout with a glossy nose (dog, bear, pig)
355
+
356
+ ```ts
357
+ materials: {
358
+ nose: { color: '#1d1410', f0: 0.06, spec: 1.8, shininess: 200, rim: 0.3, gloss: 0.9 },
359
+ snout: { color: '#ffe3c2', f0: 0.04, spec: 0.8, shininess: 60, rim: 0.8, sss: 0.2, sssColor: '#fff0dc' },
360
+ },
361
+ parts: [
362
+ { bone: 'head', shape: 'ellipsoid', at: [0, 0.1, 0.22], radii: [0.12, 0.08, 0.09], blend: 0.04, material: 'snout' },
363
+ { bone: 'head', shape: 'ellipsoid', at: [0, 0.14, 0.31], radii: [0.04, 0.03, 0.025], material: 'nose' },
364
+ ],
365
+ face: { kind: 'mouth', bone: 'head', on: ['snout'], at: [0, 0.07, 0.3], width: 0.045, color: '#4a2418', tongue: '#ff7d8a' },
366
+ ```
367
+
368
+ With a snout, the mouth goes on the snout (`on: ['snout']`), and the eyes
369
+ move up a little (y 0.24). Pig: a pink snout with two nostrils carved by
370
+ subtraction, listed right after it:
371
+ `{ bone: 'head', shape: 'sphere', at: [-0.03, 0.1, 0.31], radius: 0.014, op: 'sub', material: 'snout' }` and its mirror.
372
+
373
+ ### Small beak (bird, duck, penguin)
374
+
375
+ ```ts
376
+ materials: {
377
+ beak: { color: '#ff7a1a', f0: 0.05, spec: 1, shininess: 80, rim: 0.4, gloss: 0.6 },
378
+ },
379
+ parts: [
380
+ { bone: 'head', shape: 'roundCone', from: [0, 0.12, 0.24], to: [0, 0.09, 0.37], r1: 0.065, r2: 0.015, material: 'beak' },
381
+ ],
382
+ /* the mouth moves under the beak */
383
+ face: { kind: 'mouth', bone: 'head', on: ['skin'], at: [0, 0.03, 0.22], width: 0.04, color: '#5a2a1a', tongue: '#ff7d8a' },
384
+ ```
385
+
386
+ A beak replaces the mouth: leave out `face`, or keep a tiny mouth below the
387
+ beak. For a beak that opens when speaking, see `pip` (two halves on `beak`
388
+ and `jaw` bones driven by `talk`).
389
+
390
+ ### Freckles, whiskers and a belly
391
+
392
+ ```ts
393
+ paints: [
394
+ /* freckles: tiny sphere paints on the surface under the eyes */
395
+ { bone: 'head', on: ['skin'], kind: 'sphere', at: [-0.17, 0.12, 0.23], radius: 0.012, color: '#c46a3a', soft: 0.2 },
396
+ { bone: 'head', on: ['skin'], kind: 'sphere', at: [-0.2, 0.14, 0.21], radius: 0.012, color: '#c46a3a', soft: 0.2 },
397
+ { bone: 'head', on: ['skin'], kind: 'sphere', at: [0.17, 0.12, 0.23], radius: 0.012, color: '#c46a3a', soft: 0.2 },
398
+ { bone: 'head', on: ['skin'], kind: 'sphere', at: [0.2, 0.14, 0.21], radius: 0.012, color: '#c46a3a', soft: 0.2 },
399
+ /* a pale belly on the front of the body */
400
+ { bone: 'body', on: ['skin'], kind: 'ellipse', at: [0, 0.16, 0.12], radii: [0.12, 0.1], color: '#fff2dc', soft: 0.3 },
401
+ ],
402
+ parts: [
403
+ /* whiskers: thin capsules sticking out of the cheeks */
404
+ { bone: 'head', shape: 'capsule', from: [-0.2, 0.1, 0.2], to: [-0.36, 0.13, 0.2], radius: 0.006, material: 'whisker' },
405
+ { bone: 'head', shape: 'capsule', from: [-0.2, 0.08, 0.2], to: [-0.36, 0.06, 0.2], radius: 0.006, material: 'whisker' },
406
+ { bone: 'head', shape: 'capsule', from: [0.2, 0.1, 0.2], to: [0.36, 0.13, 0.2], radius: 0.006, material: 'whisker' },
407
+ { bone: 'head', shape: 'capsule', from: [0.2, 0.08, 0.2], to: [0.36, 0.06, 0.2], radius: 0.006, material: 'whisker' },
408
+ ],
409
+ materials: {
410
+ whisker: { color: '#fff8ee', f0: 0.04, spec: 0.6, shininess: 60, rim: 0.5 },
411
+ },
412
+ ```
413
+
414
+ ---
415
+
416
+ ## Patterns
417
+
418
+ ### Stripes (bee, tiger, sweater)
419
+
420
+ Paint with huge, hard-edged spheres whose edges cross the body. Each sphere
421
+ paints everything inside it, so a sphere centred far below the body paints a
422
+ band at its top edge; later paints cover earlier ones.
423
+
424
+ ```ts
425
+ paints: [
426
+ /* bands across the body: each sphere colours everything below its top edge */
427
+ { bone: 'body', on: ['skin'], kind: 'sphere', at: [0, -0.55, 0], radius: 0.8, color: '#2b2118', soft: 0.02 },
428
+ { bone: 'body', on: ['skin'], kind: 'sphere', at: [0, -0.63, 0], radius: 0.8, color: '#ffc21a', soft: 0.02 },
429
+ { bone: 'body', on: ['skin'], kind: 'sphere', at: [0, -0.71, 0], radius: 0.8, color: '#2b2118', soft: 0.02 },
430
+ ],
431
+ ```
432
+
433
+ Each band's top edge is at `at.y + radius` (0.25, 0.17 and 0.09 here, on a
434
+ body from y 0 to 0.38); each paint covers the lower part of the one before
435
+ it, leaving a band. Small `soft` keeps the edges crisp. For more bands, add
436
+ more spheres, each 0.08 lower.
437
+
438
+ ### Spots (mushroom, ladybird, cow, dalmatian)
439
+
440
+ Sphere paints with `at` **on the surface** (use the formula at the top of
441
+ this file). Vary the radius (0.03 to 0.07) and spread them unevenly, with a
442
+ few on the back and sides so the character looks right when it turns.
443
+
444
+ ```ts
445
+ paints: [
446
+ { bone: 'head', on: ['skin'], kind: 'sphere', at: [0.143, 0.343, 0.2], radius: 0.05, color: '#ffffff', soft: 0.15 },
447
+ { bone: 'head', on: ['skin'], kind: 'sphere', at: [-0.19, 0.38, 0.1], radius: 0.04, color: '#ffffff', soft: 0.15 },
448
+ ],
449
+ ```
450
+
451
+ Raised spots (bumps you can see from the side) are small `sphere` parts half
452
+ buried in the surface instead of paints; see `berry` and `cap`.
453
+
454
+ ### Patches (panda eye patches, a dark muzzle)
455
+
456
+ Use parts with their own material rather than paints when the patch should
457
+ have a raised edge, and paints when it should be flat. Panda: see `bao`.
458
+
459
+ ---
460
+
461
+ ## Bodies that are objects
462
+
463
+ The face goes on the front of the object; the rest of the spec is the same.
464
+
465
+ ### Cup or mug
466
+
467
+ ```ts
468
+ materials: {
469
+ cup: { color: '#f4efe6', f0: 0.06, spec: 1.6, shininess: 140, rim: 0.6, gloss: 0.85 },
470
+ coffee: { color: '#6b3f22', f0: 0.04, spec: 1.2, shininess: 90, rim: 0.3, gloss: 0.7 },
471
+ },
472
+ bones: [
473
+ { name: 'body', pivot: [0, 0, 0], drive: { ry: [['yaw', 0.4]], rz: [['roll', 0.4]], rx: [['pitch', -0.3]] } },
474
+ ],
475
+ parts: [
476
+ { bone: 'body', shape: 'cylinder', at: [0, 0.3, 0], radius: 0.28, half: 0.28, round: 0.06, material: 'cup' },
477
+ /* the opening: a slightly smaller cylinder carved out of the top */
478
+ { bone: 'body', shape: 'cylinder', at: [0, 0.6, 0], radius: 0.24, half: 0.06, round: 0.02, op: 'sub', blend: 0.01, material: 'cup' },
479
+ /* the drink inside */
480
+ { bone: 'body', shape: 'cylinder', at: [0, 0.53, 0], radius: 0.245, half: 0.012, round: 0.01, material: 'coffee' },
481
+ /* the handle: a capsule loop on the side */
482
+ { bone: 'body', shape: 'capsule', from: [0.27, 0.45, 0], to: [0.38, 0.4, 0], radius: 0.035, material: 'cup' },
483
+ { bone: 'body', shape: 'capsule', from: [0.38, 0.4, 0], to: [0.38, 0.2, 0], radius: 0.035, material: 'cup' },
484
+ { bone: 'body', shape: 'capsule', from: [0.38, 0.2, 0], to: [0.27, 0.16, 0], radius: 0.035, material: 'cup' },
485
+ ],
486
+ ```
487
+
488
+ On a one-bone character, every `bone` (eyes, face, paints, anchors) is
489
+ `'body'`. The full version with foam art and steam is `latte`.
490
+
491
+ ### Toaster (a box with a slot and something popping out)
492
+
493
+ A complete object character: one `body` bone carries the box, the face and
494
+ the anchors; the toast has its own springy bone so it bobs.
495
+
496
+ ```ts
497
+ materials: {
498
+ shell: { color: '#e5483f', f0: 0.06, spec: 1.6, shininess: 160, rim: 0.5, gloss: 0.8 },
499
+ toast: { color: '#d9913f', f0: 0.03, spec: 0.5, shininess: 30, rim: 0.5, sheen: 0.3 },
500
+ crumb: { color: '#f3d9a4', f0: 0.03, spec: 0.3, shininess: 20, rim: 0.4 },
501
+ chrome: { color: '#8f98ae', f0: 0.45, spec: 1, shininess: 120, metal: 0.85, rim: 0.3, gloss: 0.7 },
502
+ foot: { color: '#26252b', f0: 0.04, spec: 0.6, shininess: 40, rim: 0.3 },
503
+ },
504
+ bones: [
505
+ { name: 'body', pivot: [0, 0, 0], drive: { ry: [['yaw', 0.4]], rz: [['roll', 0.3]], rx: [['pitch', -0.25]] } },
506
+ { name: 'toast', parent: 'body', pivot: [0, 0.5, 0], jiggle: { axis: [0, 1, 0], length: 0.14, freq: 3, damping: 0.15, maxAngle: 0.35 } },
507
+ ],
508
+ parts: [
509
+ { bone: 'body', shape: 'roundBox', at: [0, 0.29, 0], half: [0.3, 0.22, 0.2], round: 0.07, material: 'shell' },
510
+ /* the slot: listed right after the box so it carves it */
511
+ { bone: 'body', shape: 'roundBox', at: [0, 0.5, 0], half: [0.19, 0.05, 0.035], round: 0.02, op: 'sub', material: 'shell' },
512
+ { bone: 'body', shape: 'capsule', from: [0.31, 0.38, 0], to: [0.36, 0.38, 0], radius: 0.025, material: 'chrome' },
513
+ { bone: 'body', shape: 'ellipsoid', at: [-0.18, 0.04, 0], radii: [0.06, 0.04, 0.12], material: 'foot' },
514
+ { bone: 'body', shape: 'ellipsoid', at: [0.18, 0.04, 0], radii: [0.06, 0.04, 0.12], material: 'foot' },
515
+ /* the slice: a crust, with the soft crumb just in front of it */
516
+ { bone: 'toast', shape: 'roundBox', at: [0, 0.08, 0], half: [0.16, 0.13, 0.022], round: 0.05, material: 'toast' },
517
+ { bone: 'toast', shape: 'roundBox', at: [0, 0.08, 0.004], half: [0.135, 0.105, 0.022], round: 0.04, material: 'crumb' },
518
+ ],
519
+ paints: [
520
+ { bone: 'body', on: ['shell'], kind: 'sphere', at: [-0.19, 0.25, 0.2], radius: 0.045, color: '#ff9a9a', soft: 0.6, opacity: 0.8 },
521
+ { bone: 'body', on: ['shell'], kind: 'sphere', at: [0.19, 0.25, 0.2], radius: 0.045, color: '#ff9a9a', soft: 0.6, opacity: 0.8 },
522
+ ],
523
+ eyes: { kind: 'ball', bone: 'body', at: [[-0.1, 0.32, 0.165], [0.1, 0.32, 0.165]], radius: 0.05, iris: '#2b1d14', irisSize: 0.8, pupilSize: 0.5, lid: 'shell' },
524
+ face: { kind: 'mouth', bone: 'body', on: ['shell'], at: [0, 0.23, 0.2], width: 0.05, color: '#5a1a14', tongue: '#ff7d8a' },
525
+ anchors: {
526
+ top: { bone: 'toast', at: [0.12, 0.24, 0] },
527
+ mouth: { bone: 'body', at: [0, 0.23, 0.23] },
528
+ ear: { bone: 'body', at: [-0.31, 0.32, 0] },
529
+ feet: { bone: 'root', at: [0, 0.02, 0.15] },
530
+ },
531
+ acting: {
532
+ celebrating: { toast: { rz: [0, 0.25, 4, 0] } },
533
+ sleeping: { toast: { rx: 0.35 } },
534
+ sad: { toast: { rx: 0.45, rz: 0.2 } },
535
+ error: { toast: { rz: [0, 0.2, 11, 0] } },
536
+ },
537
+ ```
538
+
539
+ ### Flower pot
540
+
541
+ A `roundCone` wider at the top, cut flat at the top with a clip: see `cacti`.
542
+
543
+ ### Box robot
544
+
545
+ A `roundBox` head with `round` about 0.06, a `plate` screen on the front and
546
+ an LED `screen` face instead of eyes and a mouth: see `volt`.
547
+
548
+ ### Floating blob (cloud, ghost, slime)
549
+
550
+ Several spheres melted together with `blend: 0.1` to `0.15`, and
551
+ `motion: { float: 0.15 }`: see `drift` and `boo`.