@adatechnology/game-animals-three 0.0.0-stage → 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1160 @@
1
+ // src/index.ts
2
+ import { GAIT_PHASES } from "@adatechnology/game-animals";
3
+ import { collectReferencedNodes, createNodeSkin } from "@adatechnology/game-props3d";
4
+
5
+ // src/animate-bird.ts
6
+ var { PI } = Math;
7
+ var animateBird = (rig, params) => {
8
+ const { time: t, delta: dt, clip } = params;
9
+ const species = rig.species;
10
+ const spec = species.body;
11
+ const isVulture = species.id === "vulture";
12
+ const isChicken = species.id === "chicken";
13
+ const isLapwing = species.id === "southern_lapwing";
14
+ const flying = clip === "run" && !isChicken;
15
+ const hz = clip === "run" && !flying ? species.runHz : clip === "walk" ? species.walkHz : 0;
16
+ rig.phase += dt * (flying ? isVulture ? 1.1 : 4.5 : hz) * 2 * PI;
17
+ const targetHeight = flying ? rig.size * 1.2 : 0;
18
+ rig.trunk.position.y += (targetHeight - rig.trunk.position.y) * Math.min(1, dt * 3);
19
+ rig.legs.forEach((leg, index) => {
20
+ if (flying) {
21
+ leg.hip.rotation.x = 1.2;
22
+ leg.knee.rotation.x = -1.6;
23
+ } else if (clip === "idle") {
24
+ leg.hip.rotation.x = 0;
25
+ leg.knee.rotation.x = 0;
26
+ } else {
27
+ const phase = rig.phase + index * PI;
28
+ leg.hip.rotation.x = -Math.sin(phase) * 0.45;
29
+ leg.knee.rotation.x = -Math.max(0, Math.cos(phase)) * 0.7;
30
+ }
31
+ leg.foot.rotation.x = -(leg.hip.rotation.x + leg.knee.rotation.x);
32
+ });
33
+ rig.wings.forEach((wing) => {
34
+ const { side, shoulder, elbow } = wing;
35
+ if (flying) {
36
+ const flap = isVulture ? 0.25 + Math.sin(rig.phase) * 0.18 : Math.sin(rig.phase) * 0.9;
37
+ shoulder.rotation.y += (0 - shoulder.rotation.y) * Math.min(1, dt * 6);
38
+ shoulder.rotation.z = side * flap;
39
+ elbow.rotation.z = side * (isVulture ? 0.08 : Math.sin(rig.phase - 0.6) * 0.4);
40
+ elbow.rotation.y = 0;
41
+ shoulder.scale.x += (1 - shoulder.scale.x) * Math.min(1, dt * 6);
42
+ } else {
43
+ const alert = isLapwing && clip === "idle" ? Math.max(0, Math.sin(t * 1.3)) * 0.9 : 0;
44
+ const flutter = isChicken && clip === "run" ? Math.abs(Math.sin(rig.phase * 1.5)) * 0.8 : 0;
45
+ shoulder.rotation.y += (side * PI / 2 * 0.97 - alert * side * 0.9 - shoulder.rotation.y) * Math.min(1, dt * 6);
46
+ shoulder.rotation.z = side * (-0.06 + flutter + alert * 0.6);
47
+ elbow.rotation.z = side * 0.04;
48
+ elbow.rotation.y = 0;
49
+ shoulder.scale.x += ((alert > 0.1 ? 1 : 0.58) - shoulder.scale.x) * Math.min(1, dt * 6);
50
+ }
51
+ });
52
+ const peck = isChicken && clip === "idle" ? Math.max(0, Math.sin(t * 1.6)) ** 8 * 1.2 : 0;
53
+ if (rig.neck) {
54
+ rig.neck.rotation.x = rig.neckAngle + (clip === "walk" && !isVulture ? Math.max(0, Math.sin(rig.phase * 2)) * 0.35 : 0) + peck + (flying ? 0.6 : 0);
55
+ rig.neck.rotation.y = clip === "idle" ? Math.sin(t * 0.9) * 0.5 : 0;
56
+ }
57
+ if (rig.jaw) rig.jaw.rotation.x = isLapwing && clip === "idle" ? Math.max(0, Math.sin(t * 1.3)) * Math.abs(Math.sin(t * 14)) * 0.5 : 0;
58
+ if (rig.tailRoot) rig.tailRoot.rotation.x = (spec.tailA || 0) + (flying ? 0.6 : 0);
59
+ };
60
+
61
+ // src/animate-quadruped.ts
62
+ var { PI: PI2 } = Math;
63
+ var animateQuadruped = (rig, params, gaits) => {
64
+ const { time: t, delta: dt, clip } = params;
65
+ const species = rig.species;
66
+ const spec = species.body;
67
+ if (rig.dead) {
68
+ rig.root.rotation.z += (PI2 / 2 * 0.97 - rig.root.rotation.z) * Math.min(1, dt * 3);
69
+ rig.root.position.y += (spec.R * (spec.sx || 1) * 0.85 - rig.root.position.y) * Math.min(1, dt * 3);
70
+ rig.legs.forEach((leg) => {
71
+ leg.hip.rotation.x = leg.angle1 + (leg.isFront ? -0.5 : 0.4);
72
+ leg.knee.rotation.x = 0.05;
73
+ leg.foot.rotation.x = 0;
74
+ });
75
+ if (rig.neck) rig.neck.rotation.x = rig.neckAngle + 0.35;
76
+ if (rig.jaw) rig.jaw.rotation.x = 0.25;
77
+ return;
78
+ }
79
+ const hz = (clip === "run" ? species.runHz : clip === "walk" ? species.walkHz : 0) * (rig.infected ? 1.2 : 1);
80
+ rig.phase += dt * hz * 2 * PI2;
81
+ const gait = clip === "run" ? species.gallop ? gaits.gallop : gaits.trot : gaits.walk;
82
+ const amplitude = clip === "run" ? 0.62 : 0.34;
83
+ const flex = clip === "run" ? 1.15 : 0.75;
84
+ rig.legs.forEach((leg, index) => {
85
+ if (clip === "idle") {
86
+ leg.hip.rotation.x = leg.angle1;
87
+ leg.knee.rotation.x = leg.angle2;
88
+ } else {
89
+ const phase = rig.phase + gait[index];
90
+ const limit = rig.infected && index === 1 ? 0.35 : 1;
91
+ leg.hip.rotation.x = leg.angle1 - Math.sin(phase) * amplitude * limit;
92
+ leg.knee.rotation.x = leg.angle2 + Math.max(0, Math.cos(phase)) * flex * limit;
93
+ }
94
+ leg.foot.rotation.x = -(leg.hip.rotation.x + leg.knee.rotation.x) * 0.85;
95
+ });
96
+ const bob = clip === "idle" ? 0 : Math.abs(Math.sin(rig.phase)) * (clip === "run" ? 0.05 : 0.018) * rig.size;
97
+ rig.trunk.position.y = bob * 0.6;
98
+ const bodyGroup = rig.bodyGroup;
99
+ if (bodyGroup) {
100
+ bodyGroup.rotation.x = clip === "run" && species.gallop ? Math.sin(rig.phase + PI2 * 0.4) * 0.07 : 0;
101
+ bodyGroup.scale.y = 1 + (clip === "idle" ? Math.sin(t * 2.2) * 0.012 : 0);
102
+ }
103
+ const low = rig.infected ? 0.35 : 0;
104
+ if (rig.neck) {
105
+ rig.neck.rotation.x = rig.neckAngle + low + (clip === "idle" ? Math.sin(t * 0.7) * 0.08 : Math.sin(rig.phase * 2) * 0.04);
106
+ rig.neck.rotation.y = clip === "idle" ? Math.sin(t * 0.45) * 0.35 : 0;
107
+ }
108
+ if (rig.head) rig.head.rotation.z = rig.infected ? Math.sin(t * 11) * Math.sin(t * 1.7) * 0.12 : 0;
109
+ if (rig.jaw) rig.jaw.rotation.x = rig.infected ? 0.25 + Math.max(0, Math.sin(t * 6)) * 0.25 : clip === "run" ? 0.12 : 0;
110
+ const wagging = species.id === "dog" && !rig.infected;
111
+ rig.tail.forEach((segment, index) => {
112
+ segment.rotation.z = Math.sin(t * (wagging ? 9 : 2.2) - index * 0.6) * (wagging ? 0.35 : 0.12);
113
+ });
114
+ };
115
+
116
+ // src/animate-snake.ts
117
+ var { PI: PI3 } = Math;
118
+ var bodyPath = (THREE, rig, params, spacing) => {
119
+ const { time: t, clip } = params;
120
+ const points = [];
121
+ for (let index = 0; index < rig.segmentCount; index++) {
122
+ let x;
123
+ let z;
124
+ let y = 0;
125
+ if (clip === "walk") {
126
+ z = 0.6 - index * spacing;
127
+ x = Math.sin(rig.phase * 2.2 - index * 0.42) * 0.08 * Math.min(1, index / 6 + 0.3);
128
+ } else {
129
+ const strike = clip === "run" ? Math.max(0, Math.sin(t * 2.2)) ** 6 : 0;
130
+ if (index < 9) {
131
+ const k = index / 9;
132
+ z = 0.08 + (1 - k) * 0.12 + strike * (1 - k) * 0.35;
133
+ y = (1 - k) * 0.13 * (1 - strike * 0.6);
134
+ x = Math.sin(k * PI3 * 1.6) * 0.05 * (1 - strike);
135
+ } else {
136
+ const angle = (index - 9) * 0.36;
137
+ const radius = 0.16 - (index - 9) * 45e-4;
138
+ x = Math.cos(angle) * radius;
139
+ z = Math.sin(angle) * radius - 0.05;
140
+ y = (index - 9) * 12e-4;
141
+ }
142
+ }
143
+ points.push(new THREE.Vector3(x, y, z));
144
+ }
145
+ return points;
146
+ };
147
+ var animateSnake = (THREE, rig, params) => {
148
+ const { time: t, delta: dt, clip } = params;
149
+ const spacing = rig.segmentLength * (params.split ? 1.5 : 1);
150
+ if (clip === "walk") rig.phase += dt * 2.4;
151
+ const points = bodyPath(THREE, rig, params, spacing);
152
+ const at = (index) => points[index];
153
+ for (let index = 0; index < rig.segmentCount; index++) {
154
+ const group = rig.segments[index].group;
155
+ group.position.copy(at(index));
156
+ const direction = index === 0 ? at(0).clone().sub(at(1)) : at(index - 1).clone().sub(at(index));
157
+ group.rotation.set(0, Math.atan2(direction.x, direction.z), 0);
158
+ group.rotateX(-Math.atan2(direction.y, Math.hypot(direction.x, direction.z)));
159
+ }
160
+ const head = rig.head;
161
+ const headDirection = at(0).clone().sub(at(1)).normalize();
162
+ head.position.copy(at(0)).addScaledVector(headDirection, 0.02);
163
+ head.rotation.set(0, Math.atan2(headDirection.x, headDirection.z), 0);
164
+ head.rotateX(-Math.atan2(headDirection.y, Math.hypot(headDirection.x, headDirection.z)) * 0.5);
165
+ const jaw = rig.jaw;
166
+ jaw.rotation.x = clip === "run" ? Math.max(0, Math.sin(t * 2.2)) ** 6 * 0.9 : 0;
167
+ const tongue = rig.tongue;
168
+ tongue.visible = Math.sin(t * 5) > 0.3;
169
+ tongue.scale.z = 1 + Math.sin(t * 20) * 0.3;
170
+ };
171
+
172
+ // src/bird.ts
173
+ import { birdSize } from "@adatechnology/game-animals";
174
+
175
+ // src/context.ts
176
+ var createStream = (seed) => {
177
+ let state = seed;
178
+ return () => {
179
+ state |= 0;
180
+ state = state + 1831565813 | 0;
181
+ let t = Math.imul(state ^ state >>> 15, 1 | state);
182
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
183
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
184
+ };
185
+ };
186
+ var addMesh = (THREE, parent, geometry, material, x, y, z, rx, ry, rz) => {
187
+ const mesh = new THREE.Mesh(geometry, material);
188
+ mesh.position.set(x || 0, y || 0, z || 0);
189
+ if (rx || ry || rz) mesh.rotation.set(rx || 0, ry || 0, rz || 0);
190
+ mesh.castShadow = true;
191
+ mesh.receiveShadow = true;
192
+ parent.add(mesh);
193
+ return mesh;
194
+ };
195
+ var createRigTools = (THREE, rig) => {
196
+ const tools = {
197
+ material(color, extras) {
198
+ const material = new THREE.MeshStandardMaterial(Object.assign({ color, roughness: 0.8, metalness: 0, flatShading: false }, extras || {}));
199
+ material.userData.base = material.color.clone();
200
+ rig.materials.push(material);
201
+ return material;
202
+ },
203
+ ball(parent, radius, material, x, y, z, sx, sy, sz) {
204
+ const mesh = addMesh(THREE, parent, new THREE.SphereGeometry(radius, 16, 12), material, x, y, z);
205
+ if (sx) mesh.scale.set(sx, sy, sz);
206
+ return mesh;
207
+ },
208
+ limb: (parent, radius1, radius2, length, material) => addMesh(THREE, parent, new THREE.CylinderGeometry(radius2, radius1, length, 14, 1).translate(0, -length / 2, 0), material),
209
+ joint(parent, radius, material, x, y, z) {
210
+ const joint = tools.ball(parent, radius, material, x, y, z);
211
+ joint.userData.joint = 1;
212
+ rig.joints.push(joint);
213
+ return joint;
214
+ },
215
+ pivot(parent, x, y, z, offset) {
216
+ const group = new THREE.Group();
217
+ group.position.set(x, y, z);
218
+ parent.add(group);
219
+ rig.pivots.push({ group, rest: group.position.clone(), offset: offset || new THREE.Vector3() });
220
+ return group;
221
+ },
222
+ eyes(head, radius, x, y, z) {
223
+ const eyeMaterial = tools.material(1118481, { roughness: 0.25 });
224
+ rig.eyeMaterial = eyeMaterial;
225
+ for (const side of [-1, 1]) rig.eyes.push(tools.ball(head, radius, eyeMaterial, side * x, y, z));
226
+ }
227
+ };
228
+ return tools;
229
+ };
230
+ var createRig = (THREE, species, options) => {
231
+ const rig = {
232
+ species,
233
+ root: new THREE.Group(),
234
+ trunk: new THREE.Group(),
235
+ materials: [],
236
+ joints: [],
237
+ pivots: [],
238
+ legs: [],
239
+ tail: [],
240
+ ears: [],
241
+ infectionParts: [],
242
+ eyes: [],
243
+ wings: [],
244
+ segments: [],
245
+ phase: options.phase,
246
+ size: 1,
247
+ segmentLength: 0,
248
+ segmentCount: 0,
249
+ home: new THREE.Vector3(),
250
+ infected: false,
251
+ dead: false,
252
+ neckAngle: 0,
253
+ headAngle: 0,
254
+ triangles: 0
255
+ };
256
+ rig.root.add(rig.trunk);
257
+ return rig;
258
+ };
259
+
260
+ // src/bird.ts
261
+ var buildWings = (THREE, rig, spec, parts) => {
262
+ const tools = createRigTools(THREE, rig);
263
+ const { bodyGroup, main, dark } = parts;
264
+ for (const side of [-1, 1]) {
265
+ const shoulder = tools.pivot(bodyGroup, side * spec.r * spec.s[0] * 1.02, spec.r * 0.22, spec.r * 0.3, new THREE.Vector3(side * 0.12, 0.03, 0).multiplyScalar(rig.size));
266
+ tools.joint(shoulder, spec.r * 0.14, main, 0, 0, 0);
267
+ tools.ball(shoulder, 0.5, spec.wingM ? tools.material(spec.wingM) : dark, side * spec.wi * 0.5, 0, -spec.wc * 0.3, spec.wi * 1.05, spec.r * 0.16, spec.wc);
268
+ const elbow = tools.pivot(shoulder, side * spec.wi, 0, 0, new THREE.Vector3(side * 0.06, 0, 0).multiplyScalar(rig.size));
269
+ tools.joint(elbow, spec.r * 0.1, main, 0, 0, 0);
270
+ tools.ball(elbow, 0.5, dark, side * spec.wo * 0.45, 0, -spec.wc * 0.32, spec.wo * 1.05, spec.r * 0.12, spec.wc * 0.75);
271
+ if (spec.fingers) {
272
+ for (let finger = 0; finger < 4; finger++) {
273
+ addMesh(THREE, elbow, new THREE.BoxGeometry(spec.wo * 0.35, spec.r * 0.03, spec.wc * 0.12), dark, side * (spec.wo + spec.wo * 0.12), 0, -spec.wc * 0.05 - finger * spec.wc * 0.18, 0, side * (0.05 + finger * 0.12), 0);
274
+ }
275
+ }
276
+ if (spec.spur) addMesh(THREE, elbow, new THREE.ConeGeometry(spec.r * 0.06, spec.r * 0.25, 5), tools.material(12595242), 0, 0, spec.wc * 0.2, Math.PI / 2, 0, 0);
277
+ rig.wings.push({ shoulder, elbow, side });
278
+ }
279
+ };
280
+ var buildLegs = (THREE, rig, spec, parts) => {
281
+ const tools = createRigTools(THREE, rig);
282
+ const { bodyGroup, main, leg } = parts;
283
+ for (const side of [-1, 1]) {
284
+ const hip = tools.pivot(bodyGroup, side * spec.r * 0.38, -spec.r * 0.55, spec.r * 0.05, new THREE.Vector3(side * 0.06, -0.05, 0).multiplyScalar(rig.size));
285
+ tools.joint(hip, spec.r * 0.18, main, 0, 0, 0);
286
+ tools.limb(hip, spec.r * 0.16, spec.r * 0.09, spec.shank, spec.featherLeg ? main : leg);
287
+ const ankle = tools.pivot(hip, 0, -spec.shank, 0, new THREE.Vector3(0, -0.04, 0).multiplyScalar(rig.size));
288
+ tools.joint(ankle, spec.r * 0.06, leg, 0, 0, 0);
289
+ tools.limb(ankle, spec.r * 0.055, spec.r * 0.05, spec.tars, leg);
290
+ const foot = tools.pivot(ankle, 0, -spec.tars, 0);
291
+ for (const angle of [-0.45, 0, 0.45]) addMesh(THREE, foot, new THREE.BoxGeometry(spec.r * 0.05, spec.r * 0.04, spec.r * 0.55).translate(0, 0, spec.r * 0.25), leg, 0, -spec.r * 0.02, 0, 0, angle, 0);
292
+ addMesh(THREE, foot, new THREE.BoxGeometry(spec.r * 0.05, spec.r * 0.04, spec.r * 0.25).translate(0, 0, -spec.r * 0.12), leg, 0, -spec.r * 0.02, 0);
293
+ rig.legs.push({ hip, knee: ankle, foot, isFront: false, angle1: 0.35, angle2: -0.5, side });
294
+ }
295
+ };
296
+ var buildBird = (THREE, rig, options) => {
297
+ const spec = rig.species.body;
298
+ const tools = createRigTools(THREE, rig);
299
+ const colors = spec.c;
300
+ const main = tools.material(colors.main);
301
+ const dark = tools.material(colors.dark);
302
+ const leg = tools.material(colors.leg);
303
+ const beak = tools.material(colors.beak, { roughness: 0.5 });
304
+ const belly = tools.material(colors.belly || colors.main);
305
+ rig.main = main;
306
+ const hipY = spec.tars + spec.shank * 0.9;
307
+ rig.size = birdSize(spec);
308
+ const bodyGroup = tools.pivot(rig.trunk, 0, hipY + spec.r * 0.55, 0);
309
+ rig.bodyGroup = bodyGroup;
310
+ bodyGroup.rotation.x = spec.pitch || 0;
311
+ tools.ball(bodyGroup, spec.r, main, 0, 0, 0, spec.s[0], spec.s[1], spec.s[2]);
312
+ tools.ball(bodyGroup, spec.r * 0.9, belly, 0, -spec.r * 0.22, spec.r * 0.15, spec.s[0] * 0.9, spec.s[1] * 0.8, spec.s[2] * 0.85);
313
+ const tail = tools.pivot(bodyGroup, 0, spec.r * 0.15, -spec.r * spec.s[2] * 0.85, new THREE.Vector3(0, 0, -0.08).multiplyScalar(rig.size));
314
+ tail.rotation.x = spec.tailA || -0.3;
315
+ rig.tailRoot = tail;
316
+ addMesh(THREE, tail, new THREE.BoxGeometry(spec.tailW, spec.r * 0.08, spec.tailL).translate(0, 0, -spec.tailL / 2), spec.tailM ? tools.material(spec.tailM) : dark);
317
+ buildWings(THREE, rig, spec, { bodyGroup, main, dark });
318
+ const neck = tools.pivot(bodyGroup, 0, spec.r * 0.45, spec.r * spec.s[2] * 0.75, new THREE.Vector3(0, 0.05, 0.08).multiplyScalar(rig.size));
319
+ neck.rotation.x = spec.neckA;
320
+ rig.neck = neck;
321
+ rig.neckAngle = spec.neckA;
322
+ tools.joint(neck, spec.nr * 1.1, spec.neckM ? tools.material(spec.neckM) : main, 0, 0, 0);
323
+ addMesh(THREE, neck, new THREE.CylinderGeometry(spec.nr * 0.85, spec.nr, spec.nl, 12).translate(0, spec.nl / 2, 0), spec.neckM ? tools.material(spec.neckM) : main);
324
+ const head = tools.pivot(neck, 0, spec.nl, 0, new THREE.Vector3(0, 0.05, 0.05).multiplyScalar(rig.size));
325
+ head.rotation.x = -spec.neckA + (spec.headA || 0);
326
+ rig.head = head;
327
+ rig.headAngle = head.rotation.x;
328
+ const headMaterial = spec.headM ? tools.material(spec.headM) : main;
329
+ tools.ball(head, spec.hr, headMaterial, 0, 0, 0, spec.hs ? spec.hs[0] : 1, spec.hs ? spec.hs[1] : 1, spec.hs ? spec.hs[2] : 1.1);
330
+ addMesh(THREE, head, new THREE.ConeGeometry(spec.br, spec.bl, 8).rotateX(Math.PI / 2).translate(0, 0, spec.bl / 2), beak, 0, -spec.hr * 0.1, spec.hr * 0.85, spec.hook || 0, 0, 0);
331
+ const jaw = tools.pivot(head, 0, -spec.hr * 0.25, spec.hr * 0.8);
332
+ rig.jaw = jaw;
333
+ addMesh(THREE, jaw, new THREE.ConeGeometry(spec.br * 0.7, spec.bl * 0.85, 8).rotateX(Math.PI / 2).translate(0, 0, spec.bl * 0.42), beak).scale.set(1, 0.5, 1);
334
+ tools.eyes(head, spec.hr * 0.17, spec.hr * 0.68, spec.hr * 0.2, spec.hr * 0.35);
335
+ if (spec.eyeC && rig.eyeMaterial) {
336
+ rig.eyeMaterial.color.set(spec.eyeC);
337
+ rig.eyeMaterial.userData.base = rig.eyeMaterial.color.clone();
338
+ }
339
+ options.extra?.({ THREE, rig, tools }, { bodyGroup, head, neck, main, dark });
340
+ buildLegs(THREE, rig, spec, { bodyGroup, main, leg });
341
+ return rig;
342
+ };
343
+
344
+ // src/extras.ts
345
+ var cow = ({ THREE, tools }, k) => {
346
+ tools.ball(k.bodyGroup, 0.2, k.main, 0, k.Rb * 1.1, k.L * 0.33, 0.95, 1.1, 1.1);
347
+ tools.ball(k.neck, 0.13, k.main, 0, 0.12, 0.08, 0.55, 1.4, 1.05);
348
+ for (const side of [-1, 1]) addMesh(THREE, k.head, new THREE.ConeGeometry(0.025, 0.13, 8), tools.material(13616808), side * 0.12, k.hd.r * 1.05, k.hd.r * 0.05, 0, 0, side * -0.9);
349
+ };
350
+ var horse = ({ THREE, rig, tools }, k) => {
351
+ for (let index = 0; index < 9; index++) addMesh(THREE, k.neck, new THREE.BoxGeometry(0.035, 0.16, 0.08), k.dark, 0, 0.06 + index * 0.065, -0.12 + index * 4e-3, -0.25, 0, 0);
352
+ addMesh(THREE, k.head, new THREE.BoxGeometry(0.03, 0.12, 0.06), k.dark, 0, k.hd.r * 1.05, k.hd.r * 0.3, 0.6, 0, 0);
353
+ const last = rig.tail[rig.tail.length - 1];
354
+ tools.ball(last, 0.08, k.dark, 0, -0.42, 0, 0.7, 4.5, 0.7);
355
+ };
356
+ var wildBoar = ({ THREE, tools }, k) => {
357
+ for (let index = 0; index < 12; index++) addMesh(THREE, k.bodyGroup, new THREE.BoxGeometry(0.03, 0.1, 0.06), k.dark, 0, k.Rb * 1.12 - index * 4e-3, k.L * 0.38 - index * 0.06, -0.2, 0, 0);
358
+ for (const side of [-1, 1])
359
+ addMesh(THREE, k.head, new THREE.ConeGeometry(0.012, 0.09, 6), tools.material(15261900), side * 0.07, k.hd.r * -0.25 + 0.03, k.hd.r * 0.25 + k.hd.r * k.hd.s[2] * 0.55 + 0.13, -0.6, 0, side * 0.5);
360
+ };
361
+ var chicken = ({ tools }, k) => {
362
+ const red = tools.material(12593182);
363
+ for (let index = 0; index < 4; index++) tools.ball(k.head, 0.014, red, 0, 0.045, -0.012 + index * 0.014, 0.5, 1, 0.8);
364
+ tools.ball(k.head, 0.012, red, 0, -0.04, 0.04, 0.5, 1.3, 0.7);
365
+ };
366
+ var southernLapwing = ({ THREE, tools }, k) => {
367
+ const black = tools.material(1447446);
368
+ tools.ball(k.bodyGroup, 0.05, black, 0, 0.03, 0.08, 1.1, 0.9, 0.6);
369
+ addMesh(THREE, k.head, new THREE.BoxGeometry(6e-3, 6e-3, 0.06).translate(0, 0, -0.03), black, 0, 0.025, -0.01, 0.5, 0, 0);
370
+ tools.ball(k.head, 0.026, black, 0, -5e-3, 0.02, 1.05, 0.7, 0.9);
371
+ };
372
+ var QUADRUPED_EXTRAS = { cow, horse, wild_boar: wildBoar };
373
+ var BIRD_EXTRAS = { chicken, southern_lapwing: southernLapwing };
374
+
375
+ // src/quadruped.ts
376
+ import { quadrupedLayout } from "@adatechnology/game-animals";
377
+
378
+ // src/textures.ts
379
+ var canvasTexture = (THREE, document, width, height, draw) => {
380
+ const canvas = document.createElement("canvas");
381
+ canvas.width = width;
382
+ canvas.height = height;
383
+ draw(canvas.getContext("2d"), width, height);
384
+ const texture = new THREE.CanvasTexture(canvas);
385
+ texture.colorSpace = THREE.SRGBColorSpace;
386
+ return texture;
387
+ };
388
+ var spotsTexture = (THREE, document, base, spot) => canvasTexture(THREE, document, 256, 256, (painter, width, height) => {
389
+ painter.fillStyle = base;
390
+ painter.fillRect(0, 0, width, height);
391
+ const next = createStream(11);
392
+ for (let index = 0; index < 70; index++) {
393
+ const x = next() * width;
394
+ const y = next() * height;
395
+ const radius = 5 + next() * 7;
396
+ painter.strokeStyle = spot;
397
+ painter.lineWidth = 3;
398
+ painter.beginPath();
399
+ painter.arc(x, y, radius, 0, 7);
400
+ painter.stroke();
401
+ painter.fillStyle = "rgba(120,70,20,.45)";
402
+ painter.beginPath();
403
+ painter.arc(x, y, radius * 0.6, 0, 7);
404
+ painter.fill();
405
+ }
406
+ for (let index = 0; index < 60; index++) {
407
+ painter.fillStyle = spot;
408
+ painter.beginPath();
409
+ painter.arc(next() * width, next() * height, 1.5 + next() * 2.5, 0, 7);
410
+ painter.fill();
411
+ }
412
+ });
413
+ var stripesTexture = (THREE, document, base, stripe) => canvasTexture(THREE, document, 128, 128, (painter, width) => {
414
+ painter.fillStyle = base;
415
+ painter.fillRect(0, 0, width, 128);
416
+ painter.fillStyle = stripe;
417
+ for (let index = 0; index < 9; index++) painter.fillRect(0, index * 14 + index % 2 * 3, width, 4);
418
+ });
419
+ var createCoatTexture = (THREE, document, marker) => marker.kind === "stripes" ? stripesTexture(THREE, document, marker.base, marker.stripe) : spotsTexture(THREE, document, marker.base, marker.spot);
420
+
421
+ // src/profile.ts
422
+ var profileGeometry = (THREE, length, profile, segments) => {
423
+ const points = profile.map((radius, index) => new THREE.Vector2(index / (profile.length - 1), radius));
424
+ const curve = new THREE.SplineCurve(points);
425
+ const outline = [];
426
+ outline.push(new THREE.Vector2(1e-3, -length / 2 - 0));
427
+ for (let step = 0; step <= 30; step++) {
428
+ const point = curve.getPoint(step / 30);
429
+ outline.push(new THREE.Vector2(Math.max(1e-3, point.y), -length / 2 + point.x * length));
430
+ }
431
+ outline.push(new THREE.Vector2(1e-3, length / 2));
432
+ return new THREE.LatheGeometry(outline, segments || 22).rotateX(Math.PI / 2);
433
+ };
434
+
435
+ // src/quadruped.ts
436
+ var RIB_COUNT = 4;
437
+ var LEG_DEFINITIONS = [
438
+ [1, 1, "F"],
439
+ [-1, 1, "F"],
440
+ [1, -1, "H"],
441
+ [-1, -1, "H"]
442
+ ];
443
+ var buildBodyAndLegs = (THREE, rig, spec, main, belly, dark) => {
444
+ const tools = createRigTools(THREE, rig);
445
+ const { L, R: radius } = spec;
446
+ const leg = spec.leg;
447
+ const layout = quadrupedLayout(spec);
448
+ const { frontHipHeight, hindHipHeight, footHeight, bodyHeight, span } = layout;
449
+ const frontAngle1 = leg.fa1 ?? 0.06;
450
+ const frontAngle2 = leg.fa2 ?? -0.08;
451
+ const hindAngle1 = leg.ha1 ?? -0.32;
452
+ const hindAngle2 = leg.ha2 ?? 0.58;
453
+ rig.size = layout.size;
454
+ const bodyGroup = tools.pivot(rig.trunk, 0, bodyHeight, 0);
455
+ rig.bodyGroup = bodyGroup;
456
+ const body = addMesh(THREE, bodyGroup, profileGeometry(THREE, L, spec.prof.map((value) => value * radius)), main);
457
+ body.scale.set(spec.sx || 1, spec.sy || 1, 1);
458
+ body.rotation.x = Math.atan2(hindHipHeight - frontHipHeight, span) * 0.6;
459
+ rig.body = body;
460
+ const belt = addMesh(THREE, bodyGroup, profileGeometry(THREE, L * 0.8, spec.prof.map((value) => value * 0.8 * radius)), belly);
461
+ belt.scale.set((spec.sx || 1) * 0.92, (spec.sy || 1) * 0.85, 1);
462
+ belt.position.y = -radius * 0.2;
463
+ LEG_DEFINITIONS.forEach(([sx, sz, kind]) => {
464
+ const front = kind === "F";
465
+ const upper = front ? leg.fu : leg.hu;
466
+ const lower = front ? leg.fl : leg.hl;
467
+ const height = front ? frontHipHeight : hindHipHeight;
468
+ const hip = tools.pivot(rig.trunk, sx * radius * (spec.sx || 1) * 0.55, height, sz * span / 2, new THREE.Vector3(sx * 0.18, -0.06, sz * 0.12).multiplyScalar(rig.size));
469
+ tools.ball(hip, leg.r * (front ? 1.9 : 2.3), main, 0, -upper * 0.18, front ? 0 : -leg.r * 0.3, 0.75, 1.25, 1);
470
+ tools.joint(hip, leg.r * 1.15, main, 0, 0, 0);
471
+ tools.limb(hip, leg.r * 1.25, leg.r * 0.95, upper, main);
472
+ const knee = tools.pivot(hip, 0, -upper, 0, new THREE.Vector3(0, -0.07 * rig.size, 0));
473
+ tools.joint(knee, leg.r * 0.95, main, 0, 0, 0);
474
+ tools.limb(knee, leg.r * 0.92, leg.r * 0.68, lower, main);
475
+ const foot = tools.pivot(knee, 0, -lower, 0, new THREE.Vector3(0, -0.05 * rig.size, 0));
476
+ if (leg.hoof) {
477
+ tools.joint(foot, leg.r * 0.72, dark, 0, 0, 0);
478
+ addMesh(THREE, foot, new THREE.CylinderGeometry(leg.r * 0.8, leg.r * 1.05, footHeight, 12).translate(0, -footHeight / 2, 0), dark);
479
+ } else {
480
+ tools.joint(foot, leg.r * 0.66, main, 0, 0, 0);
481
+ tools.ball(foot, leg.r * 0.95, leg.dark ? dark : main, 0, -footHeight * 0.55, leg.r * 0.5, 1, 0.62, 1.45);
482
+ }
483
+ rig.legs.push({ hip, knee, foot, isFront: front, angle1: front ? frontAngle1 : hindAngle1, angle2: front ? frontAngle2 : hindAngle2, side: sx });
484
+ });
485
+ return { bodyGroup, bodyHeight };
486
+ };
487
+ var buildHead = (THREE, rig, spec, parts) => {
488
+ const tools = createRigTools(THREE, rig);
489
+ const { bodyGroup, main, belly, nose } = parts;
490
+ const { L, R: radius } = spec;
491
+ const neckSpec = spec.neck;
492
+ const headSpec = spec.head;
493
+ const neck = tools.pivot(bodyGroup, 0, radius * (spec.sy || 1) * (neckSpec.y ?? 0.35), L / 2 * (neckSpec.z ?? 0.78), new THREE.Vector3(0, 0.05, 0.12).multiplyScalar(rig.size));
494
+ neck.rotation.x = neckSpec.ang;
495
+ rig.neck = neck;
496
+ rig.neckAngle = neckSpec.ang;
497
+ tools.joint(neck, neckSpec.r * 1.05, main, 0, 0, 0);
498
+ addMesh(THREE, neck, new THREE.CylinderGeometry(neckSpec.r2 || neckSpec.r * 0.85, neckSpec.r, neckSpec.len, 14).translate(0, neckSpec.len / 2, 0), main);
499
+ const head = tools.pivot(neck, 0, neckSpec.len, 0, new THREE.Vector3(0, 0.06, 0.08).multiplyScalar(rig.size));
500
+ head.rotation.x = -neckSpec.ang + (headSpec.down || 0);
501
+ rig.head = head;
502
+ rig.headAngle = head.rotation.x;
503
+ tools.joint(head, neckSpec.r2 || neckSpec.r * 0.85, main, 0, 0, 0);
504
+ tools.ball(head, headSpec.r, main, 0, headSpec.r * 0.25, headSpec.r * 0.25, headSpec.s[0], headSpec.s[1], headSpec.s[2]);
505
+ const snoutZ = headSpec.r * 0.25 + headSpec.r * headSpec.s[2] * 0.55;
506
+ const snout = addMesh(THREE, head, new THREE.CylinderGeometry(headSpec.sr2, headSpec.sr1, headSpec.sl, 14).rotateX(Math.PI / 2), headSpec.muzzle ? tools.material(headSpec.muzzle) : main, 0, headSpec.r * (headSpec.sy ?? 0), snoutZ + headSpec.sl / 2);
507
+ snout.scale.set(1, headSpec.sq || 1, 1);
508
+ tools.ball(head, headSpec.sr2 * 1.02, nose, 0, headSpec.r * (headSpec.sy ?? 0) + headSpec.sr2 * 0.1, snoutZ + headSpec.sl, 1, 0.85 * (headSpec.sq || 1), 0.45);
509
+ const jaw = tools.pivot(head, 0, headSpec.r * (headSpec.sy ?? 0) - headSpec.sr1 * 0.55 * (headSpec.sq || 1), snoutZ - headSpec.sr1 * 0.2, new THREE.Vector3(0, -0.04, 0.04).multiplyScalar(rig.size));
510
+ rig.jaw = jaw;
511
+ addMesh(THREE, jaw, new THREE.CylinderGeometry(headSpec.sr2 * 0.7, headSpec.sr1 * 0.75, headSpec.sl * 0.92, 12).rotateX(Math.PI / 2).translate(0, 0, headSpec.sl * 0.46), belly).scale.set(1, 0.45, 1);
512
+ const blood = tools.ball(jaw, headSpec.sr2 * 0.9, rig.woundMaterial || (rig.woundMaterial = new THREE.MeshStandardMaterial({ color: 6950924, roughness: 0.3 })), 0, 0, headSpec.sl * 0.9, 1.1, 0.6, 0.6);
513
+ tools.eyes(head, headSpec.r * 0.13, headSpec.r * headSpec.s[0] * 0.52, headSpec.r * 0.5, headSpec.r * 0.25 + headSpec.r * headSpec.s[2] * 0.55);
514
+ const ears = spec.ears;
515
+ for (const side of [-1, 1]) {
516
+ const earGroup = tools.pivot(head, side * headSpec.r * headSpec.s[0] * 0.55, headSpec.r * (0.25 + headSpec.s[1] * 0.75), headSpec.r * 0.1, new THREE.Vector3(side * 0.04, 0.04, 0).multiplyScalar(rig.size));
517
+ rig.ears.push(earGroup);
518
+ if (ears.t === "cone") addMesh(THREE, earGroup, new THREE.ConeGeometry(ears.s * 0.45, ears.s, 8).translate(0, ears.s / 2, 0), main, 0, 0, 0, ears.fwd || 0, 0, side * -(ears.tilt ?? 0.3));
519
+ else if (ears.t === "round") tools.ball(earGroup, ears.s, main, 0, ears.s * 0.4, 0, 1, 1, 0.35);
520
+ else if (ears.t === "flop") {
521
+ const ear = tools.ball(earGroup, ears.s, main, side * ears.s * 0.9, -ears.s * 0.1, 0, 1.5, 0.45, 0.7);
522
+ ear.rotation.z = side * -0.25;
523
+ }
524
+ }
525
+ return { neck, head, blood, neckSpec, headSpec };
526
+ };
527
+ var buildTail = (THREE, rig, spec, parts) => {
528
+ const tools = createRigTools(THREE, rig);
529
+ const tailSpec = spec.tail;
530
+ if (!tailSpec || !tailSpec.n) return;
531
+ const { bodyGroup, main, dark } = parts;
532
+ const { L, R: radius } = spec;
533
+ let parent = tools.pivot(bodyGroup, 0, radius * (spec.sy || 1) * (tailSpec.y ?? 0.3), -L / 2 * 0.95, new THREE.Vector3(0, 0.02, -0.12).multiplyScalar(rig.size));
534
+ parent.rotation.x = tailSpec.ang;
535
+ rig.tailRoot = parent;
536
+ const segmentLength = tailSpec.len / tailSpec.n;
537
+ for (let index = 0; index < tailSpec.n; index++) {
538
+ const radius1 = tailSpec.r * (1 - index / tailSpec.n * 0.6);
539
+ const radius2 = tailSpec.r * (1 - (index + 1) / tailSpec.n * 0.6);
540
+ tools.limb(parent, radius1, radius2, segmentLength, tailSpec.dark && index === tailSpec.n - 1 ? dark : main);
541
+ tools.joint(parent, radius1, main, 0, 0, 0);
542
+ rig.tail.push(parent);
543
+ if (index < tailSpec.n - 1) {
544
+ const next = tools.pivot(parent, 0, -segmentLength, 0);
545
+ next.rotation.x = tailSpec.curl || 0;
546
+ parent = next;
547
+ } else if (tailSpec.tuft) tools.ball(parent, tailSpec.r * 2.2, dark, 0, -segmentLength - tailSpec.r, 0, 1, 1.8, 1);
548
+ }
549
+ if (tailSpec.bushy) tools.ball(rig.tail[0], tailSpec.r * 2.2, dark, 0, -tailSpec.len * 0.55, 0, 1, tailSpec.len * 2.2 / tailSpec.r / 6, 1);
550
+ };
551
+ var buildInfectionParts = (THREE, rig, spec, parts) => {
552
+ const tools = createRigTools(THREE, rig);
553
+ const { bodyGroup, neck, blood, neckSpec } = parts;
554
+ const { L, R: radius } = spec;
555
+ const sideX = radius * (spec.sx || 1);
556
+ const sideY = radius * (spec.sy || 1);
557
+ const list = [blood];
558
+ const wound = (parent, size, x, y, z, sx, sy, sz) => tools.ball(parent, size, rig.woundMaterial || (rig.woundMaterial = new THREE.MeshStandardMaterial({ color: 5902862, roughness: 0.45 })), x, y, z, sx || 1, sy || 1, sz || 1);
559
+ list.push(wound(bodyGroup, radius * 0.28, sideX * 0.92, sideY * 0.15, L * 0.05, 0.35, 1, 1.3));
560
+ list.push(wound(bodyGroup, radius * 0.2, -sideX * 0.9, sideY * 0.3, -L * 0.2, 0.35, 1, 1.1));
561
+ list.push(wound(neck, neckSpec.r * 0.5, neckSpec.r * 0.85, neckSpec.len * 0.5, 0, 0.4, 1, 1));
562
+ const bone = tools.material(14274488, { roughness: 0.6 });
563
+ for (let index = 0; index < RIB_COUNT; index++) {
564
+ const rib = addMesh(THREE, bodyGroup, new THREE.TorusGeometry(radius * 0.95, radius * 0.035, 6, 16, Math.PI * 0.42), bone, sideX * 0.12, sideY * 0.05, L * 0.12 - index * L * 0.07, 0, Math.PI / 2, -0.3);
565
+ rib.scale.set(1, 1.02 * (spec.sy || 1), spec.sx || 1);
566
+ list.push(rib);
567
+ }
568
+ rig.infectionParts.push(...list);
569
+ list.forEach((part) => part.visible = false);
570
+ };
571
+ var buildQuadruped = (THREE, rig, options) => {
572
+ const spec = rig.species.body;
573
+ const tools = createRigTools(THREE, rig);
574
+ const colors = spec.c;
575
+ const texture = spec.texture ? createCoatTexture(THREE, options.document, spec.texture) : void 0;
576
+ const main = tools.material(texture ? 16777215 : colors.main, texture ? { map: texture } : void 0);
577
+ const belly = tools.material(colors.belly);
578
+ const dark = tools.material(colors.dark);
579
+ const nose = tools.material(colors.nose || colors.dark, { roughness: 0.4 });
580
+ rig.main = main;
581
+ rig.dark = dark;
582
+ const { bodyGroup, bodyHeight } = buildBodyAndLegs(THREE, rig, spec, main, belly, dark);
583
+ const { neck, head, blood, neckSpec, headSpec } = buildHead(THREE, rig, spec, { bodyGroup, main, belly, nose });
584
+ buildTail(THREE, rig, spec, { bodyGroup, main, dark });
585
+ options.extra?.({ THREE, rig, tools }, { bodyGroup, head, neck, main, belly, dark, L: spec.L, Rb: spec.R, bodyY: bodyHeight, hd: headSpec, nk: neckSpec });
586
+ if (rig.species.outcome === "turns") buildInfectionParts(THREE, rig, spec, { bodyGroup, neck, blood, neckSpec });
587
+ else blood.visible = false;
588
+ return rig;
589
+ };
590
+
591
+ // src/snake.ts
592
+ var SEGMENT_COUNT = 34;
593
+ var SEGMENT_LENGTH = 0.042;
594
+ var buildSnake = (THREE, rig) => {
595
+ const tools = createRigTools(THREE, rig);
596
+ rig.size = 0.5;
597
+ rig.segmentLength = SEGMENT_LENGTH;
598
+ rig.segmentCount = SEGMENT_COUNT;
599
+ const primary = tools.material(8022602);
600
+ const secondary = tools.material(3812898);
601
+ const belly = tools.material(12101768);
602
+ rig.main = primary;
603
+ for (let index = 0; index < SEGMENT_COUNT; index++) {
604
+ const t = index / (SEGMENT_COUNT - 1);
605
+ const radius = 0.012 + 0.03 * Math.sin(Math.min(1, t * 1.15) * Math.PI) * (t < 0.1 ? 0.75 + t * 2.5 : 1) + (t < 0.05 ? 0.012 : 0);
606
+ const material = index % 4 === 1 || index % 4 === 2 ? secondary : primary;
607
+ const group = new THREE.Group();
608
+ rig.trunk.add(group);
609
+ const sphere = tools.ball(group, radius, material, 0, radius, 0, 1, 0.78, 1.5);
610
+ sphere.userData.joint = index % 3 === 0 ? 1 : 0;
611
+ if (index % 3 === 0) rig.joints.push(sphere);
612
+ rig.segments.push({ group, radius });
613
+ }
614
+ const head = new THREE.Group();
615
+ rig.trunk.add(head);
616
+ rig.head = head;
617
+ tools.ball(head, 0.032, primary, 0, 0.03, 0.025, 1.25, 0.6, 1.45);
618
+ tools.ball(head, 0.024, secondary, 0, 0.04, 0, 1.3, 0.4, 0.9);
619
+ tools.ball(head, 0.02, primary, 0, 0.028, 0.06, 0.85, 0.55, 1.1);
620
+ const jaw = new THREE.Group();
621
+ jaw.position.set(0, 0.018, 0);
622
+ head.add(jaw);
623
+ rig.jaw = jaw;
624
+ tools.ball(jaw, 0.026, belly, 0, 0, 0.035, 1.1, 0.35, 1.5);
625
+ tools.eyes(head, 6e-3, 0.03, 0.04, 0.045);
626
+ rig.tongue = addMesh(THREE, head, new THREE.BoxGeometry(4e-3, 2e-3, 0.05), tools.material(11542560), 0, 0.024, 0.1);
627
+ return rig;
628
+ };
629
+
630
+ // src/visual-state.ts
631
+ var INFECTED_TINT = 8227430;
632
+ var POOL_COLOR = 3803142;
633
+ var applyVisualState = (THREE, rig, state) => {
634
+ rig.infected = state === "infected";
635
+ rig.dead = state === "dead";
636
+ const tint = new THREE.Color(INFECTED_TINT);
637
+ rig.materials.forEach((material) => {
638
+ if (material === rig.eyeMaterial || material === rig.woundMaterial) return;
639
+ material.color.copy(material.userData.base);
640
+ if (rig.infected) material.color.lerp(tint, 0.45).multiplyScalar(0.72);
641
+ });
642
+ const eye = rig.eyeMaterial;
643
+ if (eye) {
644
+ if (rig.infected) {
645
+ eye.color.set(14473392);
646
+ eye.emissive = new THREE.Color(9078368);
647
+ eye.emissiveIntensity = 0.8;
648
+ } else {
649
+ eye.color.copy(eye.userData.base);
650
+ eye.emissive = new THREE.Color(0);
651
+ }
652
+ }
653
+ rig.infectionParts.forEach((part) => part.visible = rig.infected);
654
+ const firstEar = rig.ears[0];
655
+ if (firstEar) firstEar.visible = !rig.infected;
656
+ if (!rig.dead) {
657
+ rig.root.rotation.z = 0;
658
+ rig.root.position.y = 0;
659
+ }
660
+ if (rig.pool) {
661
+ rig.pool.visible = rig.dead;
662
+ return;
663
+ }
664
+ if (!rig.dead) return;
665
+ const pool = new THREE.Mesh(new THREE.CircleGeometry(1, 22).rotateX(-Math.PI / 2), new THREE.MeshStandardMaterial({ color: POOL_COLOR, roughness: 0.25 }));
666
+ pool.position.set(rig.home.x, 5e-3, rig.home.z + 0.1);
667
+ pool.receiveShadow = true;
668
+ pool.scale.setScalar(rig.size * 0.45);
669
+ rig.root.parent?.add(pool);
670
+ rig.pool = pool;
671
+ };
672
+
673
+ // src/views.ts
674
+ var setJointView = (rig, on, jointMaterial) => {
675
+ rig.materials.forEach((material) => {
676
+ if (material === rig.eyeMaterial) return;
677
+ material.transparent = on;
678
+ material.opacity = on ? 0.28 : 1;
679
+ material.depthWrite = !on;
680
+ material.needsUpdate = true;
681
+ });
682
+ rig.joints.forEach((joint) => {
683
+ var _a;
684
+ if (on) (_a = joint.userData).restMaterial ?? (_a.restMaterial = joint.material);
685
+ joint.material = on ? jointMaterial : joint.userData.restMaterial;
686
+ joint.renderOrder = on ? 2 : 0;
687
+ });
688
+ };
689
+ var setSplit = (rig, on) => {
690
+ rig.pivots.forEach((pivot) => {
691
+ pivot.group.userData.target = on ? pivot.rest.clone().add(pivot.offset) : pivot.rest.clone();
692
+ });
693
+ };
694
+ var updateSplit = (rig, delta) => {
695
+ rig.pivots.forEach((pivot) => {
696
+ const target = pivot.group.userData.target;
697
+ if (target) pivot.group.position.lerp(target, Math.min(1, delta * 5));
698
+ });
699
+ };
700
+
701
+ // src/line-up.ts
702
+ import { lineUpLength } from "@adatechnology/game-animals";
703
+ var lineUpAnimals = (rigs, options = {}) => {
704
+ const start = options.start ?? -19;
705
+ const gap = options.gap ?? 0.85;
706
+ let cursor = start;
707
+ for (const rig of rigs) {
708
+ const length = lineUpLength(rig.species.body);
709
+ cursor += length / 2;
710
+ rig.root.position.set(cursor, 0, 0);
711
+ rig.home = rig.root.position.clone();
712
+ rig.root.rotation.y = rig.species.body.kind === "snake" ? options.snakeYaw ?? 2.2 : options.yaw ?? 1.1;
713
+ cursor += length / 2 + gap;
714
+ }
715
+ const offset = -(cursor - gap + start) / 2;
716
+ for (const rig of rigs) {
717
+ rig.root.position.x += offset;
718
+ rig.home.x += offset;
719
+ }
720
+ };
721
+
722
+ // src/test-area/module.ts
723
+ import { lineUpLength as lineUpLength2, visualStateOf as visualStateOf2 } from "@adatechnology/game-animals";
724
+
725
+ // src/test-area/scenarios.ts
726
+ import { visualStateOf } from "@adatechnology/game-animals";
727
+ var SKIN_COUNT = 40;
728
+ var createScenarios = (deps) => {
729
+ const scenario = (id, run) => ({ id, name: deps.text[id] ?? id, run });
730
+ const rate = () => 0.5;
731
+ return [
732
+ scenario("visual_states", ({ expect }) => {
733
+ const animals = deps.ensure();
734
+ for (const species of deps.species()) {
735
+ const rig = animals.build(species);
736
+ animals.setVisualState(rig, "infected");
737
+ expect(rig.infected && !rig.dead, `${species.id}: infected`);
738
+ animals.setVisualState(rig, "dead");
739
+ expect(rig.dead && !rig.infected, `${species.id}: dead`);
740
+ animals.setVisualState(rig, "alive");
741
+ expect(!rig.dead && !rig.infected, `${species.id}: alive again`);
742
+ }
743
+ }),
744
+ scenario("gait_moves_legs", ({ expect }) => {
745
+ const animals = deps.ensure();
746
+ const species = deps.species().find((entry) => entry.body.kind === "quadruped");
747
+ if (!species) return expect(false, "no quadruped among the species");
748
+ const rig = animals.build(species);
749
+ animals.animate(rig, { time: 0, delta: 0, clip: "walk" });
750
+ const before = rig.legs.map((leg) => leg.hip.rotation.x);
751
+ animals.animate(rig, { time: 0.4, delta: 0.4, clip: "walk" });
752
+ expect(rig.legs.some((leg, index) => leg.hip.rotation.x !== before[index]), `${species.id}: a walk step swings a hip`);
753
+ }),
754
+ scenario("outbreak_rule", ({ expect }) => {
755
+ const turns = deps.species().find((entry) => entry.outcome === "turns");
756
+ const immune = deps.species().find((entry) => entry.outcome === "immune");
757
+ const on = deps.rebuildOutbreak(true);
758
+ if (!on || !turns) return expect(false, "no outbreak rule or no species that turns");
759
+ expect(on.bite(turns.id, rate, 0) !== void 0, `a bite on ${turns.id} starts a fate with the rule on`);
760
+ if (immune) expect(on.bite(immune.id, rate, 0) === void 0, `${immune.id} shrugs the bite off`);
761
+ const off = deps.rebuildOutbreak(false);
762
+ expect(off?.bite(turns.id, rate, 0) === void 0, "with the rule off a bite does nothing");
763
+ deps.rebuildOutbreak(true);
764
+ }),
765
+ scenario("carcass_lure", ({ expect }) => {
766
+ const outbreak = deps.rebuildOutbreak(true);
767
+ const dies = deps.species().find((entry) => entry.outbreak?.carcass);
768
+ if (!outbreak || !dies) return expect(false, "no outbreak rule or no species that leaves a carcass");
769
+ const carcass = outbreak.carcass(dies.id, [0, 0, 0], 0);
770
+ expect(carcass !== void 0, `${dies.id} leaves a carcass`);
771
+ if (!carcass) return;
772
+ expect(outbreak.lures(carcass, carcass.lureRadiusMetres - 1, 1), "a zombie inside the radius is drawn");
773
+ expect(!outbreak.lures(carcass, carcass.lureRadiusMetres + 1, 1), "a zombie outside it is not");
774
+ }),
775
+ scenario("bite_to_turn_timer", ({ expect }) => {
776
+ const outbreak = deps.rebuildOutbreak(true);
777
+ const turns = deps.species().find((entry) => entry.outcome === "turns");
778
+ if (!outbreak || !turns) return expect(false, "no outbreak rule or no species that turns");
779
+ const fate = outbreak.bite(turns.id, rate, 0);
780
+ if (!fate) return expect(false, "the bite took");
781
+ expect(outbreak.stateOf(fate, 0) === "incubating", "it incubates at first");
782
+ expect(outbreak.stateOf(fate, fate.dueAt + 1) === "turned", "it has turned once the time is up (the bench clock runs it accelerated)");
783
+ expect(visualStateOf(turns.outcome, true) === "infected", "and shows the infected look");
784
+ }),
785
+ scenario("skins_40", ({ expect }) => {
786
+ const result = deps.skins();
787
+ expect(result.count === SKIN_COUNT, `${SKIN_COUNT} animals are in the field`, { count: result.count });
788
+ expect(result.meshes.drawCalls <= SKIN_COUNT + 16, "the skins are about one draw call each", result.meshes);
789
+ expect(result.nodes > 0, "each skin keeps the nodes the animation moves", { nodes: result.nodes });
790
+ deps.clearSkins();
791
+ })
792
+ ];
793
+ };
794
+
795
+ // src/test-area/text.ts
796
+ var TEXT = {
797
+ en: {
798
+ group: "Animals",
799
+ name: "Animals area",
800
+ controls: { visual_state: "Visual state", gait: "Gait", speed: "Animation speed", outbreak: "Outbreak rule", time_scale: "Bite clock" },
801
+ hints: {
802
+ visual_state: "The look of every animal: alive, infected (grey coat, glowing eyes, wounds) or dead (lying down, a pool of blood)",
803
+ gait: "idle, walk or run; the cycle of each species runs at its own rate",
804
+ outbreak: "Off: a bite does nothing. On: a species that turns incubates and turns, one that dies dies, the rest shrug it off",
805
+ time_scale: "Game seconds per real second for the bite timers (60: ten minutes pass in ten seconds)"
806
+ },
807
+ options: { visual_state: { alive: "Alive", infected: "Infected", dead: "Dead" }, gait: { idle: "Idle", walk: "Walk", run: "Run" } },
808
+ actions: { bite_all: "Bite every animal", recover: "Heal every animal", skin_40: "Add 40 animals as node skins", clear_skins: "Remove the skins" },
809
+ layers: { joints: "Joints", split: "Split view", sound_range: "Sound range", carcass_lure: "Carcass lure radius" },
810
+ metrics: { animals: "Animals", draw_calls: "Draw calls", triangles: "Triangles", nodes_per_skin: "Nodes per skin", incubating: "Incubating", turned: "Turned", dead: "Dead" },
811
+ scenarios: {
812
+ visual_states: "Alive, infected and dead for every species",
813
+ gait_moves_legs: "The gait moves the legs",
814
+ outbreak_rule: "Outbreak rule on and off",
815
+ carcass_lure: "A carcass lures zombies within its radius",
816
+ bite_to_turn_timer: "Bite, incubation, turn (accelerated)",
817
+ skins_40: "40 animals as node skins"
818
+ }
819
+ },
820
+ "pt-BR": {
821
+ group: "Animais",
822
+ name: "\xC1rea de animais",
823
+ controls: { visual_state: "Estado visual", gait: "Andar", speed: "Velocidade da anima\xE7\xE3o", outbreak: "Regra de surto", time_scale: "Rel\xF3gio da mordida" },
824
+ hints: {
825
+ visual_state: "O aspecto de todos os bichos: vivo, infectado (pelo cinza, olhos brilhando, feridas) ou morto (deitado, po\xE7a de sangue)",
826
+ gait: "idle, walk ou run; o ciclo de cada esp\xE9cie anda no seu ritmo",
827
+ outbreak: "Desligada: a mordida n\xE3o faz nada. Ligada: a esp\xE9cie que vira incuba e vira, a que morre morre, as outras n\xE3o sentem",
828
+ time_scale: "Segundos de jogo por segundo real nos prazos da mordida (60: dez minutos passam em dez segundos)"
829
+ },
830
+ options: { visual_state: { alive: "Vivo", infected: "Infectado", dead: "Morto" }, gait: { idle: "Parado", walk: "Andando", run: "Correndo" } },
831
+ actions: { bite_all: "Morder todos os bichos", recover: "Curar todos os bichos", skin_40: "P\xF4r 40 bichos como pele de n\xF3s", clear_skins: "Tirar as peles" },
832
+ layers: { joints: "Juntas", split: "Vista aberta", sound_range: "Alcance do som", carcass_lure: "Raio de atra\xE7\xE3o da carca\xE7a" },
833
+ metrics: { animals: "Bichos", draw_calls: "Chamadas de desenho", triangles: "Tri\xE2ngulos", nodes_per_skin: "N\xF3s por pele", incubating: "Incubando", turned: "Viraram", dead: "Morreram" },
834
+ scenarios: {
835
+ visual_states: "Vivo, infectado e morto em cada esp\xE9cie",
836
+ gait_moves_legs: "O andar mexe as pernas",
837
+ outbreak_rule: "Regra de surto ligada e desligada",
838
+ carcass_lure: "A carca\xE7a atrai zumbis dentro do raio",
839
+ bite_to_turn_timer: "Mordida, incuba\xE7\xE3o, vira (acelerado)",
840
+ skins_40: "40 bichos como pele de n\xF3s"
841
+ }
842
+ }
843
+ };
844
+
845
+ // src/test-area/module.ts
846
+ var ANIMALS_AREA_ID = "animals_area";
847
+ var ANIMAL_ITEM_PREFIX = "animal_";
848
+ var SKIN_COUNT2 = 40;
849
+ var SKIN_FIELD = { x0: -18, z0: -10, columns: 8, gap: 4.5 };
850
+ var DEFAULT_TIME_SCALE = 60;
851
+ function createAnimalsTestModule(options = {}) {
852
+ const locale = options.locale ?? "pt-BR";
853
+ const text = TEXT[locale] ?? TEXT["pt-BR"];
854
+ const species = options.species ?? [];
855
+ const settings = { visual: "alive", gait: "walk", speed: 1, outbreak: true, timeScale: DEFAULT_TIME_SCALE };
856
+ let three = null;
857
+ let animals = null;
858
+ let host = null;
859
+ let outbreak = null;
860
+ let time = 0;
861
+ let clock = 0;
862
+ let skinGroup = null;
863
+ let skins = [];
864
+ let rings = [];
865
+ const placed = /* @__PURE__ */ new Map();
866
+ const random = () => 0.5;
867
+ const drawSeed = options.seed ?? 7;
868
+ const bitten = () => ({ turned: [...placed.values()].filter((p) => p.counted === "turned").length, dead: [...placed.values()].filter((p) => p.counted === "dead").length });
869
+ const ensure = (THREE) => {
870
+ three = THREE;
871
+ animals ?? (animals = createAnimalsThree(THREE, { seed: drawSeed, ...options.document ? { document: options.document } : {} }));
872
+ return animals;
873
+ };
874
+ const rebuildOutbreak = () => {
875
+ outbreak = options.createOutbreak ? options.createOutbreak({ species, enabled: settings.outbreak, timeScale: settings.timeScale }) : null;
876
+ };
877
+ rebuildOutbreak();
878
+ const item = (animal) => ({
879
+ id: `${ANIMAL_ITEM_PREFIX}${animal.id}`,
880
+ name: animal.name[locale] ?? animal.id,
881
+ movable: false,
882
+ build: (THREE) => {
883
+ const rig = ensure(THREE).build(animal);
884
+ placed.set(rig.root.uuid, { id: rig.root.uuid, rig });
885
+ if (settings.visual !== "alive") animals?.setVisualState(rig, settings.visual);
886
+ return rig.root;
887
+ }
888
+ });
889
+ const statusOf = (entry) => {
890
+ if (!entry.fate || !outbreak) return;
891
+ const state = outbreak.stateOf(entry.fate, clock);
892
+ if (state === "incubating") return;
893
+ entry.counted = state === "turned" ? "turned" : "dead";
894
+ animals?.setVisualState(entry.rig, visualStateOf2(entry.rig.species.outcome, true, state === "dead"));
895
+ };
896
+ const clearRings = () => {
897
+ for (const ring of rings) ring.parent?.remove(ring);
898
+ rings = [];
899
+ };
900
+ const refreshLayers = () => {
901
+ if (!host?.bench || !animals || !three) return;
902
+ clearRings();
903
+ for (const entry of placed.values()) {
904
+ animals.setJointView(entry.rig, host.bench.layer("joints"));
905
+ animals.setSplit(entry.rig, host.bench.layer("split"));
906
+ }
907
+ const ring = (radius, color, parent) => {
908
+ const mesh = new three.Mesh(new three.RingGeometry(radius - 0.15, radius, 96).rotateX(-Math.PI / 2), new three.MeshBasicMaterial({ color, transparent: true, opacity: 0.55, depthWrite: false }));
909
+ mesh.position.y = 0.03;
910
+ parent.add(mesh);
911
+ rings.push(mesh);
912
+ };
913
+ for (const entry of placed.values()) {
914
+ if (!entry.rig.root.parent) continue;
915
+ const radius = entry.rig.species.soundRangeMetres;
916
+ if (host.bench.layer("sound_range") && radius) ring(radius, 4500223, entry.rig.root);
917
+ const lure = entry.rig.species.outbreak?.carcass?.lureRadiusMetres;
918
+ if (host.bench.layer("carcass_lure") && lure) ring(lure, 16733491, entry.rig.root);
919
+ }
920
+ };
921
+ const addSkins = () => {
922
+ if (!animals || !three || !host) return;
923
+ skinGroup ?? (skinGroup = new three.Group());
924
+ if (!skinGroup.parent) host.scene.add(skinGroup);
925
+ for (let index = skins.length; index < SKIN_COUNT2; index++) {
926
+ const animal = species[index % Math.max(1, species.length)];
927
+ const rig = animals.build(animal);
928
+ const skin = animals.createSkin(rig);
929
+ rig.root.position.set(SKIN_FIELD.x0 + index % SKIN_FIELD.columns * SKIN_FIELD.gap, 0, SKIN_FIELD.z0 - Math.floor(index / SKIN_FIELD.columns) * SKIN_FIELD.gap);
930
+ rig.root.traverse((object) => {
931
+ if (object.isMesh) object.visible = false;
932
+ });
933
+ skinGroup.add(rig.root, skin.mesh);
934
+ skins.push({ rig, skin });
935
+ }
936
+ };
937
+ const clearSkins = () => {
938
+ skinGroup?.parent?.remove(skinGroup);
939
+ skinGroup = null;
940
+ skins = [];
941
+ };
942
+ const applyVisual = () => {
943
+ for (const entry of placed.values()) {
944
+ if (entry.fate) continue;
945
+ animals?.setVisualState(entry.rig, settings.visual);
946
+ }
947
+ };
948
+ const onChange = (change) => {
949
+ if (change.kind === "layer") return refreshLayers();
950
+ if (change.kind === "action") {
951
+ if (change.id === "bite_all") {
952
+ for (const entry of placed.values()) {
953
+ entry.fate = outbreak?.bite(entry.rig.species.id, random, clock);
954
+ if (entry.fate) animals?.setVisualState(entry.rig, visualStateOf2(entry.rig.species.outcome, true));
955
+ }
956
+ } else if (change.id === "recover") {
957
+ for (const entry of placed.values()) {
958
+ delete entry.fate;
959
+ delete entry.counted;
960
+ animals?.setVisualState(entry.rig, settings.visual);
961
+ }
962
+ } else if (change.id === "skin_40") addSkins();
963
+ else if (change.id === "clear_skins") clearSkins();
964
+ return;
965
+ }
966
+ switch (change.id) {
967
+ case "visual_state":
968
+ settings.visual = String(change.value);
969
+ applyVisual();
970
+ break;
971
+ case "gait":
972
+ settings.gait = String(change.value);
973
+ break;
974
+ case "speed":
975
+ settings.speed = Number(change.value);
976
+ break;
977
+ case "outbreak":
978
+ settings.outbreak = Boolean(change.value);
979
+ if (outbreak) outbreak.enabled = settings.outbreak;
980
+ break;
981
+ case "time_scale":
982
+ settings.timeScale = Number(change.value);
983
+ rebuildOutbreak();
984
+ break;
985
+ default:
986
+ break;
987
+ }
988
+ };
989
+ const frame = (dt) => {
990
+ time += dt * settings.speed;
991
+ clock += dt;
992
+ for (const [id, entry] of placed) {
993
+ if (!entry.rig.root.parent) {
994
+ placed.delete(id);
995
+ continue;
996
+ }
997
+ statusOf(entry);
998
+ animals?.animate(entry.rig, { time, delta: dt * settings.speed, clip: settings.gait, split: host?.bench?.layer("split") ?? false });
999
+ animals?.updateSplit(entry.rig, dt);
1000
+ }
1001
+ for (const { rig, skin } of skins) {
1002
+ animals?.animate(rig, { time, delta: dt * settings.speed, clip: settings.gait });
1003
+ rig.root.updateMatrixWorld(true);
1004
+ skin.update();
1005
+ }
1006
+ };
1007
+ const countMeshes = (only) => {
1008
+ let drawCalls = 0;
1009
+ let triangles = 0;
1010
+ const count = (root) => root.traverse((object) => {
1011
+ const mesh = object;
1012
+ if (!mesh.isMesh || !mesh.visible) return;
1013
+ drawCalls += 1;
1014
+ const geometry = mesh.geometry;
1015
+ triangles += geometry.index ? geometry.index.count / 3 : geometry.attributes.position.count / 3;
1016
+ });
1017
+ if (only !== "skins") for (const entry of placed.values()) count(entry.rig.root);
1018
+ for (const { skin } of skins) count(skin.mesh);
1019
+ return { drawCalls, triangles: Math.round(triangles) };
1020
+ };
1021
+ const deps = {
1022
+ text: text.scenarios,
1023
+ species: () => species,
1024
+ ensure: () => ensure(three ?? host?.THREE),
1025
+ outbreak: () => outbreak,
1026
+ rebuildOutbreak: (enabled) => {
1027
+ settings.outbreak = enabled;
1028
+ rebuildOutbreak();
1029
+ return outbreak;
1030
+ },
1031
+ skins: () => {
1032
+ ensure(three ?? host?.THREE);
1033
+ addSkins();
1034
+ return { count: skins.length, meshes: countMeshes("skins"), nodes: skins[0]?.skin.nodes.length ?? 0, rich: skins[0]?.rig.triangles ?? 0 };
1035
+ },
1036
+ clearSkins
1037
+ };
1038
+ return {
1039
+ id: ANIMALS_AREA_ID,
1040
+ name: text.name,
1041
+ group: text.group,
1042
+ get placed() {
1043
+ return placed;
1044
+ },
1045
+ items: () => species.map(item),
1046
+ area: () => {
1047
+ const gap = 0.85;
1048
+ let cursor = 0;
1049
+ const spots = species.map((animal) => {
1050
+ const length = lineUpLength2(animal.body);
1051
+ const x = cursor + length / 2;
1052
+ cursor += length + gap;
1053
+ return { animal, x };
1054
+ });
1055
+ const offset = -(cursor - gap) / 2;
1056
+ return {
1057
+ placements: spots.map(({ animal, x }) => ({ item: `${ANIMAL_ITEM_PREFIX}${animal.id}`, position: [x + offset, 0, 0], rotationY: animal.body.kind === "snake" ? 2.2 : 1.1 })),
1058
+ view: { eye: [0, 4, Math.max(10, (cursor - gap) * 0.55)], target: [0, 0.6, 0] }
1059
+ };
1060
+ },
1061
+ controls: () => [
1062
+ { kind: "choice", id: "visual_state", name: text.controls.visual_state, hint: text.hints.visual_state, options: ["alive", "infected", "dead"].map((id) => ({ id, name: text.options.visual_state[id] })), default: "alive" },
1063
+ { kind: "choice", id: "gait", name: text.controls.gait, hint: text.hints.gait, options: ["idle", "walk", "run"].map((id) => ({ id, name: text.options.gait[id] })), default: "walk" },
1064
+ { kind: "number", id: "speed", name: text.controls.speed, min: 0, max: 4, step: 0.25, default: 1 },
1065
+ { kind: "boolean", id: "outbreak", name: text.controls.outbreak, hint: text.hints.outbreak, default: true },
1066
+ { kind: "number", id: "time_scale", name: text.controls.time_scale, hint: text.hints.time_scale, min: 1, max: 600, step: 1, default: DEFAULT_TIME_SCALE },
1067
+ { kind: "action", id: "bite_all", name: text.actions.bite_all },
1068
+ { kind: "action", id: "recover", name: text.actions.recover },
1069
+ { kind: "action", id: "skin_40", name: text.actions.skin_40 },
1070
+ { kind: "action", id: "clear_skins", name: text.actions.clear_skins }
1071
+ ],
1072
+ layers: () => ["joints", "split", "sound_range", "carcass_lure"].map((id) => ({ id, name: text.layers[id], default: false })),
1073
+ metrics: () => [
1074
+ { id: "animals", name: text.metrics.animals, read: () => placed.size + skins.length },
1075
+ { id: "draw_calls", name: text.metrics.draw_calls, read: () => countMeshes().drawCalls },
1076
+ { id: "triangles", name: text.metrics.triangles, read: () => countMeshes().triangles },
1077
+ { id: "nodes_per_skin", name: text.metrics.nodes_per_skin, read: () => skins[0]?.skin.nodes.length ?? 0 },
1078
+ { id: "incubating", name: text.metrics.incubating, read: () => [...placed.values()].filter((p) => p.fate && !p.counted).length },
1079
+ { id: "turned", name: text.metrics.turned, read: () => bitten().turned },
1080
+ { id: "dead", name: text.metrics.dead, read: () => bitten().dead }
1081
+ ],
1082
+ scenarios: () => createScenarios(deps),
1083
+ attach(lent) {
1084
+ host = lent;
1085
+ ensure(lent.THREE);
1086
+ const stopChange = lent.bench?.onChange(onChange);
1087
+ const stopFrame = lent.onFrame(frame);
1088
+ return () => {
1089
+ stopChange?.();
1090
+ stopFrame();
1091
+ clearRings();
1092
+ clearSkins();
1093
+ host = null;
1094
+ };
1095
+ }
1096
+ };
1097
+ }
1098
+
1099
+ // src/index.ts
1100
+ var countTriangles = (root) => {
1101
+ let triangles = 0;
1102
+ root.traverse((object) => {
1103
+ const mesh = object;
1104
+ if (!mesh.isMesh) return;
1105
+ const geometry = mesh.geometry;
1106
+ triangles += geometry.index ? geometry.index.count / 3 : geometry.attributes.position.count / 3;
1107
+ });
1108
+ return Math.round(triangles);
1109
+ };
1110
+ var createAnimalsThree = (THREE, options = {}) => {
1111
+ const random = options.random ?? createStream(options.seed ?? 7);
1112
+ const documentOf = () => {
1113
+ const found = options.document ?? globalThis.document;
1114
+ if (!found) throw new Error("game-animals-three: a species with a coat texture needs `options.document` (no global document found)");
1115
+ return found;
1116
+ };
1117
+ const jointMaterial = new THREE.MeshStandardMaterial({ color: 14722376, emissive: 14722376, emissiveIntensity: 0.8, roughness: 0.4 });
1118
+ const gaits = GAIT_PHASES;
1119
+ return {
1120
+ jointMaterial,
1121
+ build(species, buildOptions = {}) {
1122
+ const rig = createRig(THREE, species, { phase: buildOptions.phase ?? random() * 6 });
1123
+ if (species.body.kind === "snake") buildSnake(THREE, rig);
1124
+ else if (species.body.kind === "bird") buildBird(THREE, rig, { extra: BIRD_EXTRAS[species.id] });
1125
+ else buildQuadruped(THREE, rig, { document: species.body.texture ? documentOf() : void 0, extra: QUADRUPED_EXTRAS[species.id] });
1126
+ rig.triangles = countTriangles(rig.root);
1127
+ rig.root.traverse((object) => {
1128
+ if (object.isMesh) object.userData.speciesId = species.id;
1129
+ });
1130
+ return rig;
1131
+ },
1132
+ animate(rig, params) {
1133
+ const kind = rig.species.body.kind;
1134
+ if (kind === "snake") animateSnake(THREE, rig, params);
1135
+ else if (kind === "bird") animateBird(rig, params);
1136
+ else animateQuadruped(rig, params, gaits);
1137
+ },
1138
+ setVisualState: (rig, state) => applyVisualState(THREE, rig, state),
1139
+ setJointView: (rig, on) => setJointView(rig, on, jointMaterial),
1140
+ setSplit,
1141
+ updateSplit,
1142
+ createSkin: (rig) => createNodeSkin(THREE, rig.root, { nodes: collectReferencedNodes(rig, ["species"]) })
1143
+ };
1144
+ };
1145
+ export {
1146
+ ANIMALS_AREA_ID,
1147
+ ANIMAL_ITEM_PREFIX,
1148
+ BIRD_EXTRAS,
1149
+ QUADRUPED_EXTRAS,
1150
+ applyVisualState,
1151
+ createAnimalsTestModule,
1152
+ createAnimalsThree,
1153
+ createCoatTexture,
1154
+ lineUpAnimals,
1155
+ profileGeometry,
1156
+ setJointView,
1157
+ setSplit,
1158
+ updateSplit
1159
+ };
1160
+ //# sourceMappingURL=index.js.map