@adatechnology/game-characters-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,3857 @@
1
+ // src/instanced/body-chunk.ts
2
+ var BODY_CHUNK = `
3
+ attribute float bone; attribute vec4 vcol; attribute vec3 pivot; attribute vec3 ppivot;
4
+ attribute vec4 iA; attribute vec4 iB; attribute vec4 iC; attribute vec3 iCol; attribute vec3 iEye;
5
+ uniform float uTime;
6
+ varying vec3 vTint; varying float vGlow;
7
+ mat3 rX(float a){ float c=cos(a), s=sin(a); return mat3(1.,0.,0., 0.,c,s, 0.,-s,c); }
8
+ mat3 rY(float a){ float c=cos(a), s=sin(a); return mat3(c,0.,-s, 0.,1.,0., s,0.,c); }
9
+ mat3 rZ(float a){ float c=cos(a), s=sin(a); return mat3(c,s,0., -s,c,0., 0.,0.,1.); }
10
+ bool miss(float bit){ if (iC.z > 1.5) return false; return mod(floor(iC.x / bit), 2.) > 0.5; }
11
+ vec3 bodyAnim(vec3 p, inout vec3 nrm){
12
+ float ph = iA.w*6.2831, gait = iB.z, pose = iB.w - floor(iB.w/16.)*16., zomb = iC.z;
13
+ float side = (bone==2.||bone==4.||bone==7.||bone==9.) ? -1. : 1.;
14
+ // cascata: cada junta atrasa em rela\xE7\xE3o ao quadril (Besti\xE1rio 12.3)
15
+ float lag = bone==1.||bone==6.||bone==11. ? 0.55 : (bone>=7.&&bone<=8. ? 0.35 : (bone==2.||bone==3. ? 0.2 : 0.));
16
+ float sw = sin(ph - lag);
17
+ mat3 Rp = mat3(1.), Rc = mat3(1.);
18
+ bool isArm = bone==2.||bone==3., isFore = bone==7.||bone==8., isThigh = bone==4.||bone==5., isShin = bone==9.||bone==10.;
19
+ float legS = (bone==4.||bone==9.) ? 1. : -1.;
20
+ float walkS = sin(ph) * legS;
21
+ if (isThigh || isShin) Rp = rX(-walkS*0.5*gait);
22
+ if (isShin) Rc = rX(max(0., cos(ph + (legS>0.?0.:3.1416)))*0.85*gait);
23
+ if (pose==6.) { if (isThigh) Rp = rX(-1.1); if (isShin) Rc = rX(1.4); }
24
+ if (pose==5.) { if (isThigh) Rp = rX(-0.95 - walkS*0.3*gait); if (isShin) Rc = rX(1.9); } // agachado: joelho dobra, o p\xE9 fica no ch\xE3o
25
+ if (pose==7.) { if (isThigh) Rp = rX(-1.15); if (isShin) Rc = rX(2.3); } // ajoelhado comendo
26
+ if (pose==2. && isShin && fract(iA.w*3.)*legS > 0.3) Rc = rX(fract(iA.w*3.)*1.3); // corpo com um joelho dobrado
27
+ float armS = sin(ph) * (side>0. ? 1. : -1.);
28
+ if (isArm || isFore) {
29
+ // zumbi: bra\xE7os ca\xEDdos vagando; levanta (iC.z = 1 + 0,4 \xD7 alcance) quando percebe algu\xE9m ou vai agarrar
30
+ float raise = clamp((zomb - 1.)/0.4, 0., 1.);
31
+ float fwd = zomb > 0.5 && zomb < 1.5 ? mix(-0.12 + armS*0.3*gait + 0.05*sin(uTime*1.3+iA.w), -1.35 + armS*0.18 + 0.1*sin(uTime*2.+iA.w), raise) : armS*0.45*gait;
32
+ if (pose==3.) fwd = -1.55 + 0.35*sin(uTime*9.+iA.w);
33
+ if (pose==4.) fwd = -2.6;
34
+ if (pose==6.) fwd = -2.0;
35
+ if (pose==2.) fwd = (fract(iA.w*5.) > 0.5 && side > 0.) ? -2.8 : -0.35 + 0.3*sin(iA.w*5. + side);
36
+ if (pose==1.) fwd = -2.6 + 0.5*sin(ph)*side; // puxa o corpo com os bra\xE7os
37
+ if (pose==7.) fwd = -0.9 + 0.3*sin(uTime*6.+iA.w);
38
+ Rp = rX(fwd) * rZ(side*(pose==2. ? 0.5 + 0.3*sin(iA.w*3.) : 0.08));
39
+ // armado (iC.z = 2 + pitch codificado): as duas m\xE3os na arma, bra\xE7os seguem a mira
40
+ if (zomb > 1.5 && pose != 2.) { float aim = (zomb - 2.)*3. - 1.; Rp = rX(-1.42 + aim*0.85 + 0.02*sin(ph)*gait) * rZ(side < 0. ? 0.36 : -0.54); } // arma na m\xE3o direita de verdade (o osso 'D' fica em +x, que \xE9 a esquerda do boneco virado para +z); a esquerda encontra na empunhadura
41
+ }
42
+ if (isFore) Rc = rX(zomb > 1.5 && pose != 2. ? (side < 0. ? -0.12 : -0.04) : zomb > 0.5 ? mix(-0.3 - max(0., armS)*0.3*gait, -0.15, clamp((zomb - 1.)/0.4, 0., 1.)) : -0.35 - max(0., armS)*0.4*gait);
43
+ if (bone==11.) Rc = rX(pose==3.||pose==4. ? 0.35 + 0.2*sin(uTime*14.) : 0.05);
44
+ vec3 q = p; vec3 n = nrm;
45
+ if (isFore || isShin) { q = Rc*(q - pivot) + pivot; n = Rc*n; q = Rp*(q - ppivot) + ppivot; n = Rp*n; }
46
+ else if (isArm || isThigh || bone==11.) { q = Rp*(q - pivot) + pivot; n = Rp*n; q = Rc*(q - pivot) + pivot; n = Rc*n; }
47
+ // humano armado andando de lado: quadril e pernas giram para a dire\xE7\xE3o do passo (iC.x leva o \xE2ngulo)
48
+ if (zomb > 1.5 && (isThigh || isShin)) { mat3 hy = rY((iC.x - 0.5)*10.); q = hy*(q - vec3(0.,0.9,0.)) + vec3(0.,0.9,0.); n = hy*n; }
49
+ // partes faltando: colapsa no piv\xF4
50
+ if ((bone==1.||bone==6.||bone==11.) && miss(1.)) q = pivot;
51
+ if (bone==11. && miss(512.)) q = pivot;
52
+ if ((bone==2.||bone==7.) && miss(2.)) q = pivot; if ((bone==3.||bone==8.) && miss(4.)) q = pivot;
53
+ if ((bone==4.||bone==9.) && miss(8.)) q = pivot; if ((bone==5.||bone==10.) && miss(16.)) q = pivot;
54
+ if (bone==7. && miss(32.)) q = pivot; if (bone==8. && miss(64.)) q = pivot;
55
+ if (bone==9. && miss(128.)) q = pivot; if (bone==10. && miss(256.)) q = pivot;
56
+ // corpo: inclina\xE7\xE3o, balan\xE7o, agachar, rastejar, ca\xEDdo
57
+ bool upper = !(isThigh || isShin);
58
+ float zz = zomb > 0.5 && zomb < 1.5 ? 1. : 0.;
59
+ float lean = zz > 0.5 ? 0.12 + 0.3*min(1., gait) : 0.04*gait;
60
+ if (pose==3.) lean += 0.15;
61
+ if (pose==5.) lean += 0.25;
62
+ if (pose==7.) lean += 0.8;
63
+ if (upper) { q -= vec3(0.,0.95,0.); q = rX(lean)*q; n = rX(lean)*n; q += vec3(0.,0.95,0.); }
64
+ vec3 hp = rX(lean)*(pivot - vec3(0.,0.95,0.)) + vec3(0.,0.95,0.); // pesco\xE7o j\xE1 inclinado: a cabe\xE7a n\xE3o descola
65
+ if (bone==1.||bone==6.||bone==11.) { q -= hp; mat3 h = rY(sin(uTime*0.7+iA.w)*0.25*zz) * rZ(0.18*zz*sin(iA.w*3.)); q = h*q; n = h*n; q += hp; }
66
+ q.y += abs(sin(ph))*0.035*gait - (pose==5. ? 0.35 : pose==7. ? 0.5 : 0.);
67
+ if (pose==1. && (bone==1.||bone==6.||bone==11.)) { mat3 hc = rX(-1.25); q = hc*(q - hp) + hp; n = hc*n; } // rastejante levanta a cabe\xE7a
68
+ if (pose==1.) { mat3 c = rX(1.42); q = c*(q - vec3(0.,0.95,0.)) + vec3(0.,0.2,0.); n = c*n; }
69
+ if (pose==2.) { float flip = step(0.5, fract(iA.w*1.7)); float fk = clamp(gait, 0., 1.); fk = fk*fk*(3.-2.*fk); /* queda animada: passada leva o progresso */ mat3 c = rX((-1.5 + flip*3.0)*fk) * rZ(sin(iA.w*7.)*0.3*fk); /* de costas ou de bru\xE7os */ q = c*(q - vec3(0.,0.95,0.)) + mix(vec3(0.,0.95,0.), vec3(0.,0.16,-0.6 + flip*1.2), fk); n = c*n; }
70
+ nrm = n;
71
+ return q;
72
+ }
73
+ `;
74
+ var HIP_PIVOT = "vec3(0.,0.95,0.)";
75
+ var createBodyChunk = ({ hipHeight = 0.95 } = {}) => hipHeight === 0.95 ? BODY_CHUNK : BODY_CHUNK.replaceAll(HIP_PIVOT, `vec3(0.,${hipHeight},0.)`);
76
+
77
+ // src/instanced/body-geometry.ts
78
+ import { TEAM_GAME } from "@adatechnology/game-characters";
79
+ var negativeFirst = (first, second) => first[0] <= second[0] ? [first, second] : [second, first];
80
+ var jointsOf = (body) => {
81
+ const at = (id) => body.joints[id];
82
+ const [shoulderE, shoulderD] = negativeFirst(at("shoulderL"), at("shoulderR"));
83
+ const [elbowE, elbowD] = negativeFirst(at("elbowL"), at("elbowR"));
84
+ const [thighE, thighD] = negativeFirst(at("hipL"), at("hipR"));
85
+ const [kneeE, kneeD] = negativeFirst(at("kneeL"), at("kneeR"));
86
+ return { hip: at("hip"), neck: at("neck"), shoulderE, shoulderD, elbowE, elbowD, thighE, thighD, kneeE, kneeD };
87
+ };
88
+ var GAME_UPPER_ARM = 0.31;
89
+ var GAME_THIGH = 0.42;
90
+ var scaleOf = (length, gameLength) => Math.abs(length - gameLength) < 1e-9 ? 1 : length / gameLength;
91
+ var distance = (from, to) => Math.abs(from[1] - to[1]);
92
+ var tagGeometry = (geometry, bone, pivot, material, parent = [0, 0, 0], three) => {
93
+ const source = geometry.index ? geometry.toNonIndexed() : geometry;
94
+ const count = source.attributes.position.count;
95
+ source.setAttribute("bone", new three.Float32BufferAttribute(new Float32Array(count).fill(bone), 1));
96
+ const colors = new Float32Array(count * 4);
97
+ for (let index = 0; index < count; index++) colors[index * 4 + 3] = material;
98
+ source.setAttribute("vcol", new three.Float32BufferAttribute(colors, 4));
99
+ const pivots = new Float32Array(count * 3);
100
+ const parents = new Float32Array(count * 3);
101
+ for (let index = 0; index < count; index++) {
102
+ pivots.set(pivot, index * 3);
103
+ parents.set(parent, index * 3);
104
+ }
105
+ source.setAttribute("pivot", new three.Float32BufferAttribute(pivots, 3));
106
+ source.setAttribute("ppivot", new three.Float32BufferAttribute(parents, 3));
107
+ source.deleteAttribute("uv");
108
+ return source;
109
+ };
110
+ var MERGED_ATTRIBUTES = [["position", 3], ["normal", 3], ["bone", 1], ["vcol", 4], ["pivot", 3], ["ppivot", 3]];
111
+ var mergeBody = (list, three) => {
112
+ const total = list.reduce((sum, geometry) => sum + geometry.attributes.position.count, 0);
113
+ const merged = new three.BufferGeometry();
114
+ for (const [name, size] of MERGED_ATTRIBUTES) {
115
+ const array = new Float32Array(total * size);
116
+ let offset = 0;
117
+ for (const geometry of list) {
118
+ const part = geometry.attributes[name].array;
119
+ array.set(part, offset);
120
+ offset += part.length;
121
+ }
122
+ merged.setAttribute(name, new three.BufferAttribute(array, size));
123
+ }
124
+ merged.computeBoundingSphere();
125
+ return merged;
126
+ };
127
+ var buildInstancedBody = (three, { segments, body = TEAM_GAME }) => {
128
+ const joints = jointsOf(body);
129
+ const parts = [];
130
+ const half = Math.max(4, segments >> 1);
131
+ const { Vector2, LatheGeometry, CylinderGeometry, SphereGeometry } = three;
132
+ const limb = (radiusA, radiusB, length, radial) => {
133
+ const geometry = new CylinderGeometry(radiusB, radiusA, length, radial, 1, false);
134
+ geometry.translate(0, -length / 2, 0);
135
+ return geometry;
136
+ };
137
+ const ball = (radius, radial) => new SphereGeometry(radius, radial, Math.max(3, Math.round(radial * 0.75)));
138
+ const tag = (geometry, bone, pivot, material, parent) => void parts.push(tagGeometry(geometry, bone, pivot, material, parent, three));
139
+ const profile = (points) => points.map(([radius, height]) => new Vector2(radius, height));
140
+ const hip = joints.hip;
141
+ const neck = joints.neck;
142
+ const torso = new LatheGeometry(profile([[0, 0.86], [0.17, 0.88], [0.165, 0.98], [0.15, 1.1], [0.175, 1.26], [0.19, 1.38], [0.14, 1.47], [0.06, 1.52], [0, 1.53]]), segments);
143
+ torso.scale(1.12, 1, 0.64);
144
+ tag(torso, 0, hip, 1);
145
+ const pelvis = new LatheGeometry(profile([[0, 0.78], [0.15, 0.8], [0.172, 0.88], [0.165, 0.97], [0, 0.98]]), segments);
146
+ pelvis.scale(1.12, 1, 0.7);
147
+ tag(pelvis, 0, hip, 2);
148
+ const neckPart = limb(0.055, 0.05, 0.12, half);
149
+ neckPart.translate(0, 1.6, 0);
150
+ tag(neckPart, 1, neck, 0);
151
+ const head = ball(0.13, segments);
152
+ head.scale(0.96, 1.1, 1.02);
153
+ head.translate(0, 1.67, 0.01);
154
+ tag(head, 1, neck, 0);
155
+ const hair = new SphereGeometry(0.137, segments, Math.max(3, segments >> 1), 0, Math.PI * 2, 0, Math.PI * 0.42);
156
+ hair.scale(0.98, 1.1, 1.04);
157
+ hair.rotateX(-0.35);
158
+ hair.translate(0, 1.68, -0.01);
159
+ tag(hair, 1, neck, 4);
160
+ const jaw = ball(0.085, half);
161
+ jaw.scale(1, 0.55, 1);
162
+ jaw.translate(0, 1.58, 0.05);
163
+ tag(jaw, 11, neck, 0);
164
+ for (const side of [-1, 1]) {
165
+ const eye = ball(0.02, 4);
166
+ eye.scale(1.2, 0.7, 0.5);
167
+ eye.translate(side * 0.045, 1.69, 0.125);
168
+ tag(eye, 6, neck, 3);
169
+ }
170
+ const armLengthScale = scaleOf(distance(joints.shoulderE, joints.elbowE), GAME_UPPER_ARM);
171
+ const legLengthScale = scaleOf(distance(joints.thighE, joints.kneeE), GAME_THIGH);
172
+ const arms = [[-1, 2, 7, joints.shoulderE, joints.elbowE], [1, 3, 8, joints.shoulderD, joints.elbowD]];
173
+ for (const [, bone, forearmBone, shoulder, elbow] of arms) {
174
+ const upper = limb(0.074, 0.06, 0.31 * armLengthScale, half);
175
+ upper.translate(shoulder[0], shoulder[1], 0);
176
+ tag(upper, bone, shoulder, 1);
177
+ const joint = ball(0.07, half);
178
+ joint.translate(shoulder[0], shoulder[1], 0);
179
+ tag(joint, bone, shoulder, 1);
180
+ const forearm = limb(0.06, 0.046, 0.29 * armLengthScale, half);
181
+ forearm.translate(elbow[0], elbow[1], 0);
182
+ tag(forearm, forearmBone, elbow, 0, shoulder);
183
+ const hand = ball(0.05, half);
184
+ hand.scale(0.8, 1.2, 0.6);
185
+ hand.translate(elbow[0], elbow[1] - 0.33 * armLengthScale, 0);
186
+ tag(hand, forearmBone, elbow, 0, shoulder);
187
+ }
188
+ const legs = [[-1, 4, 9, joints.thighE, joints.kneeE], [1, 5, 10, joints.thighD, joints.kneeD]];
189
+ for (const [, bone, shinBone, hipJoint, knee] of legs) {
190
+ const thigh = limb(0.105, 0.078, 0.42 * legLengthScale, half);
191
+ thigh.translate(hipJoint[0], hipJoint[1], 0);
192
+ tag(thigh, bone, hipJoint, 2);
193
+ const kneeJoint = ball(0.075, half);
194
+ kneeJoint.translate(knee[0], knee[1], 0);
195
+ tag(kneeJoint, shinBone, knee, 2, hipJoint);
196
+ const shin = limb(0.077, 0.056, knee[1] - 0.1, half);
197
+ shin.translate(knee[0], knee[1], 0);
198
+ tag(shin, shinBone, knee, 2, hipJoint);
199
+ const foot = ball(0.06, half);
200
+ foot.scale(0.95, 0.72, 1.85);
201
+ foot.translate(knee[0], 0.06, 0.05);
202
+ tag(foot, shinBone, knee, 5, hipJoint);
203
+ }
204
+ return mergeBody(parts, three);
205
+ };
206
+
207
+ // src/instanced/body-material.ts
208
+ var createBodyMaterial = (three, { depth = false, time, chunk }) => {
209
+ const bodyChunk = createBodyChunk(chunk);
210
+ const material = depth ? new three.MeshDepthMaterial({ depthPacking: three.RGBADepthPacking }) : new three.MeshLambertMaterial({ color: 16777215 });
211
+ material.onBeforeCompile = (shader) => {
212
+ shader.uniforms.uTime = time;
213
+ shader.vertexShader = shader.vertexShader.replace("#include <common>", "#include <common>\n" + bodyChunk).replace(
214
+ "#include <begin_vertex>",
215
+ `
216
+ vec3 _n = normal;
217
+ vec3 transformed = bodyAnim(position, _n);
218
+ transformed *= vec3(iB.y, iB.x, iB.y);
219
+ transformed = rY(iA.z)*transformed + vec3(iA.x, floor(iB.w/16.)/1000. - 2., iA.y);
220
+ // pele: tons brasileiros variados, vai para cinza esverdeado (0x6f7a5c) com a infec\xE7\xE3o
221
+ vec3 skin = mix(vec3(0.93,0.74,0.6), vec3(0.42,0.27,0.18), iC.w);
222
+ skin = mix(skin, vec3(0.435,0.478,0.36), iC.y);
223
+ vec3 pants = iCol*0.45 + vec3(0.08,0.09,0.12);
224
+ float mat = vcol.w;
225
+ vTint = mat==6. ? vcol.rgb : mat==0. ? skin : mat==1. ? (dot(vcol.rgb, vcol.rgb) > 0. ? mix(iCol, vcol.rgb, 0.3) : iCol) : mat==2. ? pants : mat==4. ? mix(vec3(0.12,0.09,0.07), vec3(0.5), iC.y*0.4) : mat==5. ? vec3(0.13) : vec3(0.9);
226
+ vGlow = mat==3. && dot(iEye, iEye) > 0.01 ? 1. : 0.;
227
+ if (mat==3. && vGlow < 0.5) vTint = vec3(0.08);
228
+ `
229
+ ).replace("#include <beginnormal_vertex>", "#include <beginnormal_vertex>\n { vec3 _nn = objectNormal; bodyAnim(position, _nn); objectNormal = rY(iA.z)*_nn; }");
230
+ if (!depth) {
231
+ shader.fragmentShader = shader.fragmentShader.replace("#include <common>", "#include <common>\nvarying vec3 vTint; varying float vGlow;").replace("#include <color_fragment>", "#include <color_fragment>\n diffuseColor.rgb = vTint;").replace("#include <dithering_fragment>", "#include <dithering_fragment>\n if (vGlow > 0.5) gl_FragColor = vec4(vTint*0.0 + gl_FragColor.rgb*0.0 + EYE*2.4, 1.);");
232
+ shader.vertexShader = shader.vertexShader.replace("varying vec3 vTint;", "varying vec3 vTint; varying vec3 vEyeC;").replace("vGlow = mat==3.", "vEyeC = iEye; vGlow = mat==3.");
233
+ shader.fragmentShader = shader.fragmentShader.replace("varying vec3 vTint;", "varying vec3 vTint; varying vec3 vEyeC;").replace("EYE", "vEyeC");
234
+ }
235
+ };
236
+ return material;
237
+ };
238
+
239
+ // src/instanced/body-batch.ts
240
+ var ATTRIBUTES = [["iA", 4], ["iB", 4], ["iC", 4], ["iCol", 3], ["iEye", 3]];
241
+ var MIN_HEIGHT = -2;
242
+ var MAX_HEIGHT = 8;
243
+ var HEIGHT_STEPS = 1e3;
244
+ var POSE_SLOTS = 16;
245
+ var createBodyBatch = (three, geometry, options) => {
246
+ const { max, castShadow = true, time } = options;
247
+ const copy = geometry.clone();
248
+ const attributes = {};
249
+ for (const [name, size] of ATTRIBUTES) {
250
+ const attribute = new three.InstancedBufferAttribute(new Float32Array(max * size), size);
251
+ attribute.setUsage(three.DynamicDrawUsage);
252
+ copy.setAttribute(name, attribute);
253
+ attributes[name] = attribute;
254
+ }
255
+ const mesh = new three.InstancedMesh(copy, createBodyMaterial(three, { time }), max);
256
+ mesh.customDepthMaterial = createBodyMaterial(three, { depth: true, time });
257
+ mesh.frustumCulled = false;
258
+ mesh.castShadow = castShadow;
259
+ mesh.receiveShadow = true;
260
+ mesh.count = 0;
261
+ let count = 0;
262
+ return {
263
+ mesh,
264
+ max,
265
+ get count() {
266
+ return count;
267
+ },
268
+ begin() {
269
+ count = 0;
270
+ },
271
+ push({ a, b, c, color, eye, y = 0 }) {
272
+ if (count >= max) return;
273
+ const index = count++;
274
+ const fourOffset = index * 4;
275
+ const threeOffset = index * 3;
276
+ attributes.iA.array.set(a, fourOffset);
277
+ attributes.iB.array.set([b[0], b[1], b[2], b[3] + POSE_SLOTS * Math.round((Math.min(MAX_HEIGHT, Math.max(MIN_HEIGHT, y)) - MIN_HEIGHT) * HEIGHT_STEPS)], fourOffset);
278
+ attributes.iC.array.set(c, fourOffset);
279
+ attributes.iCol.array.set([color.r, color.g, color.b], threeOffset);
280
+ attributes.iEye.array.set(eye ? [eye.r, eye.g, eye.b] : [0, 0, 0], threeOffset);
281
+ },
282
+ end() {
283
+ mesh.count = count;
284
+ for (const attribute of Object.values(attributes)) {
285
+ attribute.clearUpdateRanges();
286
+ attribute.addUpdateRange(0, count * attribute.itemSize);
287
+ attribute.needsUpdate = true;
288
+ }
289
+ }
290
+ };
291
+ };
292
+
293
+ // src/instanced/lod.ts
294
+ var LOD_SEGMENTS = [16, 10, 5];
295
+ var LOD_BAKE = [1, 0.5, 0.28];
296
+ var LOD_DISTANCES = [18, 55];
297
+ var LOD_BATCH_CAPACITY = [160, 700, 2600];
298
+ var LOD_CASTS_SHADOW = [true, true, false];
299
+ var lodAt = (distance2, distances = LOD_DISTANCES) => distance2 < distances[0] ? 0 : distance2 < distances[1] ? 1 : 2;
300
+
301
+ // src/instanced/bake.ts
302
+ var BAKE_NEUTRAL_POSE = { lean: 0, armL: 0, armR: 0, elbL: 0, elbR: 0, legL: 0, legR: 0, kneeL: 0, kneeR: 0, headX: 0, headZ: 0, drop: 0, armOut: 0, neck: 0, headFwd: 0 };
303
+ var BLOOD_LIFT_NEAR = 6e-3;
304
+ var BLOOD_LIFT_FAR = 0.012;
305
+ var FAR_LOD = 0.6;
306
+ var EYE_SCALE = 2.2;
307
+ var EYE_DEPTH_SCALE = 1.4;
308
+ var bakeBody = (three, builders, spec, lod) => {
309
+ const root = spec.retrato && builders.buildPortrait ? builders.buildPortrait(spec.retrato, lod) : builders.buildBest({ ...spec, pose: { ...BAKE_NEUTRAL_POSE, ...spec.pose ?? {} } }, lod);
310
+ const rig = root.userData.rig;
311
+ const P = rig.P;
312
+ rig.fig.position.y = 0;
313
+ rig.upper.rotation.set(0, 0, 0);
314
+ rig.headG.rotation.set(0, 0, 0);
315
+ for (const arm of rig.arms) {
316
+ arm.g.rotation.set(0, 0, 0);
317
+ arm.el.rotation.set(0, 0, 0);
318
+ }
319
+ for (const leg of rig.legs) {
320
+ leg.g.rotation.set(0, 0, 0);
321
+ leg.kn.rotation.set(0, 0, 0);
322
+ }
323
+ root.updateMatrixWorld(true);
324
+ const worldPosition2 = (object) => object.getWorldPosition(new three.Vector3()).toArray();
325
+ const bones = /* @__PURE__ */ new Map();
326
+ bones.set(rig.headG, [1, worldPosition2(rig.headG), [0, 0, 0]]);
327
+ for (const arm of rig.arms) {
328
+ const shoulder = worldPosition2(arm.g);
329
+ bones.set(arm.g, [arm.s < 0 ? 2 : 3, shoulder, [0, 0, 0]]);
330
+ bones.set(arm.el, [arm.s < 0 ? 7 : 8, worldPosition2(arm.el), shoulder]);
331
+ }
332
+ for (const leg of rig.legs) {
333
+ const hip = worldPosition2(leg.g);
334
+ bones.set(leg.g, [leg.s < 0 ? 4 : 5, hip, [0, 0, 0]]);
335
+ bones.set(leg.kn, [leg.s < 0 ? 9 : 10, worldPosition2(leg.kn), hip]);
336
+ }
337
+ const trunk = [0, [0, 0.9 * P.s, 0], [0, 0, 0]];
338
+ const parts = [];
339
+ const topMaterial = spec.retrato ? null : builders.topMaterial();
340
+ root.traverse((object) => {
341
+ const mesh = object;
342
+ const material = mesh.material;
343
+ if (!mesh.isMesh || !mesh.geometry || material.transparent) return;
344
+ for (let ancestor = object; ancestor; ancestor = ancestor.parent) if (!ancestor.visible) return;
345
+ let tag = trunk;
346
+ for (let ancestor = object.parent; ancestor; ancestor = ancestor.parent) {
347
+ const found = bones.get(ancestor);
348
+ if (found) {
349
+ tag = found;
350
+ break;
351
+ }
352
+ }
353
+ let [bone, pivot, parentPivot] = tag;
354
+ if (object.userData.bone === 11) {
355
+ bone = 11;
356
+ pivot = bones.get(rig.headG)[1];
357
+ parentPivot = [0, 0, 0];
358
+ }
359
+ const isEye2 = !!object.userData.eye;
360
+ const color = material.color;
361
+ const materialId = isEye2 ? 3 : material === topMaterial ? 1 : 6;
362
+ if (isEye2 && bone === 1) bone = 6;
363
+ const geometry = mesh.geometry.clone();
364
+ geometry.applyMatrix4(mesh.matrixWorld);
365
+ const isBlood = material.polygonOffset && material.isMeshStandardMaterial;
366
+ if (isBlood) liftAlongNormals(geometry, lod < FAR_LOD ? BLOOD_LIFT_FAR : BLOOD_LIFT_NEAR);
367
+ if (isEye2) enlargeEye(three, geometry, lod < FAR_LOD ? 1 : 0.68);
368
+ const colors = isBlood ? [Math.max(color.r * 1.7, 0.3), Math.min(color.g * 1.7, 0.04), Math.min(color.b * 1.7, 0.04), materialId] : [color.r, color.g, color.b, materialId];
369
+ parts.push({ g: geometry, bone, pv: pivot, pp: parentPivot, col: colors });
370
+ });
371
+ return { geo: mergeBaked(three, parts), s: P.s, bulk: P.bulk, verts: parts.reduce((sum, part) => sum + part.g.attributes.position.count, 0) };
372
+ };
373
+ var liftAlongNormals = (geometry, lift) => {
374
+ const positions = geometry.attributes.position;
375
+ const normals = geometry.attributes.normal;
376
+ for (let index = 0; index < positions.count; index++) positions.setXYZ(index, positions.getX(index) + normals.getX(index) * lift, positions.getY(index) + normals.getY(index) * lift, positions.getZ(index) + normals.getZ(index) * lift);
377
+ };
378
+ var enlargeEye = (three, geometry, factor) => {
379
+ geometry.computeBoundingBox();
380
+ const center = geometry.boundingBox.getCenter(new three.Vector3());
381
+ geometry.translate(-center.x, -center.y, -center.z);
382
+ geometry.scale(EYE_SCALE * factor, EYE_SCALE * factor, EYE_DEPTH_SCALE * factor);
383
+ geometry.translate(center.x, center.y, center.z);
384
+ };
385
+ var mergeBaked = (three, parts) => {
386
+ let vertexTotal = 0;
387
+ let indexTotal = 0;
388
+ for (const part of parts) {
389
+ vertexTotal += part.g.attributes.position.count;
390
+ indexTotal += part.g.index ? part.g.index.count : part.g.attributes.position.count;
391
+ }
392
+ const position = new Float32Array(vertexTotal * 3);
393
+ const normal = new Float32Array(vertexTotal * 3);
394
+ const color = new Float32Array(vertexTotal * 4);
395
+ const bone = new Float32Array(vertexTotal);
396
+ const pivot = new Float32Array(vertexTotal * 3);
397
+ const parentPivot = new Float32Array(vertexTotal * 3);
398
+ const index = vertexTotal > 65535 ? new Uint32Array(indexTotal) : new Uint16Array(indexTotal);
399
+ let vertexOffset = 0;
400
+ let indexOffset = 0;
401
+ for (const part of parts) {
402
+ const count = part.g.attributes.position.count;
403
+ position.set(part.g.attributes.position.array, vertexOffset * 3);
404
+ normal.set(part.g.attributes.normal.array, vertexOffset * 3);
405
+ for (let vertex = 0; vertex < count; vertex++) {
406
+ color.set(part.col, (vertexOffset + vertex) * 4);
407
+ bone[vertexOffset + vertex] = part.bone;
408
+ pivot.set(part.pv, (vertexOffset + vertex) * 3);
409
+ parentPivot.set(part.pp, (vertexOffset + vertex) * 3);
410
+ }
411
+ if (part.g.index) {
412
+ const source = part.g.index.array;
413
+ for (let item = 0; item < source.length; item++) index[indexOffset++] = source[item] + vertexOffset;
414
+ } else for (let vertex = 0; vertex < count; vertex++) index[indexOffset++] = vertexOffset + vertex;
415
+ vertexOffset += count;
416
+ part.g.dispose();
417
+ }
418
+ const out = new three.BufferGeometry();
419
+ out.setAttribute("position", new three.BufferAttribute(position, 3));
420
+ out.setAttribute("normal", new three.BufferAttribute(normal, 3));
421
+ out.setAttribute("vcol", new three.BufferAttribute(color, 4));
422
+ out.setAttribute("bone", new three.BufferAttribute(bone, 1));
423
+ out.setAttribute("pivot", new three.BufferAttribute(pivot, 3));
424
+ out.setAttribute("ppivot", new three.BufferAttribute(parentPivot, 3));
425
+ out.setIndex(new three.BufferAttribute(index, 1));
426
+ out.computeBoundingSphere();
427
+ return out;
428
+ };
429
+
430
+ // src/instanced/variants.ts
431
+ import { variantCountFor, variantDecay, variantSeed } from "@adatechnology/game-characters";
432
+ var bakeVariants = (three, builders, types) => {
433
+ const variants = [];
434
+ const byType = /* @__PURE__ */ new Map();
435
+ for (const type of types) {
436
+ const indexes = [];
437
+ for (let variant = 0; variant < variantCountFor(type.id); variant++) {
438
+ const decay = variantDecay(variant);
439
+ const seed = variantSeed(type.index, variant);
440
+ const baked = LOD_BAKE.map((lod) => bakeBody(three, builders, { type: type.name, decay, seed }, lod));
441
+ indexes.push(variants.length);
442
+ variants.push({ typeId: type.id, variant, seed, decay, s: baked[0].s, bulk: baked[0].bulk, verts: baked.map((body) => body.verts), geometries: baked.map((body) => body.geo) });
443
+ }
444
+ byType.set(type.id, indexes);
445
+ }
446
+ return { variants, byType };
447
+ };
448
+ var createVariantBatches = (three, variant, time) => variant.geometries.map((geometry, level) => createBodyBatch(three, geometry, { max: LOD_BATCH_CAPACITY[level], castShadow: LOD_CASTS_SHADOW[level], time }));
449
+
450
+ // src/instanced/flying-limbs.ts
451
+ var GRAVITY = 9.8;
452
+ var FLOOR_Y = 0.07;
453
+ var BOUNCE = -0.3;
454
+ var FRICTION = 0.6;
455
+ var SPIN_DAMPING = 0.5;
456
+ var REST_SPEED = 0.4;
457
+ var LEG_COLOR = 2896704;
458
+ var createFlyingLimbs = (three, { max = 40, random, effects = {} }) => {
459
+ const limbs = [];
460
+ const mesh = new three.InstancedMesh(new three.CapsuleGeometry(0.065, 0.32, 2, 6), new three.MeshLambertMaterial({ color: 16777215 }), max);
461
+ mesh.count = 0;
462
+ mesh.castShadow = true;
463
+ mesh.setColorAt(0, new three.Color());
464
+ const matrix = new three.Matrix4();
465
+ const quaternion = new three.Quaternion();
466
+ const scale = new three.Vector3();
467
+ const position = new three.Vector3();
468
+ const euler = new three.Euler();
469
+ const color = new three.Color();
470
+ return {
471
+ mesh,
472
+ limbs,
473
+ launch({ x, z, kind, color: sleeve, blood }) {
474
+ const isLeg = kind === "leg";
475
+ const limb = limbs.length < max ? {} : limbs.shift();
476
+ Object.assign(limb, {
477
+ x,
478
+ y: isLeg ? 0.5 : 1.2,
479
+ z,
480
+ vx: (random() - 0.5) * 3,
481
+ vy: 2 + random() * 2,
482
+ vz: (random() - 0.5) * 3,
483
+ rx: random() * 6,
484
+ rz: random() * 6,
485
+ w: (random() - 0.5) * 10,
486
+ t: 0,
487
+ rest: false,
488
+ len: isLeg ? 1.3 : 1,
489
+ col: isLeg ? LEG_COLOR : sleeve
490
+ });
491
+ limbs.push(limb);
492
+ limb.cor = blood.color;
493
+ limb.seco = blood.dried;
494
+ limb.splat = !blood.bleeds;
495
+ effects.onLaunch?.(limb, kind);
496
+ return limb;
497
+ },
498
+ update(dt) {
499
+ limbs.forEach((limb, index) => {
500
+ if (!limb.rest) {
501
+ limb.t += dt;
502
+ limb.vy -= GRAVITY * dt;
503
+ limb.x += limb.vx * dt;
504
+ limb.y += limb.vy * dt;
505
+ limb.z += limb.vz * dt;
506
+ limb.rx += limb.w * dt;
507
+ if (limb.y < FLOOR_Y) {
508
+ limb.y = FLOOR_Y;
509
+ limb.vy *= BOUNCE;
510
+ limb.vx *= FRICTION;
511
+ limb.vz *= FRICTION;
512
+ limb.w *= SPIN_DAMPING;
513
+ if (!limb.splat) {
514
+ limb.splat = true;
515
+ effects.onSplat?.({ x: limb.x, z: limb.z, radius: 0.2 + random() * 0.2, rotation: random() * 6.28, color: limb.cor, dried: limb.seco });
516
+ }
517
+ if (Math.abs(limb.vy) < REST_SPEED) {
518
+ limb.rest = true;
519
+ limb.rx = Math.PI / 2;
520
+ }
521
+ }
522
+ }
523
+ matrix.compose(position.set(limb.x, limb.y, limb.z), quaternion.setFromEuler(euler.set(limb.rx, limb.rz, 0)), scale.set(1, limb.len, 1));
524
+ mesh.setMatrixAt(index, matrix);
525
+ mesh.setColorAt(index, color.set(limb.col));
526
+ });
527
+ mesh.count = limbs.length;
528
+ mesh.instanceMatrix.needsUpdate = true;
529
+ if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
530
+ }
531
+ };
532
+ };
533
+
534
+ // src/rich/animator.ts
535
+ import { CASCADE, animateProfile, lerpPose, stepCascade } from "@adatechnology/game-characters";
536
+ var rigOf = (root) => root.userData.rig;
537
+ var apply = (rig, sample) => {
538
+ if (sample.hipY !== void 0) rig.fig.position.y = sample.hipY;
539
+ if (sample.upper.x !== void 0) rig.upper.rotation.x = sample.upper.x;
540
+ if (sample.upper.y !== void 0) rig.upper.rotation.y = sample.upper.y;
541
+ if (sample.upper.z !== void 0) rig.upper.rotation.z = sample.upper.z;
542
+ if (sample.head.x !== void 0) rig.headG.rotation.x = sample.head.x;
543
+ if (sample.head.y !== void 0) rig.headG.rotation.y = sample.head.y;
544
+ rig.arms.forEach((arm, index) => {
545
+ const target = sample.arms[index];
546
+ if (target?.shoulder !== void 0) arm.g.rotation.x = target.shoulder;
547
+ if (target?.elbow !== void 0) arm.el.rotation.x = target.elbow;
548
+ });
549
+ rig.legs.forEach((leg, index) => {
550
+ const target = sample.legs[index];
551
+ if (target?.hip !== void 0) leg.g.rotation.x = target.hip;
552
+ if (target?.knee !== void 0) leg.kn.rotation.x = target.knee;
553
+ });
554
+ };
555
+ var createRigAnimator = (options = {}) => {
556
+ const gaits = options.gaits ?? {};
557
+ const rates = options.cascade ?? CASCADE;
558
+ const restOf = (rig) => ({
559
+ hipY: rig.dropBase,
560
+ lean: rig.leanBase,
561
+ headX: rig.hxBase,
562
+ arms: [
563
+ { shoulder: rig.arms[0].base, elbow: rig.arms[0].eBase },
564
+ { shoulder: rig.arms[1].base, elbow: rig.arms[1].eBase }
565
+ ],
566
+ legs: [
567
+ { hip: rig.legs[0].base, knee: rig.legs[0].kBase, ...rig.legs[0].lost ? { lost: true } : {} },
568
+ { hip: rig.legs[1].base, knee: rig.legs[1].kBase, ...rig.legs[1].lost ? { lost: true } : {} }
569
+ ]
570
+ });
571
+ const animate0 = (root, time, stride) => {
572
+ const rig = rigOf(root);
573
+ if (!rig) return void 0;
574
+ const profile = gaits[rig.type];
575
+ const sample = animateProfile({ ...profile ? { profile } : {}, rest: restOf(rig), time, phase: rig.ph, ...stride ? { stride } : {} });
576
+ apply(rig, sample);
577
+ return sample;
578
+ };
579
+ return {
580
+ restOf,
581
+ animate0,
582
+ animate(root, time, stride) {
583
+ const rig = rigOf(root);
584
+ if (!rig) return;
585
+ animate0(root, time, stride);
586
+ rig.dangles.forEach((arm) => {
587
+ arm.el.rotation.x = -(arm.g.rotation.x + rig.upper.rotation.x) + Math.sin(time * 3.2 + rig.ph) * 0.3;
588
+ });
589
+ rig.flaps.forEach((flap, index) => {
590
+ var _a;
591
+ const base = (_a = flap.userData).rx0 ?? (_a.rx0 = flap.rotation.x);
592
+ flap.rotation.x = base + Math.sin(time * 4 + index + rig.ph) * 0.18;
593
+ });
594
+ },
595
+ cascade(rig, dt) {
596
+ if (!rig.cur) {
597
+ const joints = [
598
+ [rig.fig.position, "y", "hip"],
599
+ [rig.upper.rotation, "x", "spine"],
600
+ [rig.upper.rotation, "y", "spine"],
601
+ [rig.upper.rotation, "z", "spine"],
602
+ [rig.headG.rotation, "x", "head"],
603
+ [rig.headG.rotation, "y", "head"],
604
+ [rig.headG.rotation, "z", "head"]
605
+ ];
606
+ rig.arms.forEach((arm) => joints.push([arm.g.rotation, "x", "shoulder"], [arm.el.rotation, "x", "elbow"]));
607
+ rig.legs.forEach((leg) => joints.push([leg.g.rotation, "x", "leg"], [leg.kn.rotation, "x", "knee"]));
608
+ rig.jl = joints;
609
+ rig.cur = new Map(joints.map((joint) => [joint, joint[0][joint[1]]]));
610
+ return;
611
+ }
612
+ for (const joint of rig.jl) {
613
+ const target = joint[0][joint[1]];
614
+ const value = stepCascade(rig.cur.get(joint), target, dt, joint[2], rates);
615
+ rig.cur.set(joint, value);
616
+ joint[0][joint[1]] = value;
617
+ }
618
+ },
619
+ setPose(rig, q) {
620
+ if (q.lean != null) rig.upper.rotation.x = q.lean;
621
+ if (q.twist != null) rig.upper.rotation.y = q.twist;
622
+ if (q.side != null) rig.upper.rotation.z = q.side;
623
+ if (q.drop != null) rig.fig.position.y = rig.dropBase + q.drop;
624
+ if (q.headX != null) rig.headG.rotation.x = q.headX;
625
+ if (q.headY != null) rig.headG.rotation.y = q.headY;
626
+ if (q.headZ != null) rig.headG.rotation.z = q.headZ;
627
+ const [armR, armL] = rig.arms;
628
+ if (q.armR != null) armR.g.rotation.x = q.armR;
629
+ if (q.armL != null) armL.g.rotation.x = q.armL;
630
+ if (q.elbR != null) armR.el.rotation.x = q.elbR;
631
+ if (q.elbL != null) armL.el.rotation.x = q.elbL;
632
+ if (q.armRz != null) armR.g.rotation.z = q.armRz;
633
+ if (q.armLz != null) armL.g.rotation.z = q.armLz;
634
+ const [legL, legR] = rig.legs;
635
+ if (q.legL != null) legL.g.rotation.x = q.legL;
636
+ if (q.legR != null) legR.g.rotation.x = q.legR;
637
+ if (q.kneeL != null) legL.kn.rotation.x = q.kneeL;
638
+ if (q.kneeR != null) legR.kn.rotation.x = q.kneeR;
639
+ },
640
+ lerpPose
641
+ };
642
+ };
643
+
644
+ // src/rich/best-builder.ts
645
+ import { createBloodStream, createRng, drawAppearance } from "@adatechnology/game-characters";
646
+ var createBestBuilder = (THREE, { types, random = Math.random }) => {
647
+ const options = { types, random };
648
+ let LK = 1, topM_ref = null;
649
+ const SPH = (r, w = 8, h = 6, ...a) => new THREE.SphereGeometry(r, Math.max(4, Math.round(w * LK)), Math.max(3, Math.round(h * LK)), ...a);
650
+ const CYL = (a, b, h, rs = 8, ...x) => new THREE.CylinderGeometry(a, b, h, Math.max(4, Math.round(rs * LK)), ...x);
651
+ const TOR = (r, t, rs = 8, ts = 6, ...x) => new THREE.TorusGeometry(r, t, Math.max(3, Math.round(rs * LK)), Math.max(4, Math.round(ts * LK)), ...x);
652
+ const BCARDS = options.types.map((c) => ({ n: c.name, eye: "#" + c.eye.toString(16).padStart(6, "0") }));
653
+ const glowTex = null;
654
+ const rng = createRng;
655
+ const BLOB = (() => {
656
+ const sh = new THREE.Shape(), N = 24, R = rng(123), a = [R() * 6, R() * 6, R() * 6];
657
+ for (let i = 0; i < N; i++) {
658
+ const t = i / N * 6.2832, r = 1 + 0.14 * Math.sin(3 * t + a[0]) + 0.08 * Math.sin(5 * t + a[1]) + 0.05 * Math.sin(9 * t + a[2]) + (R() - 0.5) * 0.1 + (i % 5 === 0 ? R() * 0.22 : 0);
659
+ i ? sh.lineTo(Math.cos(t) * r, Math.sin(t) * r) : sh.moveTo(Math.cos(t) * r, Math.sin(t) * r);
660
+ }
661
+ sh.closePath();
662
+ const g = new THREE.ShapeGeometry(sh);
663
+ g.rotateX(-Math.PI / 2);
664
+ return g;
665
+ })();
666
+ const SKIN = [13482920, 9740420, 8090698, 9206878];
667
+ const BLOOD = [10361618, 7214096, 4860440, 1840658];
668
+ const CLOTH = [4878230, 9321275, 6113850, 5208643, 7040888, 4153146, 5917294, 9071162];
669
+ const PANTS = 2896704, GRAY = 4868678, RED = 8000012;
670
+ const col = (a, b, t) => new THREE.Color(a).lerp(new THREE.Color(b), t);
671
+ const geoBox = (w, h, d) => new THREE.BoxGeometry(w, h, d);
672
+ const box = (w, h, d, m) => new THREE.Mesh(geoBox(w, h, d), m);
673
+ const limbGeo = (w, len) => {
674
+ const g = geoBox(w, len, w);
675
+ g.translate(0, -len / 2, 0);
676
+ return g;
677
+ };
678
+ const basic = (c, o) => new THREE.MeshBasicMaterial(Object.assign({ color: c }, o || {}));
679
+ const glow = (c, s, op) => {
680
+ const sp = new THREE.Sprite(new THREE.SpriteMaterial({ map: glowTex, color: c, transparent: true, blending: THREE.AdditiveBlending, depthWrite: false, opacity: op ?? 0.9 }));
681
+ sp.scale.setScalar(s);
682
+ return sp;
683
+ };
684
+ const PI = Math.PI;
685
+ let SEG = 16;
686
+ const TORSO_P = [[0.18, 0.86], [0.205, 0.95], [0.195, 1.03], [0.19, 1.12], [0.225, 1.27], [0.26, 1.4], [0.265, 1.47], [0.21, 1.55], [0.085, 1.6]];
687
+ const TORSO_F = [[0.17, 0.86], [0.215, 0.95], [0.2, 1.02], [0.165, 1.1], [0.2, 1.25], [0.245, 1.38], [0.25, 1.46], [0.2, 1.54], [0.085, 1.6]];
688
+ const torsoGeo = (sx, sz, fem) => {
689
+ const g = new THREE.LatheGeometry(new THREE.SplineCurve((fem ? TORSO_F : TORSO_P).map((p) => new THREE.Vector2(p[0], p[1]))).getPoints(Math.max(8, Math.round(30 * LK))), Math.max(6, Math.round(24 * LK)));
690
+ g.scale(1.12 * (sx || 1), 1, 0.64 * (sz || 1));
691
+ return g;
692
+ };
693
+ const cylDown = (r1, r2, len) => CYL(r1, r2, len, SEG, 1).translate(0, -len / 2, 0);
694
+ const capGeo = (r, len) => SPH(r, 20, 10, 0, PI * 2, 0, len || PI / 2);
695
+ const BASE = { s: 1, bulk: 1, lean: 0.15, armL: -1.15, armR: -0.85, elbL: -0.25, elbR: -0.4, legL: -0.15, legR: 0.25, kneeL: 0.15, kneeR: 0.35, headX: 0.08, headZ: 0.22, drop: -0.02, neck: 0 };
696
+ const TYPES = {
697
+ Lento: {},
698
+ Corredor: { s: 0.97, bulk: 0.88, lean: 0.75, armL: -1.35, armR: -1.05, elbL: -0.08, elbR: -0.12, legL: -0.75, legR: 0.55, kneeL: 0.45, kneeR: 1.25, headX: -0.55, headZ: 0, drop: -0.1 },
699
+ Brutamonte: { s: 1.42, bulk: 1.32, lean: 0.3, armL: -0.4, armR: -0.25, elbL: -0.35, elbR: -0.25, legL: -0.12, legR: 0.12, kneeL: 0.12, kneeR: 0.12, headX: -0.2, headZ: 0, neck: -0.07, drop: -0.02 },
700
+ Rastejante: { noLegs: true, lean: 1.42, drop: -0.74, armL: -2.75, armR: -2.35, elbL: -0.5, elbR: -0.3, headX: -1.35, headZ: 0.1 },
701
+ Gritador: { bulk: 0.84, lean: -0.18, armL: 0.35, armR: 0.45, elbL: -0.6, elbR: -0.5, legL: -0.05, legR: 0.1, kneeL: 0.05, kneeR: 0.1, headX: -0.65, headZ: 0, neck: 0.12, mouth: true, drop: 0 },
702
+ Inchado: { bloat: true, s: 1.03, bulk: 1.15, lean: 0, armL: -0.55, armR: -0.45, elbL: -0.3, elbR: -0.3, legL: 0, legR: 0, kneeL: 0.05, kneeR: 0.05, headZ: 0.1, drop: 0, armOut: 0.45 },
703
+ Espreitador: { bulk: 0.86, lean: 1.2, drop: -0.36, legL: -1.05, legR: -0.85, kneeL: 1.75, kneeR: 1.6, armL: -1.1, armR: -1.35, elbL: -0.05, elbR: -0.1, headX: -1.05, headZ: 0, dim: true },
704
+ Farejador: { lean: 0.45, headX: -0.75, headFwd: 0.07, armL: -0.35, armR: -0.2, elbL: -0.45, elbR: -0.4, legL: -0.25, legR: 0.2, kneeL: 0.3, kneeR: 0.45, headZ: 0.12, nose: true, drop: -0.04 },
705
+ Saltador: { bulk: 0.86, lean: 0.65, drop: -0.36, legL: -1.15, legR: -0.9, kneeL: 1.9, kneeR: 1.7, armL: 0.7, armR: 0.5, elbL: -0.6, elbR: -0.5, headX: -0.7, headZ: 0 },
706
+ Cego: { lean: 0.05, armL: -0.6, armR: -0.35, elbL: -0.5, elbR: -0.7, legL: -0.08, legR: 0.1, kneeL: 0.08, kneeR: 0.12, headX: -0.25, headZ: 0.45, blind: true, mouth: true },
707
+ Afogado: { bulk: 1.08, lean: 0.22, armL: -0.25, armR: -0.15, elbL: -0.1, elbR: -0.15, headX: 0.35, headZ: 0.3, wet: true },
708
+ Carbonizado: { lean: 0.2, armL: -1, armR: -0.6, elbL: -0.6, elbR: -0.9, headX: 0.05, headZ: -0.15, char: true },
709
+ Devorador: { lean: 0.95, drop: -0.42, legL: -1.45, legR: -1.3, kneeL: 2.3, kneeR: 2.2, armL: -1.55, armR: -1.35, elbL: -0.7, elbR: -0.9, headX: 0.25, headZ: 0, gore: true }
710
+ };
711
+ const OUTFITS = {
712
+ policial: { cloth: 2042436, pants: 1711916, vest: 1382170, hat: "cap", hatC: 1778232 },
713
+ militar: { cloth: 4938290, pants: 4147755, vest: 3752234, hat: "helmet", hatC: 4871474 },
714
+ bombeiro: { cloth: 11044682, pants: 9401152, stripes: 14275146, hat: "fire", hatC: 10691612 },
715
+ motociclista: { cloth: 1579034, pants: 2237739, hat: "moto", hatC: 1118740 },
716
+ operario: { cloth: 7040888, pants: 2896704, vest: 14708778, hat: "hardhat", hatC: 15122746 },
717
+ medico: { cloth: 8368328, pants: 7314096, mask: true },
718
+ cacador: { cloth: 5917230, pants: 4866096, hat: "cap", hatC: 14708778 }
719
+ };
720
+ function build(spec) {
721
+ const ti = Math.max(0, BCARDS.findIndex((x) => x.n === spec.type)), z = BCARDS[ti];
722
+ const P = Object.assign({}, BASE, TYPES[spec.type] || {}, spec.pose || {}), d = spec.decay ?? 0, life = spec.life ?? 1, cut = spec.cut || {};
723
+ P.s *= spec.h ?? 1;
724
+ P.bulk *= spec.bulk ?? 1;
725
+ if (life === 0) {
726
+ P.s *= 0.94;
727
+ P.bulk *= 0.9;
728
+ }
729
+ if (life === 2) {
730
+ P.lean += 0.28;
731
+ P.headX -= 0.18;
732
+ P.armL *= 0.8;
733
+ P.armR *= 0.8;
734
+ P.kneeL += 0.1;
735
+ P.kneeR += 0.1;
736
+ }
737
+ if (spec.lying) Object.assign(P, { armL: 0.2, armR: -0.15, elbL: -0.15, elbR: -0.3, legL: -0.05, legR: 0.12, kneeL: 0.05, kneeR: 0.2, lean: 0, drop: 0, headX: 0, headZ: 0.35 });
738
+ const op = spec.opacity ?? 1;
739
+ const cache = {};
740
+ const M = (c) => {
741
+ const k = new THREE.Color(c).getHex();
742
+ if (cache[k]) return cache[k];
743
+ const m = new THREE.MeshLambertMaterial({ color: c });
744
+ if (op < 1) {
745
+ m.transparent = true;
746
+ m.opacity = op;
747
+ }
748
+ return cache[k] = m;
749
+ };
750
+ const O = spec.outfit ? OUTFITS[spec.outfit] || {} : {};
751
+ const BR = createBloodStream(spec.seed ?? 7);
752
+ const uni = !!spec.uniform;
753
+ const { traits: T, random: VR } = drawAppearance({
754
+ seed: spec.seed ?? options.random() * 1e9 | 0,
755
+ outfit: { cloth: O.cloth, pants: O.pants, hat: O.hat },
756
+ uniform: uni,
757
+ typeIndex: ti,
758
+ clothIndex: spec.clothIdx,
759
+ female: spec.fem,
760
+ allowFemale: spec.type !== "Brutamonte",
761
+ tone: spec.tone,
762
+ hairStyle: spec.hs || void 0,
763
+ hairColor: spec.hair,
764
+ wet: !!P.wet,
765
+ charred: !!P.char,
766
+ human: !!spec.human,
767
+ life
768
+ });
769
+ const fem = T.female, tone = T.tone;
770
+ const skinC = spec.skin ?? (uni ? SKIN[d] : col(tone, SKIN[d], [0.45, 0.65, 0.8, 0.88][d]));
771
+ const topC = T.topColor, pantC = T.pantsColor;
772
+ const V = { sleeve: T.sleeve, hair: T.hair, hx: T.headWidth, hy: T.headHeight, jaw: T.jaw, nose: T.nose, beard: T.beard, glasses: T.glasses, cap: T.cap, capC: T.capColor, bag: T.bag, bagC: T.bagColor, torn: T.torn, apron: T.apron };
773
+ const hairC = T.hairColor;
774
+ const skinFinal = P.char ? 1840658 : P.wet ? col(skinC, 8032160, 0.55) : skinC;
775
+ const skinM = M(skinFinal);
776
+ const topM = M(P.char ? col(topC, 1380880, 0.75) : P.wet ? col(topC, 1712688, 0.45) : col(topC, GRAY, d * 0.3)), pantsM = M(col(pantC, GRAY, d * 0.3)), shoeM = M(T.shoeColor), red = M(RED);
777
+ const add = (p, geo, m, x, y, z2, rx, ry, rz) => {
778
+ const o = new THREE.Mesh(geo, m);
779
+ o.position.set(x || 0, y || 0, z2 || 0);
780
+ if (rx || ry || rz) o.rotation.set(rx || 0, ry || 0, rz || 0);
781
+ p.add(o);
782
+ return o;
783
+ };
784
+ const ball = (p, r, m, x, y, z2) => add(p, SPH(r, SEG, 12), m, x, y, z2);
785
+ topM_ref = topM;
786
+ const SKM = {}, skM = (c) => SKM[c] || (SKM[c] = new THREE.MeshStandardMaterial({ color: c, roughness: 0.3, metalness: 0, polygonOffset: true, polygonOffsetFactor: -4, polygonOffsetUnits: -4 }));
787
+ const flat = (p, x, y, zz, sx, sy, c) => {
788
+ const m = new THREE.Mesh(BLOB, skM(c));
789
+ m.rotation.set(PI / 2, 0, BR() * 6.28);
790
+ m.scale.set(sx, 1, sy);
791
+ m.position.set(x, y, zz);
792
+ p.add(m);
793
+ return m;
794
+ };
795
+ const splat = (p, x, y, zz, r, c, n) => {
796
+ const a = flat(p, x, y, zz + 4e-3, r, r * (0.7 + BR() * 0.5), c);
797
+ for (let k = 0; k < (n || 0); k++) {
798
+ const an = BR() * 6.28, dd = r * (1.2 + BR() * 0.9), q = r * (0.12 + BR() * 0.18);
799
+ flat(p, x + Math.cos(an) * dd, y + Math.sin(an) * dd, zz + 4e-3, q, q * 0.7, c);
800
+ }
801
+ if (n) run(p, x + r * 0.15, y - r * 0.5, y - r * (2.2 + BR() * 1.2), zz + 3e-3, zz, c, r * 0.35);
802
+ return a;
803
+ };
804
+ const run = (p, x, y0, y1, z0, z1, c, w) => {
805
+ const L = y0 - y1, sh = new THREE.Shape();
806
+ sh.moveTo(-w / 2, 0);
807
+ sh.lineTo(w / 2, 0);
808
+ sh.quadraticCurveTo(w * 0.3, -L * 0.5, w * 0.18, -L);
809
+ sh.lineTo(-w * 0.12, -L);
810
+ sh.quadraticCurveTo(-w * 0.35, -L * 0.5, -w / 2, 0);
811
+ const g = new THREE.ShapeGeometry(sh), m = new THREE.Mesh(g, skM(c));
812
+ m.position.set(x, y0, z0);
813
+ m.rotation.x = -Math.atan2(z0 - z1, L);
814
+ p.add(m);
815
+ const bl = new THREE.Mesh(SPH(w * 0.42, 10, 8), skM(c));
816
+ bl.scale.set(1, 1.35, 0.5);
817
+ bl.position.set(x + w * 0.03, y1, z1);
818
+ p.add(bl);
819
+ return bl;
820
+ };
821
+ const stump = (p, r, y, up) => {
822
+ const st = ball(p, r * 1.02, red, 0, y, 0);
823
+ st.scale.set(1, 0.35, 1);
824
+ add(p, CYL(r * 0.32, r * 0.36, 0.07, 10), M(15261900), 0, y + (up ? 0.04 : -0.04), 0);
825
+ };
826
+ const ring = (p, r, h, m, y, sx, sz) => {
827
+ const o = add(p, CYL(r, r, h, 24), m, 0, y, 0);
828
+ o.scale.set(sx || 1.12, 1, sz || 0.7);
829
+ return o;
830
+ };
831
+ const root = new THREE.Group(), fig = new THREE.Group();
832
+ const drip = (p, x, y, zz, kind, every) => {
833
+ if (d >= 3 && kind !== "bile") return;
834
+ const o = new THREE.Object3D();
835
+ o.position.set(x, y, zz);
836
+ o.userData.drip = { kind: kind || (d < 1 ? "blood" : "old"), every: every || (d < 1 ? 1.2 : 2.4) };
837
+ p.add(o);
838
+ };
839
+ fig.scale.set(P.s * P.bulk, P.s, P.s * Math.min(1.15, P.bulk));
840
+ fig.position.y = P.drop * P.s;
841
+ root.add(fig);
842
+ const pel = ball(fig, 0.2, pantsM, 0, 0.92, 0);
843
+ pel.scale.set(fem ? 1.16 : 1.08, 0.62, 0.74);
844
+ const legs = [];
845
+ [[-1, P.legL, P.kneeL], [1, P.legR, P.kneeR]].forEach(([s, r, kr]) => {
846
+ const g = new THREE.Group();
847
+ g.position.set(s * (fem ? 0.115 : 0.105), 0.9, 0);
848
+ fig.add(g);
849
+ add(g, cylDown(P.bloat ? 0.13 : fem ? 0.11 : 0.105, P.bloat ? 0.095 : 0.078, 0.42), pantsM);
850
+ const kn = new THREE.Group();
851
+ kn.position.y = -0.42;
852
+ g.add(kn);
853
+ ball(kn, 0.078, pantsM);
854
+ const lost = P.noLegs || cut.legs;
855
+ if (lost) {
856
+ stump(kn, 0.08, -0.01);
857
+ drip(kn, 0, -0.05, 0, null, 1);
858
+ g.rotation.x = 1.45;
859
+ } else {
860
+ add(kn, cylDown(0.077, 0.056, 0.38), pantsM);
861
+ const sh = ball(kn, 0.075, shoeM, 0, -0.43, 0.06);
862
+ sh.scale.set(0.95, 0.7, 1.75);
863
+ g.rotation.x = r;
864
+ kn.rotation.x = kr;
865
+ }
866
+ legs.push({ g, kn, s, base: g.rotation.x, kBase: kn.rotation.x, lost });
867
+ });
868
+ const upper = new THREE.Group();
869
+ upper.position.y = 0.9;
870
+ upper.rotation.x = P.lean;
871
+ fig.add(upper);
872
+ const U = new THREE.Group();
873
+ U.position.y = -0.9;
874
+ upper.add(U);
875
+ let frontZ = 0.175;
876
+ if (P.bloat) {
877
+ const sick = col(skinC, 8362578, 0.45), sickM = M(sick), veinM = M(col(sick, 2767386, 0.55));
878
+ const chest = add(U, torsoGeo(1.25, 1.35), topM);
879
+ chest.scale.set(1, 1, 1);
880
+ const belly = add(U, SPH(0.34, 24, 18), sickM, 0, 1.06, 0.1);
881
+ belly.scale.set(1.12, 1, 1.08);
882
+ const shirt = add(U, SPH(0.355, 24, 10, 0, PI * 2, 0, PI * 0.42), topM, 0, 1.08, 0.1);
883
+ shirt.scale.set(1.12, 1, 1.08);
884
+ for (let k = 0; k < 7; k++) {
885
+ const a = -1 + k * 0.33 + Math.sin(k * 3.1) * 0.12, b = 0.05 + k * 37 % 5 * 0.11;
886
+ const vv = add(U, SPH(0.012, 8, 6), veinM, Math.sin(a) * 0.36, 1 - b * 0.5, 0.1 + Math.cos(a) * 0.36);
887
+ vv.scale.set(1, 6, 1);
888
+ vv.rotation.z = a * 0.8;
889
+ }
890
+ const pus = [[0.12, 1], [-0.17, 0.92], [0.02, 0.86], [-0.06, 1.12], [0.2, 0.88], [-0.22, 1.05]];
891
+ drip(U, 0.05, 0.8, 0.3, "bile", 1.6);
892
+ pus.forEach(([x, y], k) => {
893
+ const zz = 0.1 + Math.sqrt(Math.max(0.01, 0.34 * 0.34 - x * x / 1.25 - (y - 1.06) ** 2)) * 1.08;
894
+ const r = 0.022 + k % 3 * 0.01;
895
+ add(U, SPH(r, 10, 8), basic(z.eye), x, y, zz).userData.eye = 1;
896
+ const g = glow(z.eye, r * 5, 0.55);
897
+ g.position.set(x, y, zz + 0.02);
898
+ U.add(g);
899
+ });
900
+ add(U, CYL(0.09, 0.1, 0.12, 16), sickM, 0, 1.62, 0);
901
+ frontZ = 0.38;
902
+ } else add(U, torsoGeo(1, 1, fem), topM);
903
+ if (P.char) {
904
+ const emb = [[0.1, 1.2, 0.17], [-0.12, 1.35, 0.17], [0.02, 1.02, 0.16], [-0.15, 1.08, 0.15], [0.16, 1.42, 0.15], [0, 1.5, -0.15], [0.12, 1.1, -0.16]];
905
+ emb.forEach(([x, y, zz]) => {
906
+ const c = add(U, SPH(0.018, 8, 6), basic(16738842), x, y, zz);
907
+ c.userData.eye = 1;
908
+ c.scale.set(2.2, 0.6, 0.5);
909
+ const g = glow(16734736, 0.12, 0.7);
910
+ g.position.set(x, y, zz * 1.1);
911
+ U.add(g);
912
+ });
913
+ emb.forEach(([x, y, zz], k) => {
914
+ if (k % 2) return;
915
+ const o = new THREE.Object3D();
916
+ o.position.set(x, y, zz * 1.1);
917
+ o.userData.ember = { smoke: k === 0 || k === 4 };
918
+ U.add(o);
919
+ });
920
+ }
921
+ if (P.gore) {
922
+ splat(U, 0, 1.4, frontZ + 0.01, 0.07, 9046538, 5);
923
+ splat(U, -0.08, 1.18, frontZ + 0.01, 0.05, 9046538, 3);
924
+ }
925
+ if (V.bag && !P.bloat) {
926
+ const bm = M(V.bagC), sm = M(col(V.bagC, 1118481, 0.5));
927
+ const b = ball(U, 0.2, bm, 0, 1.22, -0.25);
928
+ b.scale.set(1.05, 1.25, 0.62);
929
+ const f = ball(U, 0.12, bm, 0, 1.12, -0.36);
930
+ f.scale.set(1.2, 0.8, 0.5);
931
+ [-1, 1].forEach((sx) => {
932
+ const st = add(U, TOR(0.2, 0.018, 6, 18, PI * 1.1), sm, sx * 0.14, 1.3, 0);
933
+ st.rotation.set(0, PI / 2, 0);
934
+ });
935
+ }
936
+ if (spec.type === "Brutamonte") {
937
+ [-1, 1].forEach((sx) => {
938
+ const t = ball(U, 0.17, topM, sx * 0.2, 1.53, -0.03);
939
+ t.scale.set(1.2, 0.8, 1);
940
+ });
941
+ const hb = ball(U, 0.2, topM, 0, 1.45, -0.12);
942
+ hb.scale.set(1.5, 0.9, 0.8);
943
+ }
944
+ if (O.vest) {
945
+ const v = add(U, CYL(0.275, 0.24, 0.48, 24), M(O.vest), 0, 1.3, 0);
946
+ v.scale.set(1.12, 1, 0.7);
947
+ for (let i = 0; i < 3; i++) add(U, geoBox(0.11, 0.13, 0.05), M(O.vest), -0.15 + i * 0.15, 1.17, 0.17);
948
+ }
949
+ if (O.stripes) {
950
+ ring(U, 0.205, 0.05, M(O.stripes), 1.03, 1.13, 0.68);
951
+ ring(U, 0.268, 0.05, M(O.stripes), 1.42, 1.13, 0.67);
952
+ }
953
+ if (d === 3 && !P.bloat) [0, 1, 2].forEach((k) => add(U, geoBox(0.2, 0.022, 0.02), M(14208952), 0, 1.3 + k * 0.065, 0.168));
954
+ if (!spec.human) {
955
+ const bk = new THREE.Group();
956
+ bk.rotation.y = PI;
957
+ U.add(bk);
958
+ splat(bk, -0.05, 1.3, frontZ + 0.01, 0.07, col(BLOOD[Math.min(d, 2)], 1705990, 0.2), 2);
959
+ }
960
+ if (!spec.human) {
961
+ const wy = P.bloat ? 1.25 : 1.36, hy = P.bloat ? 1.02 : 0.98;
962
+ run(U, 0.08, wy - 0.03, hy, frontZ + 0.012, frontZ - 0.01, col(BLOOD[d], 1705990, 0.35), 0.022);
963
+ drip(U, 0.083, hy - 0.012, frontZ - 5e-3);
964
+ const bs = 0.07 + 0.05 * (3 - d);
965
+ splat(U, 0.07, P.bloat ? 1.25 : 1.36, frontZ + (O.vest ? 0.02 : 0), bs * 0.42, col(BLOOD[d], 1705990, 0.35), d < 2 ? 3 : 1);
966
+ }
967
+ for (let k = 0; k < (spec.wounds || 0); k++) splat(U, -0.13 + k * 0.13, 1.15 + 0.13 * (k % 2), frontZ + 0.012, 0.032, 2753795, 2);
968
+ if (cut.gut) {
969
+ const gm = M(11553360);
970
+ for (let k = 0; k < 6; k++) {
971
+ const q = add(U, TOR(0.05, 0.022, 6, 10), gm, k % 2 ? 0.04 : -0.03, 1 - k * 0.06, frontZ + 0.02, 0, k * 0.7, 0);
972
+ q.userData.flap = true;
973
+ }
974
+ const hole = ball(U, 0.1, red, 0, 1.05, frontZ - 0.03);
975
+ hole.scale.set(1.2, 1, 0.3);
976
+ drip(U, 0, 0.75, frontZ + 0.02, null, 0.5);
977
+ }
978
+ if (cut.flaps) {
979
+ [[0.12, 1.2], [-0.1, 1.35], [0.02, 0.98]].forEach(([x, y], k) => {
980
+ const f = add(U, geoBox(0.07, 0.12, 0.012), k % 2 ? skinM : M(10103338), x, y, frontZ + 0.03);
981
+ f.geometry.translate(0, -0.06, 0);
982
+ f.userData.flap = true;
983
+ drip(U, x, y - 0.12, frontZ + 0.03, null, 1.5);
984
+ });
985
+ }
986
+ const neckLen = 0.14 + Math.max(0, P.neck);
987
+ if (!cut.head) add(U, CYL(0.058, 0.068, neckLen, SEG), skinM, 0, 1.63 + Math.max(0, P.neck) / 2, 0);
988
+ const headG = new THREE.Group();
989
+ headG.position.set(0, 1.64 + P.neck, P.headFwd || 0);
990
+ headG.rotation.x = P.headX;
991
+ headG.rotation.z = P.headZ;
992
+ U.add(headG);
993
+ const Hd = new THREE.Group();
994
+ Hd.position.y = -1.64;
995
+ headG.add(Hd);
996
+ if (cut.head) {
997
+ const jaw = add(Hd, SPH(0.12, 16, 12), skinM, 0, 1.69, 0.03);
998
+ jaw.scale.set(1, 0.6, 1);
999
+ drip(U, 0, 1.6, 0.1, null, 0.6);
1000
+ const hst = ball(Hd, 0.11, red, 0, 1.74, 0.02);
1001
+ hst.scale.set(1, 0.35, 1);
1002
+ add(Hd, CYL(0.03, 0.035, 0.08, 10), M(15261900), 0, 1.78, 0);
1003
+ splat(U, 0, 1.5, 0.2, 0.05, BLOOD[0], 3);
1004
+ } else {
1005
+ if (cut.blown) {
1006
+ head0: {
1007
+ const hb = add(Hd, SPH(0.165, 20, 16, 0, PI * 2, PI * 0.55, PI * 0.45), skinM, 0, 1.8, 0);
1008
+ hb.scale.set(0.96, 1.1, 1.02);
1009
+ const cv = ball(Hd, 0.15, red, 0, 1.82, 0);
1010
+ cv.scale.set(0.95, 0.3, 0.95);
1011
+ for (let k = 0; k < 5; k++) {
1012
+ const f = ball(Hd, 0.03, k % 2 ? M(15261900) : red, Math.cos(k * 1.3) * 0.13, 1.86, Math.sin(k * 1.3) * 0.13);
1013
+ f.scale.set(1.4, 0.6, 0.9);
1014
+ }
1015
+ ;
1016
+ drip(Hd, 0, 1.7, 0.1, null, 0.4);
1017
+ }
1018
+ }
1019
+ const head = add(Hd, SPH(0.165, 20, 16), skinM, 0, 1.8, 0);
1020
+ if (cut.blown) head.visible = false;
1021
+ head.scale.set(0.96 * V.hx * (P.bloat ? 1.12 : 1), 1.1 * V.hy, 1.02 * (P.bloat ? 1.08 : 1));
1022
+ const jaw = add(Hd, SPH(0.12, 16, 12), skinM, 0, 1.715, 0.03);
1023
+ jaw.userData.bone = 11;
1024
+ jaw.scale.set(V.jaw * (P.bloat ? 1.3 : 1), 0.72 * (P.bloat ? 1.25 : 1), 1);
1025
+ if (cut.jaw) {
1026
+ jaw.geometry.translate(0, 0, 0.06);
1027
+ jaw.position.set(0.03, 1.7, -0.04);
1028
+ jaw.rotation.set(0.75, 0, 0.35);
1029
+ add(Hd, SPH(0.07, 10, 8), red, 0, 1.72, 0.11).scale.set(1.3, 0.5, 0.4);
1030
+ drip(Hd, 0.02, 1.6, 0.12, null, 0.6);
1031
+ jaw.userData.flap = true;
1032
+ }
1033
+ [-1, 1].forEach((s) => {
1034
+ const e = ball(Hd, 0.038, skinM, s * 0.158, 1.79, -5e-3);
1035
+ e.scale.set(0.6, 1, 0.9);
1036
+ });
1037
+ const nose = add(Hd, geoBox(0.045, 0.065, 0.05), skinM, 0, 1.765, 0.172);
1038
+ nose.scale.set(V.nose, V.nose, 1);
1039
+ if (P.nose) nose.scale.set(1.5, 1.4, 2.2);
1040
+ let covered = false;
1041
+ if (O.hat) {
1042
+ const c = M(O.hatC);
1043
+ covered = true;
1044
+ if (O.hat === "cap") {
1045
+ const o = add(Hd, capGeo(0.182), c, 0, 1.84, 0);
1046
+ o.scale.set(1, 0.78, 1.02);
1047
+ add(Hd, geoBox(0.28, 0.022, 0.17), c, 0, 1.85, 0.2, 0.12, 0, 0);
1048
+ }
1049
+ if (O.hat === "helmet") {
1050
+ const o = add(Hd, capGeo(0.205, PI * 0.55), c, 0, 1.82, -0.01);
1051
+ o.scale.set(1, 1, 1.08);
1052
+ }
1053
+ if (O.hat === "hardhat") {
1054
+ const o = add(Hd, capGeo(0.198), c, 0, 1.86, 0);
1055
+ o.scale.set(1, 1.02, 1.08);
1056
+ add(Hd, CYL(0.25, 0.25, 0.02, 24), c, 0, 1.865, 0.02);
1057
+ }
1058
+ if (O.hat === "fire") {
1059
+ const o = add(Hd, capGeo(0.205), c, 0, 1.86, 0);
1060
+ o.scale.set(1, 1.02, 1.1);
1061
+ const br = add(Hd, CYL(0.27, 0.27, 0.02, 24), c, 0, 1.86, -0.06);
1062
+ br.scale.set(1, 1, 1.25);
1063
+ add(Hd, geoBox(0.09, 0.08, 0.02), M(14200896), 0, 1.96, 0.205);
1064
+ }
1065
+ if (O.hat === "moto") {
1066
+ const o = add(Hd, SPH(0.235, 20, 16), c, 0, 1.81, -0.01);
1067
+ o.scale.set(0.98, 1.04, 1.06);
1068
+ add(Hd, SPH(0.226, 20, 6, PI * 0.18, PI * 0.64, PI * 0.36, PI * 0.2), new THREE.MeshLambertMaterial({ color: 2240572 }), 0, 1.81, 0.02);
1069
+ }
1070
+ }
1071
+ if (O.mask) add(Hd, geoBox(0.24, 0.12, 0.04), M(15265520), 0, 1.72, 0.15);
1072
+ if (!covered && V.cap) {
1073
+ const c = M(V.capC);
1074
+ const o = add(Hd, capGeo(0.182), c, 0, 1.84, 0);
1075
+ o.scale.set(1, 0.78, 1.02);
1076
+ add(Hd, geoBox(0.28, 0.022, 0.17), c, 0, 1.85, 0.2, 0.12, 0, 0);
1077
+ covered = true;
1078
+ }
1079
+ const hc = M(hairC);
1080
+ if (!cut.blown && !(d === 3 && VR() < 0.6)) {
1081
+ const hairCap = (r, len, tilt) => {
1082
+ const o = add(Hd, capGeo(r + 8e-3, len), hc, 0, 1.8, -4e-3);
1083
+ o.scale.set(0.97 * V.hx, 1.15 * V.hy, 1.04);
1084
+ o.rotation.x = tilt;
1085
+ return o;
1086
+ };
1087
+ const backBand = (t0, tl) => {
1088
+ const o = add(Hd, SPH(0.172, 20, 8, PI * 0.85, PI * 1.3, t0, tl), hc, 0, 1.8, 0);
1089
+ o.scale.set(0.97 * V.hx, 1.1 * V.hy, 1.04);
1090
+ return o;
1091
+ };
1092
+ const hs = V.hair;
1093
+ if (!covered && (hs === "short" || hs === "bun" || hs === "long")) hairCap(0.174, PI / 2, -0.38);
1094
+ if (hs === "short" || hs === "bun" || hs === "long") backBand(PI * 0.45, PI * 0.22);
1095
+ if (!covered && hs === "bun") ball(Hd, 0.085, hc, 0, 1.92, -0.15);
1096
+ if (hs === "long") {
1097
+ const l = add(Hd, CYL(0.15, 0.12, 0.42, 16, 1, false, PI * 0.6, PI * 0.8), hc, 0, 1.66, -0.02);
1098
+ l.scale.set(1.05, 1, 1.1);
1099
+ }
1100
+ if (!covered && hs === "buzz") hairCap(0.17, PI * 0.45, -0.32);
1101
+ if (hs === "bald") backBand(PI * 0.42, PI * 0.2);
1102
+ if (!covered && hs === "afro") {
1103
+ const a = add(Hd, SPH(0.23, 16, 12, 0, PI * 2, 0, PI * 0.62), hc, 0, 1.93, -0.08);
1104
+ a.scale.set(1.08, 0.85, 0.95);
1105
+ }
1106
+ }
1107
+ if (V.beard) {
1108
+ const b = add(Hd, SPH(0.128, 16, 12), hc, 0, 1.7, 0.035);
1109
+ b.scale.set(1.02 * V.jaw, 0.78, 1);
1110
+ }
1111
+ if (V.glasses) {
1112
+ add(Hd, geoBox(0.27, 0.012, 0.015), M(1710618), 0, 1.84, 0.175);
1113
+ [-1, 1].forEach((s) => {
1114
+ add(Hd, geoBox(0.09, 0.055, 0.012), new THREE.MeshLambertMaterial({ color: 9416896, transparent: true, opacity: 0.3 }), s * 0.06, 1.817, 0.18);
1115
+ add(Hd, geoBox(0.012, 0.012, 0.17), M(1710618), s * 0.15, 1.83, 0.09);
1116
+ });
1117
+ }
1118
+ if (P.mouth) {
1119
+ const m = add(Hd, geoBox(0.1, 0.09, 0.03), basic(328965), 0, 1.69, 0.14);
1120
+ }
1121
+ if (P.gore) splat(Hd, 0, 1.7, 0.15, 0.05, 9046538, 3);
1122
+ else if (!spec.human) {
1123
+ add(Hd, geoBox(0.125, 0.055, 0.014), M(10115666), 0, 1.69, 0.143);
1124
+ add(Hd, geoBox(0.085, 0.03, 0.014), M(1311491), 0, 1.69, 0.151);
1125
+ }
1126
+ const moto = O.hat === "moto", eyeZ = moto ? 0.245 : 0.162, dim = spec.eyeDim ?? (P.dim || moto), eye = spec.eye ?? z.eye;
1127
+ [-1, 1].forEach((s) => {
1128
+ if (cut.blown) return;
1129
+ if (P.blind && !spec.human) {
1130
+ add(Hd, SPH(0.03, 10, 8), M(15262936), s * 0.058, 1.815, 0.152);
1131
+ return;
1132
+ }
1133
+ if (spec.human) {
1134
+ add(Hd, geoBox(0.05, 0.03, 0.012), M(15262940), s * 0.058, 1.815, 0.156);
1135
+ add(Hd, geoBox(0.022, 0.024, 0.012), M(1709074), s * 0.058, 1.814, 0.163);
1136
+ add(Hd, geoBox(0.06, 0.012, 0.02), M(hairC), s * 0.058, 1.85, 0.158, 0, 0, -s * 0.12);
1137
+ return;
1138
+ }
1139
+ if (spec.eyesOff) {
1140
+ add(Hd, geoBox(0.05, 0.028, 0.02), basic(2763298), s * 0.058, 1.815, 0.158);
1141
+ return;
1142
+ }
1143
+ add(Hd, geoBox(0.052, 0.03, 0.02), basic(eye), s * 0.058, 1.815, eyeZ).userData.eye = 1;
1144
+ const g = glow(eye, dim ? 0.1 : 0.15, dim ? 0.3 : 0.85);
1145
+ g.position.set(s * 0.058, 1.815, eyeZ + 0.025);
1146
+ Hd.add(g);
1147
+ });
1148
+ }
1149
+ const arms = [];
1150
+ [[1, P.armR, P.elbR, cut.armR], [-1, P.armL, P.elbL, cut.armL]].forEach(([s, r, er, isCut]) => {
1151
+ const g = new THREE.Group();
1152
+ g.position.set(s * (P.bloat ? 0.37 : fem ? 0.285 : 0.305), 1.475, 0);
1153
+ U.add(g);
1154
+ const upM = V.sleeve === "tank" ? skinM : topM, foreM = V.sleeve === "long" ? topM : skinM, ab = P.bloat ? 1.35 : 1;
1155
+ ball(g, 0.088 * ab, upM);
1156
+ add(g, cylDown(0.083 * ab, 0.075 * ab, 0.31), upM);
1157
+ const el = new THREE.Group();
1158
+ el.position.y = -0.31;
1159
+ g.add(el);
1160
+ if (isCut) {
1161
+ stump(el, 0.07, 0);
1162
+ drip(el, 0, -0.05, 0, null, 0.9);
1163
+ } else {
1164
+ ball(el, 0.06 * ab, foreM);
1165
+ add(el, cylDown(0.06 * ab, 0.046 * ab, 0.29), foreM);
1166
+ const h = ball(el, 0.055 * ab, skinM, 0, -0.33, 5e-3);
1167
+ h.scale.set(0.85, 1.15, 0.7);
1168
+ if (spec.bandage && s < 0) add(el, CYL(0.068, 0.064, 0.09, SEG), M(15262936), 0, -0.17, 0);
1169
+ }
1170
+ const dang = s > 0 ? cut.dangleR : cut.dangleL;
1171
+ if (dang && !isCut) {
1172
+ const fl = add(el, CYL(0.065, 0.05, 0.06, 10), red, 0, 0.01, 0);
1173
+ drip(el, 0, -0.33, 0, null, 0.8);
1174
+ el.userData.dangle = true;
1175
+ }
1176
+ if (s > 0 ? cut.shoulderR : cut.shoulderL) {
1177
+ g.children.slice().forEach((c) => {
1178
+ if (c !== el) g.remove(c);
1179
+ });
1180
+ el.visible = false;
1181
+ stump(g, 0.085, 0);
1182
+ drip(g, 0, -0.05, 0, null, 0.7);
1183
+ }
1184
+ g.rotation.x = r;
1185
+ g.rotation.z = s * (0.07 + (P.armOut || 0));
1186
+ el.rotation.x = isCut ? 0 : er;
1187
+ arms.push({ g, el, s, base: r, eBase: el.rotation.x });
1188
+ });
1189
+ const flaps = [];
1190
+ root.traverse((o) => {
1191
+ if (o.userData.flap) flaps.push(o);
1192
+ });
1193
+ const rig = { flaps, dangles: arms.filter((a) => a.el.userData.dangle), type: spec.type || "Lento", fig, upper, headG, arms, legs, P, ph: spec.phase ?? options.random() * 6, dropBase: fig.position.y, leanBase: P.lean, hxBase: P.headX };
1194
+ if (spec.lying) {
1195
+ root.rotation.x = -PI / 2;
1196
+ root.position.set(0, 0.16 * P.s, 0.9 * P.s);
1197
+ const w = new THREE.Group();
1198
+ w.add(root);
1199
+ return w;
1200
+ }
1201
+ root.userData.rig = rig;
1202
+ return root;
1203
+ }
1204
+ return { build, TYPES, setLOD(k) {
1205
+ LK = k;
1206
+ SEG = Math.max(5, Math.round(16 * k));
1207
+ }, topM: () => topM_ref };
1208
+ };
1209
+
1210
+ // src/rich/person-builder.ts
1211
+ import { MOVES, createRng as createRng2 } from "@adatechnology/game-characters";
1212
+
1213
+ // src/rich/accessories/face-features.ts
1214
+ var FACE_FEATURES = [
1215
+ {
1216
+ ids: ["glasses", "glasses_y"],
1217
+ apply: ({ Bx, Hd, THREE, add, cmat, f }) => {
1218
+ const lens = new THREE.MeshStandardMaterial({ color: f.includes("glasses_y") ? 14200880 : 9416896, roughness: 0.15, metalness: 0.3, transparent: true, opacity: 0.35 });
1219
+ add(Hd, Bx(0.27, 0.012, 0.015), cmat(1710618), 0, 1.84, 0.175);
1220
+ for (const s of [-1, 1]) {
1221
+ add(Hd, Bx(0.09, 0.055, 0.012), lens, s * 0.06, 1.817, 0.178);
1222
+ add(Hd, Bx(0.012, 0.012, 0.17), cmat(1710618), s * 0.15, 1.83, 0.09);
1223
+ }
1224
+ ;
1225
+ }
1226
+ },
1227
+ {
1228
+ ids: ["mustache"],
1229
+ apply: ({ Bx, Hd, add, hc }) => {
1230
+ add(Hd, Bx(0.13, 0.035, 0.03), hc, 0, 1.728, 0.165);
1231
+ }
1232
+ },
1233
+ {
1234
+ ids: ["mustache_w"],
1235
+ apply: ({ Bx, Hd, add, cmat }) => {
1236
+ add(Hd, Bx(0.17, 0.045, 0.03), cmat(15131868), 0, 1.726, 0.166);
1237
+ }
1238
+ },
1239
+ {
1240
+ ids: ["beard_g"],
1241
+ apply: ({ Hd, THREE, add, cmat }) => {
1242
+ const b = add(Hd, new THREE.SphereGeometry(0.128, 16, 12), cmat(9079430), 0, 1.7, 0.035);
1243
+ b.scale.set(1.02, 0.78, 1);
1244
+ }
1245
+ },
1246
+ {
1247
+ ids: ["stubble"],
1248
+ apply: ({ Hd, THREE, add, cmat, scaleHex, sp }) => {
1249
+ const b = add(Hd, new THREE.SphereGeometry(0.123, 16, 12), cmat(scaleHex(sp.skin, 0.62)), 0, 1.705, 0.032);
1250
+ b.scale.set(1, 0.74, 1);
1251
+ }
1252
+ },
1253
+ {
1254
+ ids: ["mask"],
1255
+ apply: ({ Bx, Hd, add, cmat }) => {
1256
+ add(Hd, Bx(0.24, 0.12, 0.04), cmat(11456734), 0, 1.72, 0.15);
1257
+ }
1258
+ },
1259
+ {
1260
+ ids: ["goggles"],
1261
+ apply: ({ Hd, PI, THREE, add, cmat }) => {
1262
+ const b = add(Hd, new THREE.CylinderGeometry(0.168, 0.168, 0.035, 20, 1, true), cmat(1710618), 0, 1.9, 0);
1263
+ b.scale.set(0.97, 1, 1.04);
1264
+ for (const s of [-1, 1]) add(Hd, new THREE.CylinderGeometry(0.04, 0.04, 0.04, 16).rotateX(PI / 2), cmat(4876890, { roughness: 0.2, metalness: 0.5 }), s * 0.06, 1.91, 0.165);
1265
+ }
1266
+ },
1267
+ {
1268
+ ids: ["earrings"],
1269
+ apply: ({ Hd, PI, THREE, add, cmat }) => {
1270
+ for (const s of [-1, 1]) add(Hd, new THREE.TorusGeometry(0.028, 7e-3, 4, 8), cmat(14200896, { metalness: 0.8, roughness: 0.3 }), s * 0.162, 1.74, 0, 0, PI / 2, 0);
1271
+ }
1272
+ },
1273
+ {
1274
+ ids: ["cig"],
1275
+ apply: ({ Bx, Hd, THREE, add, cmat }) => {
1276
+ add(Hd, Bx(0.014, 0.014, 0.09), cmat(15658734), 0.05, 1.7, 0.19);
1277
+ const e = add(Hd, Bx(0.017, 0.017, 0.017), new THREE.MeshBasicMaterial({ color: 16738858 }), 0.05, 1.7, 0.237);
1278
+ e.castShadow = false;
1279
+ }
1280
+ },
1281
+ {
1282
+ ids: ["mirror"],
1283
+ apply: ({ Bx, Hd, add, cmat }) => {
1284
+ add(Hd, Bx(0.27, 0.012, 0.015), cmat(1710618), 0, 1.84, 0.175);
1285
+ for (const s of [-1, 1]) {
1286
+ add(Hd, Bx(0.095, 0.06, 0.014), cmat(13159632, { metalness: 0.95, roughness: 0.12 }), s * 0.06, 1.817, 0.179);
1287
+ add(Hd, Bx(0.012, 0.012, 0.17), cmat(1710618), s * 0.15, 1.83, 0.09);
1288
+ }
1289
+ ;
1290
+ }
1291
+ },
1292
+ {
1293
+ ids: ["labgoggles"],
1294
+ apply: ({ Hd, PI, THREE, add, cmat }) => {
1295
+ const b = add(Hd, new THREE.CylinderGeometry(0.168, 0.168, 0.03, 10, 1, true), cmat(2779786), 0, 1.92, 0);
1296
+ b.scale.set(0.97, 1, 1.04);
1297
+ for (const s of [-1, 1]) add(Hd, new THREE.CylinderGeometry(0.045, 0.045, 0.04, 8).rotateX(PI / 2), cmat(10470616, { roughness: 0.1, transparent: true, opacity: 0.7 }), s * 0.06, 1.93, 0.165);
1298
+ }
1299
+ },
1300
+ {
1301
+ ids: ["beard"],
1302
+ apply: ({ Hd, THREE, add, hc }) => {
1303
+ const b = add(Hd, new THREE.SphereGeometry(0.128, 8, 6), hc, 0, 1.7, 0.035);
1304
+ b.scale.set(1.02, 0.8, 1);
1305
+ }
1306
+ }
1307
+ ];
1308
+
1309
+ // src/rich/accessories/hands.ts
1310
+ var buildHand = ({ THREE, add, hg, handM, s, detailed }) => {
1311
+ const fingers = [];
1312
+ if (!detailed) return { fingers, th: null };
1313
+ for (let f = 0; f < 4; f++) {
1314
+ const fg = new THREE.Group();
1315
+ fg.position.set(0, -0.078, 0.027 - f * 0.018);
1316
+ hg.add(fg);
1317
+ const len = [0.034, 0.038, 0.036, 0.03][f];
1318
+ add(fg, new THREE.CylinderGeometry(0.0105, 0.0105, len, 8).translate(0, -len / 2, 0), handM, 0, 0, 0);
1319
+ const f2 = new THREE.Group();
1320
+ f2.position.y = -len;
1321
+ fg.add(f2);
1322
+ add(f2, new THREE.CylinderGeometry(0.0105, 9e-3, len * 0.85, 8).translate(0, -len * 0.42, 0), handM, 0, 0, 0);
1323
+ add(f2, new THREE.SphereGeometry(95e-4, 8, 6), handM, 0, -len * 0.85, 0);
1324
+ fingers.push(fg, f2);
1325
+ }
1326
+ const th = new THREE.Group();
1327
+ th.position.set(-s * 0.018, -0.03, 0.034);
1328
+ th.rotation.set(-0.5, 0, -s * 0.45);
1329
+ hg.add(th);
1330
+ add(th, new THREE.CylinderGeometry(0.012, 0.01, 0.045, 8).translate(0, -0.022, 0), handM, 0, 0, 0);
1331
+ add(th, new THREE.SphereGeometry(0.01, 8, 6), handM, 0, -0.045, 0);
1332
+ return { fingers, th };
1333
+ };
1334
+
1335
+ // src/rich/accessories/hand-items.ts
1336
+ var HAND_ITEMS = {
1337
+ axe: ({ Bx, add, cmat, g, metal, woodM }) => {
1338
+ add(g, Bx(0.05, 0.78, 0.05), woodM, 0, -0.18, 0);
1339
+ add(g, Bx(0.04, 0.14, 0.26), cmat(10103328), 0, -0.55, 0.09);
1340
+ add(g, Bx(0.045, 0.14, 0.05), metal, 0, -0.55, 0.23);
1341
+ return;
1342
+ },
1343
+ machete: ({ Bx, add, cmat, g, metal }) => {
1344
+ add(g, Bx(0.05, 0.14, 0.06), cmat(1118481), 0, -0.02, 0);
1345
+ add(g, Bx(0.015, 0.5, 0.08), metal, 0, -0.34, 0.015);
1346
+ return;
1347
+ },
1348
+ wrench: ({ Bx, add, g, metal }) => {
1349
+ add(g, Bx(0.05, 0.5, 0.07), metal, 0, -0.18, 0);
1350
+ add(g, Bx(0.05, 0.08, 0.18), metal, 0, -0.45, 0.04);
1351
+ return;
1352
+ },
1353
+ hammer: ({ Bx, add, dark, g, woodM }) => {
1354
+ add(g, Bx(0.04, 0.34, 0.04), woodM, 0, -0.13, 0);
1355
+ add(g, Bx(0.055, 0.07, 0.18), dark, 0, -0.3, 0.02);
1356
+ return;
1357
+ },
1358
+ hoe: ({ Bx, add, g, metal, woodM }) => {
1359
+ add(g, Bx(0.04, 1.42, 0.04), woodM, 0, -0.06, 0);
1360
+ add(g, Bx(0.24, 0.04, 0.14), metal, 0, -0.74, 0.07, 0.35, 0, 0);
1361
+ return;
1362
+ },
1363
+ baton: ({ THREE, add, cmat, g }) => {
1364
+ add(g, new THREE.CylinderGeometry(0.022, 0.022, 0.55, 16), cmat(1118481), 0, -0.2, 0);
1365
+ return;
1366
+ },
1367
+ knife: ({ Bx, add, cmat, g, metal }) => {
1368
+ add(g, Bx(0.035, 0.1, 0.04), cmat(1118481), 0, -0.02, 0);
1369
+ add(g, Bx(0.01, 0.24, 0.05), metal, 0, -0.18, 5e-3);
1370
+ return;
1371
+ },
1372
+ pistol: ({ Bx, add, dark, g }) => {
1373
+ add(g, Bx(0.04, 0.11, 0.06), dark, 0, -0.04, 0);
1374
+ add(g, Bx(0.04, 0.06, 0.2), dark, 0, 0.02, 0.07);
1375
+ return;
1376
+ },
1377
+ pliers: ({ Bx, add, cmat, g, metal }) => {
1378
+ add(g, Bx(0.03, 0.2, 0.03), cmat(12595754), 0.015, -0.08, 0);
1379
+ add(g, Bx(0.03, 0.2, 0.03), cmat(12595754), -0.015, -0.08, 0);
1380
+ add(g, Bx(0.04, 0.08, 0.03), metal, 0, -0.21, 0);
1381
+ return;
1382
+ },
1383
+ medbag: ({ Bx, add, cmat, g }) => {
1384
+ add(g, Bx(0.36, 0.24, 0.18), cmat(15000282), 0, -0.16, 0);
1385
+ add(g, Bx(0.03, 0.09, 0.01), cmat(12071466), 0, -0.16, 0.095);
1386
+ add(g, Bx(0.09, 0.03, 0.01), cmat(12071466), 0, -0.16, 0.095);
1387
+ add(g, Bx(0.18, 0.04, 0.03), cmat(2763306), 0, -0.02, 0);
1388
+ return;
1389
+ },
1390
+ toolbox: ({ Bx, add, cmat, dark, g }) => {
1391
+ add(g, Bx(0.42, 0.2, 0.2), cmat(11745834, { metalness: 0.3, roughness: 0.5 }), 0, -0.14, 0);
1392
+ add(g, Bx(0.24, 0.04, 0.04), dark, 0, -0.01, 0);
1393
+ return;
1394
+ },
1395
+ book: ({ Bx, add, cmat, g }) => {
1396
+ add(g, Bx(0.05, 0.26, 0.2), cmat(2771562), 0, -0.12, 0.02);
1397
+ add(g, Bx(0.052, 0.24, 0.01), cmat(14736587), 0, -0.12, 0.12);
1398
+ return;
1399
+ },
1400
+ bottle: ({ THREE, add, cmat, g }) => {
1401
+ add(g, new THREE.CylinderGeometry(0.045, 0.045, 0.2, 16), new THREE.MeshStandardMaterial({ color: 3828282, roughness: 0.15, metalness: 0.2, transparent: true, opacity: 0.85, flatShading: false }), 0, -0.1, 0.02);
1402
+ add(g, new THREE.CylinderGeometry(0.018, 0.03, 0.08, 16), cmat(3828282), 0, 0.03, 0.02);
1403
+ return;
1404
+ },
1405
+ clipboard: ({ Bx, add, cmat, g }) => {
1406
+ add(g, Bx(0.03, 0.32, 0.24), cmat(6965802), 0, -0.12, 0.04);
1407
+ add(g, Bx(0.035, 0.26, 0.2), cmat(15263452), 0, -0.13, 0.04);
1408
+ return;
1409
+ },
1410
+ shovel: ({ Bx, THREE, add, cmat, g, metal, woodM }) => {
1411
+ add(g, new THREE.CylinderGeometry(0.022, 0.022, 1.3, 6), woodM, 0, -0.3, 0);
1412
+ add(g, Bx(0.05, 0.18, 0.05), cmat(12855338), 0, 0.22, 0);
1413
+ add(g, Bx(0.22, 0.28, 0.03), metal, 0, -1, 0.03);
1414
+ return;
1415
+ },
1416
+ slingshot: ({ Bx, add, cmat, g, woodM }) => {
1417
+ add(g, Bx(0.03, 0.16, 0.03), woodM, 0, -0.02, 0);
1418
+ add(g, Bx(0.03, 0.12, 0.03), woodM, -0.04, -0.14, 0, 0, 0, -0.4);
1419
+ add(g, Bx(0.03, 0.12, 0.03), woodM, 0.04, -0.14, 0, 0, 0, 0.4);
1420
+ add(g, Bx(0.1, 0.015, 0.015), cmat(12855338), 0, -0.2, 0);
1421
+ return;
1422
+ },
1423
+ bible: ({ Bx, add, cmat, g }) => {
1424
+ add(g, Bx(0.06, 0.24, 0.18), cmat(3810326), 0, -0.12, 0.03);
1425
+ add(g, Bx(0.062, 0.02, 0.182), cmat(13673018), 0, -0.02, 0.03);
1426
+ return;
1427
+ },
1428
+ flare: ({ THREE, add, cmat, g }) => {
1429
+ {
1430
+ add(g, new THREE.CylinderGeometry(0.03, 0.03, 0.26, 6), cmat(15230506), 0, -0.06, 0.02);
1431
+ add(g, new THREE.CylinderGeometry(0.035, 0.02, 0.06, 6), new THREE.MeshBasicMaterial({ color: 16756848 }), 0, 0.1, 0.02);
1432
+ const L = new THREE.PointLight(16738858, 1.4, 4, 2);
1433
+ L.position.set(0, 0.15, 0.05);
1434
+ g.add(L);
1435
+ g.rotation.x = -0.5;
1436
+ return;
1437
+ }
1438
+ },
1439
+ radio_h: ({ Bx, THREE, add, cmat, g }) => {
1440
+ add(g, Bx(0.07, 0.2, 0.11), cmat(3816768), 0, -0.08, 0.03);
1441
+ add(g, new THREE.CylinderGeometry(8e-3, 8e-3, 0.34, 4), cmat(10132122), 0, 0.15, 0.05);
1442
+ add(g, Bx(0.075, 0.05, 0.115), cmat(14204992), 0, -0.12, 0.03);
1443
+ return;
1444
+ },
1445
+ pump: ({ Bx, add, cmat, dark, g }) => {
1446
+ add(g, Bx(0.05, 0.82, 0.05), dark, 0, -0.22, 0.02);
1447
+ add(g, Bx(0.07, 0.15, 0.09), cmat(2763306), 0, -0.05, 0.02);
1448
+ add(g, Bx(0.065, 0.14, 0.07), cmat(3811868), 0, -0.5, 0.02);
1449
+ add(g, Bx(0.03, 0.7, 0.03), dark, 0, -0.2, 0.07);
1450
+ return;
1451
+ },
1452
+ mic: ({ THREE, add, cmat, g }) => {
1453
+ add(g, new THREE.CylinderGeometry(0.022, 0.028, 0.18, 16), cmat(1710618), 0, 0, 0);
1454
+ add(g, new THREE.DodecahedronGeometry(0.045, 0), cmat(6975090, { metalness: 0.6 }), 0, 0.1, 0);
1455
+ g.rotation.x = -0.4;
1456
+ return;
1457
+ }
1458
+ };
1459
+
1460
+ // src/rich/accessories/hats.ts
1461
+ var HATS = [
1462
+ {
1463
+ kinds: ["cap", "capback"],
1464
+ apply: ({ Bx, Hd, PI, add, c, cap, k }) => {
1465
+ const o = add(Hd, cap(0.182, PI / 2), c, 0, 1.84, 0);
1466
+ o.scale.set(1, 0.78, 1.02);
1467
+ add(Hd, Bx(0.28, 0.022, 0.17), c, 0, 1.85, k === "cap" ? 0.2 : -0.2, k === "cap" ? 0.12 : -0.12, 0, 0);
1468
+ }
1469
+ },
1470
+ {
1471
+ kinds: ["beret"],
1472
+ apply: ({ Hd, THREE, add, c }) => {
1473
+ const o = add(Hd, new THREE.CylinderGeometry(0.2, 0.185, 0.07, 20), c, 0.025, 1.95, 0, 0, 0, 0.16);
1474
+ o.scale.set(1, 1, 1.05);
1475
+ }
1476
+ },
1477
+ {
1478
+ kinds: ["hardhat"],
1479
+ apply: ({ Bx, Hd, PI, THREE, add, c, cap }) => {
1480
+ const o = add(Hd, cap(0.198, PI / 2), c, 0, 1.86, 0);
1481
+ o.scale.set(1, 1.02, 1.08);
1482
+ add(Hd, new THREE.CylinderGeometry(0.25, 0.25, 0.02, 20), c, 0, 1.865, 0.02);
1483
+ add(Hd, Bx(0.03, 0.06, 0.38), c, 0, 2.04, 0);
1484
+ }
1485
+ },
1486
+ {
1487
+ kinds: ["fire"],
1488
+ apply: ({ Bx, Hd, PI, THREE, add, c, cap, cmat }) => {
1489
+ const o = add(Hd, cap(0.205, PI / 2), c, 0, 1.86, 0);
1490
+ o.scale.set(1, 1.02, 1.1);
1491
+ const br = add(Hd, new THREE.CylinderGeometry(0.27, 0.27, 0.02, 20), c, 0, 1.86, -0.06);
1492
+ br.scale.set(1, 1, 1.25);
1493
+ add(Hd, Bx(0.09, 0.08, 0.02), cmat(14200896, { metalness: 0.7 }), 0, 1.96, 0.205);
1494
+ }
1495
+ },
1496
+ {
1497
+ kinds: ["straw"],
1498
+ apply: ({ Hd, THREE, add, c, cmat }) => {
1499
+ add(Hd, new THREE.CylinderGeometry(0.38, 0.38, 0.022, 24), c, 0, 1.93, 0);
1500
+ add(Hd, new THREE.CylinderGeometry(0.155, 0.18, 0.15, 20), c, 0, 2.01, 0);
1501
+ add(Hd, new THREE.CylinderGeometry(0.182, 0.182, 0.03, 20), cmat(5913122), 0, 1.955, 0);
1502
+ }
1503
+ },
1504
+ {
1505
+ kinds: ["moto"],
1506
+ apply: ({ Hd, PI, THREE, add, c, cmat }) => {
1507
+ const o = add(Hd, new THREE.SphereGeometry(0.235, 20, 16), c, 0, 1.81, -0.01);
1508
+ o.scale.set(0.98, 1.04, 1.06);
1509
+ add(Hd, new THREE.SphereGeometry(0.226, 20, 10, PI * 0.18, PI * 0.64, PI * 0.36, PI * 0.2), new THREE.MeshStandardMaterial({ color: 1053722, roughness: 0.1, metalness: 0.8, flatShading: false }), 0, 1.81, 0.02);
1510
+ add(Hd, new THREE.CylinderGeometry(0.215, 0.2, 0.05, 20), cmat(12071466), 0, 1.6, -0.01);
1511
+ }
1512
+ },
1513
+ {
1514
+ kinds: ["bandana"],
1515
+ apply: ({ Bx, Hd, PI, add, c, cap }) => {
1516
+ const o = add(Hd, cap(0.18, PI * 0.48), c, 0, 1.8, -5e-3);
1517
+ o.scale.set(0.98, 1.1, 1.05);
1518
+ o.rotation.x = -0.4;
1519
+ add(Hd, Bx(0.07, 0.12, 0.03), c, 0, 1.76, -0.19, 0.3, 0, 0);
1520
+ }
1521
+ },
1522
+ {
1523
+ kinds: ["helmet"],
1524
+ apply: ({ Bx, Hd, PI, THREE, add, c, cap, cmat, sp }) => {
1525
+ const o = add(Hd, cap(0.205, PI / 2), c, 0, 1.835, -5e-3);
1526
+ o.scale.set(1, 0.98, 1.08);
1527
+ add(Hd, new THREE.CylinderGeometry(0.208, 0.212, 0.04, 20), c, 0, 1.84, -5e-3);
1528
+ if (sp.hat.tape) add(Hd, new THREE.CylinderGeometry(0.205, 0.207, 0.035, 20), cmat(sp.hat.tape), 0, 1.9, -5e-3);
1529
+ if (sp.hat.photo) {
1530
+ add(Hd, Bx(0.07, 0.08, 0.012), cmat(16052448), 0.13, 1.9, 0.14, 0, 0.75, 0);
1531
+ add(Hd, Bx(0.05, 0.055, 0.014), cmat(9071184), 0.13, 1.9, 0.141, 0, 0.75, 0);
1532
+ }
1533
+ if (sp.hat.nvg) {
1534
+ add(Hd, Bx(0.07, 0.05, 0.06), cmat(1710618), 0, 2, 0.175);
1535
+ for (const s of [-1, 1]) add(Hd, new THREE.CylinderGeometry(0.024, 0.024, 0.09, 10).rotateX(PI / 2), cmat(2767402), s * 0.032, 1.965, 0.235);
1536
+ }
1537
+ for (const s of [-1, 1]) add(Hd, Bx(0.012, 0.15, 0.012), cmat(1710618), s * 0.16, 1.74, 0.04);
1538
+ }
1539
+ }
1540
+ ];
1541
+
1542
+ // src/rich/accessories/extras.ts
1543
+ var EXTRAS = {
1544
+ steth: ({ Bx, PI, THREE, U, add, cmat }) => {
1545
+ add(U, Bx(0.022, 0.24, 0.022), cmat(2763824), 0.085, 1.45, 0.17);
1546
+ add(U, Bx(0.022, 0.24, 0.022), cmat(2763824), -0.085, 1.45, 0.17);
1547
+ add(U, new THREE.CylinderGeometry(0.038, 0.038, 0.02, 16).rotateX(PI / 2), cmat(10133670, { metalness: 0.8, roughness: 0.3 }), 0.03, 1.31, 0.18);
1548
+ return;
1549
+ },
1550
+ mask_chin: ({ Bx, Hd, add, m }) => {
1551
+ add(Hd, Bx(0.24, 0.07, 0.04), m, 0, 1.645, 0.12);
1552
+ return;
1553
+ },
1554
+ vest: ({ Bx, THREE, U, add, m }) => {
1555
+ {
1556
+ const v = add(U, new THREE.CylinderGeometry(0.275, 0.24, 0.48, 20), m, 0, 1.3, 0);
1557
+ v.scale.set(1.12, 1, 0.7);
1558
+ for (let i = 0; i < 3; i++) add(U, Bx(0.11, 0.13, 0.05), m, -0.15 + i * 0.15, 1.17, 0.17);
1559
+ return;
1560
+ }
1561
+ },
1562
+ pouches: ({ Bx, U, add, m }) => {
1563
+ add(U, Bx(0.1, 0.12, 0.09), m, 0.17, 0.95, -0.15);
1564
+ add(U, Bx(0.1, 0.12, 0.09), m, -0.17, 0.95, -0.15);
1565
+ return;
1566
+ },
1567
+ belt: ({ U, m, ring }) => {
1568
+ ring(U, 0.212, 0.07, m, 0.95, 1.12, 0.72);
1569
+ return;
1570
+ },
1571
+ holster: ({ Bx, add, cmat, legs }) => {
1572
+ add(legs[1].g, Bx(0.06, 0.2, 0.11), cmat(1118481), 0.105, -0.1, 0);
1573
+ return;
1574
+ },
1575
+ radio: ({ Bx, U, add, cmat }) => {
1576
+ add(U, Bx(0.075, 0.12, 0.05), cmat(1710618), 0.15, 1.42, 0.18);
1577
+ add(U, Bx(0.012, 0.1, 0.012), cmat(1710618), 0.17, 1.52, 0.18);
1578
+ return;
1579
+ },
1580
+ stripes: ({ THREE, U, add, arms, m, ring }) => {
1581
+ ring(U, 0.205, 0.05, m, 1.03, 1.13, 0.68);
1582
+ ring(U, 0.268, 0.05, m, 1.42, 1.13, 0.67);
1583
+ arms.forEach((a) => {
1584
+ const r = add(a.el, new THREE.CylinderGeometry(0.058, 0.054, 0.05, 18), m, 0, -0.17, 0);
1585
+ });
1586
+ return;
1587
+ },
1588
+ legstripes: ({ THREE, add, legs, m }) => {
1589
+ legs.forEach((l) => add(l.kn, new THREE.CylinderGeometry(0.07, 0.067, 0.05, 18), m, 0, -0.22, 0));
1590
+ return;
1591
+ },
1592
+ stains: ({ R, U, splat }) => {
1593
+ for (let i = 0; i < 4; i++) splat(U, -0.16 + R() * 0.32, 1.15 + R() * 0.3, 0.158, 0.025 + R() * 0.02, 1381653, 2);
1594
+ return;
1595
+ },
1596
+ stains_y: ({ R, U, splat }) => {
1597
+ for (let i = 0; i < 4; i++) splat(U, -0.2 + R() * 0.4, 1 + R() * 0.4, 0.2, 0.03 + R() * 0.02, 13217856, 2);
1598
+ return;
1599
+ },
1600
+ rag: ({ Bx, U, add, m }) => {
1601
+ add(U, Bx(0.08, 0.16, 0.03), m, -0.17, 0.92, 0.15);
1602
+ return;
1603
+ },
1604
+ apron: ({ Bx, U, add, legs, m }) => {
1605
+ add(U, Bx(0.4, 0.38, 0.025), m, 0, 1.2, 0.158, -0.07, 0, 0);
1606
+ add(U, Bx(0.42, 0.2, 0.025), m, 0, 0.95, 0.158);
1607
+ legs.forEach((l) => add(l.g, Bx(0.2, 0.36, 0.025), m, -l.s * 0.01, -0.19, 0.112));
1608
+ add(U, Bx(0.02, 0.2, 0.02), m, 0.11, 1.52, 0.15);
1609
+ add(U, Bx(0.02, 0.2, 0.02), m, -0.11, 1.52, 0.15);
1610
+ return;
1611
+ },
1612
+ buttons: ({ Bx, U, add, cmat }) => {
1613
+ for (let i = 0; i < 3; i++) add(U, Bx(0.03, 0.03, 0.02), cmat(2763306), 0.1, 1.48 - i * 0.08, 0.165);
1614
+ return;
1615
+ },
1616
+ earpro: ({ PI, THREE, U, add, m }) => {
1617
+ {
1618
+ const r = add(U, new THREE.TorusGeometry(0.13, 0.022, 8, 20, PI), m, 0, 1.6, -0.02, -PI / 2 + 0.3, 0, 0);
1619
+ for (const s of [-1, 1]) add(U, new THREE.CylinderGeometry(0.055, 0.055, 0.06, 16).rotateZ(PI / 2), m, s * 0.14, 1.57, 0.05);
1620
+ return;
1621
+ }
1622
+ },
1623
+ coil: ({ PI, THREE, U, add, m }) => {
1624
+ add(U, new THREE.TorusGeometry(0.16, 0.032, 8, 20), m, 0.2, 1.38, 0, 0, PI / 2, 0.5);
1625
+ return;
1626
+ },
1627
+ deliverybox: ({ Bx, U, add, cmat, m }) => {
1628
+ add(U, Bx(0.52, 0.5, 0.44), m, 0, 1.32, -0.38);
1629
+ add(U, Bx(0.53, 0.06, 0.45), cmat(15263970), 0, 1.43, -0.38);
1630
+ for (const s of [-1, 1]) add(U, Bx(0.04, 0.46, 0.03), cmat(1118481), s * 0.14, 1.3, 0.17, -0.1, 0, 0);
1631
+ return;
1632
+ },
1633
+ backpack: ({ Bx, THREE, U, add, cmat, m }) => {
1634
+ {
1635
+ const b = add(U, new THREE.CylinderGeometry(0.2, 0.22, 0.54, 16), m, 0, 1.24, -0.26);
1636
+ b.scale.set(1.15, 1, 0.6);
1637
+ add(U, Bx(0.3, 0.16, 0.07), m, 0, 1.08, -0.4);
1638
+ for (const s of [-1, 1]) add(U, Bx(0.045, 0.46, 0.03), cmat(1710618), s * 0.15, 1.3, 0.17, -0.1, 0, 0);
1639
+ return;
1640
+ }
1641
+ },
1642
+ helmetpack: ({ Bx, THREE, U, add, cmat, m }) => {
1643
+ {
1644
+ const h = add(U, new THREE.SphereGeometry(0.15, 18, 14), m, 0.2, 0.98, -0.42);
1645
+ h.scale.set(1, 1.05, 1.05);
1646
+ add(U, Bx(0.09, 0.07, 0.02), cmat(14729248), 0.24, 1, -0.565, 0, 0.25, 0);
1647
+ add(U, new THREE.CylinderGeometry(0.14, 0.13, 0.04, 18), cmat(12071466), 0.2, 0.85, -0.42);
1648
+ return;
1649
+ }
1650
+ },
1651
+ hood: ({ PI, THREE, U, add, m }) => {
1652
+ {
1653
+ const h = add(U, new THREE.TorusGeometry(0.13, 0.06, 8, 20, PI * 1.2), m, 0, 1.6, -0.03, PI / 2, 0, PI * -0.1);
1654
+ h.scale.set(1.1, 1.2, 1);
1655
+ return;
1656
+ }
1657
+ },
1658
+ pocket: ({ Bx, U, add, m }) => {
1659
+ add(U, Bx(0.32, 0.13, 0.03), m, 0, 1.03, 0.135);
1660
+ return;
1661
+ },
1662
+ sweatervest: ({ U, add, fem, m, torsoGeo }) => {
1663
+ {
1664
+ const g = torsoGeo(fem);
1665
+ g.scale(1.04, 1, 1.1);
1666
+ add(U, g, m, 0, 0, 0);
1667
+ return;
1668
+ }
1669
+ },
1670
+ collar: ({ Bx, U, add, m }) => {
1671
+ add(U, Bx(0.18, 0.05, 0.05), m, 0, 1.58, 0.1);
1672
+ return;
1673
+ },
1674
+ satchel: ({ Bx, U, add, cmat, m }) => {
1675
+ add(U, Bx(0.045, 0.82, 0.02), cmat(3811866), 0, 1.2, 0.18, 0, 0, 0.68);
1676
+ add(U, Bx(0.28, 0.24, 0.1), m, -0.1, 0.92, -0.2);
1677
+ return;
1678
+ },
1679
+ blouse: ({ Bx, U, add, m }) => {
1680
+ add(U, Bx(0.12, 0.26, 0.02), m, 0, 1.38, 0.157);
1681
+ return;
1682
+ },
1683
+ cross: ({ Bx, U, add, m }) => {
1684
+ add(U, Bx(0.022, 0.085, 0.015), m, 0, 1.37, 0.178);
1685
+ add(U, Bx(0.055, 0.02, 0.015), m, 0, 1.39, 0.178);
1686
+ return;
1687
+ },
1688
+ armband: ({ THREE, add, arms, m }) => {
1689
+ add(arms[1].g, new THREE.CylinderGeometry(0.08, 0.078, 0.08, 18), m, 0, -0.12, 0);
1690
+ return;
1691
+ },
1692
+ tiedsleeves: ({ Bx, U, add, legs, m, ring }) => {
1693
+ ring(U, 0.215, 0.1, m, 0.95, 1.12, 0.74);
1694
+ legs.forEach((l) => add(l.g, Bx(0.1, 0.26, 0.05), m, l.s * 0.02, -0.1, 0.105));
1695
+ return;
1696
+ },
1697
+ labcoat: ({ Bx, THREE, U, add, arms, cmat, fem, legs, m, ring, torsoGeo }) => {
1698
+ {
1699
+ const g = torsoGeo(fem);
1700
+ g.scale(1.06, 1, 1.12);
1701
+ add(U, g, m, 0, 0, 0);
1702
+ ring(U, 0.22, 0.14, m, 0.9, 1.12, 0.76);
1703
+ legs.forEach((l) => add(l.g, new THREE.CylinderGeometry(0.145, 0.165, 0.46, 20, 1, true).translate(0, -0.21, 0), m, 0, 0, 0));
1704
+ add(U, Bx(0.025, 0.62, 0.012), cmat(10132114), 0, 1.2, 0.19);
1705
+ arms.forEach((a) => {
1706
+ add(a.g, new THREE.CylinderGeometry(0.085, 0.075, 0.31, 18).translate(0, -0.155, 0), m, 0, 0, 0);
1707
+ add(a.el, new THREE.CylinderGeometry(0.072, 0.064, 0.27, 18).translate(0, -0.135, 0), m, 0, 0, 0);
1708
+ });
1709
+ add(U, Bx(0.1, 0.09, 0.02), m, -0.15, 1.4, 0.19);
1710
+ return;
1711
+ }
1712
+ },
1713
+ rosary: ({ Bx, PI, THREE, add, arms, m }) => {
1714
+ add(arms[1].el, new THREE.TorusGeometry(0.055, 0.012, 4, 10), m, 0, -0.27, 0, PI / 2, 0, 0);
1715
+ add(arms[1].el, Bx(0.015, 0.06, 0.01), m, 0, -0.33, 0.06);
1716
+ return;
1717
+ },
1718
+ watch: ({ Bx, PI, THREE, U, add, cmat }) => {
1719
+ {
1720
+ const g = cmat(13673018, { metalness: 0.8, roughness: 0.3 });
1721
+ add(U, new THREE.CylinderGeometry(0.05, 0.05, 0.02, 10).rotateX(PI / 2), g, 0.12, 1.06, 0.135);
1722
+ add(U, Bx(0.015, 0.14, 0.01), g, 0.06, 1.1, 0.135, 0, 0, 0.9);
1723
+ return;
1724
+ }
1725
+ },
1726
+ chain: ({ PI, THREE, add, legs, m, root }) => {
1727
+ {
1728
+ add(legs[0].kn, new THREE.TorusGeometry(0.085, 0.015, 4, 8), m, 0, -0.34, 0, PI / 2, 0, 0);
1729
+ for (let i = 0; i < 6; i++) add(root, new THREE.TorusGeometry(0.045, 0.012, 4, 8), m, 0.2 + i * 0.07, 0.03, -0.06 - i * 0.06, i % 2 ? PI / 2 : 0, 0.6, 0);
1730
+ return;
1731
+ }
1732
+ },
1733
+ unicorn: ({ Bx, THREE, U, add, cmat, m }) => {
1734
+ add(U, Bx(0.32, 0.36, 0.16), m, 0, 1.24, -0.24);
1735
+ add(U, new THREE.ConeGeometry(0.035, 0.16, 5), cmat(15910986), 0, 1.5, -0.24);
1736
+ for (const s of [-1, 1]) {
1737
+ add(U, Bx(0.05, 0.07, 0.04), cmat(15239864), s * 0.09, 1.45, -0.24);
1738
+ add(U, Bx(0.04, 0.42, 0.03), cmat(11561672), s * 0.13, 1.3, 0.16, -0.1, 0, 0);
1739
+ }
1740
+ return;
1741
+ },
1742
+ whistle: ({ Bx, PI, THREE, U, add, cmat }) => {
1743
+ for (const s of [-1, 1]) add(U, Bx(0.012, 0.22, 0.01), cmat(10133670), s * 0.06, 1.48, 0.17, 0, 0, -s * 0.35);
1744
+ add(U, new THREE.CylinderGeometry(0.022, 0.022, 0.08, 6).rotateZ(PI / 2), cmat(13685976, { metalness: 0.9, roughness: 0.2 }), 0, 1.36, 0.19);
1745
+ return;
1746
+ },
1747
+ pen: ({ Hd, THREE, add, cmat }) => {
1748
+ add(Hd, new THREE.CylinderGeometry(0.01, 0.01, 0.26, 5), cmat(2773192), 0, 1.92, -0.15, 0, 0, 1.3);
1749
+ return;
1750
+ },
1751
+ photo: ({ Bx, U, add, cmat }) => {
1752
+ for (const s of [-1, 1]) add(U, Bx(0.012, 0.22, 0.01), cmat(13673018), s * 0.05, 1.48, 0.17, 0, 0, -s * 0.3);
1753
+ add(U, Bx(0.07, 0.09, 0.01), cmat(16052448), 0, 1.34, 0.185);
1754
+ return;
1755
+ },
1756
+ pins: ({ PI, THREE, U, add, cmat, k }) => {
1757
+ [[14174778, 0.1, 1.38], [15913030, 0.17, 1.3], [4898954, 0.12, 1.22], [15790320, -0.15, 1.36]].forEach(([k2, x, y]) => add(U, new THREE.CylinderGeometry(0.028, 0.028, 0.02, 8).rotateX(PI / 2), cmat(k2), x, y, 0.185));
1758
+ return;
1759
+ },
1760
+ recorder: ({ Bx, PI, THREE, U, add, cmat }) => {
1761
+ add(U, Bx(0.13, 0.09, 0.05), cmat(1710618), 0.15, 1.28, 0.215);
1762
+ for (const s of [-1, 1]) add(U, new THREE.CylinderGeometry(0.02, 0.02, 0.052, 8).rotateX(PI / 2), cmat(14191146), 0.15 + s * 0.03, 1.29, 0.22);
1763
+ return;
1764
+ },
1765
+ beads: ({ Bx, PI, add, arms, cmat }) => {
1766
+ {
1767
+ const cs = [15222842, 15913030, 4890848, 5947498, 15239864, 16777215];
1768
+ for (let i = 0; i < 8; i++) {
1769
+ const a = i / 8 * PI * 2;
1770
+ add(arms[0].el, Bx(0.03, 0.03, 0.03), cmat(cs[i % 6]), Math.cos(a) * 0.055, -0.26, Math.sin(a) * 0.055);
1771
+ }
1772
+ return;
1773
+ }
1774
+ },
1775
+ badge: ({ Bx, U, add, cmat }) => {
1776
+ for (const s of [-1, 1]) add(U, Bx(0.012, 0.22, 0.01), cmat(2775720), s * 0.05, 1.48, 0.17, 0, 0, -s * 0.3);
1777
+ add(U, Bx(0.08, 0.11, 0.01), cmat(16053492), 0, 1.34, 0.185);
1778
+ add(U, Bx(0.04, 0.04, 0.012), cmat(5933768), 0, 1.36, 0.19);
1779
+ return;
1780
+ },
1781
+ stethc: ({ Bx, PI, THREE, U, add, cmat, m }) => {
1782
+ add(U, new THREE.TorusGeometry(0.14, 0.018, 5, 10, PI), m, 0, 1.6, -0.02, -PI / 2 + 0.3, 0, 0);
1783
+ add(U, Bx(0.02, 0.2, 0.02), m, -0.1, 1.42, 0.19);
1784
+ add(U, new THREE.CylinderGeometry(0.035, 0.035, 0.02, 8).rotateX(PI / 2), cmat(12633288, { metalness: 0.9, roughness: 0.2 }), -0.1, 1.31, 0.2);
1785
+ return;
1786
+ },
1787
+ bandolier: ({ Bx, U, add, cmat }) => {
1788
+ for (let i = 0; i < 9; i++) add(U, Bx(0.035, 0.08, 0.035), cmat(12855338), -0.22 + i * 0.055, 1.44 - i * 0.055, 0.2, 0, 0, -0.75);
1789
+ add(U, Bx(0.05, 0.75, 0.02), cmat(4864546), 0, 1.22, 0.195, 0, 0, -0.75);
1790
+ return;
1791
+ },
1792
+ scarf: ({ PI, THREE, U, add, m, ring }) => {
1793
+ ring(U, 0.1, 0.08, m, 1.57, 1.3, 1.2);
1794
+ add(U, new THREE.ConeGeometry(0.08, 0.14, 3), m, 0, 1.47, 0.15, PI, 0, 0);
1795
+ return;
1796
+ },
1797
+ feather: ({ Bx, Hd, add, cmat }) => {
1798
+ add(Hd, Bx(0.02, 0.24, 0.06), cmat(657932), 0.17, 2, -0.04, 0, 0, -0.3);
1799
+ return;
1800
+ },
1801
+ amulet: ({ Bx, U, add, cmat }) => {
1802
+ for (const s of [-1, 1]) add(U, Bx(0.012, 0.22, 0.01), cmat(5913122), s * 0.05, 1.48, 0.17, 0, 0, -s * 0.3);
1803
+ add(U, Bx(0.07, 0.09, 0.03), cmat(8014378), 0, 1.34, 0.19);
1804
+ return;
1805
+ },
1806
+ rifle_front: ({ Bx, THREE, U, add, arms, cmat, k }) => {
1807
+ {
1808
+ const g = new THREE.Group();
1809
+ g.position.set(0.02, 1.15, 0.26);
1810
+ g.rotation.z = 0.95;
1811
+ U.add(g);
1812
+ const dk = cmat(1842463, { metalness: 0.6, roughness: 0.45 });
1813
+ add(g, Bx(0.08, 0.3, 0.06), cmat(k === "dbl_front" ? 5913122 : 2763816), 0, -0.38, 0);
1814
+ add(g, Bx(0.05, 0.85, 0.05), dk, 0, 0.2, 0);
1815
+ if (k === "dbl_front") {
1816
+ add(g, Bx(0.05, 0.6, 0.05), dk, 0.05, 0.3, 0);
1817
+ add(g, Bx(0.11, 0.06, 0.08), cmat(15262936), 0, -0.25, 0);
1818
+ } else {
1819
+ add(g, Bx(0.07, 0.14, 0.09), dk, 0, -0.08, 0.04);
1820
+ if (k === "rifle_tape") {
1821
+ add(g, Bx(0.08, 0.08, 0.08), cmat(4872762), 0, 0.3, 0);
1822
+ add(g, Bx(0.08, 0.08, 0.08), cmat(6974026), 0, 0.08, 0);
1823
+ }
1824
+ }
1825
+ arms.forEach((a) => a.grip = true);
1826
+ return;
1827
+ }
1828
+ },
1829
+ belly: ({ U, ball, topM }) => {
1830
+ {
1831
+ const b = ball(U, 0.16, topM, 0, 1.03, 0.105);
1832
+ b.scale.set(1.05, 1.08, 0.95);
1833
+ return;
1834
+ }
1835
+ },
1836
+ refl: ({ Bx, THREE, U, add, cmat, m, ring }) => {
1837
+ {
1838
+ const v = add(U, new THREE.CylinderGeometry(0.287, 0.252, 0.46, 20), m, 0, 1.3, 0);
1839
+ v.scale.set(1.13, 1, 0.72);
1840
+ ring(U, 0.29, 0.035, cmat(13159632, { metalness: 0.3, roughness: 0.4 }), 1.2, 1.13, 0.73);
1841
+ ring(U, 0.3, 0.035, cmat(13159632, { metalness: 0.3, roughness: 0.4 }), 1.37, 1.13, 0.73);
1842
+ add(U, Bx(0.17, 0.055, 0.02), cmat(1714794), 0, 1.47, 0.205);
1843
+ return;
1844
+ }
1845
+ },
1846
+ plate: ({ Bx, THREE, U, add, cmat, m }) => {
1847
+ {
1848
+ const v = add(U, new THREE.CylinderGeometry(0.28, 0.25, 0.44, 20), m, 0, 1.3, 0);
1849
+ v.scale.set(1.12, 1, 0.72);
1850
+ for (let i = 0; i < 3; i++) add(U, Bx(0.08, 0.14, 0.06), m, -0.1 + i * 0.1, 1.16, 0.2);
1851
+ add(U, Bx(0.07, 0.11, 0.045), cmat(1710618), 0.16, 1.42, 0.2);
1852
+ for (const s of [-1, 1]) add(U, Bx(0.07, 0.04, 0.2), m, s * 0.17, 1.53, 0);
1853
+ return;
1854
+ }
1855
+ },
1856
+ keyring: ({ Bx, PI, THREE, U, add, cmat }) => {
1857
+ {
1858
+ add(U, new THREE.TorusGeometry(0.12, 9e-3, 4, 14, PI), cmat(2763306), 0, 1.5, 0.07, PI / 2 + 0.6, 0, 0);
1859
+ add(U, new THREE.TorusGeometry(0.05, 8e-3, 4, 12), cmat(12103840, { metalness: 0.9, roughness: 0.25 }), 0, 1.34, 0.2);
1860
+ for (let i = 0; i < 7; i++) add(U, Bx(0.013, 0.075, 6e-3), cmat(i % 2 ? 13676640 : 12106944, { metalness: 0.9, roughness: 0.3 }), -0.045 + i * 0.015, 1.28, 0.21, 0, 0, (i - 3) * 0.18);
1861
+ return;
1862
+ }
1863
+ },
1864
+ satradio: ({ Bx, U, add, cmat, m }) => {
1865
+ {
1866
+ add(U, Bx(0.3, 0.4, 0.18), m, 0, 1.28, -0.25);
1867
+ add(U, Bx(0.012, 0.62, 0.012), cmat(1710618), 0.1, 1.72, -0.3, 0, 0, 0.15);
1868
+ add(U, Bx(0.08, 0.06, 0.03), cmat(3815994), -0.08, 1.4, -0.35);
1869
+ for (const s of [-1, 1]) add(U, Bx(0.045, 0.46, 0.03), cmat(1710618), s * 0.15, 1.3, 0.17, -0.1, 0, 0);
1870
+ return;
1871
+ }
1872
+ },
1873
+ binoc: ({ Bx, PI, THREE, U, add, cmat }) => {
1874
+ for (const s of [-1, 1]) add(U, new THREE.CylinderGeometry(0.03, 0.034, 0.12, 10).rotateX(PI / 2), cmat(2500644), s * 0.038, 1.38, 0.26);
1875
+ add(U, Bx(0.03, 0.03, 0.04), cmat(2500644), 0, 1.38, 0.25);
1876
+ return;
1877
+ },
1878
+ tq: ({ Bx, THREE, add, arms, cmat }) => {
1879
+ add(arms[1].g, new THREE.CylinderGeometry(0.09, 0.087, 0.055, 16), cmat(14165546), 0, -0.05, 0);
1880
+ add(arms[1].g, Bx(0.03, 0.05, 0.04), cmat(1710618), 0, -0.05, 0.09);
1881
+ return;
1882
+ },
1883
+ cuia: ({ THREE, U, add, cmat }) => {
1884
+ add(U, new THREE.SphereGeometry(0.055, 12, 10), cmat(6978106), -0.21, 0.9, 0.12);
1885
+ add(U, new THREE.CylinderGeometry(6e-3, 6e-3, 0.13, 6), cmat(12633288, { metalness: 0.9, roughness: 0.2 }), -0.21, 0.99, 0.12, 0, 0, 0.3);
1886
+ add(U, new THREE.CylinderGeometry(0.04, 0.04, 0.015, 12), cmat(12099696), -0.21, 0.95, 0.12);
1887
+ return;
1888
+ },
1889
+ card: ({ Bx, U, add, cmat }) => {
1890
+ for (const s of [-1, 1]) add(U, Bx(0.012, 0.22, 0.01), cmat(2763306), s * 0.05, 1.5, 0.2, 0, 0, -s * 0.3);
1891
+ add(U, Bx(0.08, 0.11, 0.01), cmat(14164e3), 0, 1.36, 0.235);
1892
+ add(U, Bx(0.05, 0.012, 0.012), cmat(15790320), 0, 1.34, 0.24);
1893
+ return;
1894
+ },
1895
+ mapfold: ({ Bx, U, add, cmat }) => {
1896
+ add(U, Bx(0.1, 0.13, 0.025), cmat(15260848), -0.13, 1.33, 0.215, 0, 0, 0.1);
1897
+ add(U, Bx(0.1, 0.012, 0.027), cmat(9071162), -0.13, 1.35, 0.215);
1898
+ return;
1899
+ },
1900
+ motoband: ({ Hd, THREE, add, cmat }) => {
1901
+ add(Hd, new THREE.CylinderGeometry(0.238, 0.238, 0.045, 20), cmat(14729248), 0, 1.84, -0.01);
1902
+ return;
1903
+ },
1904
+ rifle_sup: ({ Bx, THREE, U, add, arms, cmat }) => {
1905
+ {
1906
+ const g = new THREE.Group();
1907
+ g.position.set(0.02, 1.15, 0.27);
1908
+ g.rotation.z = 0.95;
1909
+ U.add(g);
1910
+ const dk = cmat(1842463, { metalness: 0.6, roughness: 0.45 });
1911
+ add(g, Bx(0.07, 0.26, 0.06), cmat(3815988), 0, -0.36, 0);
1912
+ add(g, Bx(0.06, 0.5, 0.06), dk, 0, 0, 0);
1913
+ add(g, Bx(0.07, 0.14, 0.09), dk, 0, -0.06, 0.05);
1914
+ add(g, Bx(0.05, 0.08, 0.07), cmat(2763306), 0, 0.12, 0.06);
1915
+ add(g, new THREE.CylinderGeometry(0.03, 0.03, 0.24, 10), cmat(2763564, { metalness: 0.5, roughness: 0.5 }), 0, 0.37, 0);
1916
+ add(g, Bx(0.04, 0.08, 0.05), cmat(1710618), 0, 0.05, -0.05);
1917
+ arms.forEach((a) => a.grip = true);
1918
+ return;
1919
+ }
1920
+ },
1921
+ gloves: () => {
1922
+ },
1923
+ blood: ({ R, U, splat }) => {
1924
+ for (let i = 0; i < 3; i++) splat(U, 0.06 + R() * 0.14, 1.1 + R() * 0.3, 0.2, 0.022 + R() * 0.02, 3874068, 3);
1925
+ return;
1926
+ },
1927
+ shirtpeek: ({ Bx, U, add, m }) => {
1928
+ add(U, Bx(0.16, 0.26, 0.02), m, 0, 1.38, 0.163);
1929
+ return;
1930
+ },
1931
+ suspenders: ({ Bx, U, add, m }) => {
1932
+ for (const s of [-1, 1]) add(U, Bx(0.035, 0.3, 0.02), m, s * 0.11, 1.4, 0.163);
1933
+ return;
1934
+ }
1935
+ };
1936
+ EXTRAS.headphones = EXTRAS.earpro;
1937
+ EXTRAS.rifle_tape = EXTRAS.rifle_front;
1938
+ EXTRAS.dbl_front = EXTRAS.rifle_front;
1939
+
1940
+ // src/rich/person-kit.ts
1941
+ var createPersonKit = (THREE, { random: R, createCanvas = () => document.createElement("canvas") }) => {
1942
+ const T = THREE;
1943
+ const scaleHex = (hex2, factor) => {
1944
+ const channel = (shift) => (hex2 >> shift & 255) / 255 * factor * 255;
1945
+ return channel(16) << 16 ^ channel(8) << 8 ^ channel(0) << 0;
1946
+ };
1947
+ const Bx = (x, y, z) => new T.BoxGeometry(x, y, z);
1948
+ function add(parent, geo, m, x, y, z, rx, ry, rz) {
1949
+ const o = new T.Mesh(geo, m);
1950
+ o.position.set(x, y, z);
1951
+ if (rx || ry || rz) o.rotation.set(rx || 0, ry || 0, rz || 0);
1952
+ o.castShadow = true;
1953
+ o.receiveShadow = true;
1954
+ parent.add(o);
1955
+ return o;
1956
+ }
1957
+ function canvasTex(w, h, draw) {
1958
+ const cv = createCanvas();
1959
+ cv.width = w;
1960
+ cv.height = h;
1961
+ const context = cv.getContext("2d");
1962
+ if (context) draw(context, w, h);
1963
+ return new T.CanvasTexture(cv);
1964
+ }
1965
+ function chessTex(c1, c2) {
1966
+ const a = new THREE.Color(c1), b = new THREE.Color(c2);
1967
+ return canvasTex(32, 32, (g) => {
1968
+ g.fillStyle = "#" + a.getHexString();
1969
+ g.fillRect(0, 0, 32, 32);
1970
+ g.fillStyle = "#" + b.getHexString();
1971
+ g.globalAlpha = 0.75;
1972
+ g.fillRect(0, 6, 32, 4);
1973
+ g.fillRect(0, 22, 32, 4);
1974
+ g.fillRect(6, 0, 4, 32);
1975
+ g.fillRect(22, 0, 4, 32);
1976
+ g.globalAlpha = 1;
1977
+ });
1978
+ }
1979
+ const SEG = 16;
1980
+ const ccache = {};
1981
+ function cmat(c, o) {
1982
+ if (!o && ccache[c]) return ccache[c];
1983
+ const m = new THREE.MeshStandardMaterial(Object.assign({ color: c, roughness: 0.82, metalness: 0, flatShading: false }, o || {}));
1984
+ if (!o) ccache[c] = m;
1985
+ return m;
1986
+ }
1987
+ const PI = Math.PI;
1988
+ function limb(parent, r1, r2, len, m) {
1989
+ return add(parent, new THREE.CylinderGeometry(r1, r2, len, SEG, 1).translate(0, -len / 2, 0), m, 0, 0, 0);
1990
+ }
1991
+ function mlimb(parent, prof, len, m) {
1992
+ const pts = prof.map((r, i) => new THREE.Vector2(r, -len + i / (prof.length - 1) * len)).reverse();
1993
+ pts.reverse();
1994
+ const sm = new THREE.SplineCurve(prof.slice().reverse().map((r, i) => new THREE.Vector2(r, -len + i / (prof.length - 1) * len))).getPoints(14);
1995
+ return add(parent, new THREE.LatheGeometry(sm, SEG), m, 0, 0, 0);
1996
+ }
1997
+ function ball(parent, r, m, x, y, z) {
1998
+ return add(parent, new THREE.SphereGeometry(r, SEG, 12), m, x || 0, y || 0, z || 0);
1999
+ }
2000
+ function ring(parent, r, h, m, y, sx, sz) {
2001
+ const o = add(parent, new THREE.CylinderGeometry(r, r, h, 20), m, 0, y, 0);
2002
+ o.scale.set(sx || 1.12, 1, sz || 0.7);
2003
+ return o;
2004
+ }
2005
+ function cap(r, len) {
2006
+ return new THREE.SphereGeometry(r, 20, 10, 0, PI * 2, 0, len || PI / 2);
2007
+ }
2008
+ function torsoGeo(fem) {
2009
+ const P = fem ? [[0.17, 0.86], [0.215, 0.95], [0.2, 1.02], [0.165, 1.1], [0.2, 1.25], [0.245, 1.38], [0.25, 1.46], [0.2, 1.54], [0.085, 1.6]] : [[0.18, 0.86], [0.205, 0.95], [0.195, 1.03], [0.19, 1.12], [0.225, 1.27], [0.26, 1.4], [0.265, 1.47], [0.21, 1.55], [0.085, 1.6]];
2010
+ const pts = new THREE.SplineCurve(P.map((p) => new THREE.Vector2(p[0], p[1]))).getPoints(30);
2011
+ const g = new THREE.LatheGeometry(pts, 24);
2012
+ g.scale(1.12, 1, 0.64);
2013
+ return g;
2014
+ }
2015
+ function blobR(n, rough, seed) {
2016
+ const f = [];
2017
+ const ph = [seed * 1.7, seed * 3.1, seed * 5.3, seed * 7.9];
2018
+ const sp = [];
2019
+ const ns = rough > 0.5 ? Math.floor(R() * 4) : 0;
2020
+ for (let k = 0; k < ns; k++) sp.push([R() * 6.28, 0.45 + R() * 0.7]);
2021
+ for (let i = 0; i < n; i++) {
2022
+ const a = i / n * PI * 2;
2023
+ let r = 1 + 0.16 * Math.sin(2 * a + ph[0]) + 0.1 * Math.sin(3 * a + ph[1]) + rough * (0.08 * Math.sin(7 * a + ph[2]) + 0.05 * Math.sin(13 * a + ph[3])) + rough * 0.05 * (R() - 0.5);
2024
+ sp.forEach(([sa, l]) => {
2025
+ const d = Math.atan2(Math.sin(a - sa), Math.cos(a - sa));
2026
+ r += l * Math.exp(-d * d * 90);
2027
+ });
2028
+ f.push(Math.max(0.35, r));
2029
+ }
2030
+ return f;
2031
+ }
2032
+ function blobGeo(rad, rx, ry) {
2033
+ const sh = new THREE.Shape();
2034
+ rad.forEach((r, i) => {
2035
+ const a = i / rad.length * PI * 2;
2036
+ const x = Math.cos(a) * r * rx, y = Math.sin(a) * r * ry;
2037
+ i ? sh.lineTo(x, y) : sh.moveTo(x, y);
2038
+ });
2039
+ return new THREE.ShapeGeometry(sh);
2040
+ }
2041
+ const stainMs = {};
2042
+ function stainM(c, o) {
2043
+ const k = c + "|" + o;
2044
+ return stainMs[k] || (stainMs[k] = new THREE.MeshStandardMaterial(Object.assign({ color: c, roughness: 0.95, metalness: 0, polygonOffset: true, polygonOffsetFactor: -2, polygonOffsetUnits: -2, side: THREE.DoubleSide }, o || {})));
2045
+ }
2046
+ function splat(parent, x, y, z, r, color, n, out, kind) {
2047
+ const skin = kind === "skin", fresh = color === 9046538, push = (o) => {
2048
+ o.castShadow = false;
2049
+ if (out) out.push(o);
2050
+ return o;
2051
+ };
2052
+ const sy = 0.8 + R() * 0.4, seed = R() * 10;
2053
+ const core = skin ? stainM(color, { roughness: 0.12, metalness: 0.1 }) : stainM(scaleHex(color, 0.72));
2054
+ const a = push(add(parent, blobGeo(blobR(40, skin ? 0.4 : 0.9, seed), r * 0.82, r * 0.82 * sy), core, x, y, z + 4e-3));
2055
+ if (!skin) {
2056
+ push(add(parent, blobGeo(blobR(48, 1.6, seed + 0.3), r * 1.3, r * 1.3 * sy), stainM(color, { transparent: true, opacity: 0.5, depthWrite: false }), x, y, z + 2e-3));
2057
+ }
2058
+ for (let k = 0; k < (n || 0); k++) {
2059
+ const ang = R() * 6.28, d = r * (1.2 + R() * 0.9), rr = r * (0.16 + R() * 0.2);
2060
+ push(add(parent, blobGeo(blobR(14, 0.5, R() * 10), rr, rr * (1 + R() * 0.6)), core, x + Math.cos(ang) * d, y + Math.sin(ang) * d - (k === 0 ? r * 0.6 : 0), z + 4e-3, 0, 0, ang));
2061
+ }
2062
+ if (n && (fresh || skin)) {
2063
+ const L = r * (1.4 + R() * 1.2), w = r * 0.16;
2064
+ const g = new THREE.Shape();
2065
+ g.moveTo(-w, 0);
2066
+ g.quadraticCurveTo(-w * 0.6, -L * 0.6, -w * 0.35, -L);
2067
+ g.absarc(0, -L, w * 0.6, PI, 0, false);
2068
+ g.quadraticCurveTo(w * 0.6, -L * 0.6, w, 0);
2069
+ push(add(parent, new THREE.ShapeGeometry(g), core, x + r * 0.15, y - r * 0.55, z + 5e-3));
2070
+ }
2071
+ return a;
2072
+ }
2073
+ return { THREE, PI, SEG, R, scaleHex, Bx, add, cmat, ball, ring, cap, limb, mlimb, torsoGeo, splat, stainM, chessTex };
2074
+ };
2075
+
2076
+ // src/rich/person-builder.ts
2077
+ var PAGE_EYES = { lento: 14926657, corredor: 15022379, brutamonte: 15698474 };
2078
+ var createPersonBuilder = (THREE, options = {}) => {
2079
+ const kit = createPersonKit(THREE, { random: options.random ?? createRng2(77), createCanvas: options.createCanvas });
2080
+ const { PI, R, scaleHex, Bx, add, cmat, ball, ring, cap, limb, mlimb, torsoGeo, splat, stainM, chessTex } = kit;
2081
+ const reduce = options.reducedMotion ?? false;
2082
+ const detailed = (options.detail ?? 0) === 0;
2083
+ const EYE = { ...PAGE_EYES, ...options.zombieEyes };
2084
+ const T = THREE;
2085
+ const ZSKIN2 = new T.Color(7305820);
2086
+ function build(sp, idx) {
2087
+ const root = new THREE.Group();
2088
+ const H = sp.h || 1, B = sp.bulk || 1, fem = !!sp.fem;
2089
+ const fig = new THREE.Group();
2090
+ fig.scale.set(B, H, Math.min(1.12, B));
2091
+ root.add(fig);
2092
+ const skinM = new THREE.MeshStandardMaterial({ color: sp.skin, roughness: 0.8, flatShading: false });
2093
+ const topM = sp.top.plaid ? new THREE.MeshStandardMaterial({ map: chessTex(sp.top.c, sp.top.plaid), roughness: 0.9, flatShading: false }) : cmat(sp.top.c);
2094
+ const pantsM = cmat(sp.pants.c), shoeM = cmat(sp.shoes);
2095
+ const exHas = (k) => (sp.ex || []).some((e) => e[0] === k);
2096
+ const pel = ball(fig, 0.2, sp.pants.skirt ? cmat(sp.pants.c) : pantsM, 0, 0.92, 0);
2097
+ pel.scale.set(fem ? 1.16 : 1.08, 0.62, 0.74);
2098
+ const legs = [];
2099
+ for (const s of [-1, 1]) {
2100
+ const g = new THREE.Group();
2101
+ g.position.set(s * (fem ? 0.115 : 0.105), 0.9, 0);
2102
+ fig.add(g);
2103
+ const thighM = sp.pants.skirt ? skinM : pantsM;
2104
+ mlimb(g, fem ? [0.113, 0.117, 0.106, 0.09, 0.078] : [0.106, 0.112, 0.104, 0.09, 0.078], 0.42, thighM);
2105
+ const kn = new THREE.Group();
2106
+ kn.position.y = -0.42;
2107
+ g.add(kn);
2108
+ ball(kn, 0.078, thighM);
2109
+ const shinM = sp.pants.skirt ? skinM : pantsM;
2110
+ mlimb(kn, [0.077, 0.081, 0.07, 0.058, 0.052], 0.38, shinM);
2111
+ const kc = ball(kn, 0.04, shinM, 0, -5e-3, 0.058);
2112
+ kc.scale.set(1.05, 1.15, 0.7);
2113
+ const calf = ball(kn, 0.05, shinM, 0, -0.13, -0.03);
2114
+ calf.scale.set(1.05, 1.8, 1);
2115
+ const sh = ball(kn, 0.074, shoeM, 0, -0.425, 0.055);
2116
+ sh.scale.set(0.95, 0.72, 1.85);
2117
+ const so = add(kn, new THREE.CylinderGeometry(0.071, 0.071, 0.022, 18), cmat(1447446), 0, -0.468, 0.055);
2118
+ so.scale.set(1, 1, 1.95);
2119
+ if (sp.pants.skirt) add(g, new THREE.CylinderGeometry(0.135, 0.178, 0.62, 20, 1, true).translate(0, -0.28, 0), pantsM, 0, 0, 0);
2120
+ legs.push({ g, kn, s });
2121
+ }
2122
+ for (const s of [-1, 1]) {
2123
+ const gl = ball(fig, 0.1, pantsM, s * 0.085, 0.87, -0.075);
2124
+ gl.scale.set(1, 0.9, 0.72);
2125
+ }
2126
+ if (sp.pants.skirt) {
2127
+ const sk = add(fig, new THREE.CylinderGeometry(0.2, 0.245, 0.2, 24), pantsM, 0, 0.87, 0);
2128
+ sk.scale.set(1.12, 1, 0.8);
2129
+ }
2130
+ const upper = new THREE.Group();
2131
+ upper.position.y = 0.9;
2132
+ fig.add(upper);
2133
+ const U = new THREE.Group();
2134
+ U.position.y = -0.9;
2135
+ upper.add(U);
2136
+ add(U, torsoGeo(fem), topM, 0, 0, 0);
2137
+ {
2138
+ const trapM = sp.top.sl === "tank" ? skinM : topM;
2139
+ for (const s of [-1, 1]) {
2140
+ const tp = ball(U, 0.1, trapM, s * 0.12, 1.545, -0.02);
2141
+ tp.scale.set(1.35, 0.5, 0.85);
2142
+ const pc = ball(U, fem ? 0.072 : 0.09, topM, s * (fem ? 0.072 : 0.085), fem ? 1.36 : 1.38, fem ? 0.088 : 0.1);
2143
+ pc.scale.set(fem ? 1 : 1.1, fem ? 0.85 : 0.55, fem ? 0.6 : 0.35);
2144
+ }
2145
+ if (sp.top.sl === "tank") for (const s of [-1, 1]) add(U, new THREE.CylinderGeometry(0.011, 0.011, 0.16, 8), skinM, s * 0.08, 1.565, 0.1, 0, 0, s * 1.35);
2146
+ }
2147
+ add(U, new THREE.CylinderGeometry(0.058, 0.068, 0.14, 16), skinM, 0, 1.63, 0);
2148
+ const headG = new THREE.Group();
2149
+ headG.position.set(0, 1.64, 0);
2150
+ U.add(headG);
2151
+ const Hd = new THREE.Group();
2152
+ Hd.position.y = -1.64;
2153
+ headG.add(Hd);
2154
+ const head = add(Hd, new THREE.SphereGeometry(0.165, 20, 16), skinM, 0, 1.8, 0);
2155
+ head.scale.set(0.96, 1.1, 1.02);
2156
+ const jaw = add(Hd, new THREE.SphereGeometry(0.12, 16, 12), skinM, 0, 1.715, 0.03);
2157
+ jaw.scale.set(1, 0.72, 1);
2158
+ for (const s of [-1, 1]) {
2159
+ const e = ball(Hd, 0.038, skinM, s * 0.158, 1.79, -5e-3);
2160
+ e.scale.set(0.6, 1, 0.9);
2161
+ }
2162
+ add(Hd, Bx(0.045, 0.065, 0.05), skinM, 0, 1.765, 0.172);
2163
+ const eyeM = new THREE.MeshBasicMaterial({ color: 1710618 });
2164
+ const whiteM = cmat(15328472);
2165
+ for (const s of [-1, 1]) {
2166
+ const w = add(Hd, new THREE.SphereGeometry(0.022, 12, 8), whiteM, s * 0.058, 1.815, 0.146);
2167
+ w.scale.set(1.25, 0.8, 0.6);
2168
+ w.castShadow = false;
2169
+ const e = add(Hd, new THREE.SphereGeometry(0.011, 10, 8), eyeM, s * 0.058, 1.814, 0.159);
2170
+ e.scale.set(1, 1, 0.5);
2171
+ e.castShadow = false;
2172
+ const lid = add(Hd, new THREE.SphereGeometry(0.024, 12, 6, 0, PI * 2, 0, PI * 0.45), skinM, s * 0.058, 1.818, 0.143);
2173
+ lid.scale.set(1.25, 0.85, 0.65);
2174
+ add(Hd, Bx(0.058, 0.013, 0.022), cmat(sp.hair.c), s * 0.06, 1.852, 0.155, 0, 0, -s * 0.12);
2175
+ const ck = ball(Hd, 0.045, skinM, s * 0.078, 1.772, 0.098);
2176
+ ck.scale.set(1, 0.7, 0.8);
2177
+ }
2178
+ const ridge = ball(Hd, 0.1, skinM, 0, 1.842, 0.105);
2179
+ ridge.scale.set(1.3, 0.28, 0.55);
2180
+ add(Hd, new THREE.SphereGeometry(0.021, 10, 8), skinM, 0, 1.762, 0.182);
2181
+ add(Hd, Bx(0.064, 0.013, 0.02), cmat(scaleHex(sp.skin, 0.68)), 0, 1.703, 0.156);
2182
+ const bags = add(Hd, Bx(0.2, 0.022, 0.012), new THREE.MeshBasicMaterial({ color: 3811882, transparent: true, opacity: 0 }), 0, 1.79, 0.166);
2183
+ bags.castShadow = false;
2184
+ const hc = cmat(sp.hair.c), hs = sp.hair.s;
2185
+ const hairCap = (r, len, tilt) => {
2186
+ const o = add(Hd, cap(r, len), hc, 0, 1.8, -4e-3);
2187
+ o.scale.set(0.97, 1.1, 1.04);
2188
+ o.rotation.x = tilt;
2189
+ return o;
2190
+ };
2191
+ const backBand = (t0, tl) => {
2192
+ const o = add(Hd, new THREE.SphereGeometry(0.172, 20, 10, PI * 0.85, PI * 1.3, t0, tl), hc, 0, 1.8, 0);
2193
+ o.scale.set(0.97, 1.1, 1.04);
2194
+ return o;
2195
+ };
2196
+ if (hs === "short" || hs === "bun" || hs === "long") {
2197
+ hairCap(0.174, PI / 2, -0.38);
2198
+ backBand(PI * 0.45, PI * 0.22);
2199
+ }
2200
+ if (hs === "bun") ball(Hd, 0.085, hc, 0, 1.92, -0.15);
2201
+ if (hs === "long") {
2202
+ const l = add(Hd, new THREE.CylinderGeometry(0.15, 0.12, 0.42, 16, 1, false, PI * 0.6, PI * 0.8), hc, 0, 1.66, -0.02);
2203
+ l.scale.set(1.05, 1, 1.1);
2204
+ }
2205
+ if (hs === "buzz") hairCap(0.17, PI * 0.45, -0.32);
2206
+ if (hs === "bald") backBand(PI * 0.42, PI * 0.2);
2207
+ if (hs === "afro") {
2208
+ const a = add(Hd, new THREE.DodecahedronGeometry(0.25, 0), hc, 0, 1.9, -0.045);
2209
+ a.scale.set(1.08, 0.9, 1.02);
2210
+ }
2211
+ if (hs === "pony") {
2212
+ hairCap(0.174, PI / 2, -0.38);
2213
+ backBand(PI * 0.45, PI * 0.22);
2214
+ const t = add(Hd, new THREE.CylinderGeometry(0.04, 0.025, 0.3, 6), hc, 0, 1.7, -0.2);
2215
+ t.rotation.x = 0.25;
2216
+ }
2217
+ const f = sp.face || [];
2218
+ for (const feature of FACE_FEATURES) if (feature.ids.some((id) => f.includes(id))) feature.apply({ THREE, PI, Hd, add, Bx, cmat, hc, sp, f, scaleHex });
2219
+ if (sp.hat) {
2220
+ const k = sp.hat.k, c = cmat(sp.hat.c);
2221
+ HATS.find((hat) => hat.kinds.includes(k))?.apply({ THREE, PI, Hd, add, Bx, cmat, cap, c, k, sp });
2222
+ }
2223
+ const arms = [];
2224
+ const glove = (sp.ex || []).find((e) => e[0] === "gloves");
2225
+ for (const s of [1, -1]) {
2226
+ const g = new THREE.Group();
2227
+ g.position.set(s * (fem ? 0.285 : 0.305), 1.475, 0);
2228
+ U.add(g);
2229
+ const sl = sp.top.sl;
2230
+ const upM = sl === "tank" ? skinM : topM, foreM = sl === "long" ? topM : skinM;
2231
+ const dl = ball(g, sl === "short" ? 0.09 : 0.083, upM, 0, -0.015, 0);
2232
+ dl.scale.set(1.08, 1.2, 1.05);
2233
+ mlimb(g, sl === "short" ? [0.085, 0.089, 0.086, 0.08, 0.076] : [0.074, 0.08, 0.078, 0.066, 0.06], 0.31, upM);
2234
+ const bi = ball(g, 0.042, upM, 0, -0.13, 0.026);
2235
+ bi.scale.set(1, 1.7, 1);
2236
+ const el = new THREE.Group();
2237
+ el.position.y = -0.31;
2238
+ g.add(el);
2239
+ ball(el, 0.06, foreM);
2240
+ mlimb(el, [0.06, 0.067, 0.06, 0.05, 0.044], 0.29, foreM);
2241
+ const hg = new THREE.Group();
2242
+ hg.position.y = -0.3;
2243
+ el.add(hg);
2244
+ const handM = glove ? cmat(glove[1]) : skinM;
2245
+ const palm = ball(hg, 0.048, handM, 0, -0.035, 0);
2246
+ palm.scale.set(0.6, 1.05, 1);
2247
+ const { fingers, th } = buildHand({ THREE, add, hg, handM, s, detailed });
2248
+ const mount = new THREE.Group();
2249
+ mount.position.set(0, -0.04, 0.015);
2250
+ hg.add(mount);
2251
+ g.rotation.z = s * 0.07;
2252
+ arms.push({ g, el, hg, mount, s, fingers, th });
2253
+ }
2254
+ for (const e of sp.ex || []) {
2255
+ const [k, c] = e;
2256
+ const m = c !== void 0 ? cmat(c) : null;
2257
+ EXTRAS[k]?.({ THREE, PI, U, Hd, root, add, Bx, cmat, ring, torsoGeo, legs, arms, fem, topM, m, e, k, headG, fig, upper, skinM, sp, splat, R, ball, cap, hc });
2258
+ }
2259
+ if (sp.back === "shotgun") {
2260
+ const g = new THREE.Group();
2261
+ g.position.set(0, 1.22, -0.2 - (exHas("backpack") ? 0.22 : 0));
2262
+ g.rotation.z = 0.75;
2263
+ U.add(g);
2264
+ add(g, Bx(0.05, 0.9, 0.05), cmat(1842463, { metalness: 0.6, roughness: 0.45 }), 0, 0.15, 0);
2265
+ add(g, Bx(0.07, 0.36, 0.09), cmat(6961692), 0, -0.45, 0);
2266
+ add(g, Bx(0.06, 0.22, 0.07), cmat(6961692), 0, 0.15, 0.03);
2267
+ add(U, Bx(0.04, 0.9, 0.02), cmat(3811866), 0, 1.22, 0.175, 0, 0, -0.75);
2268
+ }
2269
+ function item(kind, mount) {
2270
+ if (!kind) return;
2271
+ const g = new THREE.Group();
2272
+ mount.add(g);
2273
+ const metal = cmat(9080982, { metalness: 0.7, roughness: 0.35 }), dark = cmat(1842463, { metalness: 0.6, roughness: 0.45 }), woodM = cmat(6961692);
2274
+ HAND_ITEMS[kind]?.({ THREE, PI, add, Bx, cmat, g, metal, dark, woodM });
2275
+ }
2276
+ item(sp.hand, arms[0].mount);
2277
+ item(sp.lhand, arms[1].mount);
2278
+ if (sp.hand) arms[0].hold = true;
2279
+ if (sp.lhand) arms[1].hold = true;
2280
+ const zb = [];
2281
+ for (let i = 0; i < 4; i++) splat(U, -0.15 + R() * 0.3, 1.2 + R() * 0.25, 0.17, 0.035 + R() * 0.025, 3874068, 3, zb);
2282
+ {
2283
+ const mb = stainM(9046538, { roughness: 0.1, metalness: 0.15 });
2284
+ splat(Hd, 0, 1.672, 0.152, 0.026, 9046538, 2, zb, "skin");
2285
+ const st = add(Hd, new THREE.CylinderGeometry(35e-4, 25e-4, 0.05, 6).translate(0, -0.025, 0), mb, 0.03, 1.676, 0.15);
2286
+ zb.push(st);
2287
+ const dg = add(Hd, new THREE.SphereGeometry(7e-3, 8, 6), mb, 0.03, 1.626, 0.15);
2288
+ dg.userData.zdrop = 1;
2289
+ zb.push(dg);
2290
+ }
2291
+ zb.forEach((b) => b.visible = false);
2292
+ const tag = (o, seg) => o.traverse((m) => {
2293
+ if (m.isMesh && m.userData.seg === void 0) m.userData.seg = seg;
2294
+ });
2295
+ arms.forEach((a, k) => {
2296
+ tag(a.mount, "item");
2297
+ tag(a.hg, "hand" + k);
2298
+ tag(a.el, "fArm" + k);
2299
+ tag(a.g, "uArm" + k);
2300
+ a.g.children[0].userData.joint = 1;
2301
+ a.el.children[0].userData.joint = 1;
2302
+ });
2303
+ legs.forEach((l, k) => {
2304
+ tag(l.kn, "shin" + k);
2305
+ tag(l.g, "thigh" + k);
2306
+ l.kn.children[0].userData.joint = 1;
2307
+ });
2308
+ tag(headG, "head");
2309
+ tag(U, "torso");
2310
+ tag(fig, "pelvis");
2311
+ const SIDE2 = ["esquerdo", "direito"], LSIDE = ["direita", "esquerda"];
2312
+ const segs = { head: { node: headG, label: "Cabe\xE7a", hp: 40, r: 0.07, flip: 1 }, torso: { label: "Tronco", hp: 120 }, pelvis: { label: "Quadril", hp: 90 } };
2313
+ arms.forEach((a, k) => {
2314
+ segs["uArm" + k] = { node: a.g, label: "Bra\xE7o " + SIDE2[k], hp: 60, r: 0.085, arm: k, kind: "shoulder" };
2315
+ segs["fArm" + k] = { node: a.el, label: "Antebra\xE7o " + SIDE2[k], hp: 45, r: 0.065, arm: k, kind: "elbow" };
2316
+ segs["hand" + k] = { node: a.hg, label: "M\xE3o " + SIDE2[k], hp: 25, r: 0.05, arm: k, kind: "wrist" };
2317
+ });
2318
+ legs.forEach((l, k) => {
2319
+ segs["thigh" + k] = { node: l.g, label: "Coxa " + LSIDE[k], hp: 80, r: 0.1, leg: k };
2320
+ segs["shin" + k] = { node: l.kn, label: "Canela e p\xE9 " + LSIDE[k], hp: 55, r: 0.08, leg: k };
2321
+ });
2322
+ for (const k in segs) {
2323
+ segs[k].max = segs[k].hp;
2324
+ segs[k].state = "ok";
2325
+ }
2326
+ const mouth = add(Hd, Bx(0.1, 0.04, 0.02), cmat(3803656), 0, 1.69, 0.145);
2327
+ mouth.visible = false;
2328
+ let dog = null;
2329
+ if (sp.dog) {
2330
+ dog = new THREE.Group();
2331
+ dog.position.set(0.78, 0, 0.3);
2332
+ dog.rotation.y = -0.5;
2333
+ root.add(dog);
2334
+ const dm = cmat(11565630), dl = cmat(14200954);
2335
+ const body = add(dog, new THREE.CylinderGeometry(0.12, 0.11, 0.56, 16).rotateX(PI / 2), dm, 0, 0.42, 0);
2336
+ body.scale.set(1, 1.05, 1);
2337
+ ball(dog, 0.12, dm, 0, 0.43, 0.26);
2338
+ const hd = ball(dog, 0.1, dm, 0, 0.6, 0.38);
2339
+ add(dog, new THREE.CylinderGeometry(0.045, 0.055, 0.12, 16).rotateX(PI / 2), dl, 0, 0.57, 0.5);
2340
+ add(dog, Bx(0.03, 0.03, 0.02), cmat(1118481), 0, 0.585, 0.565);
2341
+ for (const s of [-1, 1]) {
2342
+ add(dog, new THREE.ConeGeometry(0.035, 0.09, 5), dm, s * 0.06, 0.7, 0.36, 0, 0, s * -0.3);
2343
+ }
2344
+ const dlegs = [];
2345
+ for (const a of [-1, 1]) for (const b of [-1, 1]) {
2346
+ const lg = new THREE.Group();
2347
+ lg.position.set(a * 0.075, 0.36, b * 0.21);
2348
+ dog.add(lg);
2349
+ limb(lg, 0.035, 0.028, 0.34, dm);
2350
+ dlegs.push({ lg, ph: a * b > 0 ? 0 : PI });
2351
+ }
2352
+ const tail = new THREE.Group();
2353
+ tail.position.set(0, 0.48, -0.28);
2354
+ dog.add(tail);
2355
+ add(tail, new THREE.CylinderGeometry(0.02, 0.012, 0.24, 16).rotateX(PI / 2).translate(0, 0, -0.1), dm, 0, 0, 0, 0.6, 0, 0);
2356
+ dog.userData = { tail, dlegs };
2357
+ }
2358
+ root.traverse((o) => {
2359
+ if (o.isMesh) o.userData.idx = idx;
2360
+ });
2361
+ return { mv: Object.assign({}, MOVES_IDLE), phase: 0, turnA: 0, segs, dmg: { leg: 0, dead: 0 }, pends: [], limp: [0, 0], skirt: !!sp.pants.skirt, root, fig, upper, headG, arms, legs, skinM, base: new THREE.Color(sp.skin), eyeM, bags, zb, mouth, dog, hunch: sp.hunch || 0, ph: R() * 6 };
2362
+ }
2363
+ const MOVES_IDLE = MOVES.idle;
2364
+ function pose(c, z, W, t) {
2365
+ const p = c.ph + t, freq = z ? 3.4 : 5.6, ph = t * freq + c.ph;
2366
+ const breathe = reduce ? 0 : Math.sin(p * 1.6) * 0.012;
2367
+ c.upper.scale.y = 1 + breathe;
2368
+ const hunch = c.hunch + (z ? 0.38 : 0) + W * 0.05;
2369
+ c.upper.rotation.x = hunch;
2370
+ c.upper.rotation.y = W * Math.sin(ph) * 0.1;
2371
+ c.upper.rotation.z = z ? Math.sin(ph * 0.5) * 0.06 * W + 0.05 : 0;
2372
+ c.fig.position.y = -W * Math.abs(Math.sin(ph)) * 0.035;
2373
+ c.headG.rotation.y = reduce ? 0 : Math.sin(p * 0.5) * 0.25 * (z ? 0.3 : 1) * (W ? 0.4 : 1) * (t ? 1 : 0);
2374
+ c.headG.rotation.x = -hunch * 0.6 + (z ? 0.15 : 0);
2375
+ c.headG.rotation.z = z ? 0.22 : 0;
2376
+ c.legs.forEach((l, k) => {
2377
+ const phi = ph + (k ? Math.PI : 0);
2378
+ const amp = z && k === 0 ? 0.18 : c.skirt ? 0.3 : 0.42;
2379
+ l.g.rotation.x = -W * Math.sin(phi) * amp + (z && !W ? k ? -0.1 : 0.08 : 0);
2380
+ l.kn.rotation.x = W * (Math.max(0, Math.cos(phi)) * (z && k === 0 ? 0.25 : 0.85) + 0.06) + (W ? 0 : 0.03);
2381
+ });
2382
+ c.arms.forEach((a) => {
2383
+ const raise = z ? 1 : 0;
2384
+ const base = a.hold ? -0.2 : 0;
2385
+ const swing = -a.s * Math.sin(ph) * (z ? 0.12 : 0.42) * W * (a.grip ? 0 : 1);
2386
+ a.g.rotation.x = (a.grip && !z ? -0.75 : base - raise * (z ? 1.12 : 1.3)) + swing + (z && !reduce && t ? Math.sin(p * 3 + a.s * 2) * 0.08 : 0);
2387
+ a.g.rotation.z = a.s * (0.07 + (z ? 0.1 : 0)) + (a.grip && !z ? -a.s * 0.35 : 0);
2388
+ a.el.rotation.x = z ? -0.18 : a.grip ? -1.1 : -(0.12 + (a.hold ? 0.25 : 0) + W * (0.25 + Math.max(0, a.s * Math.sin(ph)) * 0.3));
2389
+ if (a.fingers) {
2390
+ const gr = (a.hold || a.grip ? 1.25 : 0.4) * (z ? 0.5 : 1);
2391
+ a.fingers.forEach((f, j) => f.rotation.z = -a.s * gr * (j % 2 ? 1.1 : 0.9));
2392
+ a.th.rotation.z = -a.s * (0.45 + gr * 0.25);
2393
+ a.hg.rotation.x = reduce ? 0 : -W * Math.sin(ph - 0.6) * 0.15 * a.s;
2394
+ }
2395
+ });
2396
+ if (c.dog && !reduce && t) {
2397
+ const d = c.dog.userData;
2398
+ d.tail.rotation.y = Math.sin(t * 9) * 0.5;
2399
+ d.dlegs.forEach((l) => {
2400
+ l.lg.rotation.x = W * Math.sin(t * 7 + l.ph) * 0.45;
2401
+ });
2402
+ }
2403
+ }
2404
+ function zombify(c, cat) {
2405
+ c.skinM.color.copy(c.base).lerp(ZSKIN2, 1);
2406
+ c.eyeM.color.set(EYE[cat || "lento"]);
2407
+ c.zb.forEach((b) => b.visible = true);
2408
+ c.mouth.visible = true;
2409
+ c.bags.material.opacity = 0.9;
2410
+ }
2411
+ function buildPerson(p, zed) {
2412
+ const root = new THREE.Group(), chars = [];
2413
+ if (p.b) {
2414
+ const c = build(p.b, 0);
2415
+ chars.push(c);
2416
+ root.add(c.root);
2417
+ if (zed && !p.kid) zombify(c, p.zcat);
2418
+ c.z = zed && !p.kid;
2419
+ }
2420
+ (p.kids || []).forEach((k) => {
2421
+ const c = build(k, 0);
2422
+ c.root.position.x = k.x;
2423
+ c.root.rotation.y = -k.x * 0.4;
2424
+ c.z = false;
2425
+ chars.push(c);
2426
+ root.add(c.root);
2427
+ });
2428
+ chars.forEach((c) => pose(c, c.z, 0, 0));
2429
+ return { root, chars };
2430
+ }
2431
+ return { build, pose, zombify, buildPerson };
2432
+ };
2433
+
2434
+ // src/eyes.ts
2435
+ var GLOW_SIZE = 64;
2436
+ var createGlowTexture = (three, createCanvas) => {
2437
+ const make = createCanvas ?? (typeof document === "undefined" ? void 0 : () => document.createElement("canvas"));
2438
+ if (!make) return null;
2439
+ const canvas = make();
2440
+ canvas.width = canvas.height = GLOW_SIZE;
2441
+ const context = canvas.getContext("2d");
2442
+ if (!context) return null;
2443
+ const gradient = context.createRadialGradient(GLOW_SIZE / 2, GLOW_SIZE / 2, 0, GLOW_SIZE / 2, GLOW_SIZE / 2, GLOW_SIZE / 2);
2444
+ gradient.addColorStop(0, "rgba(255,255,255,1)");
2445
+ gradient.addColorStop(0.35, "rgba(255,255,255,.45)");
2446
+ gradient.addColorStop(1, "rgba(255,255,255,0)");
2447
+ context.fillStyle = gradient;
2448
+ context.fillRect(0, 0, GLOW_SIZE, GLOW_SIZE);
2449
+ return new three.CanvasTexture(canvas);
2450
+ };
2451
+ var createEye = (three, { color, radius = 0.02, glow, glowOpacity = 0.55, texture = null }) => {
2452
+ const eye = new three.Group();
2453
+ const ball = new three.Mesh(new three.SphereGeometry(radius, 10, 8), new three.MeshBasicMaterial({ color }));
2454
+ ball.userData.eye = 1;
2455
+ eye.add(ball);
2456
+ const size = glow ?? radius * 5;
2457
+ if (size > 0) {
2458
+ const sprite = new three.Sprite(new three.SpriteMaterial({ map: texture, color, transparent: true, blending: three.AdditiveBlending, depthWrite: false, opacity: glowOpacity }));
2459
+ sprite.scale.setScalar(size);
2460
+ sprite.userData.eyeGlow = 1;
2461
+ eye.add(sprite);
2462
+ }
2463
+ return eye;
2464
+ };
2465
+ var isEye = (object) => object.userData.eye === 1 || object.userData.eye === true;
2466
+ var findEyes = (three, root) => {
2467
+ const markable = [];
2468
+ const glows = [];
2469
+ const base = /* @__PURE__ */ new Map();
2470
+ root.traverse((object) => {
2471
+ const mesh = object;
2472
+ if (mesh.isMesh && isEye(object)) markable.push(mesh);
2473
+ const sprite = object;
2474
+ if (sprite.isSprite && sprite.material.blending === three.AdditiveBlending) glows.push(sprite);
2475
+ });
2476
+ const remember = (material) => {
2477
+ if (material.color && !base.has(material)) base.set(material, { color: material.color.clone(), opacity: material.opacity });
2478
+ };
2479
+ markable.forEach((mesh) => remember(mesh.material));
2480
+ glows.forEach((sprite) => remember(sprite.material));
2481
+ const setBrightness = (level) => {
2482
+ const factor = Math.min(1, Math.max(0, level));
2483
+ base.forEach((rest, material) => {
2484
+ const target = material;
2485
+ target.color.copy(rest.color).multiplyScalar(factor);
2486
+ if (material.isSpriteMaterial) material.opacity = rest.opacity * factor;
2487
+ });
2488
+ };
2489
+ const hidden = [];
2490
+ return {
2491
+ markable,
2492
+ glows,
2493
+ setBrightness,
2494
+ dim: () => setBrightness(0.25),
2495
+ off: () => setBrightness(0),
2496
+ on: () => setBrightness(1),
2497
+ isolate(only) {
2498
+ if (!only) {
2499
+ hidden.splice(0).forEach((object) => object.visible = true);
2500
+ return;
2501
+ }
2502
+ root.traverse((object) => {
2503
+ const mesh = object;
2504
+ if (mesh.isMesh && !isEye(object) && object.visible) {
2505
+ object.visible = false;
2506
+ hidden.push(object);
2507
+ }
2508
+ });
2509
+ }
2510
+ };
2511
+ };
2512
+
2513
+ // src/wounds/pendulum.ts
2514
+ var PENDULUM = {
2515
+ /** Spring toward the pose of the body: `-26 (angle + base)`. */
2516
+ spring: 26,
2517
+ /** Damping: `-1.8 v`. */
2518
+ damping: 1.8,
2519
+ /** Angular speed the blow gives the arm. */
2520
+ kick: 6,
2521
+ /** Sway added while the body walks: `sin(2 phase) * 9`. */
2522
+ walkSway: 9,
2523
+ /** Sideways swing of the node: `sin(3 angle) * 0.12`; a shoulder also keeps its side * 0.12. */
2524
+ sideSwing: 0.12,
2525
+ /** The forearm of a broken shoulder hangs at this angle. */
2526
+ forearmHang: -0.05
2527
+ };
2528
+ var createPendulum = (node, kind, side, forearm) => ({
2529
+ node,
2530
+ kind,
2531
+ side,
2532
+ ...forearm ? { forearm } : {},
2533
+ a: node.rotation.x,
2534
+ v: PENDULUM.kick
2535
+ });
2536
+ var stepPendulum = (pendulum, base, dt, walkPhase) => {
2537
+ const acceleration = -PENDULUM.spring * (pendulum.a + base) - PENDULUM.damping * pendulum.v + (walkPhase === void 0 ? 0 : Math.sin(walkPhase * 2) * PENDULUM.walkSway);
2538
+ pendulum.v += acceleration * dt;
2539
+ pendulum.a += pendulum.v * dt;
2540
+ pendulum.node.rotation.x = pendulum.a;
2541
+ pendulum.node.rotation.z = (pendulum.kind === "shoulder" ? pendulum.side * PENDULUM.sideSwing : 0) + Math.sin(pendulum.a * 3) * PENDULUM.sideSwing;
2542
+ if (pendulum.kind === "shoulder" && pendulum.forearm) pendulum.forearm.rotation.x = PENDULUM.forearmHang;
2543
+ };
2544
+
2545
+ // src/wounds/cut.ts
2546
+ var blobRadii = (n, rough, seed, random) => {
2547
+ const phases = [seed * 1.7, seed * 3.1, seed * 5.3, seed * 7.9];
2548
+ const spikes = [];
2549
+ const spikeCount = rough > 0.5 ? Math.floor(random() * 4) : 0;
2550
+ for (let k = 0; k < spikeCount; k++) spikes.push([random() * 6.28, 0.45 + random() * 0.7]);
2551
+ const radii = [];
2552
+ for (let i = 0; i < n; i++) {
2553
+ const angle = i / n * Math.PI * 2;
2554
+ let radius = 1 + 0.16 * Math.sin(2 * angle + phases[0]) + 0.1 * Math.sin(3 * angle + phases[1]) + rough * (0.08 * Math.sin(7 * angle + phases[2]) + 0.05 * Math.sin(13 * angle + phases[3])) + rough * 0.05 * (random() - 0.5);
2555
+ for (const [spikeAngle, length] of spikes) {
2556
+ const distance2 = Math.atan2(Math.sin(angle - spikeAngle), Math.cos(angle - spikeAngle));
2557
+ radius += length * Math.exp(-distance2 * distance2 * 90);
2558
+ }
2559
+ radii.push(Math.max(0.35, radius));
2560
+ }
2561
+ return radii;
2562
+ };
2563
+ var blobGeometry = (three, radii, rx, ry) => {
2564
+ const shape = new three.Shape();
2565
+ radii.forEach((radius, index) => {
2566
+ const angle = index / radii.length * Math.PI * 2;
2567
+ const x = Math.cos(angle) * radius * rx;
2568
+ const y = Math.sin(angle) * radius * ry;
2569
+ if (index) shape.lineTo(x, y);
2570
+ else shape.moveTo(x, y);
2571
+ });
2572
+ return new three.ShapeGeometry(shape);
2573
+ };
2574
+ var createWoundMaterials = (three) => {
2575
+ const wound = new three.MeshStandardMaterial({ color: 9046538, roughness: 0.18, metalness: 0.08 });
2576
+ const bone = new three.MeshStandardMaterial({ color: 15261900, roughness: 0.6 });
2577
+ const meat = new three.MeshStandardMaterial({ color: 4851206, roughness: 0.75, side: three.DoubleSide });
2578
+ const fat = new three.MeshStandardMaterial({ color: 12031338, roughness: 0.7 });
2579
+ const rim = new three.MeshStandardMaterial({ color: 9046538, roughness: 0.3, side: three.DoubleSide });
2580
+ const hole = new three.MeshStandardMaterial({ color: 1180676, roughness: 0.9 });
2581
+ const bruise = new three.MeshStandardMaterial({ color: 4857638, roughness: 0.6, transparent: true, opacity: 0.85 });
2582
+ const flesh = new three.MeshStandardMaterial({ color: 9316906, roughness: 0.55 });
2583
+ const all = [wound, bone, meat, fat, rim, hole, bruise, flesh];
2584
+ return { wound, bone, meat, fat, rim, hole, bruise, flesh, dispose: () => all.forEach((material) => material.dispose()) };
2585
+ };
2586
+ var makeCut = (three, materials, random, group, radius, up) => {
2587
+ const s = up ? 1 : -1;
2588
+ const { PI } = Math;
2589
+ const rim = new three.Mesh(blobGeometry(three, blobRadii(30, 1.4, random() * 10, random), radius * 1.12, radius * 1.12).rotateX(-s * PI / 2), materials.rim);
2590
+ rim.position.y = s * 1e-3;
2591
+ group.add(rim);
2592
+ const meat = new three.Mesh(blobGeometry(three, blobRadii(24, 0.6, random() * 10, random), radius * 0.9, radius * 0.9).rotateX(-s * PI / 2), materials.meat);
2593
+ meat.position.y = s * 4e-3;
2594
+ group.add(meat);
2595
+ const fat = new three.Mesh(new three.TorusGeometry(radius * 0.86, radius * 0.06, 6, 22), materials.fat);
2596
+ fat.rotation.x = PI / 2;
2597
+ fat.position.y = s * 5e-3;
2598
+ group.add(fat);
2599
+ const bone = new three.Mesh(new three.CylinderGeometry(radius * 0.26, radius * 0.3, radius * 0.7, 9), materials.bone);
2600
+ bone.position.set(radius * 0.12, s * radius * 0.3, 0);
2601
+ bone.rotation.z = 0.12;
2602
+ group.add(bone);
2603
+ const tip = new three.Mesh(new three.ConeGeometry(radius * 0.26, radius * 0.35, 7), materials.bone);
2604
+ tip.position.set(radius * 0.16, s * radius * 0.75, 0);
2605
+ tip.rotation.set(up ? 0 : PI, 0, 0.3);
2606
+ group.add(tip);
2607
+ for (let k = 0; k < 5; k++) {
2608
+ const angle = k / 5 * PI * 2 + random() * 0.6;
2609
+ const flap = new three.Mesh(new three.ConeGeometry(radius * 0.16, radius * (0.35 + random() * 0.35), 4), k % 2 ? materials.rim : materials.meat);
2610
+ flap.position.set(Math.cos(angle) * radius * 0.98, s * radius * 0.12, Math.sin(angle) * radius * 0.98);
2611
+ if (!up) flap.rotation.x = PI;
2612
+ group.add(flap);
2613
+ }
2614
+ const strands = [];
2615
+ for (let k = 0; k < 2; k++) {
2616
+ const angle = random() * PI * 2;
2617
+ const length = radius * (0.5 + random() * 0.6);
2618
+ const strand = new three.Mesh(new three.CylinderGeometry(radius * 0.05, radius * 0.03, length, 6).translate(0, -length / 2, 0), materials.rim);
2619
+ strand.position.set(Math.cos(angle) * radius, 0, Math.sin(angle) * radius);
2620
+ group.add(strand);
2621
+ const end = new three.Object3D();
2622
+ end.position.set(Math.cos(angle) * radius, -length, Math.sin(angle) * radius);
2623
+ group.add(end);
2624
+ strands.push(end);
2625
+ }
2626
+ group.traverse((object) => {
2627
+ object.userData.stump = 1;
2628
+ if (object.isMesh) object.castShadow = true;
2629
+ });
2630
+ return strands;
2631
+ };
2632
+ var makeStump = (three, materials, random, parent, position, radius, flip = false) => {
2633
+ const group = new three.Group();
2634
+ group.position.copy(position);
2635
+ if (flip) group.rotation.x = Math.PI;
2636
+ parent.add(group);
2637
+ return { group, strands: makeCut(three, materials, random, group, radius, false) };
2638
+ };
2639
+ var ringWound = (three, materials, parent, radius, y = 0) => {
2640
+ const group = new three.Group();
2641
+ group.position.y = y;
2642
+ parent.add(group);
2643
+ const torus = new three.Mesh(new three.TorusGeometry(radius, 0.022, 8, 18), materials.wound);
2644
+ torus.rotation.x = Math.PI / 2;
2645
+ group.add(torus);
2646
+ for (let k = 0; k < 3; k++) {
2647
+ const strip = new three.Mesh(new three.CylinderGeometry(8e-3, 8e-3, 0.12, 6), materials.wound);
2648
+ strip.position.set((k - 1) * 0.03, 0.03, 0.02);
2649
+ group.add(strip);
2650
+ }
2651
+ group.traverse((object) => object.userData.stump = 1);
2652
+ return group;
2653
+ };
2654
+
2655
+ // src/wounds/flying.ts
2656
+ var GRAVITY2 = 9.8;
2657
+ var FLOOR_OFFSET = 0.08;
2658
+ var BOUNCE_LIMIT = 1.2;
2659
+ var BOUNCE_VERTICAL = -0.25;
2660
+ var BOUNCE_HORIZONTAL = 0.5;
2661
+ var BOUNCE_SPIN = 0.4;
2662
+ var LAUNCH = { sideways: 1.6, up: 2.6, forward: 0.6, spin: [9, 6, 9] };
2663
+ var createFlyingPieces = (three, { random, groundAt = () => 0, onRest, max = 40 }) => {
2664
+ const pieces = [];
2665
+ const quaternion = new three.Quaternion();
2666
+ const euler = new three.Euler();
2667
+ const trim = () => {
2668
+ while (pieces.length > max) pieces.shift()?.object.removeFromParent();
2669
+ };
2670
+ const add = (object, velocity, spin, tag) => {
2671
+ const piece = { object, velocity, spin, rest: false, ...tag ? { tag } : {} };
2672
+ pieces.push(piece);
2673
+ trim();
2674
+ return piece;
2675
+ };
2676
+ return {
2677
+ pieces,
2678
+ launch(source, scene, direction, dress, tag) {
2679
+ source.updateWorldMatrix(true, true);
2680
+ const clone = source.clone(true);
2681
+ source.matrixWorld.decompose(clone.position, clone.quaternion, clone.scale);
2682
+ scene.add(clone);
2683
+ dress?.(clone);
2684
+ return add(
2685
+ clone,
2686
+ new three.Vector3(direction.x * LAUNCH.sideways, LAUNCH.up, direction.z * LAUNCH.sideways + LAUNCH.forward),
2687
+ new three.Vector3((random() - 0.5) * LAUNCH.spin[0], (random() - 0.5) * LAUNCH.spin[1], (random() - 0.5) * LAUNCH.spin[2]),
2688
+ tag
2689
+ );
2690
+ },
2691
+ throwObject: add,
2692
+ update(dt) {
2693
+ for (const piece of pieces) {
2694
+ if (piece.rest) continue;
2695
+ piece.velocity.y -= GRAVITY2 * dt;
2696
+ piece.object.position.addScaledVector(piece.velocity, dt);
2697
+ quaternion.setFromEuler(euler.set(piece.spin.x * dt, piece.spin.y * dt, piece.spin.z * dt));
2698
+ piece.object.quaternion.multiply(quaternion);
2699
+ const ground = groundAt(piece.object.position.x, piece.object.position.z) + FLOOR_OFFSET;
2700
+ if (piece.object.position.y >= ground) continue;
2701
+ piece.object.position.y = ground;
2702
+ if (Math.abs(piece.velocity.y) > BOUNCE_LIMIT) {
2703
+ piece.velocity.y *= BOUNCE_VERTICAL;
2704
+ piece.velocity.x *= BOUNCE_HORIZONTAL;
2705
+ piece.velocity.z *= BOUNCE_HORIZONTAL;
2706
+ piece.spin.multiplyScalar(BOUNCE_SPIN);
2707
+ } else {
2708
+ piece.rest = true;
2709
+ const yaw = euler.setFromQuaternion(piece.object.quaternion).y;
2710
+ piece.object.rotation.set(Math.PI / 2 * (random() < 0.5 ? 1 : -1), yaw, 0);
2711
+ piece.object.position.y = ground;
2712
+ onRest?.(piece, piece.object.position);
2713
+ }
2714
+ }
2715
+ },
2716
+ clear() {
2717
+ pieces.splice(0).forEach((piece) => piece.object.removeFromParent());
2718
+ }
2719
+ };
2720
+ };
2721
+
2722
+ // src/wounds/marks.ts
2723
+ var starGeometry = (three, random, radius, points) => {
2724
+ const outline = [];
2725
+ for (let i = 0; i < points * 2; i++) {
2726
+ const angle = i / (points * 2) * Math.PI * 2 + (random() - 0.5) * 0.25;
2727
+ const r = i % 2 ? radius * (0.3 + random() * 0.2) : radius * (0.7 + random() * 0.6);
2728
+ outline.push(new three.Vector2(Math.cos(angle) * r, Math.sin(angle) * r));
2729
+ }
2730
+ return new three.ShapeGeometry(new three.Shape(outline));
2731
+ };
2732
+ var woundDecal = (three, materials, segment, point, normal, radius) => {
2733
+ const decal = new three.Mesh(new three.CircleGeometry(radius * 0.62, 14), materials.bruise);
2734
+ segment.add(decal);
2735
+ decal.position.copy(segment.worldToLocal(point.clone().addScaledVector(normal, 2e-3)));
2736
+ decal.lookAt(point.clone().addScaledVector(normal, 1));
2737
+ const rim = new three.Mesh(new three.RingGeometry(radius * 0.2, radius * 0.34, 12), materials.wound);
2738
+ rim.position.z = 3e-4;
2739
+ decal.add(rim);
2740
+ const hole = new three.Mesh(new three.CircleGeometry(radius * 0.22, 10), materials.hole);
2741
+ hole.position.z = 5e-4;
2742
+ decal.add(hole);
2743
+ decal.userData.wound = 1;
2744
+ return decal;
2745
+ };
2746
+ var exitWound = (three, materials, random, segment, point, direction, damage) => {
2747
+ const meshes = [];
2748
+ segment.traverse((object) => {
2749
+ if (object.isMesh && !object.userData.wound && !object.userData.stump) meshes.push(object);
2750
+ });
2751
+ const raycaster = new three.Raycaster(point.clone().addScaledVector(direction, 0.6), direction.clone().negate(), 0, 0.6);
2752
+ const hit = raycaster.intersectObjects(meshes)[0];
2753
+ if (!hit?.face) return void 0;
2754
+ const normal = hit.face.normal.clone().transformDirection(hit.object.matrixWorld);
2755
+ const radius = 0.012 + Math.min(0.035, damage * 28e-5);
2756
+ const star = new three.Mesh(starGeometry(three, random, radius, 5 + (Math.floor(random() * 3) | 0)), materials.flesh);
2757
+ segment.add(star);
2758
+ star.position.copy(segment.worldToLocal(hit.point.clone().addScaledVector(normal, 2e-3)));
2759
+ star.lookAt(hit.point.clone().addScaledVector(normal, 1));
2760
+ star.rotateZ(random() * 6);
2761
+ star.userData.wound = 1;
2762
+ const inner = new three.Mesh(starGeometry(three, random, radius * 0.45, 4), materials.hole);
2763
+ inner.position.z = 5e-4;
2764
+ star.add(inner);
2765
+ return { point: hit.point.clone(), normal, radius };
2766
+ };
2767
+ var gouge = (three, materials, random, segment, segmentKind, point, normal, direction, damage, fling) => {
2768
+ const count = damage >= 150 ? 7 : damage >= 55 ? 4 : 2;
2769
+ const chunkMaterials = [materials.flesh, materials.bruise, materials.flesh, materials.bone, materials.meat];
2770
+ for (let i = 0; i < count; i++) {
2771
+ const size = 0.012 + random() * (damage >= 150 ? 0.04 : 0.022);
2772
+ const chunk = new three.Mesh(new three.BoxGeometry(size, size * (0.6 + random() * 0.6), size * (0.6 + random() * 0.6)), chunkMaterials[i % 5]);
2773
+ chunk.position.copy(point);
2774
+ const velocity = direction.clone().multiplyScalar(0.8 + random() * 1.5).add(normal.clone().multiplyScalar(0.6 + random())).add(new three.Vector3((random() - 0.5) * 0.8, random() * 0.8, (random() - 0.5) * 0.8));
2775
+ fling(chunk, velocity, new three.Vector3(random() * 14, random() * 14, random() * 14));
2776
+ }
2777
+ const radius = 0.018 + Math.min(0.04, damage * 25e-5);
2778
+ const crater = new three.Mesh(new three.CircleGeometry(radius, 12), materials.hole);
2779
+ segment.add(crater);
2780
+ crater.position.copy(segment.worldToLocal(point.clone().addScaledVector(normal, 3e-3)));
2781
+ crater.lookAt(point.clone().addScaledVector(normal, 1));
2782
+ const rim = new three.Mesh(new three.RingGeometry(radius, radius * 1.5, 12), materials.flesh);
2783
+ rim.position.z = -5e-4;
2784
+ crater.add(rim);
2785
+ if (["upperArm", "forearm", "thigh", "shin"].includes(segmentKind) && damage >= 55) {
2786
+ const splinter = new three.Mesh(new three.CylinderGeometry(6e-3, 6e-3, 0.03, 6), materials.bone);
2787
+ splinter.rotation.x = Math.PI / 2;
2788
+ splinter.position.z = -4e-3;
2789
+ crater.add(splinter);
2790
+ }
2791
+ crater.userData.wound = 1;
2792
+ return crater;
2793
+ };
2794
+ var explodeChunks = (three, materials, random, box, direction, fling) => {
2795
+ const centre = box.getCenter(new three.Vector3());
2796
+ const size = box.getSize(new three.Vector3()).length();
2797
+ const chunkMaterials = [materials.flesh, materials.bruise, materials.bone, materials.flesh, materials.meat];
2798
+ for (let i = 0; i < 14; i++) {
2799
+ const chunk = new three.Mesh(new three.BoxGeometry(0.015 + random() * size * 0.18, 0.015 + random() * size * 0.14, 0.015 + random() * size * 0.14), chunkMaterials[i % 5]);
2800
+ chunk.position.copy(centre).add(new three.Vector3((random() - 0.5) * size * 0.4, (random() - 0.5) * size * 0.4, (random() - 0.5) * size * 0.4));
2801
+ const velocity = direction.clone().multiplyScalar(1.5).add(new three.Vector3((random() - 0.5) * 3, random() * 2.5, (random() - 0.5) * 3));
2802
+ fling(chunk, velocity, new three.Vector3(random() * 15, random() * 15, random() * 15));
2803
+ }
2804
+ return centre;
2805
+ };
2806
+
2807
+ // src/wounds/segment-nodes.ts
2808
+ var RADIUS = { head: 0.07, upperArm: 0.085, forearm: 0.065, hand: 0.05, thigh: 0.1, shin: 0.08 };
2809
+ var SIDE = (x) => x >= 0 ? "L" : "R";
2810
+ var woundBodyOf = (root) => {
2811
+ const rig = root.userData.rig;
2812
+ if (!rig) return void 0;
2813
+ const nodes = {};
2814
+ rig.arms.forEach((arm, index) => {
2815
+ const side = SIDE(arm.g.position.x);
2816
+ nodes[`upperArm${side}`] = { node: arm.g, radius: RADIUS.upperArm, arm: { index, joint: "shoulder" } };
2817
+ nodes[`forearm${side}`] = { node: arm.el, radius: RADIUS.forearm, arm: { index, joint: "elbow" } };
2818
+ });
2819
+ rig.legs.forEach((leg, index) => {
2820
+ const side = SIDE(leg.g.position.x);
2821
+ nodes[`thigh${side}`] = { node: leg.g, radius: RADIUS.thigh, leg: index };
2822
+ nodes[`shin${side}`] = { node: leg.kn, radius: RADIUS.shin, leg: index };
2823
+ });
2824
+ nodes.head = { node: rig.headG, radius: RADIUS.head, flip: true };
2825
+ return { root, rig, nodes };
2826
+ };
2827
+
2828
+ // src/wounds/wounds.ts
2829
+ import { segmentsBelow } from "@adatechnology/game-characters";
2830
+ var worldPosition = (three, object) => object.getWorldPosition(new three.Vector3());
2831
+ var createWounds = (three, options) => {
2832
+ const { random, scene, bridge } = options;
2833
+ const materials = options.materials ?? createWoundMaterials(three);
2834
+ const flying = createFlyingPieces(three, { random, ...options.groundAt ? { groundAt: options.groundAt } : {}, ...options.maxPieces ? { max: options.maxPieces } : {} });
2835
+ const states = /* @__PURE__ */ new Map();
2836
+ const stateOf = (body) => {
2837
+ const found = states.get(body);
2838
+ if (!found) throw new Error("game-characters-three: this body is not tracked by the wounds (call `track` first)");
2839
+ return found;
2840
+ };
2841
+ const burstBlood = (change, object) => {
2842
+ const point = worldPosition(three, object);
2843
+ bridge?.burst(change, [point.x, point.y, point.z]);
2844
+ };
2845
+ const sideOf = (object, body) => new three.Vector3(object.position.x >= 0 ? 1 : -1, 0, 0).applyQuaternion(body.root.quaternion);
2846
+ const hideBelow = (state, id) => {
2847
+ for (const below of segmentsBelow(id)) state.gone.add(below);
2848
+ };
2849
+ const sever = (state, id) => {
2850
+ const target = state.body.nodes[id];
2851
+ if (!target || state.gone.has(id)) {
2852
+ state.gone.add(id);
2853
+ hideBelow(state, id);
2854
+ return;
2855
+ }
2856
+ const { node, radius, flip } = target;
2857
+ const direction = sideOf(node, state.body);
2858
+ const fell = flying.launch(node, scene, direction, (clone) => {
2859
+ makeCut(three, materials, random, clone, radius, true);
2860
+ }, id);
2861
+ void fell;
2862
+ node.visible = false;
2863
+ makeStump(three, materials, random, node.parent, node.position, radius, flip);
2864
+ state.gone.add(id);
2865
+ hideBelow(state, id);
2866
+ for (let i = state.pendulums.length - 1; i >= 0; i--) {
2867
+ const pendulum = state.pendulums[i];
2868
+ let shown = true;
2869
+ for (let o = pendulum.node; o; o = o.parent) if (!o.visible) shown = false;
2870
+ if (!shown) state.pendulums.splice(i, 1);
2871
+ }
2872
+ burstBlood("severed", node);
2873
+ if (id === "head") state.dead = true;
2874
+ };
2875
+ const destroy = (state, id, direction) => {
2876
+ const target = state.body.nodes[id];
2877
+ if (!target || state.gone.has(id)) return;
2878
+ const { node, radius, flip } = target;
2879
+ const box = new three.Box3().setFromObject(node);
2880
+ explodeChunks(three, materials, random, box, direction, (chunk, velocity, spin) => {
2881
+ scene.add(chunk);
2882
+ flying.throwObject(chunk, velocity, spin, id);
2883
+ });
2884
+ node.visible = false;
2885
+ makeStump(three, materials, random, node.parent, node.position, radius, flip);
2886
+ state.gone.add(id);
2887
+ hideBelow(state, id);
2888
+ burstBlood("destroyed", node);
2889
+ if (id === "head") state.dead = true;
2890
+ };
2891
+ const breakBone = (state, id) => {
2892
+ const target = state.body.nodes[id];
2893
+ if (!target || state.gone.has(id)) return;
2894
+ if (target.arm) {
2895
+ const { index, joint } = target.arm;
2896
+ const arm = state.body.rig.arms[index];
2897
+ const node = joint === "shoulder" ? arm.g : arm.el;
2898
+ ringWound(three, materials, node, joint === "shoulder" ? 0.078 : 0.064, joint === "shoulder" ? -0.06 : -0.01);
2899
+ const kept = state.pendulums.filter((pendulum) => pendulum.node !== node);
2900
+ state.pendulums.length = 0;
2901
+ state.pendulums.push(...kept, createPendulum(node, joint, arm.s ?? 1, joint === "shoulder" ? arm.el : void 0));
2902
+ }
2903
+ if (target.leg !== void 0) state.limp[target.leg] = true;
2904
+ };
2905
+ const split = (state, direction) => {
2906
+ const upper = state.body.rig.upper;
2907
+ if (!upper.visible) return;
2908
+ flying.launch(upper, scene, direction, (clone) => {
2909
+ makeCut(three, materials, random, clone, 0.16, false);
2910
+ }, "torso");
2911
+ upper.visible = false;
2912
+ makeStump(three, materials, random, upper.parent, upper.position, 0.16);
2913
+ for (const id of ["torso", "neck", "head", "jaw", "upperArmL", "upperArmR", "forearmL", "forearmR", "handL", "handR"]) state.gone.add(id);
2914
+ state.pendulums.length = 0;
2915
+ state.dead = true;
2916
+ burstBlood("split", upper);
2917
+ };
2918
+ const direction3 = (v, fallback) => v ? new three.Vector3(v[0], v[1], v[2]).normalize() : fallback;
2919
+ return {
2920
+ materials,
2921
+ flying,
2922
+ track(body) {
2923
+ states.set(body, { body, pendulums: [], limp: [false, false], gone: /* @__PURE__ */ new Set(), dead: false, crawling: false });
2924
+ return body;
2925
+ },
2926
+ untrack: (body) => void states.delete(body),
2927
+ handle(body, event) {
2928
+ const state = stateOf(body);
2929
+ if (event.type === "leak") {
2930
+ bridge?.handle(event);
2931
+ return;
2932
+ }
2933
+ if (event.type === "hole") {
2934
+ const target = body.nodes[event.part];
2935
+ const node = target?.node ?? body.root;
2936
+ const point = new three.Vector3(...event.worldPoint);
2937
+ const centre = worldPosition(three, node);
2938
+ const normal = point.clone().sub(centre).normalize();
2939
+ if (normal.lengthSq() < 0.5) normal.set(0, 0, 1);
2940
+ woundDecal(three, materials, node, point, normal, 0.014);
2941
+ if (event.exitPoint) {
2942
+ const exit = new three.Vector3(...event.exitPoint);
2943
+ const through = exit.clone().sub(point).normalize();
2944
+ exitWound(three, materials, random, node, exit, through, 40);
2945
+ }
2946
+ return;
2947
+ }
2948
+ if (event.type !== "body") return;
2949
+ const id = event.part;
2950
+ const fallback = new three.Vector3(0, 0.2, 1);
2951
+ const direction = direction3(event.exit, fallback);
2952
+ switch (event.change) {
2953
+ case "severed":
2954
+ if (id) sever(state, id);
2955
+ break;
2956
+ case "destroyed":
2957
+ if (id) destroy(state, id, direction);
2958
+ break;
2959
+ case "broken":
2960
+ if (id) breakBone(state, id);
2961
+ break;
2962
+ case "split":
2963
+ split(state, direction);
2964
+ break;
2965
+ case "limp":
2966
+ if (id) breakBone(state, id);
2967
+ break;
2968
+ case "crawl":
2969
+ state.crawling = true;
2970
+ break;
2971
+ case "killed":
2972
+ state.dead = true;
2973
+ break;
2974
+ case "wound": {
2975
+ const target = id ? body.nodes[id] : void 0;
2976
+ if (target && event.exit) gouge(three, materials, random, target.node, id.replace(/[LR]$/, ""), worldPosition(three, target.node), direction.clone().negate(), direction, 60, (chunk, velocity, spin) => {
2977
+ scene.add(chunk);
2978
+ flying.throwObject(chunk, velocity, spin, id);
2979
+ });
2980
+ break;
2981
+ }
2982
+ default:
2983
+ break;
2984
+ }
2985
+ },
2986
+ setWalkPhase(body, phase) {
2987
+ const state = stateOf(body);
2988
+ if (phase === void 0) delete state.walkPhase;
2989
+ else state.walkPhase = phase;
2990
+ },
2991
+ goneOf: (body) => stateOf(body).gone,
2992
+ update(dt) {
2993
+ flying.update(dt);
2994
+ for (const state of states.values()) {
2995
+ const { rig } = state.body;
2996
+ for (const pendulum of state.pendulums) {
2997
+ const arm = rig.arms.find((candidate) => candidate.g === pendulum.node || candidate.el === pendulum.node);
2998
+ const base = pendulum.kind === "shoulder" ? rig.upper.rotation.x + rig.fig.rotation.x : arm.g.rotation.x + rig.upper.rotation.x + rig.fig.rotation.x;
2999
+ stepPendulum(pendulum, base, dt, state.walkPhase);
3000
+ }
3001
+ }
3002
+ },
3003
+ applyLimp(body) {
3004
+ const state = stateOf(body);
3005
+ const bothLost = state.limp[0] && state.limp[1];
3006
+ state.limp.forEach((hurt, index) => {
3007
+ if (!hurt || bothLost) return;
3008
+ const leg = body.rig.legs[index];
3009
+ leg.g.rotation.x *= 0.3;
3010
+ leg.kn.rotation.x = 0.05;
3011
+ body.rig.upper.rotation.z += index ? -0.07 : 0.07;
3012
+ });
3013
+ },
3014
+ dispose() {
3015
+ flying.clear();
3016
+ materials.dispose();
3017
+ states.clear();
3018
+ }
3019
+ };
3020
+ };
3021
+
3022
+ // src/wounds/fluids-bridge.ts
3023
+ var BURST_LITERS = { severed: 0.35, destroyed: 0.6, split: 0.9 };
3024
+ var createFluidsBridge = ({ sink, liquid = "blood", spurtSpeed = 1.4 }) => {
3025
+ const bleed = (point, litersPerSecond, dt, direction = [0, -1, 0], pressurized = false) => {
3026
+ const liters = litersPerSecond * dt;
3027
+ if (liters <= 0) return;
3028
+ const speed = pressurized ? spurtSpeed : 0.15;
3029
+ sink.splash(liquid, point, liters, [direction[0] * speed, direction[1] * speed, direction[2] * speed]);
3030
+ };
3031
+ return {
3032
+ bleed,
3033
+ handle(event) {
3034
+ if (event.type === "leak" && event.fluid === "blood") bleed(event.point, event.rate, 1, event.direction, event.pressurized);
3035
+ },
3036
+ burst: (change, point) => sink.splash(liquid, point, BURST_LITERS[change])
3037
+ };
3038
+ };
3039
+
3040
+ // src/test-area/module.ts
3041
+ import { createCharacters, createRng as createRng3 } from "@adatechnology/game-characters";
3042
+
3043
+ // src/test-area/crowd.ts
3044
+ import { encodePoseHeight } from "@adatechnology/game-characters";
3045
+ var FIELD = { x0: 12, x1: 30, z0: -16, z1: 16 };
3046
+ var STRIDE = 1;
3047
+ var createCrowd = (three, builders, types, eyes, random) => {
3048
+ const group = new three.Group();
3049
+ const time = { value: 0 };
3050
+ let variants = [];
3051
+ let byType = /* @__PURE__ */ new Map();
3052
+ let batches = [];
3053
+ let members = [];
3054
+ let bakeMs = 0;
3055
+ const ensure = () => {
3056
+ if (variants.length > 0) return;
3057
+ const started = typeof performance === "undefined" ? Date.now() : performance.now();
3058
+ const baked = bakeVariants(three, builders, types);
3059
+ bakeMs = (typeof performance === "undefined" ? Date.now() : performance.now()) - started;
3060
+ variants = baked.variants;
3061
+ byType = baked.byType;
3062
+ batches = variants.map((variant) => createVariantBatches(three, variant, time));
3063
+ for (const set of batches) for (const batch of set) group.add(batch.mesh);
3064
+ };
3065
+ const color = (hex2) => new three.Color(hex2);
3066
+ return {
3067
+ group,
3068
+ spawn(count) {
3069
+ ensure();
3070
+ for (let i = 0; i < count; i++) {
3071
+ const type = types[Math.floor(random() * types.length)];
3072
+ const pool = byType.get(type.id);
3073
+ members.push({ variant: pool[Math.floor(random() * pool.length)], x: FIELD.x0 + random() * (FIELD.x1 - FIELD.x0), z: FIELD.z0 + random() * (FIELD.z1 - FIELD.z0), yaw: random() * Math.PI * 2, phase: random(), speed: 0.6 + random() * 0.8 });
3074
+ }
3075
+ },
3076
+ clear() {
3077
+ members = [];
3078
+ for (const set of batches) for (const batch of set) {
3079
+ batch.begin();
3080
+ batch.end();
3081
+ }
3082
+ },
3083
+ update(dt, camera) {
3084
+ time.value += dt;
3085
+ if (variants.length === 0) return;
3086
+ for (const set of batches) for (const batch of set) batch.begin();
3087
+ const pose = encodePoseHeight("walk", 0);
3088
+ for (const member of members) {
3089
+ member.x += Math.sin(member.yaw) * member.speed * dt;
3090
+ member.z += Math.cos(member.yaw) * member.speed * dt;
3091
+ if (member.x < FIELD.x0 || member.x > FIELD.x1) member.yaw = -member.yaw;
3092
+ if (member.z < FIELD.z0 || member.z > FIELD.z1) member.yaw = Math.PI - member.yaw;
3093
+ const variant = variants[member.variant];
3094
+ const distance2 = Math.hypot(member.x - camera.position.x, member.z - camera.position.z);
3095
+ const level = lodAt(distance2, LOD_DISTANCES);
3096
+ const eye = color(eyes[variant.typeId] ?? 14926657);
3097
+ batches[member.variant][level]?.push({ a: [member.x, member.z, member.yaw, member.phase], b: [variant.s, variant.bulk, STRIDE, pose], c: [0, 0.4, 1, member.phase], color: { r: 0.4, g: 0.45, b: 0.5 }, eye });
3098
+ }
3099
+ for (const set of batches) for (const batch of set) batch.end();
3100
+ },
3101
+ get count() {
3102
+ return members.length;
3103
+ },
3104
+ get bakeMs() {
3105
+ return bakeMs;
3106
+ },
3107
+ get drawCalls() {
3108
+ return batches.reduce((sum, set) => sum + set.filter((batch) => batch.count > 0).length, 0);
3109
+ },
3110
+ get triangles() {
3111
+ return batches.reduce((sum, set, index) => sum + set.reduce((inner, batch, level) => inner + batch.count * ((variants[index]?.verts[level] ?? 0) / 3), 0), 0);
3112
+ },
3113
+ trianglesPerLevel(typeId) {
3114
+ const first = byType.get(typeId)?.[0];
3115
+ return first === void 0 ? [] : variants[first].verts.map((verts) => verts / 3);
3116
+ }
3117
+ };
3118
+ };
3119
+
3120
+ // src/test-area/hits.ts
3121
+ var HIT_PRESETS = [
3122
+ { id: "9x19", hit: { type: "ballistic", energy: 500, mass: 7.5, caliber: 9, caliberId: "9x19" } },
3123
+ { id: "12ga_3", hit: { type: "ballistic", energy: 3e3, mass: 32, caliber: 8.4, pellets: 9, distance: 3, caliberId: "12ga_00buck" } },
3124
+ { id: "7_62x39", hit: { type: "ballistic", energy: 2100, mass: 7.9, caliber: 7.62, caliberId: "7.62x39" } },
3125
+ { id: "308", hit: { type: "ballistic", energy: 3504, mass: 9.7, caliber: 7.8, caliberId: "308" } },
3126
+ { id: "5_56x45", hit: { type: "ballistic", energy: 1800, mass: 4, caliber: 5.56, caliberId: "5.56x45" } },
3127
+ { id: "machete", hit: { type: "slash", energy: 120 } },
3128
+ { id: "axe", hit: { type: "slash", energy: 220 } },
3129
+ { id: "m67_1", hit: { type: "explosive", energy: 6e3, radius: 15 }, range: 1 },
3130
+ { id: "m67_3", hit: { type: "explosive", energy: 6e3, radius: 15 }, range: 3 },
3131
+ { id: "m67_6", hit: { type: "explosive", energy: 6e3, radius: 15 }, range: 6 }
3132
+ ];
3133
+ var hitPresetOf = (id) => HIT_PRESETS.find((preset2) => preset2.id === id) ?? HIT_PRESETS[0];
3134
+ var hitFor = (preset2, origin, y, known) => {
3135
+ const { caliberId, ...rest } = preset2.hit;
3136
+ const withId = caliberId !== void 0 && known.has(caliberId) ? { ...rest, caliberId } : rest;
3137
+ if (preset2.hit.type === "explosive") return { ...withId, point: [origin[0] + (preset2.range ?? 3), origin[1] + 0.2, origin[2]] };
3138
+ return { ...withId, point: [origin[0], origin[1] + y, origin[2] + 0.4], direction: [0, 0, -1] };
3139
+ };
3140
+
3141
+ // src/test-area/person-spec.ts
3142
+ var hex = (value) => parseInt(value, 16);
3143
+ var personSpecOf = (profession) => {
3144
+ const { look } = profession;
3145
+ const spec = {
3146
+ skin: hex(look.skin),
3147
+ hair: { s: look.hair.style, c: hex(look.hair.color) },
3148
+ top: { c: hex(look.top.color), sl: look.top.sleeve, ...look.top.plaid ? { plaid: hex(look.top.plaid) } : {} },
3149
+ pants: { c: hex(look.pants.color) },
3150
+ shoes: hex(look.shoes),
3151
+ ex: profession.accessories.map((accessory) => accessory.color ? [accessory.id, hex(accessory.color)] : [accessory.id])
3152
+ };
3153
+ if (profession.female) spec.fem = 1;
3154
+ if (look.hat) spec.hat = { k: look.hat.kind, c: hex(look.hat.color) };
3155
+ if (look.face && look.face.length > 0) spec.face = [...look.face];
3156
+ if (profession.hand) spec.hand = profession.hand;
3157
+ if (profession.leftHand) spec.lhand = profession.leftHand;
3158
+ return spec;
3159
+ };
3160
+
3161
+ // src/test-area/scenarios.ts
3162
+ import { SEGMENT_IDS } from "@adatechnology/game-characters";
3163
+ var LIMB_SEGMENTS = ["upperArmL", "forearmL", "handL", "upperArmR", "forearmR", "handR", "thighL", "shinL", "footL", "thighR", "shinR", "footR"];
3164
+ var BENCHMARK_BODIES = 1e3;
3165
+ var preset = (id) => HIT_PRESETS.find((entry) => entry.id === id);
3166
+ var bodyChanges = (events) => events.flatMap((event) => event.type === "body" ? [`${event.change}${event.part ? `:${event.part}` : ""}`] : []);
3167
+ var createScenarios = (deps) => {
3168
+ const person = (extra = {}, seed = 1) => deps.kit().create({ seed, kind: "person", ...extra });
3169
+ const shoot = (character, id, part) => {
3170
+ character.hit({ ...hitFor(preset(id), [0, 0, 0], 1.3, deps.kit().calibers), part });
3171
+ };
3172
+ const scenario = (id, run) => ({ id, name: deps.text[id] ?? id, run });
3173
+ return [
3174
+ scenario("region_x_caliber", ({ expect }) => {
3175
+ for (const ammo of ["9x19", "308"]) {
3176
+ for (const part of ["head", "torso", "upperArmL", "thighL"]) {
3177
+ const character = person();
3178
+ shoot(character, ammo, part);
3179
+ expect((character.lastHit?.points ?? 0) > 0, `${ammo} on ${part} does damage`, { points: character.lastHit?.points });
3180
+ }
3181
+ }
3182
+ const head = person();
3183
+ shoot(head, "308", "head");
3184
+ expect(!head.alive, "a .308 to the head kills");
3185
+ }),
3186
+ scenario("cut_at_every_joint", ({ expect }) => {
3187
+ for (const part of LIMB_SEGMENTS) {
3188
+ const character = person();
3189
+ character.hit({ type: "slash", point: [0, 1, 0], part, points: 100 });
3190
+ const segment = character.segments.find((entry) => entry.id === part);
3191
+ expect(segment?.status === "severed" || segment?.status === "destroyed", `${part} is cut off`, { status: segment?.status });
3192
+ }
3193
+ expect(LIMB_SEGMENTS.every((part) => SEGMENT_IDS.includes(part)), "every joint is a segment of the body");
3194
+ }),
3195
+ scenario("broken_bone_dangles", ({ expect }) => {
3196
+ const character = person();
3197
+ const changes = [];
3198
+ for (let blow = 0; blow < 3; blow++) changes.push(...bodyChanges(character.hit({ type: "blunt", point: [0, 1.2, 0], part: "forearmL", points: 20 })));
3199
+ expect(changes.some((change) => change.startsWith("broken")), "three blows break the forearm", { changes });
3200
+ expect(character.segments.find((entry) => entry.id === "forearmL")?.attached === true, "the broken forearm stays attached (it dangles)");
3201
+ }),
3202
+ scenario("leg_limp_crawl", ({ expect }) => {
3203
+ const character = person();
3204
+ expect(bodyChanges(character.hit({ type: "blunt", point: [0, 0.5, 0], part: "thighL", points: 40 })).includes("limp"), "a hurt leg limps");
3205
+ expect(bodyChanges(character.hit({ type: "slash", point: [0, 0.5, 0], part: "thighL", points: 40 })).includes("crawl"), "a lost thigh crawls");
3206
+ }),
3207
+ scenario("head_kill", ({ expect }) => {
3208
+ const character = person();
3209
+ shoot(character, "9x19", "head");
3210
+ expect(!character.alive, "a 9x19 to the head kills");
3211
+ const torso = person();
3212
+ shoot(torso, "9x19", "torso");
3213
+ expect(torso.alive, "a 9x19 to the torso does not");
3214
+ }),
3215
+ scenario("grenade_by_radius", ({ expect }) => {
3216
+ const lost = (id) => {
3217
+ const character = person();
3218
+ character.hit(hitFor(preset(id), [0, 0, 0], 1, deps.kit().calibers));
3219
+ return character.segments.reduce((sum, segment) => sum + (segment.max - segment.hp), 0);
3220
+ };
3221
+ const [near, middle, far] = [lost("m67_1"), lost("m67_3"), lost("m67_6")];
3222
+ expect(near > middle && middle >= far, "the nearer the blast, the more it takes", { near, middle, far });
3223
+ }),
3224
+ scenario("penetration_line", ({ expect }) => {
3225
+ const kit = deps.kit();
3226
+ const targets = Array.from({ length: 5 }, (_, index) => ({ character: person({}, index + 1), part: "torso", distance: 5 + index * 1.2 }));
3227
+ const result = kit.penetrateLine({ energyAt: () => 3504, mass: 9.7, caliber: 7.8, caliberId: "308" }, targets);
3228
+ expect(result.hits.length >= 1, "the bullet hits the first body", { hits: result.hits.length });
3229
+ const energies = result.hits.map((hit) => hit.energy);
3230
+ expect(energies.every((energy, index) => index === 0 || energy <= energies[index - 1]), "it keeps less energy at each body", { energies });
3231
+ }),
3232
+ scenario("age_0_3", ({ expect }) => {
3233
+ const THREE = deps.three();
3234
+ if (!THREE) return expect(false, "the area is not attached to a room");
3235
+ deps.ensureBuilders(THREE);
3236
+ const colors = (decay) => {
3237
+ const root = deps.buildZombie(deps.typeNames()[0], decay);
3238
+ const seen = /* @__PURE__ */ new Set();
3239
+ root.traverse((object) => {
3240
+ const material = object.material;
3241
+ if (material?.color) seen.add(material.color.getHexString());
3242
+ });
3243
+ return [...seen].sort().join(",");
3244
+ };
3245
+ const fresh = colors(0);
3246
+ expect(fresh.length > 0, "a fresh body is built");
3247
+ expect(colors(3) !== fresh, "a decrepit body looks different from a fresh one");
3248
+ }),
3249
+ scenario("vest_and_helmet", ({ expect }) => {
3250
+ const bare = person();
3251
+ const dressed = person({ protection: ["vest_police", "helmet_combat"] });
3252
+ shoot(bare, "9x19", "torso");
3253
+ shoot(dressed, "9x19", "torso");
3254
+ expect((dressed.lastHit?.points ?? 0) < (bare.lastHit?.points ?? 0), "the vest takes points off a torso hit", { bare: bare.lastHit?.points, dressed: dressed.lastHit?.points });
3255
+ shoot(dressed, "9x19", "head");
3256
+ expect(dressed.alive, "the helmet stops a 9x19 to the head", { absorbedBy: dressed.lastHit?.absorbedBy });
3257
+ }),
3258
+ scenario("bite_to_turn", ({ expect }) => {
3259
+ const undead = deps.undead();
3260
+ if (!undead?.infect) return expect(false, "no undead pack in this area");
3261
+ for (const age of ["adult", "child"]) {
3262
+ const victim = person({ age }, 9);
3263
+ const infection = undead.infect("bite", () => 0);
3264
+ expect(infection !== void 0, `a bite infects (${age})`);
3265
+ if (!infection) continue;
3266
+ infection.advance(1e3);
3267
+ expect(infection.reanimated, `the case ends in reanimation (${age})`);
3268
+ const turned = undead.turn(victim);
3269
+ expect(turned.alive && undead.traitsOf(turned) !== void 0, `the ${age} turns into a zombie`, { type: undead.traitsOf(turned)?.type });
3270
+ }
3271
+ }),
3272
+ scenario("benchmark_1000", ({ expect }) => {
3273
+ const THREE = deps.three();
3274
+ if (!THREE) return expect(false, "the area is not attached to a room");
3275
+ const crowd = deps.crowd();
3276
+ crowd.spawn(BENCHMARK_BODIES);
3277
+ expect(crowd.count >= BENCHMARK_BODIES, `${BENCHMARK_BODIES} bodies are in the field`, { count: crowd.count });
3278
+ const levels = crowd.trianglesPerLevel(deps.undead() ? "walker" : "");
3279
+ levels.forEach((triangles, level) => {
3280
+ const budget = deps.triangleBudget[level];
3281
+ if (level === levels.length - 1 && deps.triangleBudgetSoftFar) expect(true, `level ${level} body has ${Math.round(triangles)} triangles (budget ${budget})`, { triangles, budget, over: triangles > budget });
3282
+ else expect(triangles <= budget, `level ${level} body is inside ${budget} triangles`, { triangles });
3283
+ });
3284
+ crowd.clear();
3285
+ })
3286
+ ];
3287
+ };
3288
+
3289
+ // src/test-area/text.ts
3290
+ var PRESETS_EN = { "9x19": "9x19 mm", "12ga_3": "12 ga 00 at 3 m", "7_62x39": "7.62x39", "308": ".308", "5_56x45": "5.56x45", machete: "Machete", axe: "Axe", m67_1: "M67 at 1 m", m67_3: "M67 at 3 m", m67_6: "M67 at 6 m" };
3291
+ var PRESETS_PT = { "9x19": "9x19 mm", "12ga_3": "12 ga 00 a 3 m", "7_62x39": "7,62x39", "308": ".308", "5_56x45": "5,56x45", machete: "Fac\xE3o", axe: "Machado", m67_1: "M67 a 1 m", m67_3: "M67 a 3 m", m67_6: "M67 a 6 m" };
3292
+ var TEXT = {
3293
+ en: {
3294
+ group: "Characters",
3295
+ name: "Characters area",
3296
+ bodies: {},
3297
+ child: "Child",
3298
+ elder: "Elder",
3299
+ controls: { decay: "Decay", age: "Age group", penetration: "Penetration model", death_rule: "Death rule", detail: "Damage detail", moment: "Moment", pose: "Pose", cascade: "Cascade", hit_preset: "Hit", protection: "Protection", path: "Drawing path" },
3300
+ hints: {
3301
+ decay: "0 fresh .. 3 decrepit: skin, blood and cloth of bodies built from now on",
3302
+ penetration: "physical: P = sqrt(E m / A). team-game: the points of the game. Applies to bodies created from now on",
3303
+ death_rule: "vitality-or-head (the game) or head-only (the golden rule of the pages)",
3304
+ detail: "segments: the 17 parts of the near path. regions: the 6 regions of the crowd",
3305
+ moment: "The speed of the selected zombie at that moment, in the readout",
3306
+ hit_preset: "What the Fire hit button does to the body you look at",
3307
+ protection: "Worn by the bodies created from now on"
3308
+ },
3309
+ actions: { heal: "Heal the aimed body", kill: "Kill the aimed body", infect: "Bite the aimed person (accelerated)", spawn_100: "Spawn 100 in the crowd", spawn_1000: "Spawn 1000 in the crowd", fire_preset: "Fire the hit" },
3310
+ options: {
3311
+ penetration: { physical: "Physical", "team-game": "Team game" },
3312
+ death_rule: { "vitality-or-head": "Vitality or head", "head-only": "Head only" },
3313
+ detail: { segments: "Segments", regions: "Regions" },
3314
+ moment: { wander: "Wander", alert: "Alert", chase: "Chase", sprint: "Sprint", frenzy: "Frenzy", limp: "Limping", crawl: "Crawling" },
3315
+ pose: { walk: "Walk", stand: "Stand" },
3316
+ protection: { none: "None", vest_police: "Police vest", vest_military: "Military vest", helmet_combat: "Helmet", turnout_gear: "Firefighter gear", vest_helmet: "Vest and helmet" },
3317
+ path: { rich: "Rich", lod0: "Instanced LOD 0", lod1: "Instanced LOD 1", lod2: "Instanced LOD 2" },
3318
+ age: { child: "Child", young: "Young", adult: "Adult", elder: "Elder" },
3319
+ hit_preset: PRESETS_EN
3320
+ },
3321
+ layers: { capsules: "Hit capsules", joints: "Joints", bleed: "Bleed emitters", eyes_only: "Eyes only", speed: "Speed vectors" },
3322
+ metrics: { bodies: "Bodies", instances: "Instances", triangles: "Triangles", draw_calls: "Draw calls", bake_ms: "Bake time", speed: "Speed of the selected", vitality: "Vitality", bleed: "Bleed rate", segments_hp: "Segments hit points", last_crossed: "Bodies crossed by the last shot", last_energy: "Energy kept" },
3323
+ scenarios: {
3324
+ region_x_caliber: "Region x calibre",
3325
+ cut_at_every_joint: "A cut at every joint",
3326
+ broken_bone_dangles: "A broken bone dangles",
3327
+ leg_limp_crawl: "Leg: limp, then crawl",
3328
+ head_kill: "Head kills",
3329
+ grenade_by_radius: "Grenade by radius",
3330
+ penetration_line: "Penetration through a line of 5",
3331
+ age_0_3: "Decay 0 to 3",
3332
+ vest_and_helmet: "Vest and helmet",
3333
+ bite_to_turn: "Bite, fever, delirium, turn (child too)",
3334
+ benchmark_1000: "1000 bodies against the triangle budget"
3335
+ },
3336
+ log: { crossed: "Hit: {bodies} bodies crossed, {energy} of the energy kept", turned: "{from} turned into {to}" }
3337
+ },
3338
+ "pt-BR": {
3339
+ group: "Personagens",
3340
+ name: "\xC1rea de personagens",
3341
+ bodies: {},
3342
+ child: "Crian\xE7a",
3343
+ elder: "Idoso",
3344
+ controls: { decay: "Decomposi\xE7\xE3o", age: "Faixa de idade", penetration: "Modelo de penetra\xE7\xE3o", death_rule: "Regra de morte", detail: "Detalhe do dano", moment: "Momento", pose: "Pose", cascade: "Cascata", hit_preset: "Golpe", protection: "Prote\xE7\xE3o", path: "Caminho de desenho" },
3345
+ hints: {
3346
+ decay: "0 fresco .. 3 decr\xE9pito: pele, sangue e roupa dos corpos feitos daqui em diante",
3347
+ penetration: "physical: P = raiz(E m / A). team-game: os pontos do jogo. Vale para os corpos criados daqui em diante",
3348
+ death_rule: "vitality-or-head (o jogo) ou head-only (a regra de ouro das p\xE1ginas)",
3349
+ detail: "segments: as 17 partes do caminho de perto. regions: as 6 regi\xF5es da multid\xE3o",
3350
+ moment: "A velocidade do zumbi selecionado nesse momento, no mostrador",
3351
+ hit_preset: "O que o bot\xE3o Disparar faz no corpo para o qual voc\xEA olha",
3352
+ protection: "Vestida pelos corpos criados daqui em diante"
3353
+ },
3354
+ actions: { heal: "Curar o corpo mirado", kill: "Matar o corpo mirado", infect: "Morder a pessoa mirada (acelerado)", spawn_100: "P\xF4r 100 na multid\xE3o", spawn_1000: "P\xF4r 1000 na multid\xE3o", fire_preset: "Disparar o golpe" },
3355
+ options: {
3356
+ penetration: { physical: "F\xEDsica", "team-game": "Do jogo" },
3357
+ death_rule: { "vitality-or-head": "Vitalidade ou cabe\xE7a", "head-only": "S\xF3 a cabe\xE7a" },
3358
+ detail: { segments: "Segmentos", regions: "Regi\xF5es" },
3359
+ moment: { wander: "Vagando", alert: "Alerta", chase: "Persegui\xE7\xE3o", sprint: "Disparada", frenzy: "Frenesi", limp: "Mancando", crawl: "Rastejando" },
3360
+ pose: { walk: "Andar", stand: "Parado" },
3361
+ protection: { none: "Nenhuma", vest_police: "Colete policial", vest_military: "Colete militar", helmet_combat: "Capacete", turnout_gear: "Roupa de bombeiro", vest_helmet: "Colete e capacete" },
3362
+ path: { rich: "Rico", lod0: "Instanciado LOD 0", lod1: "Instanciado LOD 1", lod2: "Instanciado LOD 2" },
3363
+ age: { child: "Crian\xE7a", young: "Jovem", adult: "Adulto", elder: "Idoso" },
3364
+ hit_preset: PRESETS_PT
3365
+ },
3366
+ layers: { capsules: "C\xE1psulas de acerto", joints: "Juntas", bleed: "Pontos de sangramento", eyes_only: "S\xF3 os olhos", speed: "Vetores de velocidade" },
3367
+ metrics: { bodies: "Corpos", instances: "Inst\xE2ncias", triangles: "Tri\xE2ngulos", draw_calls: "Chamadas de desenho", bake_ms: "Tempo de assar", speed: "Velocidade do selecionado", vitality: "Vitalidade", bleed: "Taxa de sangramento", segments_hp: "Vida dos segmentos", last_crossed: "Corpos atravessados pelo \xFAltimo tiro", last_energy: "Energia que ficou" },
3368
+ scenarios: {
3369
+ region_x_caliber: "Regi\xE3o x calibre",
3370
+ cut_at_every_joint: "Um corte em cada junta",
3371
+ broken_bone_dangles: "Osso quebrado balan\xE7a",
3372
+ leg_limp_crawl: "Perna: manca, depois rasteja",
3373
+ head_kill: "A cabe\xE7a mata",
3374
+ grenade_by_radius: "Granada por raio",
3375
+ penetration_line: "Penetra\xE7\xE3o em fila de 5",
3376
+ age_0_3: "Decomposi\xE7\xE3o de 0 a 3",
3377
+ vest_and_helmet: "Colete e capacete",
3378
+ bite_to_turn: "Mordida, febre, del\xEDrio, vira (crian\xE7a tamb\xE9m)",
3379
+ benchmark_1000: "1000 corpos contra o or\xE7amento de tri\xE2ngulos"
3380
+ },
3381
+ log: { crossed: "Golpe: {bodies} corpos atravessados, {energy} da energia ficou", turned: "{from} virou {to}" }
3382
+ }
3383
+ };
3384
+
3385
+ // src/test-area/module.ts
3386
+ var CHARACTERS_AREA_ID = "characters_area";
3387
+ var ZOMBIE_ITEM_PREFIX = "zombie_";
3388
+ var PERSON_ITEM_PREFIX = "person_";
3389
+ var LANE_ITEM_COUNT = 5;
3390
+ var GAIT_MOMENT_STRIDE = 1.4;
3391
+ var DEFAULT_TRIANGLE_BUDGET = [2500, 800, 250];
3392
+ var MOMENTS_WITH_LIMP = /* @__PURE__ */ new Set(["limp", "crawl"]);
3393
+ function createCharactersTestModule(options = {}) {
3394
+ const locale = options.locale ?? "pt-BR";
3395
+ const text = TEXT[locale] ?? TEXT["pt-BR"];
3396
+ const undeadTypes = options.undeadTypes ?? [];
3397
+ const professions = options.professions ?? [];
3398
+ const settings = { decay: 0, age: "adult", penetration: "physical", deathRule: "vitality-or-head", detail: "segments", moment: "wander", pose: "walk", cascade: true, hit: "9x19", protection: "none" };
3399
+ const makeKit = () => options.createKit ? options.createKit({ penetration: settings.penetration, deathRule: settings.deathRule }) : createCharacters({ ...options.packs ? { packs: options.packs } : {}, penetration: settings.penetration, damage: { deathRule: settings.deathRule } });
3400
+ let kit = makeKit();
3401
+ let undead = options.createUndead ? options.createUndead(kit) : null;
3402
+ const random = createRng3(options.seed ?? 1);
3403
+ let seedCounter = 100;
3404
+ let host = null;
3405
+ let three = null;
3406
+ let best = null;
3407
+ let people = null;
3408
+ let wounds = null;
3409
+ let crowd = null;
3410
+ let selected = null;
3411
+ let time = 0;
3412
+ let lastCrossed = 0;
3413
+ let lastEnergy = 1;
3414
+ const entries = /* @__PURE__ */ new Map();
3415
+ const builtOf = /* @__PURE__ */ new WeakMap();
3416
+ const overlays = /* @__PURE__ */ new Map();
3417
+ const gaits = Object.fromEntries(undeadTypes.map((card) => [card.name["pt-BR"] ?? card.id, card.gait]));
3418
+ const animator = createRigAnimator({ gaits });
3419
+ const typeName = (card) => card.name["pt-BR"] ?? card.id;
3420
+ const eyeOf = (card) => parseInt(card.eye.color, 16);
3421
+ const ensureBuilders = (THREE) => {
3422
+ three = THREE;
3423
+ best ?? (best = createBestBuilder(THREE, { types: undeadTypes.map((card) => ({ name: typeName(card), eye: eyeOf(card) })), random }));
3424
+ people ?? (people = createPersonBuilder(THREE, { random, createCanvas: () => document.createElement("canvas") }));
3425
+ };
3426
+ const nextSeed = () => seedCounter += 1;
3427
+ const protectionList = () => settings.protection === "none" ? [] : settings.protection === "vest_helmet" ? ["vest_police", "helmet_combat"] : [settings.protection];
3428
+ const createCharacter = (kind, cardId, id) => {
3429
+ const seed = nextSeed();
3430
+ const common = { seed, id, age: settings.age, protection: protectionList(), detail: settings.detail };
3431
+ if (kind === "zombie" && undead) return undead.create(common, { type: cardId });
3432
+ return kit.create({ ...common, kind: kind === "zombie" ? cardId : "person", ...kind === "person" ? { preset: cardId } : {} });
3433
+ };
3434
+ const trackEntry = (entry) => {
3435
+ if (entry.body) wounds?.track(entry.body);
3436
+ };
3437
+ const wrap = (entry, character) => {
3438
+ const syncTransform = () => {
3439
+ const root = entry.root;
3440
+ character.setTransform([root.position.x, root.position.y, root.position.z], [root.quaternion.x, root.quaternion.y, root.quaternion.z, root.quaternion.w]);
3441
+ };
3442
+ const draw = (events) => {
3443
+ if (entry.body) for (const event of events) wounds?.handle(entry.body, event);
3444
+ };
3445
+ return Object.create(character, {
3446
+ hit: {
3447
+ value: (hit) => {
3448
+ syncTransform();
3449
+ selected = entry;
3450
+ const events = character.hit(hit);
3451
+ draw(events);
3452
+ const report = character.lastHit;
3453
+ lastCrossed = report?.through ? 2 : 1;
3454
+ lastEnergy = report && "exitEnergy" in report ? report.exitEnergy : lastEnergy;
3455
+ return events;
3456
+ }
3457
+ },
3458
+ update: {
3459
+ value: (dt) => {
3460
+ syncTransform();
3461
+ const events = character.update(dt);
3462
+ draw(events);
3463
+ return events;
3464
+ }
3465
+ },
3466
+ reset: {
3467
+ value: () => {
3468
+ character.reset();
3469
+ entry.root.visible = true;
3470
+ }
3471
+ }
3472
+ });
3473
+ };
3474
+ const makeItem = (kind, cardId, name) => ({
3475
+ id: `${kind === "zombie" ? ZOMBIE_ITEM_PREFIX : PERSON_ITEM_PREFIX}${cardId}`,
3476
+ name,
3477
+ movable: false,
3478
+ build: (THREE) => {
3479
+ ensureBuilders(THREE);
3480
+ const seed = nextSeed();
3481
+ if (kind === "zombie") {
3482
+ const card = undeadTypes.find((entry) => entry.id === cardId);
3483
+ const root = best.build({ type: typeName(card), seed, decay: settings.decay });
3484
+ builtOf.set(root, { kind, cardId, built: root });
3485
+ return root;
3486
+ }
3487
+ const profession = professions.find((entry) => entry.id === cardId);
3488
+ const built = people.build(profession ? personSpecOf(profession) : { skin: 12882032, hair: { s: "short", c: 2236962 }, top: { c: 4478310, sl: "long" }, pants: { c: 3355443 }, shoes: 1118481 });
3489
+ if (!built.root.userData.rig) built.root.userData.rig = built;
3490
+ builtOf.set(built.root, { kind, cardId, built });
3491
+ return built.root;
3492
+ },
3493
+ impl: (root, id) => {
3494
+ const object = root;
3495
+ const made = builtOf.get(object);
3496
+ const character = createCharacter(made.kind, made.cardId, id);
3497
+ const scale = character.spec.height / kit.body.heightMetres;
3498
+ if (Math.abs(scale - 1) > 0.01) object.scale.setScalar(scale);
3499
+ const entry = { id, kind: made.kind, cardId: made.cardId, root: object, body: woundBodyOf(object), eyes: three ? findEyes(three, object) : null, built: made.built, character, walk: 0, tilt: 0 };
3500
+ entry.character = wrap(entry, character);
3501
+ entries.set(id, entry);
3502
+ selected ?? (selected = entry);
3503
+ trackEntry(entry);
3504
+ return entry.character;
3505
+ }
3506
+ });
3507
+ const speedOf = (entry) => {
3508
+ const traits = undead?.traitsOf(entry.character);
3509
+ if (!traits || !undead) return entry.kind === "person" ? 1.4 : 0;
3510
+ const moment = settings.moment;
3511
+ return undead.speedFor(traits, { moment: MOMENTS_WITH_LIMP.has(moment) ? "chase" : moment, limp: moment === "limp", crawling: moment === "crawl" || entry.character.limping.length > 1 });
3512
+ };
3513
+ const clearOverlays = () => {
3514
+ for (const object of overlays.values()) object.parent?.remove(object);
3515
+ overlays.clear();
3516
+ };
3517
+ const overlayFor = (layer, entry) => {
3518
+ if (!three) return null;
3519
+ const group = new three.Group();
3520
+ const material = new three.MeshBasicMaterial({ color: layer === "capsules" ? 4513279 : layer === "joints" ? 16772676 : 16729190, wireframe: layer === "capsules", depthTest: false, transparent: true, opacity: 0.8 });
3521
+ if (layer === "capsules") {
3522
+ for (const part of entry.character.describe().parts) {
3523
+ const shape = part.shape;
3524
+ const geometry = shape.kind === "sphere" ? new three.SphereGeometry(shape.radius, 8, 6) : shape.kind === "cylinder" ? new three.CylinderGeometry(shape.radius, shape.radius, shape.height, 8, 1) : new three.BoxGeometry(...shape.size);
3525
+ const mesh = new three.Mesh(geometry, material);
3526
+ mesh.position.set(...part.position);
3527
+ if (part.rotation) mesh.rotation.set(...part.rotation);
3528
+ group.add(mesh);
3529
+ }
3530
+ } else if (layer === "joints") {
3531
+ for (const joint of Object.values(entry.character.rig.joints)) {
3532
+ const mesh = new three.Mesh(new three.SphereGeometry(0.025, 6, 4), material);
3533
+ mesh.position.set(...joint);
3534
+ group.add(mesh);
3535
+ }
3536
+ } else if (layer === "speed") {
3537
+ const length = Math.max(0.1, speedOf(entry));
3538
+ const arrow = new three.ArrowHelper(new three.Vector3(0, 0, 1), new three.Vector3(0, 2.1, 0), length, 6750054);
3539
+ group.add(arrow);
3540
+ } else return null;
3541
+ group.renderOrder = 10;
3542
+ return group;
3543
+ };
3544
+ const refreshLayers = () => {
3545
+ clearOverlays();
3546
+ if (!host?.bench) return;
3547
+ for (const layer of ["capsules", "joints", "speed"]) {
3548
+ if (!host.bench.layer(layer)) continue;
3549
+ for (const entry of entries.values()) {
3550
+ const overlay = overlayFor(layer, entry);
3551
+ if (!overlay) continue;
3552
+ entry.root.add(overlay);
3553
+ overlays.set(`${layer}:${entry.id}`, overlay);
3554
+ }
3555
+ }
3556
+ const eyesOnly = host.bench.layer("eyes_only");
3557
+ for (const entry of entries.values()) entry.eyes?.isolate(eyesOnly);
3558
+ };
3559
+ const animate = (entry, dt) => {
3560
+ const { root, character } = entry;
3561
+ if (!character.alive) {
3562
+ entry.tilt = Math.min(1, entry.tilt + dt * 2);
3563
+ const rig = root.userData.rig;
3564
+ if (rig) rig.fig.rotation.x = -entry.tilt * 1.35;
3565
+ entry.eyes?.off();
3566
+ return;
3567
+ }
3568
+ const speed = settings.pose === "walk" ? speedOf(entry) : 0;
3569
+ entry.walk += dt * speed;
3570
+ if (entry.kind === "person") people.pose(entry.built, false, settings.pose === "walk" ? 1 : 0, time);
3571
+ else if (settings.pose === "walk") {
3572
+ animator.animate(root, time, { speed: speed || GAIT_MOMENT_STRIDE });
3573
+ if (settings.cascade) animator.cascade(root.userData.rig, dt);
3574
+ }
3575
+ if (entry.body) {
3576
+ wounds?.setWalkPhase(entry.body, settings.pose === "walk" ? entry.walk : void 0);
3577
+ wounds?.applyLimp(entry.body);
3578
+ }
3579
+ };
3580
+ const selectedOrFirst = () => selected ?? entries.values().next().value ?? null;
3581
+ const fireOn = (entry) => {
3582
+ const preset2 = hitPresetOf(settings.hit);
3583
+ const p = entry.root.position;
3584
+ entry.character.hit(hitFor(preset2, [p.x, p.y, p.z], 1.3, kit.calibers));
3585
+ };
3586
+ const ensureCrowd = () => {
3587
+ if (crowd) return crowd;
3588
+ const built = three;
3589
+ const builders = {
3590
+ buildBest: (spec, lod) => {
3591
+ ;
3592
+ best.setLOD(lod);
3593
+ return best.build(spec);
3594
+ },
3595
+ topMaterial: () => best.topM()
3596
+ };
3597
+ const wanted = options.crowdTypes ?? ["walker", "runner"];
3598
+ const variantTypes = undeadTypes.map((card, index) => ({ id: card.id, name: typeName(card), index })).filter((type) => wanted.includes(type.id));
3599
+ crowd = createCrowd(built, builders, variantTypes, Object.fromEntries(undeadTypes.map((card) => [card.id, eyeOf(card)])), random);
3600
+ if (host) host.scene.add(crowd.group);
3601
+ return crowd;
3602
+ };
3603
+ const onChange = (change) => {
3604
+ if (change.kind === "layer") {
3605
+ refreshLayers();
3606
+ return;
3607
+ }
3608
+ if (change.kind === "action") {
3609
+ const target = selectedOrFirst();
3610
+ if (change.id === "heal") target?.character.reset();
3611
+ else if (change.id === "kill" && target) target.character.hit({ type: "ballistic", point: [target.root.position.x, 1.7, target.root.position.z + 0.3], direction: [0, 0, -1], part: "head", energy: 3504, mass: 9.7, caliber: 7.8 });
3612
+ else if (change.id === "infect" && target && undead?.infect) {
3613
+ const infection = undead.infect("bite", () => 0);
3614
+ if (infection) {
3615
+ infection.advance(1e3);
3616
+ if (infection.reanimated) target.character = wrap(target, undead.turn(target.character));
3617
+ }
3618
+ } else if (change.id === "fire_preset" && target) fireOn(target);
3619
+ else if (change.id === "spawn_100" || change.id === "spawn_1000") {
3620
+ ensureBuilders(host?.THREE ?? three);
3621
+ ensureCrowd().spawn(change.id === "spawn_100" ? 100 : 1e3);
3622
+ }
3623
+ return;
3624
+ }
3625
+ const value = change.value;
3626
+ switch (change.id) {
3627
+ case "decay":
3628
+ settings.decay = Number(value);
3629
+ break;
3630
+ case "age":
3631
+ settings.age = String(value);
3632
+ break;
3633
+ case "penetration":
3634
+ settings.penetration = String(value);
3635
+ kit = makeKit();
3636
+ undead = options.createUndead ? options.createUndead(kit) : null;
3637
+ break;
3638
+ case "death_rule":
3639
+ settings.deathRule = String(value);
3640
+ kit = makeKit();
3641
+ undead = options.createUndead ? options.createUndead(kit) : null;
3642
+ break;
3643
+ case "detail":
3644
+ settings.detail = String(value);
3645
+ break;
3646
+ case "moment":
3647
+ settings.moment = String(value);
3648
+ refreshLayers();
3649
+ break;
3650
+ case "pose":
3651
+ settings.pose = String(value);
3652
+ break;
3653
+ case "cascade":
3654
+ settings.cascade = Boolean(value);
3655
+ break;
3656
+ case "hit_preset":
3657
+ settings.hit = String(value);
3658
+ break;
3659
+ case "protection":
3660
+ settings.protection = String(value);
3661
+ break;
3662
+ default:
3663
+ break;
3664
+ }
3665
+ };
3666
+ const choice = (id, ids, fallback) => ({
3667
+ kind: "choice",
3668
+ id,
3669
+ name: text.controls[id],
3670
+ options: ids.map((optionId) => ({ id: optionId, name: text.options[id][optionId] ?? optionId })),
3671
+ default: fallback
3672
+ });
3673
+ const withHint = (control, hint) => hint ? { ...control, hint } : control;
3674
+ const scenarioDeps = {
3675
+ text: text.scenarios,
3676
+ kit: () => kit,
3677
+ undead: () => undead,
3678
+ three: () => three ?? host?.THREE,
3679
+ ensureBuilders,
3680
+ buildZombie: (name, decay) => best.build({ type: name, seed: 7, decay }),
3681
+ typeNames: () => undeadTypes.map(typeName),
3682
+ crowd: () => {
3683
+ ensureBuilders(three ?? host?.THREE);
3684
+ return ensureCrowd();
3685
+ },
3686
+ random,
3687
+ triangleBudget: options.triangleBudget ?? DEFAULT_TRIANGLE_BUDGET,
3688
+ triangleBudgetSoftFar: options.triangleBudget === void 0
3689
+ };
3690
+ const lane = () => professions.slice(0, LANE_ITEM_COUNT).map((profession) => `${PERSON_ITEM_PREFIX}${profession.id}`);
3691
+ return {
3692
+ id: CHARACTERS_AREA_ID,
3693
+ name: text.name,
3694
+ group: text.group,
3695
+ get entries() {
3696
+ return entries;
3697
+ },
3698
+ get crowd() {
3699
+ return crowd;
3700
+ },
3701
+ items: () => [
3702
+ ...undeadTypes.map((card) => makeItem("zombie", card.id, card.name[locale] ?? card.id)),
3703
+ ...professions.map((profession) => makeItem("person", profession.id, profession.name[locale] ?? profession.id))
3704
+ ],
3705
+ area: () => {
3706
+ const placements = [];
3707
+ undeadTypes.forEach((card, index) => placements.push({ item: `${ZOMBIE_ITEM_PREFIX}${card.id}`, position: [-6 + index * 1.1, 0, 3], rotationY: Math.PI }));
3708
+ professions.forEach((profession, index) => placements.push({ item: `${PERSON_ITEM_PREFIX}${profession.id}`, position: [-5.45 + index * 1.1, 0, 7], rotationY: Math.PI }));
3709
+ lane().forEach((item, index) => placements.push({ item, position: [0, 0, -3 - index * 1.4], rotationY: 0 }));
3710
+ return { placements, view: { eye: [0, 5.5, 13], target: [0, 0.6, 3] } };
3711
+ },
3712
+ controls: () => [
3713
+ withHint({ kind: "number", id: "decay", name: text.controls.decay, min: 0, max: 3, step: 1, default: 0 }, text.hints.decay),
3714
+ choice("age", ["child", "young", "adult", "elder"], "adult"),
3715
+ withHint(choice("penetration", ["physical", "team-game"], "physical"), text.hints.penetration),
3716
+ withHint(choice("death_rule", ["vitality-or-head", "head-only"], "vitality-or-head"), text.hints.death_rule),
3717
+ withHint(choice("detail", ["segments", "regions"], "segments"), text.hints.detail),
3718
+ withHint(choice("moment", ["wander", "alert", "chase", "sprint", "frenzy", "limp", "crawl"], "wander"), text.hints.moment),
3719
+ choice("pose", ["walk", "stand"], "walk"),
3720
+ { kind: "boolean", id: "cascade", name: text.controls.cascade, default: true },
3721
+ withHint(choice("hit_preset", HIT_PRESETS.map((preset2) => preset2.id), "9x19"), text.hints.hit_preset),
3722
+ withHint(choice("protection", ["none", "vest_police", "vest_military", "helmet_combat", "turnout_gear", "vest_helmet"], "none"), text.hints.protection),
3723
+ { kind: "action", id: "fire_preset", name: text.actions.fire_preset },
3724
+ { kind: "action", id: "heal", name: text.actions.heal },
3725
+ { kind: "action", id: "kill", name: text.actions.kill },
3726
+ { kind: "action", id: "infect", name: text.actions.infect },
3727
+ { kind: "action", id: "spawn_100", name: text.actions.spawn_100 },
3728
+ { kind: "action", id: "spawn_1000", name: text.actions.spawn_1000 }
3729
+ ],
3730
+ layers: () => ["capsules", "joints", "bleed", "eyes_only", "speed"].map((id) => ({ id, name: text.layers[id], default: false })),
3731
+ metrics: () => [
3732
+ { id: "bodies", name: text.metrics.bodies, read: () => entries.size },
3733
+ { id: "instances", name: text.metrics.instances, read: () => crowd?.count ?? 0 },
3734
+ { id: "triangles", name: text.metrics.triangles, max: 25e4, read: () => Math.round(crowd?.triangles ?? 0) },
3735
+ { id: "draw_calls", name: text.metrics.draw_calls, read: () => crowd?.drawCalls ?? 0 },
3736
+ { id: "bake_ms", name: text.metrics.bake_ms, unit: "ms", digits: 0, read: () => crowd?.bakeMs ?? 0 },
3737
+ { id: "speed_now", name: text.metrics.speed, unit: "m/s", digits: 2, read: () => {
3738
+ const entry = selectedOrFirst();
3739
+ return entry ? speedOf(entry) : 0;
3740
+ } },
3741
+ { id: "vitality", name: text.metrics.vitality, read: () => selectedOrFirst()?.character.state().body?.vitality ?? 0 },
3742
+ { id: "bleed_rate", name: text.metrics.bleed, digits: 3, read: () => selectedOrFirst()?.character.bleeders().reduce((sum, bleeder) => sum + (bleeder.rate ?? 0), 0) ?? 0 },
3743
+ { id: "segments_hp", name: text.metrics.segments_hp, read: () => Math.round(selectedOrFirst()?.character.segments.reduce((sum, segment) => sum + segment.hp, 0) ?? 0) },
3744
+ { id: "last_crossed", name: text.metrics.last_crossed, read: () => lastCrossed },
3745
+ { id: "last_energy", name: text.metrics.last_energy, digits: 2, read: () => lastEnergy }
3746
+ ],
3747
+ scenarios: () => createScenarios(scenarioDeps),
3748
+ attach(lent) {
3749
+ host = lent;
3750
+ ensureBuilders(lent.THREE);
3751
+ const scene = lent.scene;
3752
+ const bridge = options.fluids ? createFluidsBridge({ sink: options.fluids }) : void 0;
3753
+ wounds = createWounds(lent.THREE, { random, scene, groundAt: (x, z) => lent.groundAt(x, z), ...bridge ? { bridge } : {} });
3754
+ for (const entry of entries.values()) trackEntry(entry);
3755
+ const stopChange = lent.bench?.onChange(onChange);
3756
+ const stopFrame = lent.onFrame((dt) => {
3757
+ time += dt;
3758
+ for (const [id, entry] of entries) {
3759
+ if (!entry.root.parent) {
3760
+ entries.delete(id);
3761
+ continue;
3762
+ }
3763
+ animate(entry, dt);
3764
+ }
3765
+ wounds?.update(dt);
3766
+ crowd?.update(dt, lent.camera);
3767
+ });
3768
+ return () => {
3769
+ stopChange?.();
3770
+ stopFrame();
3771
+ clearOverlays();
3772
+ wounds?.dispose();
3773
+ wounds = null;
3774
+ if (crowd) {
3775
+ crowd.group.parent?.remove(crowd.group);
3776
+ crowd = null;
3777
+ }
3778
+ host = null;
3779
+ };
3780
+ }
3781
+ };
3782
+ }
3783
+
3784
+ // src/test-area/material.ts
3785
+ var BODY_MATERIAL_DEF = {
3786
+ id: "flesh",
3787
+ density: 1050,
3788
+ hpPerKg: 15,
3789
+ penetration: 1.2,
3790
+ ductility: 0.8,
3791
+ fracture: "tear",
3792
+ fragmentSize: [0.03, 0.1],
3793
+ damageScale: { slash: 1.6 },
3794
+ hitDebris: "drips",
3795
+ breakDebris: "drips",
3796
+ thermal: { ignitionC: 260, flameC: 700, fuelFraction: 0.25, burnRate: 4e-3, conductivity: 0.5, heatCapacity: 3.5, smoke: "grey" },
3797
+ tags: ["organic", "body"],
3798
+ compressive: 1e3
3799
+ };
3800
+ var registerBodyMaterial = (world) => {
3801
+ if (!world.materials.has(BODY_MATERIAL_DEF.id)) world.materials.set(BODY_MATERIAL_DEF.id, BODY_MATERIAL_DEF);
3802
+ };
3803
+ export {
3804
+ BAKE_NEUTRAL_POSE,
3805
+ BODY_CHUNK,
3806
+ BODY_MATERIAL_DEF,
3807
+ CHARACTERS_AREA_ID,
3808
+ EXTRAS,
3809
+ FACE_FEATURES,
3810
+ HAND_ITEMS,
3811
+ HATS,
3812
+ HIT_PRESETS,
3813
+ LOD_BAKE,
3814
+ LOD_BATCH_CAPACITY,
3815
+ LOD_CASTS_SHADOW,
3816
+ LOD_DISTANCES,
3817
+ LOD_SEGMENTS,
3818
+ PENDULUM,
3819
+ PERSON_ITEM_PREFIX,
3820
+ ZOMBIE_ITEM_PREFIX,
3821
+ bakeBody,
3822
+ bakeVariants,
3823
+ blobGeometry,
3824
+ blobRadii,
3825
+ buildInstancedBody,
3826
+ createBestBuilder,
3827
+ createBodyBatch,
3828
+ createBodyChunk,
3829
+ createBodyMaterial,
3830
+ createCharactersTestModule,
3831
+ createEye,
3832
+ createFluidsBridge,
3833
+ createFlyingLimbs,
3834
+ createFlyingPieces,
3835
+ createGlowTexture,
3836
+ createPendulum,
3837
+ createPersonBuilder,
3838
+ createRigAnimator,
3839
+ createVariantBatches,
3840
+ createWoundMaterials,
3841
+ createWounds,
3842
+ exitWound,
3843
+ explodeChunks,
3844
+ findEyes,
3845
+ gouge,
3846
+ jointsOf,
3847
+ lodAt,
3848
+ makeCut,
3849
+ makeStump,
3850
+ registerBodyMaterial,
3851
+ ringWound,
3852
+ starGeometry,
3853
+ stepPendulum,
3854
+ woundBodyOf,
3855
+ woundDecal
3856
+ };
3857
+ //# sourceMappingURL=index.js.map