@adatechnology/game-characters-three 0.7.2 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/chunk-XSMP7T6O.js +4614 -0
- package/dist/chunk-XSMP7T6O.js.map +1 -0
- package/dist/index.d.ts +431 -679
- package/dist/index.global.js +4437 -2194
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +593 -4237
- package/dist/index.js.map +1 -1
- package/dist/testing-CsC-BhNN.d.ts +808 -0
- package/dist/testing.d.ts +5 -0
- package/dist/testing.js +25 -0
- package/dist/testing.js.map +1 -0
- package/package.json +13 -7
|
@@ -0,0 +1,4614 @@
|
|
|
1
|
+
// src/test-area/module.ts
|
|
2
|
+
import { createCharacters, createRng as createRng3 } from "@adatechnology/game-characters";
|
|
3
|
+
|
|
4
|
+
// src/audio/sound.ts
|
|
5
|
+
import { GAME_SFX_BANK } from "@adatechnology/game-audio";
|
|
6
|
+
|
|
7
|
+
// src/audio/recipes.ts
|
|
8
|
+
import { DISTANCE_CURVES, sceneNoise, sceneRecipe, sceneTone, sceneVoice } from "@adatechnology/game-audio";
|
|
9
|
+
var UNDEAD_VOICE_GROUP = "undead_voice";
|
|
10
|
+
var voiced = { bus: "voices", group: UNDEAD_VOICE_GROUP };
|
|
11
|
+
var p = (param) => ({ param });
|
|
12
|
+
var SCREAM_HOARSE = {
|
|
13
|
+
id: "scream_hoarse",
|
|
14
|
+
bus: "voices",
|
|
15
|
+
order: "scream",
|
|
16
|
+
duration: [1, 1.4],
|
|
17
|
+
layers: [
|
|
18
|
+
{
|
|
19
|
+
source: { kind: "noise", offset: "random" },
|
|
20
|
+
filters: {
|
|
21
|
+
mode: "parallel",
|
|
22
|
+
bank: [
|
|
23
|
+
{ type: "bandpass", q: 6, frequency: { from: 700 * 1.15, to: 700 * 0.8, curve: "linear", over: "duration" }, level: 1 },
|
|
24
|
+
{ type: "bandpass", q: 8, frequency: { from: 1150 * 1.15, to: 1150 * 0.8, curve: "linear", over: "duration" }, level: 0.7 },
|
|
25
|
+
{ type: "bandpass", q: 10, frequency: { from: 2600 * 1.15, to: 2600 * 0.8, curve: "linear", over: "duration" }, level: 0.35 }
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
modulators: [{ target: "gain", wave: "square", rate: [31, 40], depth: 0.35 }],
|
|
29
|
+
envelope: [
|
|
30
|
+
[0, 0, "set"],
|
|
31
|
+
[0.06, 0.9, "linear"],
|
|
32
|
+
["0.6d", 0.9, "set"],
|
|
33
|
+
["1d", 1e-3, "exp", "fixed"]
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
falloff: DISTANCE_CURVES.game_scream,
|
|
38
|
+
sources: ["jogo:L62128-62142 grito"]
|
|
39
|
+
};
|
|
40
|
+
var groanLayer = (base) => ({
|
|
41
|
+
source: {
|
|
42
|
+
kind: "osc",
|
|
43
|
+
wave: "sawtooth",
|
|
44
|
+
frequency: typeof base === "number" ? { from: base, to: base * 0.7, curve: "linear", over: "duration" } : { from: base, to: { ...base, scale: 0.7 }, curve: "linear", over: "duration" }
|
|
45
|
+
},
|
|
46
|
+
filters: { mode: "serial", bank: [{ type: "bandpass", q: 3, frequency: { set: typeof base === "number" ? base * 3 : { ...base, scale: 3 } } }] },
|
|
47
|
+
envelope: [
|
|
48
|
+
[0, 0, "set"],
|
|
49
|
+
[0.15, 0.25, "linear"],
|
|
50
|
+
["1d", 1e-3, "exp", "fixed"]
|
|
51
|
+
]
|
|
52
|
+
});
|
|
53
|
+
var GROAN = {
|
|
54
|
+
id: "groan",
|
|
55
|
+
...voiced,
|
|
56
|
+
order: "groan",
|
|
57
|
+
vars: { base: [95, 135], dur: [0.9, 1.5] },
|
|
58
|
+
duration: p("dur"),
|
|
59
|
+
layers: [groanLayer(p("base"))],
|
|
60
|
+
falloff: DISTANCE_CURVES.game_voice,
|
|
61
|
+
sources: ["jogo:L62143-62155 voz gemido"]
|
|
62
|
+
};
|
|
63
|
+
var RUNNER_GROAN = {
|
|
64
|
+
id: "runner_groan",
|
|
65
|
+
...voiced,
|
|
66
|
+
order: "groan",
|
|
67
|
+
vars: { dur: [0.9, 1.5] },
|
|
68
|
+
duration: p("dur"),
|
|
69
|
+
layers: [groanLayer(300)],
|
|
70
|
+
falloff: DISTANCE_CURVES.game_voice,
|
|
71
|
+
sources: ["jogo:L62143-62155 voz corredor"]
|
|
72
|
+
};
|
|
73
|
+
var scene = (id, layers, line, options = {}) => sceneRecipe(id, layers, line, options);
|
|
74
|
+
var groan = (id, vars, voice, line) => scene(id, [sceneVoice(voice)], line, { vars, ...voiced });
|
|
75
|
+
var GROAN_WALKER = groan("groan_walker", { f0: [72, 107], d: [0.9, 2], fA: [480, 680], fB: [1050, 1350] }, { f0: p("f0"), f1: 0, d: p("d"), fA: p("fA"), fB: p("fB"), noise: 0.35, vol: 0.65 }, "L1345 groan (walker)");
|
|
76
|
+
var GROAN_BRUTE = groan("groan_brute", { f0: [48, 62], d: [0.9, 2], fA: [480, 680], fB: [1050, 1350] }, { f0: p("f0"), f1: 0, d: p("d"), fA: p("fA"), fB: p("fB"), noise: 0.35, vol: 0.65 }, "L1345 groan (brute)");
|
|
77
|
+
var GROAN_RUNNER = groan("groan_runner", { f0: [160, 250], d: [0.45, 0.85] }, { f0: p("f0"), f1: 0, d: p("d"), fA: 950, fB: 2300, noise: 0.6, vol: 0.5, wave: "square" }, "L1345 groan (runner)");
|
|
78
|
+
var SCREAM = scene(
|
|
79
|
+
"scream",
|
|
80
|
+
[
|
|
81
|
+
sceneVoice({ f0: 520, f1: 880, d: 2.6, fA: 1800, fB: 3200, noise: 0.9, vol: 1.6 }),
|
|
82
|
+
sceneVoice({ f0: 640, f1: 1020, d: 2.4, fA: 2400, fB: 3800, noise: 0.6, vol: 1.1, wave: "square" })
|
|
83
|
+
],
|
|
84
|
+
"L1347 scream",
|
|
85
|
+
{ bus: "voices" }
|
|
86
|
+
);
|
|
87
|
+
var INHALE = scene("inhale", [sceneNoise({ type: "bandpass", f: 1400, q: 1.5, a: 0.6, d: 0.9, vol: 0.5 })], "L1348 inhale", { bus: "voices" });
|
|
88
|
+
var LEAP = scene("leap", [sceneVoice({ f0: 200, f1: 330, d: 0.5, fA: 1100, fB: 2500, noise: 0.7, vol: 0.6, wave: "square" }), sceneNoise({ type: "bandpass", f: 600, f2: 1600, d: 0.5, vol: 0.4 })], "L1349 leap", { bus: "voices" });
|
|
89
|
+
var GRAB = scene("grab", [sceneNoise({ type: "bandpass", f: 500, q: 1, d: 0.25, vol: 0.7 }), sceneTone({ wave: "sawtooth", f: 90, f2: 60, d: 0.4, vol: 0.4 })], "L1350 grab", { bus: "voices" });
|
|
90
|
+
var BITE = scene(
|
|
91
|
+
"bite",
|
|
92
|
+
[
|
|
93
|
+
sceneNoise({ f: 1400, d: 0.2, vol: 1.1 }),
|
|
94
|
+
...[0, 1, 2, 3].map((k) => sceneNoise({ type: "highpass", f: 2500, d: 0.03, vol: 0.5, delay: 0.03 + k * 0.04 })),
|
|
95
|
+
sceneTone({ wave: "sawtooth", f: 320, f2: 170, d: 0.5, vol: 0.35, delay: 0.05 })
|
|
96
|
+
],
|
|
97
|
+
"L1351 bite",
|
|
98
|
+
{ bus: "voices" }
|
|
99
|
+
);
|
|
100
|
+
var HURT = scene("hurt", [sceneTone({ wave: "sawtooth", f: 150, f2: 95, d: 0.22, vol: 0.3 }), sceneNoise({ f: 600, d: 0.1, vol: 0.4 })], "L1352 hurt", { bus: "voices" });
|
|
101
|
+
var FLESH = scene("flesh", [sceneNoise({ f: 650, d: 0.08, vol: 0.6 }), sceneTone({ f: 90, f2: 45, d: 0.07, vol: 0.5 })], "L1353 flesh");
|
|
102
|
+
var FLESH_HEAD = scene("flesh_head", [sceneNoise({ f: 1100, d: 0.12, vol: 0.9 }), sceneTone({ f: 130, f2: 45, d: 0.07, vol: 0.5 })], "L1353 flesh (head)");
|
|
103
|
+
var SEVER = scene(
|
|
104
|
+
"sever",
|
|
105
|
+
[sceneNoise({ type: "bandpass", f: 500, q: 0.8, d: 0.3, vol: 1 }), sceneNoise({ type: "highpass", f: 2400, d: 0.06, vol: 0.7, delay: 0.02 }), sceneTone({ f: 110, f2: 40, d: 0.15, vol: 0.6 })],
|
|
106
|
+
"L1355 sever"
|
|
107
|
+
);
|
|
108
|
+
var GORE = scene(
|
|
109
|
+
"gore",
|
|
110
|
+
[
|
|
111
|
+
sceneNoise({ f: 900, f2: 200, d: 0.4, vol: 1.1, wet: 0.3 }),
|
|
112
|
+
...[0, 1, 2].map((k) => sceneNoise({ type: "bandpass", f: [400, 800], q: 2, d: 0.08, vol: 0.5, delay: 0.08 + k * 0.07 }))
|
|
113
|
+
],
|
|
114
|
+
"L1356 gore"
|
|
115
|
+
);
|
|
116
|
+
var CHARACTER_SOUNDS = [
|
|
117
|
+
SCREAM_HOARSE,
|
|
118
|
+
GROAN,
|
|
119
|
+
RUNNER_GROAN,
|
|
120
|
+
GROAN_WALKER,
|
|
121
|
+
GROAN_BRUTE,
|
|
122
|
+
GROAN_RUNNER,
|
|
123
|
+
SCREAM,
|
|
124
|
+
INHALE,
|
|
125
|
+
LEAP,
|
|
126
|
+
GRAB,
|
|
127
|
+
BITE,
|
|
128
|
+
HURT,
|
|
129
|
+
FLESH,
|
|
130
|
+
FLESH_HEAD,
|
|
131
|
+
SEVER,
|
|
132
|
+
GORE
|
|
133
|
+
];
|
|
134
|
+
var CHARACTER_SOUND_BANK = { id: "characters_three", sounds: CHARACTER_SOUNDS };
|
|
135
|
+
|
|
136
|
+
// src/audio/voices.json
|
|
137
|
+
var voices_default = {
|
|
138
|
+
sources: [
|
|
139
|
+
"cena:L1345 groan(z): the Brutamonte has its own, a chase speed above 3.4 is a runner's, every other body groans like a walker",
|
|
140
|
+
"cena:L1238, L1248, L1249 the scream and the inhale belong to the Gritador, the leap to the Saltador",
|
|
141
|
+
"cena:L1226 the groan when a body wakes (FEIGN to CHASE) and the game L67275/L67328 voz('corredor') when one notices the player",
|
|
142
|
+
"jogo:L66986 the groan of the game: 'corredor' for the runner category, 'gemido' for the rest; jogo:L66989 screamAt plays 'grito'",
|
|
143
|
+
"types.json: the voice line of each Bestiario type"
|
|
144
|
+
],
|
|
145
|
+
types: {
|
|
146
|
+
walker: { origin: "team", groan: "groan_walker", gameGroan: "groan" },
|
|
147
|
+
runner: { origin: "team", groan: "groan_runner", gameGroan: "runner_groan" },
|
|
148
|
+
crawler: { origin: "rule", groan: "groan_walker", gameGroan: "groan" },
|
|
149
|
+
brute: { origin: "team", groan: "groan_brute", gameGroan: "groan" },
|
|
150
|
+
sniffer: {
|
|
151
|
+
origin: "extra",
|
|
152
|
+
groan: "groan_runner",
|
|
153
|
+
gameGroan: "groan",
|
|
154
|
+
idle: [{ sound: "inhale" }, { sound: "groan_runner", delay: 0.6 }]
|
|
155
|
+
},
|
|
156
|
+
leaper: { origin: "team", groan: "groan_runner", gameGroan: "groan", special: [{ sound: "leap" }] },
|
|
157
|
+
bloater: { origin: "rule", groan: "groan_walker", gameGroan: "groan" },
|
|
158
|
+
stalker: { origin: "rule", groan: "groan_runner", gameGroan: "groan", idle: [] },
|
|
159
|
+
blind: { origin: "rule", groan: "groan_walker", gameGroan: "groan" },
|
|
160
|
+
screamer: {
|
|
161
|
+
origin: "team",
|
|
162
|
+
groan: "groan_walker",
|
|
163
|
+
gameGroan: "groan",
|
|
164
|
+
special: [{ sound: "inhale" }, { sound: "scream", delay: 1.5 }],
|
|
165
|
+
gameSpecial: [{ sound: "scream_hoarse" }]
|
|
166
|
+
},
|
|
167
|
+
charred: { origin: "rule", groan: "groan_walker", gameGroan: "groan" },
|
|
168
|
+
drowned: { origin: "rule", groan: "groan_walker", gameGroan: "groan" },
|
|
169
|
+
devourer: {
|
|
170
|
+
origin: "extra",
|
|
171
|
+
groan: "groan_walker",
|
|
172
|
+
gameGroan: "groan",
|
|
173
|
+
idle: [{ sound: "groan_walker" }, { sound: "bite", delay: 0.4 }]
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// src/audio/sound.ts
|
|
179
|
+
var DATA = voices_default;
|
|
180
|
+
var UNDEAD_VOICES = DATA.types;
|
|
181
|
+
var UNDEAD_VOICE_SOURCES = DATA.sources;
|
|
182
|
+
var BODY_SOUNDS = {
|
|
183
|
+
art_scene: {
|
|
184
|
+
wound: { sound: "flesh", head: "flesh_head" },
|
|
185
|
+
severed: { sound: "sever" },
|
|
186
|
+
destroyed: { sound: "gore" },
|
|
187
|
+
split: { sound: "gore" }
|
|
188
|
+
},
|
|
189
|
+
game: {
|
|
190
|
+
wound: { sound: "sfx_flesh" }
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
var PLAYER_SOUNDS = { grab: "grab", bite: "bite", hurt: "hurt" };
|
|
194
|
+
var HEAD_PARTS = /* @__PURE__ */ new Set(["head", "jaw"]);
|
|
195
|
+
function createCharacterSound(sink, options = {}) {
|
|
196
|
+
sink.register?.(CHARACTER_SOUND_BANK);
|
|
197
|
+
sink.register?.(GAME_SFX_BANK);
|
|
198
|
+
const style = options.style ?? "art_scene";
|
|
199
|
+
const voices = { ...UNDEAD_VOICES, ...options.voices };
|
|
200
|
+
const rules = { ...BODY_SOUNDS.art_scene, ...style === "game" ? BODY_SOUNDS.game : {}, ...options.body };
|
|
201
|
+
const queue = [];
|
|
202
|
+
const voiceOf = (typeId) => {
|
|
203
|
+
const own = voices[typeId];
|
|
204
|
+
if (own === null) return void 0;
|
|
205
|
+
return own ?? (options.fallbackType ? voices[options.fallbackType] ?? void 0 : void 0);
|
|
206
|
+
};
|
|
207
|
+
const stepsOf = (typeId, moment) => {
|
|
208
|
+
const voice = voiceOf(typeId);
|
|
209
|
+
if (!voice) return [];
|
|
210
|
+
const groan2 = style === "game" ? voice.gameGroan : voice.groan;
|
|
211
|
+
if (moment === "special") return (style === "game" ? voice.gameSpecial ?? voice.special : voice.special) ?? [];
|
|
212
|
+
if (moment === "alert") return voice.alert ?? [{ sound: style === "game" ? "runner_groan" : groan2 }];
|
|
213
|
+
return voice.idle ?? [{ sound: groan2 }];
|
|
214
|
+
};
|
|
215
|
+
const start = (sound, at, volume) => void sink.play({ sound, position: at, volume });
|
|
216
|
+
return {
|
|
217
|
+
stepsOf,
|
|
218
|
+
voice(typeId, moment, position, volume = 1) {
|
|
219
|
+
const steps = stepsOf(typeId, moment);
|
|
220
|
+
for (const step of steps) {
|
|
221
|
+
if (step.delay && step.delay > 0) queue.push({ sound: step.sound, at: position, volume, wait: step.delay });
|
|
222
|
+
else start(step.sound, position, volume);
|
|
223
|
+
}
|
|
224
|
+
return steps.length;
|
|
225
|
+
},
|
|
226
|
+
body(event, position, volume = 1) {
|
|
227
|
+
if (event.type !== "body") return false;
|
|
228
|
+
const rule = rules[event.change];
|
|
229
|
+
if (!rule) return false;
|
|
230
|
+
const head = event.part !== void 0 && HEAD_PARTS.has(event.part);
|
|
231
|
+
start(head && rule.head ? rule.head : rule.sound, position, volume);
|
|
232
|
+
return true;
|
|
233
|
+
},
|
|
234
|
+
player: (kind, volume = 1) => void sink.play({ sound: PLAYER_SOUNDS[kind], volume }),
|
|
235
|
+
update(dt) {
|
|
236
|
+
for (let i = queue.length - 1; i >= 0; i--) {
|
|
237
|
+
const item = queue[i];
|
|
238
|
+
item.wait -= dt;
|
|
239
|
+
if (item.wait <= 0) {
|
|
240
|
+
queue.splice(i, 1);
|
|
241
|
+
start(item.sound, item.at, item.volume);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
pending: () => queue.length,
|
|
246
|
+
dispose() {
|
|
247
|
+
queue.length = 0;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/eyes.ts
|
|
253
|
+
import { shapeGeometries } from "@adatechnology/game-shapes-three";
|
|
254
|
+
var GLOW_SIZE = 64;
|
|
255
|
+
var createGlowTexture = (three, createCanvas) => {
|
|
256
|
+
const make = createCanvas ?? (typeof document === "undefined" ? void 0 : () => document.createElement("canvas"));
|
|
257
|
+
if (!make) return null;
|
|
258
|
+
const canvas = make();
|
|
259
|
+
canvas.width = canvas.height = GLOW_SIZE;
|
|
260
|
+
const context = canvas.getContext("2d");
|
|
261
|
+
if (!context) return null;
|
|
262
|
+
const gradient = context.createRadialGradient(GLOW_SIZE / 2, GLOW_SIZE / 2, 0, GLOW_SIZE / 2, GLOW_SIZE / 2, GLOW_SIZE / 2);
|
|
263
|
+
gradient.addColorStop(0, "rgba(255,255,255,1)");
|
|
264
|
+
gradient.addColorStop(0.35, "rgba(255,255,255,.45)");
|
|
265
|
+
gradient.addColorStop(1, "rgba(255,255,255,0)");
|
|
266
|
+
context.fillStyle = gradient;
|
|
267
|
+
context.fillRect(0, 0, GLOW_SIZE, GLOW_SIZE);
|
|
268
|
+
return new three.CanvasTexture(canvas);
|
|
269
|
+
};
|
|
270
|
+
var createEye = (host, { color, radius = 0.02, glow, glowOpacity = 0.55, texture = null }) => {
|
|
271
|
+
const three = shapeGeometries(host);
|
|
272
|
+
const eye = new three.Group();
|
|
273
|
+
const ball = new three.Mesh(new three.SphereGeometry(radius, 10, 8), new three.MeshBasicMaterial({ color }));
|
|
274
|
+
ball.userData.eye = 1;
|
|
275
|
+
eye.add(ball);
|
|
276
|
+
const size = glow ?? radius * 5;
|
|
277
|
+
if (size > 0) {
|
|
278
|
+
const sprite = new three.Sprite(new three.SpriteMaterial({ map: texture, color, transparent: true, blending: three.AdditiveBlending, depthWrite: false, opacity: glowOpacity }));
|
|
279
|
+
sprite.scale.setScalar(size);
|
|
280
|
+
sprite.userData.eyeGlow = 1;
|
|
281
|
+
eye.add(sprite);
|
|
282
|
+
}
|
|
283
|
+
return eye;
|
|
284
|
+
};
|
|
285
|
+
var isEye = (object) => object.userData.eye === 1 || object.userData.eye === true;
|
|
286
|
+
var findEyes = (three, root) => {
|
|
287
|
+
const markable = [];
|
|
288
|
+
const glows = [];
|
|
289
|
+
const base = /* @__PURE__ */ new Map();
|
|
290
|
+
root.traverse((object) => {
|
|
291
|
+
const mesh = object;
|
|
292
|
+
if (mesh.isMesh && isEye(object)) markable.push(mesh);
|
|
293
|
+
const sprite = object;
|
|
294
|
+
if (sprite.isSprite && sprite.material.blending === three.AdditiveBlending) glows.push(sprite);
|
|
295
|
+
});
|
|
296
|
+
const remember = (material) => {
|
|
297
|
+
if (material.color && !base.has(material)) base.set(material, { color: material.color.clone(), opacity: material.opacity });
|
|
298
|
+
};
|
|
299
|
+
markable.forEach((mesh) => remember(mesh.material));
|
|
300
|
+
glows.forEach((sprite) => remember(sprite.material));
|
|
301
|
+
const setBrightness = (level) => {
|
|
302
|
+
const factor = Math.min(1, Math.max(0, level));
|
|
303
|
+
base.forEach((rest, material) => {
|
|
304
|
+
const target = material;
|
|
305
|
+
target.color.copy(rest.color).multiplyScalar(factor);
|
|
306
|
+
if (material.isSpriteMaterial) material.opacity = rest.opacity * factor;
|
|
307
|
+
});
|
|
308
|
+
};
|
|
309
|
+
const hidden = [];
|
|
310
|
+
return {
|
|
311
|
+
markable,
|
|
312
|
+
glows,
|
|
313
|
+
setBrightness,
|
|
314
|
+
dim: () => setBrightness(0.25),
|
|
315
|
+
off: () => setBrightness(0),
|
|
316
|
+
on: () => setBrightness(1),
|
|
317
|
+
isolate(only) {
|
|
318
|
+
if (!only) {
|
|
319
|
+
hidden.splice(0).forEach((object) => object.visible = true);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
root.traverse((object) => {
|
|
323
|
+
const mesh = object;
|
|
324
|
+
if (mesh.isMesh && !isEye(object) && object.visible) {
|
|
325
|
+
object.visible = false;
|
|
326
|
+
hidden.push(object);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// src/rich/animator.ts
|
|
334
|
+
import { CASCADE, animateProfile, lerpPose, stepCascade } from "@adatechnology/game-characters";
|
|
335
|
+
var rigOf = (root) => root.userData.rig;
|
|
336
|
+
var apply = (rig, sample) => {
|
|
337
|
+
if (sample.hipY !== void 0) rig.fig.position.y = sample.hipY;
|
|
338
|
+
if (sample.upper.x !== void 0) rig.upper.rotation.x = sample.upper.x;
|
|
339
|
+
if (sample.upper.y !== void 0) rig.upper.rotation.y = sample.upper.y;
|
|
340
|
+
if (sample.upper.z !== void 0) rig.upper.rotation.z = sample.upper.z;
|
|
341
|
+
if (sample.head.x !== void 0) rig.headG.rotation.x = sample.head.x;
|
|
342
|
+
if (sample.head.y !== void 0) rig.headG.rotation.y = sample.head.y;
|
|
343
|
+
rig.arms.forEach((arm, index) => {
|
|
344
|
+
const target = sample.arms[index];
|
|
345
|
+
if (target?.shoulder !== void 0) arm.g.rotation.x = target.shoulder;
|
|
346
|
+
if (target?.elbow !== void 0) arm.el.rotation.x = target.elbow;
|
|
347
|
+
});
|
|
348
|
+
rig.legs.forEach((leg, index) => {
|
|
349
|
+
const target = sample.legs[index];
|
|
350
|
+
if (target?.hip !== void 0) leg.g.rotation.x = target.hip;
|
|
351
|
+
if (target?.knee !== void 0) leg.kn.rotation.x = target.knee;
|
|
352
|
+
});
|
|
353
|
+
};
|
|
354
|
+
var createRigAnimator = (options = {}) => {
|
|
355
|
+
const gaits = options.gaits ?? {};
|
|
356
|
+
const rates = options.cascade ?? CASCADE;
|
|
357
|
+
const restOf = (rig) => ({
|
|
358
|
+
hipY: rig.dropBase,
|
|
359
|
+
lean: rig.leanBase,
|
|
360
|
+
headX: rig.hxBase,
|
|
361
|
+
arms: [
|
|
362
|
+
{ shoulder: rig.arms[0].base, elbow: rig.arms[0].eBase },
|
|
363
|
+
{ shoulder: rig.arms[1].base, elbow: rig.arms[1].eBase }
|
|
364
|
+
],
|
|
365
|
+
legs: [
|
|
366
|
+
{ hip: rig.legs[0].base, knee: rig.legs[0].kBase, ...rig.legs[0].lost ? { lost: true } : {} },
|
|
367
|
+
{ hip: rig.legs[1].base, knee: rig.legs[1].kBase, ...rig.legs[1].lost ? { lost: true } : {} }
|
|
368
|
+
]
|
|
369
|
+
});
|
|
370
|
+
const animate0 = (root, time, stride) => {
|
|
371
|
+
const rig = rigOf(root);
|
|
372
|
+
if (!rig) return void 0;
|
|
373
|
+
const profile = gaits[rig.type];
|
|
374
|
+
const sample = animateProfile({ ...profile ? { profile } : {}, rest: restOf(rig), time, phase: rig.ph, ...stride ? { stride } : {} });
|
|
375
|
+
apply(rig, sample);
|
|
376
|
+
return sample;
|
|
377
|
+
};
|
|
378
|
+
return {
|
|
379
|
+
restOf,
|
|
380
|
+
animate0,
|
|
381
|
+
animate(root, time, stride) {
|
|
382
|
+
const rig = rigOf(root);
|
|
383
|
+
if (!rig) return;
|
|
384
|
+
animate0(root, time, stride);
|
|
385
|
+
rig.dangles.forEach((arm) => {
|
|
386
|
+
arm.el.rotation.x = -(arm.g.rotation.x + rig.upper.rotation.x) + Math.sin(time * 3.2 + rig.ph) * 0.3;
|
|
387
|
+
});
|
|
388
|
+
rig.flaps.forEach((flap, index) => {
|
|
389
|
+
var _a;
|
|
390
|
+
const base = (_a = flap.userData).rx0 ?? (_a.rx0 = flap.rotation.x);
|
|
391
|
+
flap.rotation.x = base + Math.sin(time * 4 + index + rig.ph) * 0.18;
|
|
392
|
+
});
|
|
393
|
+
},
|
|
394
|
+
cascade(rig, dt) {
|
|
395
|
+
if (!rig.cur) {
|
|
396
|
+
const joints = [
|
|
397
|
+
[rig.fig.position, "y", "hip"],
|
|
398
|
+
[rig.upper.rotation, "x", "spine"],
|
|
399
|
+
[rig.upper.rotation, "y", "spine"],
|
|
400
|
+
[rig.upper.rotation, "z", "spine"],
|
|
401
|
+
[rig.headG.rotation, "x", "head"],
|
|
402
|
+
[rig.headG.rotation, "y", "head"],
|
|
403
|
+
[rig.headG.rotation, "z", "head"]
|
|
404
|
+
];
|
|
405
|
+
rig.arms.forEach((arm) => joints.push([arm.g.rotation, "x", "shoulder"], [arm.el.rotation, "x", "elbow"]));
|
|
406
|
+
rig.legs.forEach((leg) => joints.push([leg.g.rotation, "x", "leg"], [leg.kn.rotation, "x", "knee"]));
|
|
407
|
+
rig.jl = joints;
|
|
408
|
+
rig.cur = new Map(joints.map((joint) => [joint, joint[0][joint[1]]]));
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
for (const joint of rig.jl) {
|
|
412
|
+
const target = joint[0][joint[1]];
|
|
413
|
+
const value = stepCascade(rig.cur.get(joint), target, dt, joint[2], rates);
|
|
414
|
+
rig.cur.set(joint, value);
|
|
415
|
+
joint[0][joint[1]] = value;
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
setPose(rig, q) {
|
|
419
|
+
if (q.lean != null) rig.upper.rotation.x = q.lean;
|
|
420
|
+
if (q.twist != null) rig.upper.rotation.y = q.twist;
|
|
421
|
+
if (q.side != null) rig.upper.rotation.z = q.side;
|
|
422
|
+
if (q.drop != null) rig.fig.position.y = rig.dropBase + q.drop;
|
|
423
|
+
if (q.headX != null) rig.headG.rotation.x = q.headX;
|
|
424
|
+
if (q.headY != null) rig.headG.rotation.y = q.headY;
|
|
425
|
+
if (q.headZ != null) rig.headG.rotation.z = q.headZ;
|
|
426
|
+
const [armR, armL] = rig.arms;
|
|
427
|
+
if (q.armR != null) armR.g.rotation.x = q.armR;
|
|
428
|
+
if (q.armL != null) armL.g.rotation.x = q.armL;
|
|
429
|
+
if (q.elbR != null) armR.el.rotation.x = q.elbR;
|
|
430
|
+
if (q.elbL != null) armL.el.rotation.x = q.elbL;
|
|
431
|
+
if (q.armRz != null) armR.g.rotation.z = q.armRz;
|
|
432
|
+
if (q.armLz != null) armL.g.rotation.z = q.armLz;
|
|
433
|
+
const [legL, legR] = rig.legs;
|
|
434
|
+
if (q.legL != null) legL.g.rotation.x = q.legL;
|
|
435
|
+
if (q.legR != null) legR.g.rotation.x = q.legR;
|
|
436
|
+
if (q.kneeL != null) legL.kn.rotation.x = q.kneeL;
|
|
437
|
+
if (q.kneeR != null) legR.kn.rotation.x = q.kneeR;
|
|
438
|
+
},
|
|
439
|
+
lerpPose
|
|
440
|
+
};
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
// src/rich/best-builder.ts
|
|
444
|
+
import { markKitOrigin } from "@adatechnology/game-contracts";
|
|
445
|
+
import { shapeGeometries as shapeGeometries2 } from "@adatechnology/game-shapes-three";
|
|
446
|
+
import { createBloodStream, createRng, drawAppearance } from "@adatechnology/game-characters";
|
|
447
|
+
var createBestBuilder = (host, { types, random = Math.random }) => {
|
|
448
|
+
const THREE = shapeGeometries2(host);
|
|
449
|
+
const options = { types, random };
|
|
450
|
+
let LK = 1, topM_ref = null;
|
|
451
|
+
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);
|
|
452
|
+
const CYL = (a, b, h, rs = 8, ...x) => new THREE.CylinderGeometry(a, b, h, Math.max(4, Math.round(rs * LK)), ...x);
|
|
453
|
+
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);
|
|
454
|
+
const BCARDS = options.types.map((c) => ({ n: c.name, eye: "#" + c.eye.toString(16).padStart(6, "0") }));
|
|
455
|
+
const glowTex = null;
|
|
456
|
+
const rng = createRng;
|
|
457
|
+
const BLOB = (() => {
|
|
458
|
+
const sh = new THREE.Shape(), N = 24, R = rng(123), a = [R() * 6, R() * 6, R() * 6];
|
|
459
|
+
for (let i = 0; i < N; i++) {
|
|
460
|
+
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);
|
|
461
|
+
i ? sh.lineTo(Math.cos(t) * r, Math.sin(t) * r) : sh.moveTo(Math.cos(t) * r, Math.sin(t) * r);
|
|
462
|
+
}
|
|
463
|
+
sh.closePath();
|
|
464
|
+
const g = new THREE.ShapeGeometry(sh);
|
|
465
|
+
g.rotateX(-Math.PI / 2);
|
|
466
|
+
return g;
|
|
467
|
+
})();
|
|
468
|
+
const SKIN = [13482920, 9740420, 8090698, 9206878];
|
|
469
|
+
const BLOOD = [10361618, 7214096, 4860440, 1840658];
|
|
470
|
+
const CLOTH = [4878230, 9321275, 6113850, 5208643, 7040888, 4153146, 5917294, 9071162];
|
|
471
|
+
const PANTS = 2896704, GRAY = 4868678, RED = 8000012;
|
|
472
|
+
const col = (a, b, t) => new THREE.Color(a).lerp(new THREE.Color(b), t);
|
|
473
|
+
const geoBox = (w, h, d) => new THREE.BoxGeometry(w, h, d);
|
|
474
|
+
const box = (w, h, d, m) => new THREE.Mesh(geoBox(w, h, d), m);
|
|
475
|
+
const limbGeo = (w, len) => {
|
|
476
|
+
const g = geoBox(w, len, w);
|
|
477
|
+
g.translate(0, -len / 2, 0);
|
|
478
|
+
return g;
|
|
479
|
+
};
|
|
480
|
+
const basic = (c, o) => new THREE.MeshBasicMaterial(Object.assign({ color: c }, o || {}));
|
|
481
|
+
const glow = (c, s, op) => {
|
|
482
|
+
const sp = new THREE.Sprite(new THREE.SpriteMaterial({ map: glowTex, color: c, transparent: true, blending: THREE.AdditiveBlending, depthWrite: false, opacity: op ?? 0.9 }));
|
|
483
|
+
sp.scale.setScalar(s);
|
|
484
|
+
return sp;
|
|
485
|
+
};
|
|
486
|
+
const PI = Math.PI;
|
|
487
|
+
let SEG = 16;
|
|
488
|
+
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]];
|
|
489
|
+
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]];
|
|
490
|
+
const torsoGeo = (sx, sz, fem) => {
|
|
491
|
+
const g = new THREE.LatheGeometry(new THREE.SplineCurve((fem ? TORSO_F : TORSO_P).map((p2) => new THREE.Vector2(p2[0], p2[1]))).getPoints(Math.max(8, Math.round(30 * LK))), Math.max(6, Math.round(24 * LK)));
|
|
492
|
+
g.scale(1.12 * (sx || 1), 1, 0.64 * (sz || 1));
|
|
493
|
+
return g;
|
|
494
|
+
};
|
|
495
|
+
const cylDown = (r1, r2, len) => CYL(r1, r2, len, SEG, 1).translate(0, -len / 2, 0);
|
|
496
|
+
const capGeo = (r, len) => SPH(r, 20, 10, 0, PI * 2, 0, len || PI / 2);
|
|
497
|
+
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 };
|
|
498
|
+
const TYPES = {
|
|
499
|
+
Lento: {},
|
|
500
|
+
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 },
|
|
501
|
+
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 },
|
|
502
|
+
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 },
|
|
503
|
+
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 },
|
|
504
|
+
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 },
|
|
505
|
+
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 },
|
|
506
|
+
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 },
|
|
507
|
+
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 },
|
|
508
|
+
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 },
|
|
509
|
+
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 },
|
|
510
|
+
Carbonizado: { lean: 0.2, armL: -1, armR: -0.6, elbL: -0.6, elbR: -0.9, headX: 0.05, headZ: -0.15, char: true },
|
|
511
|
+
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 }
|
|
512
|
+
};
|
|
513
|
+
const OUTFITS = {
|
|
514
|
+
policial: { cloth: 2042436, pants: 1711916, vest: 1382170, hat: "cap", hatC: 1778232 },
|
|
515
|
+
militar: { cloth: 4938290, pants: 4147755, vest: 3752234, hat: "helmet", hatC: 4871474 },
|
|
516
|
+
bombeiro: { cloth: 11044682, pants: 9401152, stripes: 14275146, hat: "fire", hatC: 10691612 },
|
|
517
|
+
motociclista: { cloth: 1579034, pants: 2237739, hat: "moto", hatC: 1118740 },
|
|
518
|
+
operario: { cloth: 7040888, pants: 2896704, vest: 14708778, hat: "hardhat", hatC: 15122746 },
|
|
519
|
+
medico: { cloth: 8368328, pants: 7314096, mask: true },
|
|
520
|
+
cacador: { cloth: 5917230, pants: 4866096, hat: "cap", hatC: 14708778 }
|
|
521
|
+
};
|
|
522
|
+
function build(spec) {
|
|
523
|
+
const ti = Math.max(0, BCARDS.findIndex((x) => x.n === spec.type)), z = BCARDS[ti];
|
|
524
|
+
const P = Object.assign({}, BASE, TYPES[spec.type ?? ""] || {}, spec.pose || {}), d = spec.decay ?? 0, life = spec.life ?? 1, cut = spec.cut || {};
|
|
525
|
+
P.s *= spec.h ?? 1;
|
|
526
|
+
P.bulk *= spec.bulk ?? 1;
|
|
527
|
+
if (life === 0) {
|
|
528
|
+
P.s *= 0.94;
|
|
529
|
+
P.bulk *= 0.9;
|
|
530
|
+
}
|
|
531
|
+
if (life === 2) {
|
|
532
|
+
P.lean += 0.28;
|
|
533
|
+
P.headX -= 0.18;
|
|
534
|
+
P.armL *= 0.8;
|
|
535
|
+
P.armR *= 0.8;
|
|
536
|
+
P.kneeL += 0.1;
|
|
537
|
+
P.kneeR += 0.1;
|
|
538
|
+
}
|
|
539
|
+
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 });
|
|
540
|
+
const op = spec.opacity ?? 1;
|
|
541
|
+
const cache = {};
|
|
542
|
+
const M = (c) => {
|
|
543
|
+
const k = new THREE.Color(c).getHex();
|
|
544
|
+
if (cache[k]) return cache[k];
|
|
545
|
+
const m = new THREE.MeshLambertMaterial({ color: c });
|
|
546
|
+
if (op < 1) {
|
|
547
|
+
m.transparent = true;
|
|
548
|
+
m.opacity = op;
|
|
549
|
+
}
|
|
550
|
+
return cache[k] = m;
|
|
551
|
+
};
|
|
552
|
+
const O = spec.outfit ? OUTFITS[spec.outfit] || {} : {};
|
|
553
|
+
const BR = createBloodStream(spec.seed ?? 7);
|
|
554
|
+
const uni = !!spec.uniform;
|
|
555
|
+
const { traits: T, random: VR } = drawAppearance({
|
|
556
|
+
seed: spec.seed ?? options.random() * 1e9 | 0,
|
|
557
|
+
outfit: { cloth: O.cloth, pants: O.pants, hat: O.hat },
|
|
558
|
+
uniform: uni,
|
|
559
|
+
typeIndex: ti,
|
|
560
|
+
clothIndex: spec.clothIdx,
|
|
561
|
+
female: spec.fem,
|
|
562
|
+
allowFemale: spec.type !== "Brutamonte",
|
|
563
|
+
tone: spec.tone,
|
|
564
|
+
hairStyle: spec.hs || void 0,
|
|
565
|
+
hairColor: spec.hair,
|
|
566
|
+
wet: !!P.wet,
|
|
567
|
+
charred: !!P.char,
|
|
568
|
+
human: !!spec.human,
|
|
569
|
+
life
|
|
570
|
+
});
|
|
571
|
+
const fem = T.female, tone = T.tone;
|
|
572
|
+
const skinC = spec.skin ?? (uni ? SKIN[d] : col(tone, SKIN[d], [0.45, 0.65, 0.8, 0.88][d]));
|
|
573
|
+
const topC = T.topColor, pantC = T.pantsColor;
|
|
574
|
+
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 };
|
|
575
|
+
const hairC = T.hairColor;
|
|
576
|
+
const skinFinal = P.char ? 1840658 : P.wet ? col(skinC, 8032160, 0.55) : skinC;
|
|
577
|
+
const skinM = M(skinFinal);
|
|
578
|
+
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);
|
|
579
|
+
const add = (p2, geo, m, x, y, z2, rx, ry, rz) => {
|
|
580
|
+
const o = new THREE.Mesh(geo, m);
|
|
581
|
+
o.position.set(x || 0, y || 0, z2 || 0);
|
|
582
|
+
if (rx || ry || rz) o.rotation.set(rx || 0, ry || 0, rz || 0);
|
|
583
|
+
p2.add(o);
|
|
584
|
+
return o;
|
|
585
|
+
};
|
|
586
|
+
const ball = (p2, r, m, x, y, z2) => add(p2, SPH(r, SEG, 12), m, x, y, z2);
|
|
587
|
+
topM_ref = topM;
|
|
588
|
+
const SKM = {}, skM = (c) => SKM[String(c)] || (SKM[String(c)] = new THREE.MeshStandardMaterial({ color: c, roughness: 0.3, metalness: 0, polygonOffset: true, polygonOffsetFactor: -4, polygonOffsetUnits: -4 }));
|
|
589
|
+
const flat = (p2, x, y, zz, sx, sy, c) => {
|
|
590
|
+
const m = new THREE.Mesh(BLOB, skM(c));
|
|
591
|
+
m.rotation.set(PI / 2, 0, BR() * 6.28);
|
|
592
|
+
m.scale.set(sx, 1, sy);
|
|
593
|
+
m.position.set(x, y, zz);
|
|
594
|
+
p2.add(m);
|
|
595
|
+
return m;
|
|
596
|
+
};
|
|
597
|
+
const splat = (p2, x, y, zz, r, c, n) => {
|
|
598
|
+
const a = flat(p2, x, y, zz + 4e-3, r, r * (0.7 + BR() * 0.5), c);
|
|
599
|
+
for (let k = 0; k < (n || 0); k++) {
|
|
600
|
+
const an = BR() * 6.28, dd = r * (1.2 + BR() * 0.9), q = r * (0.12 + BR() * 0.18);
|
|
601
|
+
flat(p2, x + Math.cos(an) * dd, y + Math.sin(an) * dd, zz + 4e-3, q, q * 0.7, c);
|
|
602
|
+
}
|
|
603
|
+
if (n) run(p2, x + r * 0.15, y - r * 0.5, y - r * (2.2 + BR() * 1.2), zz + 3e-3, zz, c, r * 0.35);
|
|
604
|
+
return a;
|
|
605
|
+
};
|
|
606
|
+
const run = (p2, x, y0, y1, z0, z1, c, w) => {
|
|
607
|
+
const L = y0 - y1, sh = new THREE.Shape();
|
|
608
|
+
sh.moveTo(-w / 2, 0);
|
|
609
|
+
sh.lineTo(w / 2, 0);
|
|
610
|
+
sh.quadraticCurveTo(w * 0.3, -L * 0.5, w * 0.18, -L);
|
|
611
|
+
sh.lineTo(-w * 0.12, -L);
|
|
612
|
+
sh.quadraticCurveTo(-w * 0.35, -L * 0.5, -w / 2, 0);
|
|
613
|
+
const g = new THREE.ShapeGeometry(sh), m = new THREE.Mesh(g, skM(c));
|
|
614
|
+
m.position.set(x, y0, z0);
|
|
615
|
+
m.rotation.x = -Math.atan2(z0 - z1, L);
|
|
616
|
+
p2.add(m);
|
|
617
|
+
const bl = new THREE.Mesh(SPH(w * 0.42, 10, 8), skM(c));
|
|
618
|
+
bl.scale.set(1, 1.35, 0.5);
|
|
619
|
+
bl.position.set(x + w * 0.03, y1, z1);
|
|
620
|
+
p2.add(bl);
|
|
621
|
+
return bl;
|
|
622
|
+
};
|
|
623
|
+
const stump = (p2, r, y, up) => {
|
|
624
|
+
const st = ball(p2, r * 1.02, red, 0, y, 0);
|
|
625
|
+
st.scale.set(1, 0.35, 1);
|
|
626
|
+
add(p2, CYL(r * 0.32, r * 0.36, 0.07, 10), M(15261900), 0, y + (up ? 0.04 : -0.04), 0);
|
|
627
|
+
};
|
|
628
|
+
const ring = (p2, r, h, m, y, sx, sz) => {
|
|
629
|
+
const o = add(p2, CYL(r, r, h, 24), m, 0, y, 0);
|
|
630
|
+
o.scale.set(sx || 1.12, 1, sz || 0.7);
|
|
631
|
+
return o;
|
|
632
|
+
};
|
|
633
|
+
const root = new THREE.Group(), fig = new THREE.Group();
|
|
634
|
+
const drip = (p2, x, y, zz, kind, every) => {
|
|
635
|
+
if (d >= 3 && kind !== "bile") return;
|
|
636
|
+
const o = new THREE.Object3D();
|
|
637
|
+
o.position.set(x, y, zz);
|
|
638
|
+
o.userData.drip = { kind: kind || (d < 1 ? "blood" : "old"), every: every || (d < 1 ? 1.2 : 2.4) };
|
|
639
|
+
p2.add(o);
|
|
640
|
+
};
|
|
641
|
+
fig.scale.set(P.s * P.bulk, P.s, P.s * Math.min(1.15, P.bulk));
|
|
642
|
+
fig.position.y = P.drop * P.s;
|
|
643
|
+
root.add(fig);
|
|
644
|
+
const pel = ball(fig, 0.2, pantsM, 0, 0.92, 0);
|
|
645
|
+
pel.scale.set(fem ? 1.16 : 1.08, 0.62, 0.74);
|
|
646
|
+
const legs = [];
|
|
647
|
+
[[-1, P.legL, P.kneeL], [1, P.legR, P.kneeR]].forEach(([s, r, kr]) => {
|
|
648
|
+
const g = new THREE.Group();
|
|
649
|
+
g.position.set(s * (fem ? 0.115 : 0.105), 0.9, 0);
|
|
650
|
+
fig.add(g);
|
|
651
|
+
add(g, cylDown(P.bloat ? 0.13 : fem ? 0.11 : 0.105, P.bloat ? 0.095 : 0.078, 0.42), pantsM);
|
|
652
|
+
const kn = new THREE.Group();
|
|
653
|
+
kn.position.y = -0.42;
|
|
654
|
+
g.add(kn);
|
|
655
|
+
ball(kn, 0.078, pantsM);
|
|
656
|
+
const lost = P.noLegs || cut.legs;
|
|
657
|
+
if (lost) {
|
|
658
|
+
stump(kn, 0.08, -0.01);
|
|
659
|
+
drip(kn, 0, -0.05, 0, null, 1);
|
|
660
|
+
g.rotation.x = 1.45;
|
|
661
|
+
} else {
|
|
662
|
+
add(kn, cylDown(0.077, 0.056, 0.38), pantsM);
|
|
663
|
+
const sh = ball(kn, 0.075, shoeM, 0, -0.43, 0.06);
|
|
664
|
+
sh.scale.set(0.95, 0.7, 1.75);
|
|
665
|
+
g.rotation.x = r;
|
|
666
|
+
kn.rotation.x = kr;
|
|
667
|
+
}
|
|
668
|
+
legs.push({ g, kn, s, base: g.rotation.x, kBase: kn.rotation.x, lost });
|
|
669
|
+
});
|
|
670
|
+
const upper = new THREE.Group();
|
|
671
|
+
upper.position.y = 0.9;
|
|
672
|
+
upper.rotation.x = P.lean;
|
|
673
|
+
fig.add(upper);
|
|
674
|
+
const U = new THREE.Group();
|
|
675
|
+
U.position.y = -0.9;
|
|
676
|
+
upper.add(U);
|
|
677
|
+
let frontZ = 0.175;
|
|
678
|
+
if (P.bloat) {
|
|
679
|
+
const sick = col(skinC, 8362578, 0.45), sickM = M(sick), veinM = M(col(sick, 2767386, 0.55));
|
|
680
|
+
const chest = add(U, torsoGeo(1.25, 1.35), topM);
|
|
681
|
+
chest.scale.set(1, 1, 1);
|
|
682
|
+
const belly = add(U, SPH(0.34, 24, 18), sickM, 0, 1.06, 0.1);
|
|
683
|
+
belly.scale.set(1.12, 1, 1.08);
|
|
684
|
+
const shirt = add(U, SPH(0.355, 24, 10, 0, PI * 2, 0, PI * 0.42), topM, 0, 1.08, 0.1);
|
|
685
|
+
shirt.scale.set(1.12, 1, 1.08);
|
|
686
|
+
for (let k = 0; k < 7; k++) {
|
|
687
|
+
const a = -1 + k * 0.33 + Math.sin(k * 3.1) * 0.12, b = 0.05 + k * 37 % 5 * 0.11;
|
|
688
|
+
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);
|
|
689
|
+
vv.scale.set(1, 6, 1);
|
|
690
|
+
vv.rotation.z = a * 0.8;
|
|
691
|
+
}
|
|
692
|
+
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]];
|
|
693
|
+
drip(U, 0.05, 0.8, 0.3, "bile", 1.6);
|
|
694
|
+
pus.forEach(([x, y], k) => {
|
|
695
|
+
const zz = 0.1 + Math.sqrt(Math.max(0.01, 0.34 * 0.34 - x * x / 1.25 - (y - 1.06) ** 2)) * 1.08;
|
|
696
|
+
const r = 0.022 + k % 3 * 0.01;
|
|
697
|
+
add(U, SPH(r, 10, 8), basic(z.eye), x, y, zz).userData.eye = 1;
|
|
698
|
+
const g = glow(z.eye, r * 5, 0.55);
|
|
699
|
+
g.position.set(x, y, zz + 0.02);
|
|
700
|
+
U.add(g);
|
|
701
|
+
});
|
|
702
|
+
add(U, CYL(0.09, 0.1, 0.12, 16), sickM, 0, 1.62, 0);
|
|
703
|
+
frontZ = 0.38;
|
|
704
|
+
} else add(U, torsoGeo(1, 1, fem), topM);
|
|
705
|
+
if (P.char) {
|
|
706
|
+
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]];
|
|
707
|
+
emb.forEach(([x, y, zz]) => {
|
|
708
|
+
const c = add(U, SPH(0.018, 8, 6), basic(16738842), x, y, zz);
|
|
709
|
+
c.userData.eye = 1;
|
|
710
|
+
c.scale.set(2.2, 0.6, 0.5);
|
|
711
|
+
const g = glow(16734736, 0.12, 0.7);
|
|
712
|
+
g.position.set(x, y, zz * 1.1);
|
|
713
|
+
U.add(g);
|
|
714
|
+
});
|
|
715
|
+
emb.forEach(([x, y, zz], k) => {
|
|
716
|
+
if (k % 2) return;
|
|
717
|
+
const o = new THREE.Object3D();
|
|
718
|
+
o.position.set(x, y, zz * 1.1);
|
|
719
|
+
o.userData.ember = { smoke: k === 0 || k === 4 };
|
|
720
|
+
U.add(o);
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
if (P.gore) {
|
|
724
|
+
splat(U, 0, 1.4, frontZ + 0.01, 0.07, 9046538, 5);
|
|
725
|
+
splat(U, -0.08, 1.18, frontZ + 0.01, 0.05, 9046538, 3);
|
|
726
|
+
}
|
|
727
|
+
if (V.bag && !P.bloat) {
|
|
728
|
+
const bm = M(V.bagC), sm = M(col(V.bagC, 1118481, 0.5));
|
|
729
|
+
const b = ball(U, 0.2, bm, 0, 1.22, -0.25);
|
|
730
|
+
b.scale.set(1.05, 1.25, 0.62);
|
|
731
|
+
const f = ball(U, 0.12, bm, 0, 1.12, -0.36);
|
|
732
|
+
f.scale.set(1.2, 0.8, 0.5);
|
|
733
|
+
[-1, 1].forEach((sx) => {
|
|
734
|
+
const st = add(U, TOR(0.2, 0.018, 6, 18, PI * 1.1), sm, sx * 0.14, 1.3, 0);
|
|
735
|
+
st.rotation.set(0, PI / 2, 0);
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
if (spec.type === "Brutamonte") {
|
|
739
|
+
[-1, 1].forEach((sx) => {
|
|
740
|
+
const t = ball(U, 0.17, topM, sx * 0.2, 1.53, -0.03);
|
|
741
|
+
t.scale.set(1.2, 0.8, 1);
|
|
742
|
+
});
|
|
743
|
+
const hb = ball(U, 0.2, topM, 0, 1.45, -0.12);
|
|
744
|
+
hb.scale.set(1.5, 0.9, 0.8);
|
|
745
|
+
}
|
|
746
|
+
if (O.vest) {
|
|
747
|
+
const v = add(U, CYL(0.275, 0.24, 0.48, 24), M(O.vest), 0, 1.3, 0);
|
|
748
|
+
v.scale.set(1.12, 1, 0.7);
|
|
749
|
+
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);
|
|
750
|
+
}
|
|
751
|
+
if (O.stripes) {
|
|
752
|
+
ring(U, 0.205, 0.05, M(O.stripes), 1.03, 1.13, 0.68);
|
|
753
|
+
ring(U, 0.268, 0.05, M(O.stripes), 1.42, 1.13, 0.67);
|
|
754
|
+
}
|
|
755
|
+
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));
|
|
756
|
+
if (!spec.human) {
|
|
757
|
+
const bk = new THREE.Group();
|
|
758
|
+
bk.rotation.y = PI;
|
|
759
|
+
U.add(bk);
|
|
760
|
+
splat(bk, -0.05, 1.3, frontZ + 0.01, 0.07, col(BLOOD[Math.min(d, 2)], 1705990, 0.2), 2);
|
|
761
|
+
}
|
|
762
|
+
if (!spec.human) {
|
|
763
|
+
const wy = P.bloat ? 1.25 : 1.36, hy = P.bloat ? 1.02 : 0.98;
|
|
764
|
+
run(U, 0.08, wy - 0.03, hy, frontZ + 0.012, frontZ - 0.01, col(BLOOD[d], 1705990, 0.35), 0.022);
|
|
765
|
+
drip(U, 0.083, hy - 0.012, frontZ - 5e-3);
|
|
766
|
+
const bs = 0.07 + 0.05 * (3 - d);
|
|
767
|
+
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);
|
|
768
|
+
}
|
|
769
|
+
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);
|
|
770
|
+
if (cut.gut) {
|
|
771
|
+
const gm = M(11553360);
|
|
772
|
+
for (let k = 0; k < 6; k++) {
|
|
773
|
+
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);
|
|
774
|
+
q.userData.flap = true;
|
|
775
|
+
}
|
|
776
|
+
const hole = ball(U, 0.1, red, 0, 1.05, frontZ - 0.03);
|
|
777
|
+
hole.scale.set(1.2, 1, 0.3);
|
|
778
|
+
drip(U, 0, 0.75, frontZ + 0.02, null, 0.5);
|
|
779
|
+
}
|
|
780
|
+
if (cut.flaps) {
|
|
781
|
+
[[0.12, 1.2], [-0.1, 1.35], [0.02, 0.98]].forEach(([x, y], k) => {
|
|
782
|
+
const f = add(U, geoBox(0.07, 0.12, 0.012), k % 2 ? skinM : M(10103338), x, y, frontZ + 0.03);
|
|
783
|
+
f.geometry.translate(0, -0.06, 0);
|
|
784
|
+
f.userData.flap = true;
|
|
785
|
+
drip(U, x, y - 0.12, frontZ + 0.03, null, 1.5);
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
const neckLen = 0.14 + Math.max(0, P.neck);
|
|
789
|
+
const neck = cut.head ? void 0 : add(U, CYL(0.058, 0.068, neckLen, SEG), skinM, 0, 1.63 + Math.max(0, P.neck) / 2, 0);
|
|
790
|
+
const headG = new THREE.Group();
|
|
791
|
+
headG.position.set(0, 1.64 + P.neck, P.headFwd || 0);
|
|
792
|
+
headG.rotation.x = P.headX;
|
|
793
|
+
headG.rotation.z = P.headZ;
|
|
794
|
+
U.add(headG);
|
|
795
|
+
const Hd = new THREE.Group();
|
|
796
|
+
Hd.position.y = -1.64;
|
|
797
|
+
headG.add(Hd);
|
|
798
|
+
if (cut.head) {
|
|
799
|
+
const jaw = add(Hd, SPH(0.12, 16, 12), skinM, 0, 1.69, 0.03);
|
|
800
|
+
jaw.scale.set(1, 0.6, 1);
|
|
801
|
+
drip(U, 0, 1.6, 0.1, null, 0.6);
|
|
802
|
+
const hst = ball(Hd, 0.11, red, 0, 1.74, 0.02);
|
|
803
|
+
hst.scale.set(1, 0.35, 1);
|
|
804
|
+
add(Hd, CYL(0.03, 0.035, 0.08, 10), M(15261900), 0, 1.78, 0);
|
|
805
|
+
splat(U, 0, 1.5, 0.2, 0.05, BLOOD[0], 3);
|
|
806
|
+
} else {
|
|
807
|
+
if (cut.blown) {
|
|
808
|
+
head0: {
|
|
809
|
+
const hb = add(Hd, SPH(0.165, 20, 16, 0, PI * 2, PI * 0.55, PI * 0.45), skinM, 0, 1.8, 0);
|
|
810
|
+
hb.scale.set(0.96, 1.1, 1.02);
|
|
811
|
+
const cv = ball(Hd, 0.15, red, 0, 1.82, 0);
|
|
812
|
+
cv.scale.set(0.95, 0.3, 0.95);
|
|
813
|
+
for (let k = 0; k < 5; k++) {
|
|
814
|
+
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);
|
|
815
|
+
f.scale.set(1.4, 0.6, 0.9);
|
|
816
|
+
}
|
|
817
|
+
;
|
|
818
|
+
drip(Hd, 0, 1.7, 0.1, null, 0.4);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
const head = add(Hd, SPH(0.165, 20, 16), skinM, 0, 1.8, 0);
|
|
822
|
+
if (cut.blown) head.visible = false;
|
|
823
|
+
head.scale.set(0.96 * V.hx * (P.bloat ? 1.12 : 1), 1.1 * V.hy, 1.02 * (P.bloat ? 1.08 : 1));
|
|
824
|
+
const jaw = add(Hd, SPH(0.12, 16, 12), skinM, 0, 1.715, 0.03);
|
|
825
|
+
jaw.userData.bone = 11;
|
|
826
|
+
jaw.scale.set(V.jaw * (P.bloat ? 1.3 : 1), 0.72 * (P.bloat ? 1.25 : 1), 1);
|
|
827
|
+
if (cut.jaw) {
|
|
828
|
+
jaw.geometry.translate(0, 0, 0.06);
|
|
829
|
+
jaw.position.set(0.03, 1.7, -0.04);
|
|
830
|
+
jaw.rotation.set(0.75, 0, 0.35);
|
|
831
|
+
add(Hd, SPH(0.07, 10, 8), red, 0, 1.72, 0.11).scale.set(1.3, 0.5, 0.4);
|
|
832
|
+
drip(Hd, 0.02, 1.6, 0.12, null, 0.6);
|
|
833
|
+
jaw.userData.flap = true;
|
|
834
|
+
}
|
|
835
|
+
[-1, 1].forEach((s) => {
|
|
836
|
+
const e = ball(Hd, 0.038, skinM, s * 0.158, 1.79, -5e-3);
|
|
837
|
+
e.scale.set(0.6, 1, 0.9);
|
|
838
|
+
});
|
|
839
|
+
const nose = add(Hd, geoBox(0.045, 0.065, 0.05), skinM, 0, 1.765, 0.172);
|
|
840
|
+
nose.scale.set(V.nose, V.nose, 1);
|
|
841
|
+
if (P.nose) nose.scale.set(1.5, 1.4, 2.2);
|
|
842
|
+
let covered = false;
|
|
843
|
+
if (O.hat) {
|
|
844
|
+
const c = M(O.hatC);
|
|
845
|
+
covered = true;
|
|
846
|
+
if (O.hat === "cap") {
|
|
847
|
+
const o = add(Hd, capGeo(0.182), c, 0, 1.84, 0);
|
|
848
|
+
o.scale.set(1, 0.78, 1.02);
|
|
849
|
+
add(Hd, geoBox(0.28, 0.022, 0.17), c, 0, 1.85, 0.2, 0.12, 0, 0);
|
|
850
|
+
}
|
|
851
|
+
if (O.hat === "helmet") {
|
|
852
|
+
const o = add(Hd, capGeo(0.205, PI * 0.55), c, 0, 1.82, -0.01);
|
|
853
|
+
o.scale.set(1, 1, 1.08);
|
|
854
|
+
}
|
|
855
|
+
if (O.hat === "hardhat") {
|
|
856
|
+
const o = add(Hd, capGeo(0.198), c, 0, 1.86, 0);
|
|
857
|
+
o.scale.set(1, 1.02, 1.08);
|
|
858
|
+
add(Hd, CYL(0.25, 0.25, 0.02, 24), c, 0, 1.865, 0.02);
|
|
859
|
+
}
|
|
860
|
+
if (O.hat === "fire") {
|
|
861
|
+
const o = add(Hd, capGeo(0.205), c, 0, 1.86, 0);
|
|
862
|
+
o.scale.set(1, 1.02, 1.1);
|
|
863
|
+
const br = add(Hd, CYL(0.27, 0.27, 0.02, 24), c, 0, 1.86, -0.06);
|
|
864
|
+
br.scale.set(1, 1, 1.25);
|
|
865
|
+
add(Hd, geoBox(0.09, 0.08, 0.02), M(14200896), 0, 1.96, 0.205);
|
|
866
|
+
}
|
|
867
|
+
if (O.hat === "moto") {
|
|
868
|
+
const o = add(Hd, SPH(0.235, 20, 16), c, 0, 1.81, -0.01);
|
|
869
|
+
o.scale.set(0.98, 1.04, 1.06);
|
|
870
|
+
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);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if (O.mask) add(Hd, geoBox(0.24, 0.12, 0.04), M(15265520), 0, 1.72, 0.15);
|
|
874
|
+
if (!covered && V.cap) {
|
|
875
|
+
const c = M(V.capC);
|
|
876
|
+
const o = add(Hd, capGeo(0.182), c, 0, 1.84, 0);
|
|
877
|
+
o.scale.set(1, 0.78, 1.02);
|
|
878
|
+
add(Hd, geoBox(0.28, 0.022, 0.17), c, 0, 1.85, 0.2, 0.12, 0, 0);
|
|
879
|
+
covered = true;
|
|
880
|
+
}
|
|
881
|
+
const hc = M(hairC);
|
|
882
|
+
if (!cut.blown && !(d === 3 && VR() < 0.6)) {
|
|
883
|
+
const hairCap = (r, len, tilt) => {
|
|
884
|
+
const o = add(Hd, capGeo(r + 8e-3, len), hc, 0, 1.8, -4e-3);
|
|
885
|
+
o.scale.set(0.97 * V.hx, 1.15 * V.hy, 1.04);
|
|
886
|
+
o.rotation.x = tilt;
|
|
887
|
+
return o;
|
|
888
|
+
};
|
|
889
|
+
const backBand = (t0, tl) => {
|
|
890
|
+
const o = add(Hd, SPH(0.172, 20, 8, PI * 0.85, PI * 1.3, t0, tl), hc, 0, 1.8, 0);
|
|
891
|
+
o.scale.set(0.97 * V.hx, 1.1 * V.hy, 1.04);
|
|
892
|
+
return o;
|
|
893
|
+
};
|
|
894
|
+
const hs = V.hair;
|
|
895
|
+
if (!covered && (hs === "short" || hs === "bun" || hs === "long")) hairCap(0.174, PI / 2, -0.38);
|
|
896
|
+
if (hs === "short" || hs === "bun" || hs === "long") backBand(PI * 0.45, PI * 0.22);
|
|
897
|
+
if (!covered && hs === "bun") ball(Hd, 0.085, hc, 0, 1.92, -0.15);
|
|
898
|
+
if (hs === "long") {
|
|
899
|
+
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);
|
|
900
|
+
l.scale.set(1.05, 1, 1.1);
|
|
901
|
+
}
|
|
902
|
+
if (!covered && hs === "buzz") hairCap(0.17, PI * 0.45, -0.32);
|
|
903
|
+
if (hs === "bald") backBand(PI * 0.42, PI * 0.2);
|
|
904
|
+
if (!covered && hs === "afro") {
|
|
905
|
+
const a = add(Hd, SPH(0.23, 16, 12, 0, PI * 2, 0, PI * 0.62), hc, 0, 1.93, -0.08);
|
|
906
|
+
a.scale.set(1.08, 0.85, 0.95);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
if (V.beard) {
|
|
910
|
+
const b = add(Hd, SPH(0.128, 16, 12), hc, 0, 1.7, 0.035);
|
|
911
|
+
b.scale.set(1.02 * V.jaw, 0.78, 1);
|
|
912
|
+
}
|
|
913
|
+
if (V.glasses) {
|
|
914
|
+
add(Hd, geoBox(0.27, 0.012, 0.015), M(1710618), 0, 1.84, 0.175);
|
|
915
|
+
[-1, 1].forEach((s) => {
|
|
916
|
+
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);
|
|
917
|
+
add(Hd, geoBox(0.012, 0.012, 0.17), M(1710618), s * 0.15, 1.83, 0.09);
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
if (P.mouth) {
|
|
921
|
+
const m = add(Hd, geoBox(0.1, 0.09, 0.03), basic(328965), 0, 1.69, 0.14);
|
|
922
|
+
}
|
|
923
|
+
if (P.gore) splat(Hd, 0, 1.7, 0.15, 0.05, 9046538, 3);
|
|
924
|
+
else if (!spec.human) {
|
|
925
|
+
add(Hd, geoBox(0.125, 0.055, 0.014), M(10115666), 0, 1.69, 0.143);
|
|
926
|
+
add(Hd, geoBox(0.085, 0.03, 0.014), M(1311491), 0, 1.69, 0.151);
|
|
927
|
+
}
|
|
928
|
+
const moto = O.hat === "moto", eyeZ = moto ? 0.245 : 0.162, dim = spec.eyeDim ?? (P.dim || moto), eye = spec.eye ?? z.eye;
|
|
929
|
+
[-1, 1].forEach((s) => {
|
|
930
|
+
if (cut.blown) return;
|
|
931
|
+
if (P.blind && !spec.human) {
|
|
932
|
+
add(Hd, SPH(0.03, 10, 8), M(15262936), s * 0.058, 1.815, 0.152);
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
if (spec.human) {
|
|
936
|
+
add(Hd, geoBox(0.05, 0.03, 0.012), M(15262940), s * 0.058, 1.815, 0.156);
|
|
937
|
+
add(Hd, geoBox(0.022, 0.024, 0.012), M(1709074), s * 0.058, 1.814, 0.163);
|
|
938
|
+
add(Hd, geoBox(0.06, 0.012, 0.02), M(hairC), s * 0.058, 1.85, 0.158, 0, 0, -s * 0.12);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
if (spec.eyesOff) {
|
|
942
|
+
add(Hd, geoBox(0.05, 0.028, 0.02), basic(2763298), s * 0.058, 1.815, 0.158);
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
add(Hd, geoBox(0.052, 0.03, 0.02), basic(eye), s * 0.058, 1.815, eyeZ).userData.eye = 1;
|
|
946
|
+
const g = glow(eye, dim ? 0.1 : 0.15, dim ? 0.3 : 0.85);
|
|
947
|
+
g.position.set(s * 0.058, 1.815, eyeZ + 0.025);
|
|
948
|
+
Hd.add(g);
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
const arms = [];
|
|
952
|
+
[[1, P.armR, P.elbR, cut.armR], [-1, P.armL, P.elbL, cut.armL]].forEach(([s, r, er, isCut]) => {
|
|
953
|
+
const g = new THREE.Group();
|
|
954
|
+
g.position.set(s * (P.bloat ? 0.37 : fem ? 0.285 : 0.305), 1.475, 0);
|
|
955
|
+
U.add(g);
|
|
956
|
+
const upM = V.sleeve === "tank" ? skinM : topM, foreM = V.sleeve === "long" ? topM : skinM, ab = P.bloat ? 1.35 : 1;
|
|
957
|
+
ball(g, 0.088 * ab, upM);
|
|
958
|
+
add(g, cylDown(0.083 * ab, 0.075 * ab, 0.31), upM);
|
|
959
|
+
const el = new THREE.Group();
|
|
960
|
+
el.position.y = -0.31;
|
|
961
|
+
g.add(el);
|
|
962
|
+
if (isCut) {
|
|
963
|
+
stump(el, 0.07, 0);
|
|
964
|
+
drip(el, 0, -0.05, 0, null, 0.9);
|
|
965
|
+
} else {
|
|
966
|
+
ball(el, 0.06 * ab, foreM);
|
|
967
|
+
add(el, cylDown(0.06 * ab, 0.046 * ab, 0.29), foreM);
|
|
968
|
+
const h = ball(el, 0.055 * ab, skinM, 0, -0.33, 5e-3);
|
|
969
|
+
h.scale.set(0.85, 1.15, 0.7);
|
|
970
|
+
if (spec.bandage && s < 0) add(el, CYL(0.068, 0.064, 0.09, SEG), M(15262936), 0, -0.17, 0);
|
|
971
|
+
}
|
|
972
|
+
const dang = s > 0 ? cut.dangleR : cut.dangleL;
|
|
973
|
+
if (dang && !isCut) {
|
|
974
|
+
const fl = add(el, CYL(0.065, 0.05, 0.06, 10), red, 0, 0.01, 0);
|
|
975
|
+
drip(el, 0, -0.33, 0, null, 0.8);
|
|
976
|
+
el.userData.dangle = true;
|
|
977
|
+
}
|
|
978
|
+
if (s > 0 ? cut.shoulderR : cut.shoulderL) {
|
|
979
|
+
g.children.slice().forEach((c) => {
|
|
980
|
+
if (c !== el) g.remove(c);
|
|
981
|
+
});
|
|
982
|
+
el.visible = false;
|
|
983
|
+
stump(g, 0.085, 0);
|
|
984
|
+
drip(g, 0, -0.05, 0, null, 0.7);
|
|
985
|
+
}
|
|
986
|
+
g.rotation.x = r;
|
|
987
|
+
g.rotation.z = s * (0.07 + (P.armOut || 0));
|
|
988
|
+
el.rotation.x = isCut ? 0 : er;
|
|
989
|
+
arms.push({ g, el, s, base: r, eBase: el.rotation.x });
|
|
990
|
+
});
|
|
991
|
+
const flaps = [];
|
|
992
|
+
root.traverse((o) => {
|
|
993
|
+
if (o.userData.flap) flaps.push(o);
|
|
994
|
+
});
|
|
995
|
+
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 };
|
|
996
|
+
if (neck) Object.defineProperty(rig, "neck", { value: neck });
|
|
997
|
+
if (spec.lying) {
|
|
998
|
+
root.rotation.x = -PI / 2;
|
|
999
|
+
root.position.set(0, 0.16 * P.s, 0.9 * P.s);
|
|
1000
|
+
const w = new THREE.Group();
|
|
1001
|
+
w.add(root);
|
|
1002
|
+
return w;
|
|
1003
|
+
}
|
|
1004
|
+
root.userData.rig = rig;
|
|
1005
|
+
return root;
|
|
1006
|
+
}
|
|
1007
|
+
const marked = (spec) => markKitOrigin(build(spec), { owner: "game-characters-three", kind: "person" });
|
|
1008
|
+
return { build: marked, TYPES, setLOD(k) {
|
|
1009
|
+
LK = k;
|
|
1010
|
+
SEG = Math.max(5, Math.round(16 * k));
|
|
1011
|
+
}, topM: () => topM_ref };
|
|
1012
|
+
};
|
|
1013
|
+
|
|
1014
|
+
// src/test-area/module.ts
|
|
1015
|
+
import { createStaticBatcher, isLightBudget } from "@adatechnology/game-render-three";
|
|
1016
|
+
|
|
1017
|
+
// src/rich/person-builder.ts
|
|
1018
|
+
import { markKitOrigin as markKitOrigin2 } from "@adatechnology/game-contracts";
|
|
1019
|
+
import { shapeGeometries as shapeGeometries4 } from "@adatechnology/game-shapes-three";
|
|
1020
|
+
import { MOVES, createRng as createRng2 } from "@adatechnology/game-characters";
|
|
1021
|
+
|
|
1022
|
+
// src/rich/accessories/face-features.ts
|
|
1023
|
+
var FACE_FEATURES = [
|
|
1024
|
+
{
|
|
1025
|
+
ids: ["glasses", "glasses_y"],
|
|
1026
|
+
apply: ({ Bx, Hd, THREE, add, cmat, f }) => {
|
|
1027
|
+
const lens = new THREE.MeshStandardMaterial({ color: f.includes("glasses_y") ? 14200880 : 9416896, roughness: 0.15, metalness: 0.3, transparent: true, opacity: 0.35 });
|
|
1028
|
+
add(Hd, Bx(0.27, 0.012, 0.015), cmat(1710618), 0, 1.84, 0.175);
|
|
1029
|
+
for (const s of [-1, 1]) {
|
|
1030
|
+
add(Hd, Bx(0.09, 0.055, 0.012), lens, s * 0.06, 1.817, 0.178);
|
|
1031
|
+
add(Hd, Bx(0.012, 0.012, 0.17), cmat(1710618), s * 0.15, 1.83, 0.09);
|
|
1032
|
+
}
|
|
1033
|
+
;
|
|
1034
|
+
}
|
|
1035
|
+
},
|
|
1036
|
+
{
|
|
1037
|
+
ids: ["mustache"],
|
|
1038
|
+
apply: ({ Bx, Hd, add, hc }) => {
|
|
1039
|
+
add(Hd, Bx(0.13, 0.035, 0.03), hc, 0, 1.728, 0.165);
|
|
1040
|
+
}
|
|
1041
|
+
},
|
|
1042
|
+
{
|
|
1043
|
+
ids: ["mustache_w"],
|
|
1044
|
+
apply: ({ Bx, Hd, add, cmat }) => {
|
|
1045
|
+
add(Hd, Bx(0.17, 0.045, 0.03), cmat(15131868), 0, 1.726, 0.166);
|
|
1046
|
+
}
|
|
1047
|
+
},
|
|
1048
|
+
{
|
|
1049
|
+
ids: ["beard_g"],
|
|
1050
|
+
apply: ({ Hd, THREE, add, cmat }) => {
|
|
1051
|
+
const b = add(Hd, new THREE.SphereGeometry(0.128, 16, 12), cmat(9079430), 0, 1.7, 0.035);
|
|
1052
|
+
b.scale.set(1.02, 0.78, 1);
|
|
1053
|
+
}
|
|
1054
|
+
},
|
|
1055
|
+
{
|
|
1056
|
+
ids: ["stubble"],
|
|
1057
|
+
apply: ({ Hd, THREE, add, cmat, scaleHex, sp }) => {
|
|
1058
|
+
const b = add(Hd, new THREE.SphereGeometry(0.123, 16, 12), cmat(scaleHex(sp.skin, 0.62)), 0, 1.705, 0.032);
|
|
1059
|
+
b.scale.set(1, 0.74, 1);
|
|
1060
|
+
}
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
ids: ["mask"],
|
|
1064
|
+
apply: ({ Bx, Hd, add, cmat }) => {
|
|
1065
|
+
add(Hd, Bx(0.24, 0.12, 0.04), cmat(11456734), 0, 1.72, 0.15);
|
|
1066
|
+
}
|
|
1067
|
+
},
|
|
1068
|
+
{
|
|
1069
|
+
ids: ["goggles"],
|
|
1070
|
+
apply: ({ Hd, PI, THREE, add, cmat }) => {
|
|
1071
|
+
const b = add(Hd, new THREE.CylinderGeometry(0.168, 0.168, 0.035, 20, 1, true), cmat(1710618), 0, 1.9, 0);
|
|
1072
|
+
b.scale.set(0.97, 1, 1.04);
|
|
1073
|
+
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);
|
|
1074
|
+
}
|
|
1075
|
+
},
|
|
1076
|
+
{
|
|
1077
|
+
ids: ["earrings"],
|
|
1078
|
+
apply: ({ Hd, PI, THREE, add, cmat }) => {
|
|
1079
|
+
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);
|
|
1080
|
+
}
|
|
1081
|
+
},
|
|
1082
|
+
{
|
|
1083
|
+
ids: ["cig"],
|
|
1084
|
+
apply: ({ Bx, Hd, THREE, add, cmat }) => {
|
|
1085
|
+
add(Hd, Bx(0.014, 0.014, 0.09), cmat(15658734), 0.05, 1.7, 0.19);
|
|
1086
|
+
const e = add(Hd, Bx(0.017, 0.017, 0.017), new THREE.MeshBasicMaterial({ color: 16738858 }), 0.05, 1.7, 0.237);
|
|
1087
|
+
e.castShadow = false;
|
|
1088
|
+
}
|
|
1089
|
+
},
|
|
1090
|
+
{
|
|
1091
|
+
ids: ["mirror"],
|
|
1092
|
+
apply: ({ Bx, Hd, add, cmat }) => {
|
|
1093
|
+
add(Hd, Bx(0.27, 0.012, 0.015), cmat(1710618), 0, 1.84, 0.175);
|
|
1094
|
+
for (const s of [-1, 1]) {
|
|
1095
|
+
add(Hd, Bx(0.095, 0.06, 0.014), cmat(13159632, { metalness: 0.95, roughness: 0.12 }), s * 0.06, 1.817, 0.179);
|
|
1096
|
+
add(Hd, Bx(0.012, 0.012, 0.17), cmat(1710618), s * 0.15, 1.83, 0.09);
|
|
1097
|
+
}
|
|
1098
|
+
;
|
|
1099
|
+
}
|
|
1100
|
+
},
|
|
1101
|
+
{
|
|
1102
|
+
ids: ["labgoggles"],
|
|
1103
|
+
apply: ({ Hd, PI, THREE, add, cmat }) => {
|
|
1104
|
+
const b = add(Hd, new THREE.CylinderGeometry(0.168, 0.168, 0.03, 10, 1, true), cmat(2779786), 0, 1.92, 0);
|
|
1105
|
+
b.scale.set(0.97, 1, 1.04);
|
|
1106
|
+
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);
|
|
1107
|
+
}
|
|
1108
|
+
},
|
|
1109
|
+
{
|
|
1110
|
+
ids: ["beard"],
|
|
1111
|
+
apply: ({ Hd, THREE, add, hc }) => {
|
|
1112
|
+
const b = add(Hd, new THREE.SphereGeometry(0.128, 8, 6), hc, 0, 1.7, 0.035);
|
|
1113
|
+
b.scale.set(1.02, 0.8, 1);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
];
|
|
1117
|
+
|
|
1118
|
+
// src/rich/accessories/hands.ts
|
|
1119
|
+
var buildHand = ({ THREE, add, hg, handM, s, detailed }) => {
|
|
1120
|
+
const fingers = [];
|
|
1121
|
+
if (!detailed) return { fingers, th: null };
|
|
1122
|
+
for (let f = 0; f < 4; f++) {
|
|
1123
|
+
const fg = new THREE.Group();
|
|
1124
|
+
fg.position.set(0, -0.078, 0.027 - f * 0.018);
|
|
1125
|
+
hg.add(fg);
|
|
1126
|
+
const len = [0.034, 0.038, 0.036, 0.03][f];
|
|
1127
|
+
add(fg, new THREE.CylinderGeometry(0.0105, 0.0105, len, 8).translate(0, -len / 2, 0), handM, 0, 0, 0);
|
|
1128
|
+
const f2 = new THREE.Group();
|
|
1129
|
+
f2.position.y = -len;
|
|
1130
|
+
fg.add(f2);
|
|
1131
|
+
add(f2, new THREE.CylinderGeometry(0.0105, 9e-3, len * 0.85, 8).translate(0, -len * 0.42, 0), handM, 0, 0, 0);
|
|
1132
|
+
add(f2, new THREE.SphereGeometry(95e-4, 8, 6), handM, 0, -len * 0.85, 0);
|
|
1133
|
+
fingers.push(fg, f2);
|
|
1134
|
+
}
|
|
1135
|
+
const th = new THREE.Group();
|
|
1136
|
+
th.position.set(-s * 0.018, -0.03, 0.034);
|
|
1137
|
+
th.rotation.set(-0.5, 0, -s * 0.45);
|
|
1138
|
+
hg.add(th);
|
|
1139
|
+
add(th, new THREE.CylinderGeometry(0.012, 0.01, 0.045, 8).translate(0, -0.022, 0), handM, 0, 0, 0);
|
|
1140
|
+
add(th, new THREE.SphereGeometry(0.01, 8, 6), handM, 0, -0.045, 0);
|
|
1141
|
+
return { fingers, th };
|
|
1142
|
+
};
|
|
1143
|
+
|
|
1144
|
+
// src/rich/accessories/hand-items.ts
|
|
1145
|
+
import { LIGHT_PRIORITY } from "@adatechnology/game-render-three";
|
|
1146
|
+
var HAND_ITEMS = {
|
|
1147
|
+
axe: ({ Bx, add, cmat, g, metal, woodM }) => {
|
|
1148
|
+
add(g, Bx(0.05, 0.78, 0.05), woodM, 0, -0.18, 0);
|
|
1149
|
+
add(g, Bx(0.04, 0.14, 0.26), cmat(10103328), 0, -0.55, 0.09);
|
|
1150
|
+
add(g, Bx(0.045, 0.14, 0.05), metal, 0, -0.55, 0.23);
|
|
1151
|
+
return;
|
|
1152
|
+
},
|
|
1153
|
+
machete: ({ Bx, add, cmat, g, metal }) => {
|
|
1154
|
+
add(g, Bx(0.05, 0.14, 0.06), cmat(1118481), 0, -0.02, 0);
|
|
1155
|
+
add(g, Bx(0.015, 0.5, 0.08), metal, 0, -0.34, 0.015);
|
|
1156
|
+
return;
|
|
1157
|
+
},
|
|
1158
|
+
wrench: ({ Bx, add, g, metal }) => {
|
|
1159
|
+
add(g, Bx(0.05, 0.5, 0.07), metal, 0, -0.18, 0);
|
|
1160
|
+
add(g, Bx(0.05, 0.08, 0.18), metal, 0, -0.45, 0.04);
|
|
1161
|
+
return;
|
|
1162
|
+
},
|
|
1163
|
+
hammer: ({ Bx, add, dark, g, woodM }) => {
|
|
1164
|
+
add(g, Bx(0.04, 0.34, 0.04), woodM, 0, -0.13, 0);
|
|
1165
|
+
add(g, Bx(0.055, 0.07, 0.18), dark, 0, -0.3, 0.02);
|
|
1166
|
+
return;
|
|
1167
|
+
},
|
|
1168
|
+
hoe: ({ Bx, add, g, metal, woodM }) => {
|
|
1169
|
+
add(g, Bx(0.04, 1.42, 0.04), woodM, 0, -0.06, 0);
|
|
1170
|
+
add(g, Bx(0.24, 0.04, 0.14), metal, 0, -0.74, 0.07, 0.35, 0, 0);
|
|
1171
|
+
return;
|
|
1172
|
+
},
|
|
1173
|
+
baton: ({ THREE, add, cmat, g }) => {
|
|
1174
|
+
add(g, new THREE.CylinderGeometry(0.022, 0.022, 0.55, 16), cmat(1118481), 0, -0.2, 0);
|
|
1175
|
+
return;
|
|
1176
|
+
},
|
|
1177
|
+
knife: ({ Bx, add, cmat, g, metal }) => {
|
|
1178
|
+
add(g, Bx(0.035, 0.1, 0.04), cmat(1118481), 0, -0.02, 0);
|
|
1179
|
+
add(g, Bx(0.01, 0.24, 0.05), metal, 0, -0.18, 5e-3);
|
|
1180
|
+
return;
|
|
1181
|
+
},
|
|
1182
|
+
pistol: ({ Bx, add, dark, g }) => {
|
|
1183
|
+
add(g, Bx(0.04, 0.11, 0.06), dark, 0, -0.04, 0);
|
|
1184
|
+
add(g, Bx(0.04, 0.06, 0.2), dark, 0, 0.02, 0.07);
|
|
1185
|
+
return;
|
|
1186
|
+
},
|
|
1187
|
+
pliers: ({ Bx, add, cmat, g, metal }) => {
|
|
1188
|
+
add(g, Bx(0.03, 0.2, 0.03), cmat(12595754), 0.015, -0.08, 0);
|
|
1189
|
+
add(g, Bx(0.03, 0.2, 0.03), cmat(12595754), -0.015, -0.08, 0);
|
|
1190
|
+
add(g, Bx(0.04, 0.08, 0.03), metal, 0, -0.21, 0);
|
|
1191
|
+
return;
|
|
1192
|
+
},
|
|
1193
|
+
medbag: ({ Bx, add, cmat, g }) => {
|
|
1194
|
+
add(g, Bx(0.36, 0.24, 0.18), cmat(15000282), 0, -0.16, 0);
|
|
1195
|
+
add(g, Bx(0.03, 0.09, 0.01), cmat(12071466), 0, -0.16, 0.095);
|
|
1196
|
+
add(g, Bx(0.09, 0.03, 0.01), cmat(12071466), 0, -0.16, 0.095);
|
|
1197
|
+
add(g, Bx(0.18, 0.04, 0.03), cmat(2763306), 0, -0.02, 0);
|
|
1198
|
+
return;
|
|
1199
|
+
},
|
|
1200
|
+
toolbox: ({ Bx, add, cmat, dark, g }) => {
|
|
1201
|
+
add(g, Bx(0.42, 0.2, 0.2), cmat(11745834, { metalness: 0.3, roughness: 0.5 }), 0, -0.14, 0);
|
|
1202
|
+
add(g, Bx(0.24, 0.04, 0.04), dark, 0, -0.01, 0);
|
|
1203
|
+
return;
|
|
1204
|
+
},
|
|
1205
|
+
book: ({ Bx, add, cmat, g }) => {
|
|
1206
|
+
add(g, Bx(0.05, 0.26, 0.2), cmat(2771562), 0, -0.12, 0.02);
|
|
1207
|
+
add(g, Bx(0.052, 0.24, 0.01), cmat(14736587), 0, -0.12, 0.12);
|
|
1208
|
+
return;
|
|
1209
|
+
},
|
|
1210
|
+
bottle: ({ THREE, add, cmat, g }) => {
|
|
1211
|
+
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);
|
|
1212
|
+
add(g, new THREE.CylinderGeometry(0.018, 0.03, 0.08, 16), cmat(3828282), 0, 0.03, 0.02);
|
|
1213
|
+
return;
|
|
1214
|
+
},
|
|
1215
|
+
clipboard: ({ Bx, add, cmat, g }) => {
|
|
1216
|
+
add(g, Bx(0.03, 0.32, 0.24), cmat(6965802), 0, -0.12, 0.04);
|
|
1217
|
+
add(g, Bx(0.035, 0.26, 0.2), cmat(15263452), 0, -0.13, 0.04);
|
|
1218
|
+
return;
|
|
1219
|
+
},
|
|
1220
|
+
shovel: ({ Bx, THREE, add, cmat, g, metal, woodM }) => {
|
|
1221
|
+
add(g, new THREE.CylinderGeometry(0.022, 0.022, 1.3, 6), woodM, 0, -0.3, 0);
|
|
1222
|
+
add(g, Bx(0.05, 0.18, 0.05), cmat(12855338), 0, 0.22, 0);
|
|
1223
|
+
add(g, Bx(0.22, 0.28, 0.03), metal, 0, -1, 0.03);
|
|
1224
|
+
return;
|
|
1225
|
+
},
|
|
1226
|
+
slingshot: ({ Bx, add, cmat, g, woodM }) => {
|
|
1227
|
+
add(g, Bx(0.03, 0.16, 0.03), woodM, 0, -0.02, 0);
|
|
1228
|
+
add(g, Bx(0.03, 0.12, 0.03), woodM, -0.04, -0.14, 0, 0, 0, -0.4);
|
|
1229
|
+
add(g, Bx(0.03, 0.12, 0.03), woodM, 0.04, -0.14, 0, 0, 0, 0.4);
|
|
1230
|
+
add(g, Bx(0.1, 0.015, 0.015), cmat(12855338), 0, -0.2, 0);
|
|
1231
|
+
return;
|
|
1232
|
+
},
|
|
1233
|
+
bible: ({ Bx, add, cmat, g }) => {
|
|
1234
|
+
add(g, Bx(0.06, 0.24, 0.18), cmat(3810326), 0, -0.12, 0.03);
|
|
1235
|
+
add(g, Bx(0.062, 0.02, 0.182), cmat(13673018), 0, -0.02, 0.03);
|
|
1236
|
+
return;
|
|
1237
|
+
},
|
|
1238
|
+
flare: ({ THREE, add, cmat, g, lights, sources }) => {
|
|
1239
|
+
{
|
|
1240
|
+
add(g, new THREE.CylinderGeometry(0.03, 0.03, 0.26, 6), cmat(15230506), 0, -0.06, 0.02);
|
|
1241
|
+
add(g, new THREE.CylinderGeometry(0.035, 0.02, 0.06, 6), new THREE.MeshBasicMaterial({ color: 16756848 }), 0, 0.1, 0.02);
|
|
1242
|
+
if (lights) {
|
|
1243
|
+
const bulb = new THREE.Object3D();
|
|
1244
|
+
bulb.position.set(0, 0.15, 0.05);
|
|
1245
|
+
g.add(bulb);
|
|
1246
|
+
sources?.push(lights.addSource({ owner: "characters.flare", priority: LIGHT_PRIORITY.flare, color: 16738858, intensity: 1.4, distance: 4, decay: 2, position: [0, 0, 0], follow: bulb }));
|
|
1247
|
+
} else {
|
|
1248
|
+
const L = new THREE.PointLight(16738858, 1.4, 4, 2);
|
|
1249
|
+
L.position.set(0, 0.15, 0.05);
|
|
1250
|
+
g.add(L);
|
|
1251
|
+
}
|
|
1252
|
+
g.rotation.x = -0.5;
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
},
|
|
1256
|
+
radio_h: ({ Bx, THREE, add, cmat, g }) => {
|
|
1257
|
+
add(g, Bx(0.07, 0.2, 0.11), cmat(3816768), 0, -0.08, 0.03);
|
|
1258
|
+
add(g, new THREE.CylinderGeometry(8e-3, 8e-3, 0.34, 4), cmat(10132122), 0, 0.15, 0.05);
|
|
1259
|
+
add(g, Bx(0.075, 0.05, 0.115), cmat(14204992), 0, -0.12, 0.03);
|
|
1260
|
+
return;
|
|
1261
|
+
},
|
|
1262
|
+
pump: ({ Bx, add, cmat, dark, g }) => {
|
|
1263
|
+
add(g, Bx(0.05, 0.82, 0.05), dark, 0, -0.22, 0.02);
|
|
1264
|
+
add(g, Bx(0.07, 0.15, 0.09), cmat(2763306), 0, -0.05, 0.02);
|
|
1265
|
+
add(g, Bx(0.065, 0.14, 0.07), cmat(3811868), 0, -0.5, 0.02);
|
|
1266
|
+
add(g, Bx(0.03, 0.7, 0.03), dark, 0, -0.2, 0.07);
|
|
1267
|
+
return;
|
|
1268
|
+
},
|
|
1269
|
+
mic: ({ THREE, add, cmat, g }) => {
|
|
1270
|
+
add(g, new THREE.CylinderGeometry(0.022, 0.028, 0.18, 16), cmat(1710618), 0, 0, 0);
|
|
1271
|
+
add(g, new THREE.DodecahedronGeometry(0.045, 0), cmat(6975090, { metalness: 0.6 }), 0, 0.1, 0);
|
|
1272
|
+
g.rotation.x = -0.4;
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1276
|
+
|
|
1277
|
+
// src/rich/accessories/hats.ts
|
|
1278
|
+
var HATS = [
|
|
1279
|
+
{
|
|
1280
|
+
kinds: ["cap", "capback"],
|
|
1281
|
+
apply: ({ Bx, Hd, PI, add, c, cap, k }) => {
|
|
1282
|
+
const o = add(Hd, cap(0.182, PI / 2), c, 0, 1.84, 0);
|
|
1283
|
+
o.scale.set(1, 0.78, 1.02);
|
|
1284
|
+
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);
|
|
1285
|
+
}
|
|
1286
|
+
},
|
|
1287
|
+
{
|
|
1288
|
+
kinds: ["beret"],
|
|
1289
|
+
apply: ({ Hd, THREE, add, c }) => {
|
|
1290
|
+
const o = add(Hd, new THREE.CylinderGeometry(0.2, 0.185, 0.07, 20), c, 0.025, 1.95, 0, 0, 0, 0.16);
|
|
1291
|
+
o.scale.set(1, 1, 1.05);
|
|
1292
|
+
}
|
|
1293
|
+
},
|
|
1294
|
+
{
|
|
1295
|
+
kinds: ["hardhat"],
|
|
1296
|
+
apply: ({ Bx, Hd, PI, THREE, add, c, cap }) => {
|
|
1297
|
+
const o = add(Hd, cap(0.198, PI / 2), c, 0, 1.86, 0);
|
|
1298
|
+
o.scale.set(1, 1.02, 1.08);
|
|
1299
|
+
add(Hd, new THREE.CylinderGeometry(0.25, 0.25, 0.02, 20), c, 0, 1.865, 0.02);
|
|
1300
|
+
add(Hd, Bx(0.03, 0.06, 0.38), c, 0, 2.04, 0);
|
|
1301
|
+
}
|
|
1302
|
+
},
|
|
1303
|
+
{
|
|
1304
|
+
kinds: ["fire"],
|
|
1305
|
+
apply: ({ Bx, Hd, PI, THREE, add, c, cap, cmat }) => {
|
|
1306
|
+
const o = add(Hd, cap(0.205, PI / 2), c, 0, 1.86, 0);
|
|
1307
|
+
o.scale.set(1, 1.02, 1.1);
|
|
1308
|
+
const br = add(Hd, new THREE.CylinderGeometry(0.27, 0.27, 0.02, 20), c, 0, 1.86, -0.06);
|
|
1309
|
+
br.scale.set(1, 1, 1.25);
|
|
1310
|
+
add(Hd, Bx(0.09, 0.08, 0.02), cmat(14200896, { metalness: 0.7 }), 0, 1.96, 0.205);
|
|
1311
|
+
}
|
|
1312
|
+
},
|
|
1313
|
+
{
|
|
1314
|
+
kinds: ["straw"],
|
|
1315
|
+
apply: ({ Hd, THREE, add, c, cmat }) => {
|
|
1316
|
+
add(Hd, new THREE.CylinderGeometry(0.38, 0.38, 0.022, 24), c, 0, 1.93, 0);
|
|
1317
|
+
add(Hd, new THREE.CylinderGeometry(0.155, 0.18, 0.15, 20), c, 0, 2.01, 0);
|
|
1318
|
+
add(Hd, new THREE.CylinderGeometry(0.182, 0.182, 0.03, 20), cmat(5913122), 0, 1.955, 0);
|
|
1319
|
+
}
|
|
1320
|
+
},
|
|
1321
|
+
{
|
|
1322
|
+
kinds: ["moto"],
|
|
1323
|
+
apply: ({ Hd, PI, THREE, add, c, cmat }) => {
|
|
1324
|
+
const o = add(Hd, new THREE.SphereGeometry(0.235, 20, 16), c, 0, 1.81, -0.01);
|
|
1325
|
+
o.scale.set(0.98, 1.04, 1.06);
|
|
1326
|
+
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);
|
|
1327
|
+
add(Hd, new THREE.CylinderGeometry(0.215, 0.2, 0.05, 20), cmat(12071466), 0, 1.6, -0.01);
|
|
1328
|
+
}
|
|
1329
|
+
},
|
|
1330
|
+
{
|
|
1331
|
+
kinds: ["bandana"],
|
|
1332
|
+
apply: ({ Bx, Hd, PI, add, c, cap }) => {
|
|
1333
|
+
const o = add(Hd, cap(0.18, PI * 0.48), c, 0, 1.8, -5e-3);
|
|
1334
|
+
o.scale.set(0.98, 1.1, 1.05);
|
|
1335
|
+
o.rotation.x = -0.4;
|
|
1336
|
+
add(Hd, Bx(0.07, 0.12, 0.03), c, 0, 1.76, -0.19, 0.3, 0, 0);
|
|
1337
|
+
}
|
|
1338
|
+
},
|
|
1339
|
+
{
|
|
1340
|
+
kinds: ["helmet"],
|
|
1341
|
+
apply: ({ Bx, Hd, PI, THREE, add, c, cap, cmat, sp }) => {
|
|
1342
|
+
const o = add(Hd, cap(0.205, PI / 2), c, 0, 1.835, -5e-3);
|
|
1343
|
+
o.scale.set(1, 0.98, 1.08);
|
|
1344
|
+
add(Hd, new THREE.CylinderGeometry(0.208, 0.212, 0.04, 20), c, 0, 1.84, -5e-3);
|
|
1345
|
+
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);
|
|
1346
|
+
if (sp.hat?.photo) {
|
|
1347
|
+
add(Hd, Bx(0.07, 0.08, 0.012), cmat(16052448), 0.13, 1.9, 0.14, 0, 0.75, 0);
|
|
1348
|
+
add(Hd, Bx(0.05, 0.055, 0.014), cmat(9071184), 0.13, 1.9, 0.141, 0, 0.75, 0);
|
|
1349
|
+
}
|
|
1350
|
+
if (sp.hat?.nvg) {
|
|
1351
|
+
add(Hd, Bx(0.07, 0.05, 0.06), cmat(1710618), 0, 2, 0.175);
|
|
1352
|
+
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);
|
|
1353
|
+
}
|
|
1354
|
+
for (const s of [-1, 1]) add(Hd, Bx(0.012, 0.15, 0.012), cmat(1710618), s * 0.16, 1.74, 0.04);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
];
|
|
1358
|
+
|
|
1359
|
+
// src/rich/accessories/extras.ts
|
|
1360
|
+
var EXTRAS = {
|
|
1361
|
+
steth: ({ Bx, PI, THREE, U, add, cmat }) => {
|
|
1362
|
+
add(U, Bx(0.022, 0.24, 0.022), cmat(2763824), 0.085, 1.45, 0.17);
|
|
1363
|
+
add(U, Bx(0.022, 0.24, 0.022), cmat(2763824), -0.085, 1.45, 0.17);
|
|
1364
|
+
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);
|
|
1365
|
+
return;
|
|
1366
|
+
},
|
|
1367
|
+
mask_chin: ({ Bx, Hd, add, m }) => {
|
|
1368
|
+
add(Hd, Bx(0.24, 0.07, 0.04), m, 0, 1.645, 0.12);
|
|
1369
|
+
return;
|
|
1370
|
+
},
|
|
1371
|
+
vest: ({ Bx, THREE, U, add, m }) => {
|
|
1372
|
+
{
|
|
1373
|
+
const v = add(U, new THREE.CylinderGeometry(0.275, 0.24, 0.48, 20), m, 0, 1.3, 0);
|
|
1374
|
+
v.scale.set(1.12, 1, 0.7);
|
|
1375
|
+
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);
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
},
|
|
1379
|
+
pouches: ({ Bx, U, add, m }) => {
|
|
1380
|
+
add(U, Bx(0.1, 0.12, 0.09), m, 0.17, 0.95, -0.15);
|
|
1381
|
+
add(U, Bx(0.1, 0.12, 0.09), m, -0.17, 0.95, -0.15);
|
|
1382
|
+
return;
|
|
1383
|
+
},
|
|
1384
|
+
belt: ({ U, m, ring }) => {
|
|
1385
|
+
ring(U, 0.212, 0.07, m, 0.95, 1.12, 0.72);
|
|
1386
|
+
return;
|
|
1387
|
+
},
|
|
1388
|
+
holster: ({ Bx, add, cmat, legs }) => {
|
|
1389
|
+
add(legs[1].g, Bx(0.06, 0.2, 0.11), cmat(1118481), 0.105, -0.1, 0);
|
|
1390
|
+
return;
|
|
1391
|
+
},
|
|
1392
|
+
radio: ({ Bx, U, add, cmat }) => {
|
|
1393
|
+
add(U, Bx(0.075, 0.12, 0.05), cmat(1710618), 0.15, 1.42, 0.18);
|
|
1394
|
+
add(U, Bx(0.012, 0.1, 0.012), cmat(1710618), 0.17, 1.52, 0.18);
|
|
1395
|
+
return;
|
|
1396
|
+
},
|
|
1397
|
+
stripes: ({ THREE, U, add, arms, m, ring }) => {
|
|
1398
|
+
ring(U, 0.205, 0.05, m, 1.03, 1.13, 0.68);
|
|
1399
|
+
ring(U, 0.268, 0.05, m, 1.42, 1.13, 0.67);
|
|
1400
|
+
arms.forEach((a) => {
|
|
1401
|
+
const r = add(a.el, new THREE.CylinderGeometry(0.058, 0.054, 0.05, 18), m, 0, -0.17, 0);
|
|
1402
|
+
});
|
|
1403
|
+
return;
|
|
1404
|
+
},
|
|
1405
|
+
legstripes: ({ THREE, add, legs, m }) => {
|
|
1406
|
+
legs.forEach((l) => add(l.kn, new THREE.CylinderGeometry(0.07, 0.067, 0.05, 18), m, 0, -0.22, 0));
|
|
1407
|
+
return;
|
|
1408
|
+
},
|
|
1409
|
+
stains: ({ R, U, splat }) => {
|
|
1410
|
+
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);
|
|
1411
|
+
return;
|
|
1412
|
+
},
|
|
1413
|
+
stains_y: ({ R, U, splat }) => {
|
|
1414
|
+
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);
|
|
1415
|
+
return;
|
|
1416
|
+
},
|
|
1417
|
+
rag: ({ Bx, U, add, m }) => {
|
|
1418
|
+
add(U, Bx(0.08, 0.16, 0.03), m, -0.17, 0.92, 0.15);
|
|
1419
|
+
return;
|
|
1420
|
+
},
|
|
1421
|
+
apron: ({ Bx, U, add, legs, m }) => {
|
|
1422
|
+
add(U, Bx(0.4, 0.38, 0.025), m, 0, 1.2, 0.158, -0.07, 0, 0);
|
|
1423
|
+
add(U, Bx(0.42, 0.2, 0.025), m, 0, 0.95, 0.158);
|
|
1424
|
+
legs.forEach((l) => add(l.g, Bx(0.2, 0.36, 0.025), m, -l.s * 0.01, -0.19, 0.112));
|
|
1425
|
+
add(U, Bx(0.02, 0.2, 0.02), m, 0.11, 1.52, 0.15);
|
|
1426
|
+
add(U, Bx(0.02, 0.2, 0.02), m, -0.11, 1.52, 0.15);
|
|
1427
|
+
return;
|
|
1428
|
+
},
|
|
1429
|
+
buttons: ({ Bx, U, add, cmat }) => {
|
|
1430
|
+
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);
|
|
1431
|
+
return;
|
|
1432
|
+
},
|
|
1433
|
+
earpro: ({ PI, THREE, U, add, m }) => {
|
|
1434
|
+
{
|
|
1435
|
+
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);
|
|
1436
|
+
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);
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
},
|
|
1440
|
+
coil: ({ PI, THREE, U, add, m }) => {
|
|
1441
|
+
add(U, new THREE.TorusGeometry(0.16, 0.032, 8, 20), m, 0.2, 1.38, 0, 0, PI / 2, 0.5);
|
|
1442
|
+
return;
|
|
1443
|
+
},
|
|
1444
|
+
deliverybox: ({ Bx, U, add, cmat, m }) => {
|
|
1445
|
+
add(U, Bx(0.52, 0.5, 0.44), m, 0, 1.32, -0.38);
|
|
1446
|
+
add(U, Bx(0.53, 0.06, 0.45), cmat(15263970), 0, 1.43, -0.38);
|
|
1447
|
+
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);
|
|
1448
|
+
return;
|
|
1449
|
+
},
|
|
1450
|
+
backpack: ({ Bx, THREE, U, add, cmat, m }) => {
|
|
1451
|
+
{
|
|
1452
|
+
const b = add(U, new THREE.CylinderGeometry(0.2, 0.22, 0.54, 16), m, 0, 1.24, -0.26);
|
|
1453
|
+
b.scale.set(1.15, 1, 0.6);
|
|
1454
|
+
add(U, Bx(0.3, 0.16, 0.07), m, 0, 1.08, -0.4);
|
|
1455
|
+
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);
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
},
|
|
1459
|
+
helmetpack: ({ Bx, THREE, U, add, cmat, m }) => {
|
|
1460
|
+
{
|
|
1461
|
+
const h = add(U, new THREE.SphereGeometry(0.15, 18, 14), m, 0.2, 0.98, -0.42);
|
|
1462
|
+
h.scale.set(1, 1.05, 1.05);
|
|
1463
|
+
add(U, Bx(0.09, 0.07, 0.02), cmat(14729248), 0.24, 1, -0.565, 0, 0.25, 0);
|
|
1464
|
+
add(U, new THREE.CylinderGeometry(0.14, 0.13, 0.04, 18), cmat(12071466), 0.2, 0.85, -0.42);
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
},
|
|
1468
|
+
hood: ({ PI, THREE, U, add, m }) => {
|
|
1469
|
+
{
|
|
1470
|
+
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);
|
|
1471
|
+
h.scale.set(1.1, 1.2, 1);
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
},
|
|
1475
|
+
pocket: ({ Bx, U, add, m }) => {
|
|
1476
|
+
add(U, Bx(0.32, 0.13, 0.03), m, 0, 1.03, 0.135);
|
|
1477
|
+
return;
|
|
1478
|
+
},
|
|
1479
|
+
sweatervest: ({ U, add, fem, m, torsoGeo }) => {
|
|
1480
|
+
{
|
|
1481
|
+
const g = torsoGeo(fem);
|
|
1482
|
+
g.scale(1.04, 1, 1.1);
|
|
1483
|
+
add(U, g, m, 0, 0, 0);
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
},
|
|
1487
|
+
collar: ({ Bx, U, add, m }) => {
|
|
1488
|
+
add(U, Bx(0.18, 0.05, 0.05), m, 0, 1.58, 0.1);
|
|
1489
|
+
return;
|
|
1490
|
+
},
|
|
1491
|
+
satchel: ({ Bx, U, add, cmat, m }) => {
|
|
1492
|
+
add(U, Bx(0.045, 0.82, 0.02), cmat(3811866), 0, 1.2, 0.18, 0, 0, 0.68);
|
|
1493
|
+
add(U, Bx(0.28, 0.24, 0.1), m, -0.1, 0.92, -0.2);
|
|
1494
|
+
return;
|
|
1495
|
+
},
|
|
1496
|
+
blouse: ({ Bx, U, add, m }) => {
|
|
1497
|
+
add(U, Bx(0.12, 0.26, 0.02), m, 0, 1.38, 0.157);
|
|
1498
|
+
return;
|
|
1499
|
+
},
|
|
1500
|
+
cross: ({ Bx, U, add, m }) => {
|
|
1501
|
+
add(U, Bx(0.022, 0.085, 0.015), m, 0, 1.37, 0.178);
|
|
1502
|
+
add(U, Bx(0.055, 0.02, 0.015), m, 0, 1.39, 0.178);
|
|
1503
|
+
return;
|
|
1504
|
+
},
|
|
1505
|
+
armband: ({ THREE, add, arms, m }) => {
|
|
1506
|
+
add(arms[1].g, new THREE.CylinderGeometry(0.08, 0.078, 0.08, 18), m, 0, -0.12, 0);
|
|
1507
|
+
return;
|
|
1508
|
+
},
|
|
1509
|
+
tiedsleeves: ({ Bx, U, add, legs, m, ring }) => {
|
|
1510
|
+
ring(U, 0.215, 0.1, m, 0.95, 1.12, 0.74);
|
|
1511
|
+
legs.forEach((l) => add(l.g, Bx(0.1, 0.26, 0.05), m, l.s * 0.02, -0.1, 0.105));
|
|
1512
|
+
return;
|
|
1513
|
+
},
|
|
1514
|
+
labcoat: ({ Bx, THREE, U, add, arms, cmat, fem, legs, m, ring, torsoGeo }) => {
|
|
1515
|
+
{
|
|
1516
|
+
const g = torsoGeo(fem);
|
|
1517
|
+
g.scale(1.06, 1, 1.12);
|
|
1518
|
+
add(U, g, m, 0, 0, 0);
|
|
1519
|
+
ring(U, 0.22, 0.14, m, 0.9, 1.12, 0.76);
|
|
1520
|
+
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));
|
|
1521
|
+
add(U, Bx(0.025, 0.62, 0.012), cmat(10132114), 0, 1.2, 0.19);
|
|
1522
|
+
arms.forEach((a) => {
|
|
1523
|
+
add(a.g, new THREE.CylinderGeometry(0.085, 0.075, 0.31, 18).translate(0, -0.155, 0), m, 0, 0, 0);
|
|
1524
|
+
add(a.el, new THREE.CylinderGeometry(0.072, 0.064, 0.27, 18).translate(0, -0.135, 0), m, 0, 0, 0);
|
|
1525
|
+
});
|
|
1526
|
+
add(U, Bx(0.1, 0.09, 0.02), m, -0.15, 1.4, 0.19);
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
},
|
|
1530
|
+
rosary: ({ Bx, PI, THREE, add, arms, m }) => {
|
|
1531
|
+
add(arms[1].el, new THREE.TorusGeometry(0.055, 0.012, 4, 10), m, 0, -0.27, 0, PI / 2, 0, 0);
|
|
1532
|
+
add(arms[1].el, Bx(0.015, 0.06, 0.01), m, 0, -0.33, 0.06);
|
|
1533
|
+
return;
|
|
1534
|
+
},
|
|
1535
|
+
watch: ({ Bx, PI, THREE, U, add, cmat }) => {
|
|
1536
|
+
{
|
|
1537
|
+
const g = cmat(13673018, { metalness: 0.8, roughness: 0.3 });
|
|
1538
|
+
add(U, new THREE.CylinderGeometry(0.05, 0.05, 0.02, 10).rotateX(PI / 2), g, 0.12, 1.06, 0.135);
|
|
1539
|
+
add(U, Bx(0.015, 0.14, 0.01), g, 0.06, 1.1, 0.135, 0, 0, 0.9);
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
},
|
|
1543
|
+
chain: ({ PI, THREE, add, legs, m, root }) => {
|
|
1544
|
+
{
|
|
1545
|
+
add(legs[0].kn, new THREE.TorusGeometry(0.085, 0.015, 4, 8), m, 0, -0.34, 0, PI / 2, 0, 0);
|
|
1546
|
+
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);
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
},
|
|
1550
|
+
unicorn: ({ Bx, THREE, U, add, cmat, m }) => {
|
|
1551
|
+
add(U, Bx(0.32, 0.36, 0.16), m, 0, 1.24, -0.24);
|
|
1552
|
+
add(U, new THREE.ConeGeometry(0.035, 0.16, 5), cmat(15910986), 0, 1.5, -0.24);
|
|
1553
|
+
for (const s of [-1, 1]) {
|
|
1554
|
+
add(U, Bx(0.05, 0.07, 0.04), cmat(15239864), s * 0.09, 1.45, -0.24);
|
|
1555
|
+
add(U, Bx(0.04, 0.42, 0.03), cmat(11561672), s * 0.13, 1.3, 0.16, -0.1, 0, 0);
|
|
1556
|
+
}
|
|
1557
|
+
return;
|
|
1558
|
+
},
|
|
1559
|
+
whistle: ({ Bx, PI, THREE, U, add, cmat }) => {
|
|
1560
|
+
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);
|
|
1561
|
+
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);
|
|
1562
|
+
return;
|
|
1563
|
+
},
|
|
1564
|
+
pen: ({ Hd, THREE, add, cmat }) => {
|
|
1565
|
+
add(Hd, new THREE.CylinderGeometry(0.01, 0.01, 0.26, 5), cmat(2773192), 0, 1.92, -0.15, 0, 0, 1.3);
|
|
1566
|
+
return;
|
|
1567
|
+
},
|
|
1568
|
+
photo: ({ Bx, U, add, cmat }) => {
|
|
1569
|
+
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);
|
|
1570
|
+
add(U, Bx(0.07, 0.09, 0.01), cmat(16052448), 0, 1.34, 0.185);
|
|
1571
|
+
return;
|
|
1572
|
+
},
|
|
1573
|
+
pins: ({ PI, THREE, U, add, cmat, k }) => {
|
|
1574
|
+
[[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));
|
|
1575
|
+
return;
|
|
1576
|
+
},
|
|
1577
|
+
recorder: ({ Bx, PI, THREE, U, add, cmat }) => {
|
|
1578
|
+
add(U, Bx(0.13, 0.09, 0.05), cmat(1710618), 0.15, 1.28, 0.215);
|
|
1579
|
+
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);
|
|
1580
|
+
return;
|
|
1581
|
+
},
|
|
1582
|
+
beads: ({ Bx, PI, add, arms, cmat }) => {
|
|
1583
|
+
{
|
|
1584
|
+
const cs = [15222842, 15913030, 4890848, 5947498, 15239864, 16777215];
|
|
1585
|
+
for (let i = 0; i < 8; i++) {
|
|
1586
|
+
const a = i / 8 * PI * 2;
|
|
1587
|
+
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);
|
|
1588
|
+
}
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
},
|
|
1592
|
+
badge: ({ Bx, U, add, cmat }) => {
|
|
1593
|
+
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);
|
|
1594
|
+
add(U, Bx(0.08, 0.11, 0.01), cmat(16053492), 0, 1.34, 0.185);
|
|
1595
|
+
add(U, Bx(0.04, 0.04, 0.012), cmat(5933768), 0, 1.36, 0.19);
|
|
1596
|
+
return;
|
|
1597
|
+
},
|
|
1598
|
+
stethc: ({ Bx, PI, THREE, U, add, cmat, m }) => {
|
|
1599
|
+
add(U, new THREE.TorusGeometry(0.14, 0.018, 5, 10, PI), m, 0, 1.6, -0.02, -PI / 2 + 0.3, 0, 0);
|
|
1600
|
+
add(U, Bx(0.02, 0.2, 0.02), m, -0.1, 1.42, 0.19);
|
|
1601
|
+
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);
|
|
1602
|
+
return;
|
|
1603
|
+
},
|
|
1604
|
+
bandolier: ({ Bx, U, add, cmat }) => {
|
|
1605
|
+
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);
|
|
1606
|
+
add(U, Bx(0.05, 0.75, 0.02), cmat(4864546), 0, 1.22, 0.195, 0, 0, -0.75);
|
|
1607
|
+
return;
|
|
1608
|
+
},
|
|
1609
|
+
scarf: ({ PI, THREE, U, add, m, ring }) => {
|
|
1610
|
+
ring(U, 0.1, 0.08, m, 1.57, 1.3, 1.2);
|
|
1611
|
+
add(U, new THREE.ConeGeometry(0.08, 0.14, 3), m, 0, 1.47, 0.15, PI, 0, 0);
|
|
1612
|
+
return;
|
|
1613
|
+
},
|
|
1614
|
+
feather: ({ Bx, Hd, add, cmat }) => {
|
|
1615
|
+
add(Hd, Bx(0.02, 0.24, 0.06), cmat(657932), 0.17, 2, -0.04, 0, 0, -0.3);
|
|
1616
|
+
return;
|
|
1617
|
+
},
|
|
1618
|
+
amulet: ({ Bx, U, add, cmat }) => {
|
|
1619
|
+
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);
|
|
1620
|
+
add(U, Bx(0.07, 0.09, 0.03), cmat(8014378), 0, 1.34, 0.19);
|
|
1621
|
+
return;
|
|
1622
|
+
},
|
|
1623
|
+
rifle_front: ({ Bx, THREE, U, add, arms, cmat, k }) => {
|
|
1624
|
+
{
|
|
1625
|
+
const g = new THREE.Group();
|
|
1626
|
+
g.position.set(0.02, 1.15, 0.26);
|
|
1627
|
+
g.rotation.z = 0.95;
|
|
1628
|
+
U.add(g);
|
|
1629
|
+
const dk = cmat(1842463, { metalness: 0.6, roughness: 0.45 });
|
|
1630
|
+
add(g, Bx(0.08, 0.3, 0.06), cmat(k === "dbl_front" ? 5913122 : 2763816), 0, -0.38, 0);
|
|
1631
|
+
add(g, Bx(0.05, 0.85, 0.05), dk, 0, 0.2, 0);
|
|
1632
|
+
if (k === "dbl_front") {
|
|
1633
|
+
add(g, Bx(0.05, 0.6, 0.05), dk, 0.05, 0.3, 0);
|
|
1634
|
+
add(g, Bx(0.11, 0.06, 0.08), cmat(15262936), 0, -0.25, 0);
|
|
1635
|
+
} else {
|
|
1636
|
+
add(g, Bx(0.07, 0.14, 0.09), dk, 0, -0.08, 0.04);
|
|
1637
|
+
if (k === "rifle_tape") {
|
|
1638
|
+
add(g, Bx(0.08, 0.08, 0.08), cmat(4872762), 0, 0.3, 0);
|
|
1639
|
+
add(g, Bx(0.08, 0.08, 0.08), cmat(6974026), 0, 0.08, 0);
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
arms.forEach((a) => a.grip = true);
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
},
|
|
1646
|
+
belly: ({ U, ball, topM }) => {
|
|
1647
|
+
{
|
|
1648
|
+
const b = ball(U, 0.16, topM, 0, 1.03, 0.105);
|
|
1649
|
+
b.scale.set(1.05, 1.08, 0.95);
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
},
|
|
1653
|
+
refl: ({ Bx, THREE, U, add, cmat, m, ring }) => {
|
|
1654
|
+
{
|
|
1655
|
+
const v = add(U, new THREE.CylinderGeometry(0.287, 0.252, 0.46, 20), m, 0, 1.3, 0);
|
|
1656
|
+
v.scale.set(1.13, 1, 0.72);
|
|
1657
|
+
ring(U, 0.29, 0.035, cmat(13159632, { metalness: 0.3, roughness: 0.4 }), 1.2, 1.13, 0.73);
|
|
1658
|
+
ring(U, 0.3, 0.035, cmat(13159632, { metalness: 0.3, roughness: 0.4 }), 1.37, 1.13, 0.73);
|
|
1659
|
+
add(U, Bx(0.17, 0.055, 0.02), cmat(1714794), 0, 1.47, 0.205);
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
},
|
|
1663
|
+
plate: ({ Bx, THREE, U, add, cmat, m }) => {
|
|
1664
|
+
{
|
|
1665
|
+
const v = add(U, new THREE.CylinderGeometry(0.28, 0.25, 0.44, 20), m, 0, 1.3, 0);
|
|
1666
|
+
v.scale.set(1.12, 1, 0.72);
|
|
1667
|
+
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);
|
|
1668
|
+
add(U, Bx(0.07, 0.11, 0.045), cmat(1710618), 0.16, 1.42, 0.2);
|
|
1669
|
+
for (const s of [-1, 1]) add(U, Bx(0.07, 0.04, 0.2), m, s * 0.17, 1.53, 0);
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
},
|
|
1673
|
+
keyring: ({ Bx, PI, THREE, U, add, cmat }) => {
|
|
1674
|
+
{
|
|
1675
|
+
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);
|
|
1676
|
+
add(U, new THREE.TorusGeometry(0.05, 8e-3, 4, 12), cmat(12103840, { metalness: 0.9, roughness: 0.25 }), 0, 1.34, 0.2);
|
|
1677
|
+
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);
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
},
|
|
1681
|
+
satradio: ({ Bx, U, add, cmat, m }) => {
|
|
1682
|
+
{
|
|
1683
|
+
add(U, Bx(0.3, 0.4, 0.18), m, 0, 1.28, -0.25);
|
|
1684
|
+
add(U, Bx(0.012, 0.62, 0.012), cmat(1710618), 0.1, 1.72, -0.3, 0, 0, 0.15);
|
|
1685
|
+
add(U, Bx(0.08, 0.06, 0.03), cmat(3815994), -0.08, 1.4, -0.35);
|
|
1686
|
+
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);
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
},
|
|
1690
|
+
binoc: ({ Bx, PI, THREE, U, add, cmat }) => {
|
|
1691
|
+
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);
|
|
1692
|
+
add(U, Bx(0.03, 0.03, 0.04), cmat(2500644), 0, 1.38, 0.25);
|
|
1693
|
+
return;
|
|
1694
|
+
},
|
|
1695
|
+
tq: ({ Bx, THREE, add, arms, cmat }) => {
|
|
1696
|
+
add(arms[1].g, new THREE.CylinderGeometry(0.09, 0.087, 0.055, 16), cmat(14165546), 0, -0.05, 0);
|
|
1697
|
+
add(arms[1].g, Bx(0.03, 0.05, 0.04), cmat(1710618), 0, -0.05, 0.09);
|
|
1698
|
+
return;
|
|
1699
|
+
},
|
|
1700
|
+
cuia: ({ THREE, U, add, cmat }) => {
|
|
1701
|
+
add(U, new THREE.SphereGeometry(0.055, 12, 10), cmat(6978106), -0.21, 0.9, 0.12);
|
|
1702
|
+
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);
|
|
1703
|
+
add(U, new THREE.CylinderGeometry(0.04, 0.04, 0.015, 12), cmat(12099696), -0.21, 0.95, 0.12);
|
|
1704
|
+
return;
|
|
1705
|
+
},
|
|
1706
|
+
card: ({ Bx, U, add, cmat }) => {
|
|
1707
|
+
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);
|
|
1708
|
+
add(U, Bx(0.08, 0.11, 0.01), cmat(14164e3), 0, 1.36, 0.235);
|
|
1709
|
+
add(U, Bx(0.05, 0.012, 0.012), cmat(15790320), 0, 1.34, 0.24);
|
|
1710
|
+
return;
|
|
1711
|
+
},
|
|
1712
|
+
mapfold: ({ Bx, U, add, cmat }) => {
|
|
1713
|
+
add(U, Bx(0.1, 0.13, 0.025), cmat(15260848), -0.13, 1.33, 0.215, 0, 0, 0.1);
|
|
1714
|
+
add(U, Bx(0.1, 0.012, 0.027), cmat(9071162), -0.13, 1.35, 0.215);
|
|
1715
|
+
return;
|
|
1716
|
+
},
|
|
1717
|
+
motoband: ({ Hd, THREE, add, cmat }) => {
|
|
1718
|
+
add(Hd, new THREE.CylinderGeometry(0.238, 0.238, 0.045, 20), cmat(14729248), 0, 1.84, -0.01);
|
|
1719
|
+
return;
|
|
1720
|
+
},
|
|
1721
|
+
rifle_sup: ({ Bx, THREE, U, add, arms, cmat }) => {
|
|
1722
|
+
{
|
|
1723
|
+
const g = new THREE.Group();
|
|
1724
|
+
g.position.set(0.02, 1.15, 0.27);
|
|
1725
|
+
g.rotation.z = 0.95;
|
|
1726
|
+
U.add(g);
|
|
1727
|
+
const dk = cmat(1842463, { metalness: 0.6, roughness: 0.45 });
|
|
1728
|
+
add(g, Bx(0.07, 0.26, 0.06), cmat(3815988), 0, -0.36, 0);
|
|
1729
|
+
add(g, Bx(0.06, 0.5, 0.06), dk, 0, 0, 0);
|
|
1730
|
+
add(g, Bx(0.07, 0.14, 0.09), dk, 0, -0.06, 0.05);
|
|
1731
|
+
add(g, Bx(0.05, 0.08, 0.07), cmat(2763306), 0, 0.12, 0.06);
|
|
1732
|
+
add(g, new THREE.CylinderGeometry(0.03, 0.03, 0.24, 10), cmat(2763564, { metalness: 0.5, roughness: 0.5 }), 0, 0.37, 0);
|
|
1733
|
+
add(g, Bx(0.04, 0.08, 0.05), cmat(1710618), 0, 0.05, -0.05);
|
|
1734
|
+
arms.forEach((a) => a.grip = true);
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
},
|
|
1738
|
+
gloves: () => {
|
|
1739
|
+
},
|
|
1740
|
+
blood: ({ R, U, splat }) => {
|
|
1741
|
+
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);
|
|
1742
|
+
return;
|
|
1743
|
+
},
|
|
1744
|
+
shirtpeek: ({ Bx, U, add, m }) => {
|
|
1745
|
+
add(U, Bx(0.16, 0.26, 0.02), m, 0, 1.38, 0.163);
|
|
1746
|
+
return;
|
|
1747
|
+
},
|
|
1748
|
+
suspenders: ({ Bx, U, add, m }) => {
|
|
1749
|
+
for (const s of [-1, 1]) add(U, Bx(0.035, 0.3, 0.02), m, s * 0.11, 1.4, 0.163);
|
|
1750
|
+
return;
|
|
1751
|
+
}
|
|
1752
|
+
};
|
|
1753
|
+
EXTRAS.headphones = EXTRAS.earpro;
|
|
1754
|
+
EXTRAS.rifle_tape = EXTRAS.rifle_front;
|
|
1755
|
+
EXTRAS.dbl_front = EXTRAS.rifle_front;
|
|
1756
|
+
|
|
1757
|
+
// src/rich/person-kit.ts
|
|
1758
|
+
import { shapeGeometries as shapeGeometries3 } from "@adatechnology/game-shapes-three";
|
|
1759
|
+
var createPersonKit = (host, { random: R, createCanvas = () => document.createElement("canvas") }) => {
|
|
1760
|
+
const THREE = shapeGeometries3(host);
|
|
1761
|
+
const T = THREE;
|
|
1762
|
+
const scaleHex = (hex2, factor) => {
|
|
1763
|
+
const channel = (shift) => (hex2 >> shift & 255) / 255 * factor * 255;
|
|
1764
|
+
return channel(16) << 16 ^ channel(8) << 8 ^ channel(0) << 0;
|
|
1765
|
+
};
|
|
1766
|
+
const Bx = (x, y, z) => new T.BoxGeometry(x, y, z);
|
|
1767
|
+
function add(parent, geo, m, x, y, z, rx, ry, rz) {
|
|
1768
|
+
const o = new T.Mesh(geo, m);
|
|
1769
|
+
o.position.set(x, y, z);
|
|
1770
|
+
if (rx || ry || rz) o.rotation.set(rx || 0, ry || 0, rz || 0);
|
|
1771
|
+
o.castShadow = true;
|
|
1772
|
+
o.receiveShadow = true;
|
|
1773
|
+
parent.add(o);
|
|
1774
|
+
return o;
|
|
1775
|
+
}
|
|
1776
|
+
function canvasTex(w, h, draw) {
|
|
1777
|
+
const cv = createCanvas();
|
|
1778
|
+
cv.width = w;
|
|
1779
|
+
cv.height = h;
|
|
1780
|
+
const context = cv.getContext("2d");
|
|
1781
|
+
if (context) draw(context, w, h);
|
|
1782
|
+
return new T.CanvasTexture(cv);
|
|
1783
|
+
}
|
|
1784
|
+
function chessTex(c1, c2) {
|
|
1785
|
+
const a = new THREE.Color(c1), b = new THREE.Color(c2);
|
|
1786
|
+
return canvasTex(32, 32, (g) => {
|
|
1787
|
+
g.fillStyle = "#" + a.getHexString();
|
|
1788
|
+
g.fillRect(0, 0, 32, 32);
|
|
1789
|
+
g.fillStyle = "#" + b.getHexString();
|
|
1790
|
+
g.globalAlpha = 0.75;
|
|
1791
|
+
g.fillRect(0, 6, 32, 4);
|
|
1792
|
+
g.fillRect(0, 22, 32, 4);
|
|
1793
|
+
g.fillRect(6, 0, 4, 32);
|
|
1794
|
+
g.fillRect(22, 0, 4, 32);
|
|
1795
|
+
g.globalAlpha = 1;
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
const SEG = 16;
|
|
1799
|
+
const ccache = {};
|
|
1800
|
+
function cmat(c, o) {
|
|
1801
|
+
if (!o && ccache[c]) return ccache[c];
|
|
1802
|
+
const m = new THREE.MeshStandardMaterial(Object.assign({ color: c, roughness: 0.82, metalness: 0, flatShading: false }, o || {}));
|
|
1803
|
+
if (!o) ccache[c] = m;
|
|
1804
|
+
return m;
|
|
1805
|
+
}
|
|
1806
|
+
const PI = Math.PI;
|
|
1807
|
+
function limb(parent, r1, r2, len, m) {
|
|
1808
|
+
return add(parent, new THREE.CylinderGeometry(r1, r2, len, SEG, 1).translate(0, -len / 2, 0), m, 0, 0, 0);
|
|
1809
|
+
}
|
|
1810
|
+
function mlimb(parent, prof, len, m) {
|
|
1811
|
+
const pts = prof.map((r, i) => new THREE.Vector2(r, -len + i / (prof.length - 1) * len)).reverse();
|
|
1812
|
+
pts.reverse();
|
|
1813
|
+
const sm = new THREE.SplineCurve(prof.slice().reverse().map((r, i) => new THREE.Vector2(r, -len + i / (prof.length - 1) * len))).getPoints(14);
|
|
1814
|
+
return add(parent, new THREE.LatheGeometry(sm, SEG), m, 0, 0, 0);
|
|
1815
|
+
}
|
|
1816
|
+
function ball(parent, r, m, x, y, z) {
|
|
1817
|
+
return add(parent, new THREE.SphereGeometry(r, SEG, 12), m, x || 0, y || 0, z || 0);
|
|
1818
|
+
}
|
|
1819
|
+
function ring(parent, r, h, m, y, sx, sz) {
|
|
1820
|
+
const o = add(parent, new THREE.CylinderGeometry(r, r, h, 20), m, 0, y, 0);
|
|
1821
|
+
o.scale.set(sx || 1.12, 1, sz || 0.7);
|
|
1822
|
+
return o;
|
|
1823
|
+
}
|
|
1824
|
+
function cap(r, len) {
|
|
1825
|
+
return new THREE.SphereGeometry(r, 20, 10, 0, PI * 2, 0, len || PI / 2);
|
|
1826
|
+
}
|
|
1827
|
+
function torsoGeo(fem) {
|
|
1828
|
+
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]];
|
|
1829
|
+
const pts = new THREE.SplineCurve(P.map((p2) => new THREE.Vector2(p2[0], p2[1]))).getPoints(30);
|
|
1830
|
+
const g = new THREE.LatheGeometry(pts, 24);
|
|
1831
|
+
g.scale(1.12, 1, 0.64);
|
|
1832
|
+
return g;
|
|
1833
|
+
}
|
|
1834
|
+
function blobR(n, rough, seed) {
|
|
1835
|
+
const f = [];
|
|
1836
|
+
const ph = [seed * 1.7, seed * 3.1, seed * 5.3, seed * 7.9];
|
|
1837
|
+
const sp = [];
|
|
1838
|
+
const ns = rough > 0.5 ? Math.floor(R() * 4) : 0;
|
|
1839
|
+
for (let k = 0; k < ns; k++) sp.push([R() * 6.28, 0.45 + R() * 0.7]);
|
|
1840
|
+
for (let i = 0; i < n; i++) {
|
|
1841
|
+
const a = i / n * PI * 2;
|
|
1842
|
+
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);
|
|
1843
|
+
sp.forEach(([sa, l]) => {
|
|
1844
|
+
const d = Math.atan2(Math.sin(a - sa), Math.cos(a - sa));
|
|
1845
|
+
r += l * Math.exp(-d * d * 90);
|
|
1846
|
+
});
|
|
1847
|
+
f.push(Math.max(0.35, r));
|
|
1848
|
+
}
|
|
1849
|
+
return f;
|
|
1850
|
+
}
|
|
1851
|
+
function blobGeo(rad, rx, ry) {
|
|
1852
|
+
const sh = new THREE.Shape();
|
|
1853
|
+
rad.forEach((r, i) => {
|
|
1854
|
+
const a = i / rad.length * PI * 2;
|
|
1855
|
+
const x = Math.cos(a) * r * rx, y = Math.sin(a) * r * ry;
|
|
1856
|
+
i ? sh.lineTo(x, y) : sh.moveTo(x, y);
|
|
1857
|
+
});
|
|
1858
|
+
return new THREE.ShapeGeometry(sh);
|
|
1859
|
+
}
|
|
1860
|
+
const stainMs = {};
|
|
1861
|
+
function stainM(c, o) {
|
|
1862
|
+
const k = c + "|" + o;
|
|
1863
|
+
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 || {})));
|
|
1864
|
+
}
|
|
1865
|
+
function splat(parent, x, y, z, r, color, n, out, kind) {
|
|
1866
|
+
const skin = kind === "skin", fresh = color === 9046538, push = (o) => {
|
|
1867
|
+
o.castShadow = false;
|
|
1868
|
+
if (out) out.push(o);
|
|
1869
|
+
return o;
|
|
1870
|
+
};
|
|
1871
|
+
const sy = 0.8 + R() * 0.4, seed = R() * 10;
|
|
1872
|
+
const core = skin ? stainM(color, { roughness: 0.12, metalness: 0.1 }) : stainM(scaleHex(color, 0.72));
|
|
1873
|
+
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));
|
|
1874
|
+
if (!skin) {
|
|
1875
|
+
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));
|
|
1876
|
+
}
|
|
1877
|
+
for (let k = 0; k < (n || 0); k++) {
|
|
1878
|
+
const ang = R() * 6.28, d = r * (1.2 + R() * 0.9), rr = r * (0.16 + R() * 0.2);
|
|
1879
|
+
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));
|
|
1880
|
+
}
|
|
1881
|
+
if (n && (fresh || skin)) {
|
|
1882
|
+
const L = r * (1.4 + R() * 1.2), w = r * 0.16;
|
|
1883
|
+
const g = new THREE.Shape();
|
|
1884
|
+
g.moveTo(-w, 0);
|
|
1885
|
+
g.quadraticCurveTo(-w * 0.6, -L * 0.6, -w * 0.35, -L);
|
|
1886
|
+
g.absarc(0, -L, w * 0.6, PI, 0, false);
|
|
1887
|
+
g.quadraticCurveTo(w * 0.6, -L * 0.6, w, 0);
|
|
1888
|
+
push(add(parent, new THREE.ShapeGeometry(g), core, x + r * 0.15, y - r * 0.55, z + 5e-3));
|
|
1889
|
+
}
|
|
1890
|
+
return a;
|
|
1891
|
+
}
|
|
1892
|
+
return { THREE, PI, SEG, R, scaleHex, Bx, add, cmat, ball, ring, cap, limb, mlimb, torsoGeo, splat, stainM, chessTex };
|
|
1893
|
+
};
|
|
1894
|
+
|
|
1895
|
+
// src/rich/person-builder.ts
|
|
1896
|
+
var PAGE_EYES = { lento: 14926657, corredor: 15022379, brutamonte: 15698474 };
|
|
1897
|
+
var createPersonBuilder = (host, options = {}) => {
|
|
1898
|
+
const THREE = shapeGeometries4(host);
|
|
1899
|
+
const kit = createPersonKit(THREE, { random: options.random ?? createRng2(77), createCanvas: options.createCanvas });
|
|
1900
|
+
const { PI, R, scaleHex, Bx, add, cmat, ball, ring, cap, limb, mlimb, torsoGeo, splat, stainM, chessTex } = kit;
|
|
1901
|
+
const reduce = options.reducedMotion ?? false;
|
|
1902
|
+
const detailed = (options.detail ?? 0) === 0;
|
|
1903
|
+
const EYE = { ...PAGE_EYES, ...options.zombieEyes };
|
|
1904
|
+
const T = THREE;
|
|
1905
|
+
const ZSKIN2 = new T.Color(7305820);
|
|
1906
|
+
function build(sp, idx) {
|
|
1907
|
+
const root = new THREE.Group();
|
|
1908
|
+
const H = sp.h || 1, B = sp.bulk || 1, fem = !!sp.fem;
|
|
1909
|
+
const fig = new THREE.Group();
|
|
1910
|
+
fig.scale.set(B, H, Math.min(1.12, B));
|
|
1911
|
+
root.add(fig);
|
|
1912
|
+
const skinM = new THREE.MeshStandardMaterial({ color: sp.skin, roughness: 0.8, flatShading: false });
|
|
1913
|
+
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);
|
|
1914
|
+
const pantsM = cmat(sp.pants.c), shoeM = cmat(sp.shoes);
|
|
1915
|
+
const exHas = (k) => (sp.ex || []).some((e) => e[0] === k);
|
|
1916
|
+
const pel = ball(fig, 0.2, sp.pants.skirt ? cmat(sp.pants.c) : pantsM, 0, 0.92, 0);
|
|
1917
|
+
pel.scale.set(fem ? 1.16 : 1.08, 0.62, 0.74);
|
|
1918
|
+
const legs = [];
|
|
1919
|
+
for (const s of [-1, 1]) {
|
|
1920
|
+
const g = new THREE.Group();
|
|
1921
|
+
g.position.set(s * (fem ? 0.115 : 0.105), 0.9, 0);
|
|
1922
|
+
fig.add(g);
|
|
1923
|
+
const thighM = sp.pants.skirt ? skinM : pantsM;
|
|
1924
|
+
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);
|
|
1925
|
+
const kn = new THREE.Group();
|
|
1926
|
+
kn.position.y = -0.42;
|
|
1927
|
+
g.add(kn);
|
|
1928
|
+
ball(kn, 0.078, thighM);
|
|
1929
|
+
const shinM = sp.pants.skirt ? skinM : pantsM;
|
|
1930
|
+
mlimb(kn, [0.077, 0.081, 0.07, 0.058, 0.052], 0.38, shinM);
|
|
1931
|
+
const kc = ball(kn, 0.04, shinM, 0, -5e-3, 0.058);
|
|
1932
|
+
kc.scale.set(1.05, 1.15, 0.7);
|
|
1933
|
+
const calf = ball(kn, 0.05, shinM, 0, -0.13, -0.03);
|
|
1934
|
+
calf.scale.set(1.05, 1.8, 1);
|
|
1935
|
+
const sh = ball(kn, 0.074, shoeM, 0, -0.425, 0.055);
|
|
1936
|
+
sh.scale.set(0.95, 0.72, 1.85);
|
|
1937
|
+
const so = add(kn, new THREE.CylinderGeometry(0.071, 0.071, 0.022, 18), cmat(1447446), 0, -0.468, 0.055);
|
|
1938
|
+
so.scale.set(1, 1, 1.95);
|
|
1939
|
+
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);
|
|
1940
|
+
legs.push({ g, kn, s });
|
|
1941
|
+
}
|
|
1942
|
+
for (const s of [-1, 1]) {
|
|
1943
|
+
const gl = ball(fig, 0.1, pantsM, s * 0.085, 0.87, -0.075);
|
|
1944
|
+
gl.scale.set(1, 0.9, 0.72);
|
|
1945
|
+
}
|
|
1946
|
+
if (sp.pants.skirt) {
|
|
1947
|
+
const sk = add(fig, new THREE.CylinderGeometry(0.2, 0.245, 0.2, 24), pantsM, 0, 0.87, 0);
|
|
1948
|
+
sk.scale.set(1.12, 1, 0.8);
|
|
1949
|
+
}
|
|
1950
|
+
const upper = new THREE.Group();
|
|
1951
|
+
upper.position.y = 0.9;
|
|
1952
|
+
fig.add(upper);
|
|
1953
|
+
const U = new THREE.Group();
|
|
1954
|
+
U.position.y = -0.9;
|
|
1955
|
+
upper.add(U);
|
|
1956
|
+
add(U, torsoGeo(fem), topM, 0, 0, 0);
|
|
1957
|
+
{
|
|
1958
|
+
const trapM = sp.top.sl === "tank" ? skinM : topM;
|
|
1959
|
+
for (const s of [-1, 1]) {
|
|
1960
|
+
const tp = ball(U, 0.1, trapM, s * 0.12, 1.545, -0.02);
|
|
1961
|
+
tp.scale.set(1.35, 0.5, 0.85);
|
|
1962
|
+
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);
|
|
1963
|
+
pc.scale.set(fem ? 1 : 1.1, fem ? 0.85 : 0.55, fem ? 0.6 : 0.35);
|
|
1964
|
+
}
|
|
1965
|
+
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);
|
|
1966
|
+
}
|
|
1967
|
+
add(U, new THREE.CylinderGeometry(0.058, 0.068, 0.14, 16), skinM, 0, 1.63, 0);
|
|
1968
|
+
const headG = new THREE.Group();
|
|
1969
|
+
headG.position.set(0, 1.64, 0);
|
|
1970
|
+
U.add(headG);
|
|
1971
|
+
const Hd = new THREE.Group();
|
|
1972
|
+
Hd.position.y = -1.64;
|
|
1973
|
+
headG.add(Hd);
|
|
1974
|
+
const head = add(Hd, new THREE.SphereGeometry(0.165, 20, 16), skinM, 0, 1.8, 0);
|
|
1975
|
+
head.scale.set(0.96, 1.1, 1.02);
|
|
1976
|
+
const jaw = add(Hd, new THREE.SphereGeometry(0.12, 16, 12), skinM, 0, 1.715, 0.03);
|
|
1977
|
+
jaw.scale.set(1, 0.72, 1);
|
|
1978
|
+
for (const s of [-1, 1]) {
|
|
1979
|
+
const e = ball(Hd, 0.038, skinM, s * 0.158, 1.79, -5e-3);
|
|
1980
|
+
e.scale.set(0.6, 1, 0.9);
|
|
1981
|
+
}
|
|
1982
|
+
add(Hd, Bx(0.045, 0.065, 0.05), skinM, 0, 1.765, 0.172);
|
|
1983
|
+
const eyeM = new THREE.MeshBasicMaterial({ color: 1710618 });
|
|
1984
|
+
const whiteM = cmat(15328472);
|
|
1985
|
+
for (const s of [-1, 1]) {
|
|
1986
|
+
const w = add(Hd, new THREE.SphereGeometry(0.022, 12, 8), whiteM, s * 0.058, 1.815, 0.146);
|
|
1987
|
+
w.scale.set(1.25, 0.8, 0.6);
|
|
1988
|
+
w.castShadow = false;
|
|
1989
|
+
const e = add(Hd, new THREE.SphereGeometry(0.011, 10, 8), eyeM, s * 0.058, 1.814, 0.159);
|
|
1990
|
+
e.scale.set(1, 1, 0.5);
|
|
1991
|
+
e.castShadow = false;
|
|
1992
|
+
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);
|
|
1993
|
+
lid.scale.set(1.25, 0.85, 0.65);
|
|
1994
|
+
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);
|
|
1995
|
+
const ck = ball(Hd, 0.045, skinM, s * 0.078, 1.772, 0.098);
|
|
1996
|
+
ck.scale.set(1, 0.7, 0.8);
|
|
1997
|
+
}
|
|
1998
|
+
const ridge = ball(Hd, 0.1, skinM, 0, 1.842, 0.105);
|
|
1999
|
+
ridge.scale.set(1.3, 0.28, 0.55);
|
|
2000
|
+
add(Hd, new THREE.SphereGeometry(0.021, 10, 8), skinM, 0, 1.762, 0.182);
|
|
2001
|
+
add(Hd, Bx(0.064, 0.013, 0.02), cmat(scaleHex(sp.skin, 0.68)), 0, 1.703, 0.156);
|
|
2002
|
+
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);
|
|
2003
|
+
bags.castShadow = false;
|
|
2004
|
+
const hc = cmat(sp.hair.c), hs = sp.hair.s;
|
|
2005
|
+
const hairCap = (r, len, tilt) => {
|
|
2006
|
+
const o = add(Hd, cap(r, len), hc, 0, 1.8, -4e-3);
|
|
2007
|
+
o.scale.set(0.97, 1.1, 1.04);
|
|
2008
|
+
o.rotation.x = tilt;
|
|
2009
|
+
return o;
|
|
2010
|
+
};
|
|
2011
|
+
const backBand = (t0, tl) => {
|
|
2012
|
+
const o = add(Hd, new THREE.SphereGeometry(0.172, 20, 10, PI * 0.85, PI * 1.3, t0, tl), hc, 0, 1.8, 0);
|
|
2013
|
+
o.scale.set(0.97, 1.1, 1.04);
|
|
2014
|
+
return o;
|
|
2015
|
+
};
|
|
2016
|
+
if (hs === "short" || hs === "bun" || hs === "long") {
|
|
2017
|
+
hairCap(0.174, PI / 2, -0.38);
|
|
2018
|
+
backBand(PI * 0.45, PI * 0.22);
|
|
2019
|
+
}
|
|
2020
|
+
if (hs === "bun") ball(Hd, 0.085, hc, 0, 1.92, -0.15);
|
|
2021
|
+
if (hs === "long") {
|
|
2022
|
+
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);
|
|
2023
|
+
l.scale.set(1.05, 1, 1.1);
|
|
2024
|
+
}
|
|
2025
|
+
if (hs === "buzz") hairCap(0.17, PI * 0.45, -0.32);
|
|
2026
|
+
if (hs === "bald") backBand(PI * 0.42, PI * 0.2);
|
|
2027
|
+
if (hs === "afro") {
|
|
2028
|
+
const a = add(Hd, new THREE.DodecahedronGeometry(0.25, 0), hc, 0, 1.9, -0.045);
|
|
2029
|
+
a.scale.set(1.08, 0.9, 1.02);
|
|
2030
|
+
}
|
|
2031
|
+
if (hs === "pony") {
|
|
2032
|
+
hairCap(0.174, PI / 2, -0.38);
|
|
2033
|
+
backBand(PI * 0.45, PI * 0.22);
|
|
2034
|
+
const t = add(Hd, new THREE.CylinderGeometry(0.04, 0.025, 0.3, 6), hc, 0, 1.7, -0.2);
|
|
2035
|
+
t.rotation.x = 0.25;
|
|
2036
|
+
}
|
|
2037
|
+
const f = sp.face || [];
|
|
2038
|
+
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 });
|
|
2039
|
+
if (sp.hat) {
|
|
2040
|
+
const k = sp.hat.k, c = cmat(sp.hat.c);
|
|
2041
|
+
HATS.find((hat) => hat.kinds.includes(k))?.apply({ THREE, PI, Hd, add, Bx, cmat, cap, c, k, sp });
|
|
2042
|
+
}
|
|
2043
|
+
const arms = [];
|
|
2044
|
+
const glove = (sp.ex || []).find((e) => e[0] === "gloves");
|
|
2045
|
+
for (const s of [1, -1]) {
|
|
2046
|
+
const g = new THREE.Group();
|
|
2047
|
+
g.position.set(s * (fem ? 0.285 : 0.305), 1.475, 0);
|
|
2048
|
+
U.add(g);
|
|
2049
|
+
const sl = sp.top.sl;
|
|
2050
|
+
const upM = sl === "tank" ? skinM : topM, foreM = sl === "long" ? topM : skinM;
|
|
2051
|
+
const dl = ball(g, sl === "short" ? 0.09 : 0.083, upM, 0, -0.015, 0);
|
|
2052
|
+
dl.scale.set(1.08, 1.2, 1.05);
|
|
2053
|
+
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);
|
|
2054
|
+
const bi = ball(g, 0.042, upM, 0, -0.13, 0.026);
|
|
2055
|
+
bi.scale.set(1, 1.7, 1);
|
|
2056
|
+
const el = new THREE.Group();
|
|
2057
|
+
el.position.y = -0.31;
|
|
2058
|
+
g.add(el);
|
|
2059
|
+
ball(el, 0.06, foreM);
|
|
2060
|
+
mlimb(el, [0.06, 0.067, 0.06, 0.05, 0.044], 0.29, foreM);
|
|
2061
|
+
const hg = new THREE.Group();
|
|
2062
|
+
hg.position.y = -0.3;
|
|
2063
|
+
el.add(hg);
|
|
2064
|
+
const handM = glove ? cmat(glove[1] ?? 16777215) : skinM;
|
|
2065
|
+
const palm = ball(hg, 0.048, handM, 0, -0.035, 0);
|
|
2066
|
+
palm.scale.set(0.6, 1.05, 1);
|
|
2067
|
+
const { fingers, th } = buildHand({ THREE, add, hg, handM, s, detailed });
|
|
2068
|
+
const mount = new THREE.Group();
|
|
2069
|
+
mount.position.set(0, -0.04, 0.015);
|
|
2070
|
+
hg.add(mount);
|
|
2071
|
+
g.rotation.z = s * 0.07;
|
|
2072
|
+
arms.push({ g, el, hg, mount, s, fingers, th });
|
|
2073
|
+
}
|
|
2074
|
+
for (const e of sp.ex || []) {
|
|
2075
|
+
const [k, c] = e;
|
|
2076
|
+
const m = c !== void 0 ? cmat(c) : topM;
|
|
2077
|
+
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 });
|
|
2078
|
+
}
|
|
2079
|
+
if (sp.back === "shotgun") {
|
|
2080
|
+
const g = new THREE.Group();
|
|
2081
|
+
g.position.set(0, 1.22, -0.2 - (exHas("backpack") ? 0.22 : 0));
|
|
2082
|
+
g.rotation.z = 0.75;
|
|
2083
|
+
U.add(g);
|
|
2084
|
+
add(g, Bx(0.05, 0.9, 0.05), cmat(1842463, { metalness: 0.6, roughness: 0.45 }), 0, 0.15, 0);
|
|
2085
|
+
add(g, Bx(0.07, 0.36, 0.09), cmat(6961692), 0, -0.45, 0);
|
|
2086
|
+
add(g, Bx(0.06, 0.22, 0.07), cmat(6961692), 0, 0.15, 0.03);
|
|
2087
|
+
add(U, Bx(0.04, 0.9, 0.02), cmat(3811866), 0, 1.22, 0.175, 0, 0, -0.75);
|
|
2088
|
+
}
|
|
2089
|
+
const lightSources = [];
|
|
2090
|
+
function item(kind, mount) {
|
|
2091
|
+
if (!kind) return;
|
|
2092
|
+
const g = new THREE.Group();
|
|
2093
|
+
mount.add(g);
|
|
2094
|
+
const metal = cmat(9080982, { metalness: 0.7, roughness: 0.35 }), dark = cmat(1842463, { metalness: 0.6, roughness: 0.45 }), woodM = cmat(6961692);
|
|
2095
|
+
HAND_ITEMS[kind]?.({ THREE, PI, add, Bx, cmat, g, metal, dark, woodM, lights: options.lights, sources: lightSources });
|
|
2096
|
+
}
|
|
2097
|
+
const [rightArm, leftArm] = arms;
|
|
2098
|
+
item(sp.hand, rightArm.mount);
|
|
2099
|
+
item(sp.lhand, leftArm.mount);
|
|
2100
|
+
if (sp.hand) rightArm.hold = true;
|
|
2101
|
+
if (sp.lhand) leftArm.hold = true;
|
|
2102
|
+
const zb = [];
|
|
2103
|
+
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);
|
|
2104
|
+
{
|
|
2105
|
+
const mb = stainM(9046538, { roughness: 0.1, metalness: 0.15 });
|
|
2106
|
+
splat(Hd, 0, 1.672, 0.152, 0.026, 9046538, 2, zb, "skin");
|
|
2107
|
+
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);
|
|
2108
|
+
zb.push(st);
|
|
2109
|
+
const dg = add(Hd, new THREE.SphereGeometry(7e-3, 8, 6), mb, 0.03, 1.626, 0.15);
|
|
2110
|
+
dg.userData.zdrop = 1;
|
|
2111
|
+
zb.push(dg);
|
|
2112
|
+
}
|
|
2113
|
+
zb.forEach((b) => b.visible = false);
|
|
2114
|
+
const tag = (o, seg) => o.traverse((m) => {
|
|
2115
|
+
if (m.isMesh && m.userData.seg === void 0) m.userData.seg = seg;
|
|
2116
|
+
});
|
|
2117
|
+
arms.forEach((a, k) => {
|
|
2118
|
+
tag(a.mount, "item");
|
|
2119
|
+
tag(a.hg, "hand" + k);
|
|
2120
|
+
tag(a.el, "fArm" + k);
|
|
2121
|
+
tag(a.g, "uArm" + k);
|
|
2122
|
+
a.g.children[0].userData.joint = 1;
|
|
2123
|
+
a.el.children[0].userData.joint = 1;
|
|
2124
|
+
});
|
|
2125
|
+
legs.forEach((l, k) => {
|
|
2126
|
+
tag(l.kn, "shin" + k);
|
|
2127
|
+
tag(l.g, "thigh" + k);
|
|
2128
|
+
l.kn.children[0].userData.joint = 1;
|
|
2129
|
+
});
|
|
2130
|
+
tag(headG, "head");
|
|
2131
|
+
tag(U, "torso");
|
|
2132
|
+
tag(fig, "pelvis");
|
|
2133
|
+
const SIDE2 = ["esquerdo", "direito"], LSIDE = ["direita", "esquerda"];
|
|
2134
|
+
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 } };
|
|
2135
|
+
arms.forEach((a, k) => {
|
|
2136
|
+
segs["uArm" + k] = { node: a.g, label: "Bra\xE7o " + SIDE2[k], hp: 60, r: 0.085, arm: k, kind: "shoulder" };
|
|
2137
|
+
segs["fArm" + k] = { node: a.el, label: "Antebra\xE7o " + SIDE2[k], hp: 45, r: 0.065, arm: k, kind: "elbow" };
|
|
2138
|
+
segs["hand" + k] = { node: a.hg, label: "M\xE3o " + SIDE2[k], hp: 25, r: 0.05, arm: k, kind: "wrist" };
|
|
2139
|
+
});
|
|
2140
|
+
legs.forEach((l, k) => {
|
|
2141
|
+
segs["thigh" + k] = { node: l.g, label: "Coxa " + LSIDE[k], hp: 80, r: 0.1, leg: k };
|
|
2142
|
+
segs["shin" + k] = { node: l.kn, label: "Canela e p\xE9 " + LSIDE[k], hp: 55, r: 0.08, leg: k };
|
|
2143
|
+
});
|
|
2144
|
+
for (const k in segs) {
|
|
2145
|
+
const seg = segs[k];
|
|
2146
|
+
seg.max = seg.hp;
|
|
2147
|
+
seg.state = "ok";
|
|
2148
|
+
}
|
|
2149
|
+
const mouth = add(Hd, Bx(0.1, 0.04, 0.02), cmat(3803656), 0, 1.69, 0.145);
|
|
2150
|
+
mouth.visible = false;
|
|
2151
|
+
let dog = null;
|
|
2152
|
+
if (sp.dog) {
|
|
2153
|
+
dog = new THREE.Group();
|
|
2154
|
+
dog.position.set(0.78, 0, 0.3);
|
|
2155
|
+
dog.rotation.y = -0.5;
|
|
2156
|
+
root.add(dog);
|
|
2157
|
+
const dm = cmat(11565630), dl = cmat(14200954);
|
|
2158
|
+
const body = add(dog, new THREE.CylinderGeometry(0.12, 0.11, 0.56, 16).rotateX(PI / 2), dm, 0, 0.42, 0);
|
|
2159
|
+
body.scale.set(1, 1.05, 1);
|
|
2160
|
+
ball(dog, 0.12, dm, 0, 0.43, 0.26);
|
|
2161
|
+
const hd = ball(dog, 0.1, dm, 0, 0.6, 0.38);
|
|
2162
|
+
add(dog, new THREE.CylinderGeometry(0.045, 0.055, 0.12, 16).rotateX(PI / 2), dl, 0, 0.57, 0.5);
|
|
2163
|
+
add(dog, Bx(0.03, 0.03, 0.02), cmat(1118481), 0, 0.585, 0.565);
|
|
2164
|
+
for (const s of [-1, 1]) {
|
|
2165
|
+
add(dog, new THREE.ConeGeometry(0.035, 0.09, 5), dm, s * 0.06, 0.7, 0.36, 0, 0, s * -0.3);
|
|
2166
|
+
}
|
|
2167
|
+
const dlegs = [];
|
|
2168
|
+
for (const a of [-1, 1]) for (const b of [-1, 1]) {
|
|
2169
|
+
const lg = new THREE.Group();
|
|
2170
|
+
lg.position.set(a * 0.075, 0.36, b * 0.21);
|
|
2171
|
+
dog.add(lg);
|
|
2172
|
+
limb(lg, 0.035, 0.028, 0.34, dm);
|
|
2173
|
+
dlegs.push({ lg, ph: a * b > 0 ? 0 : PI });
|
|
2174
|
+
}
|
|
2175
|
+
const tail = new THREE.Group();
|
|
2176
|
+
tail.position.set(0, 0.48, -0.28);
|
|
2177
|
+
dog.add(tail);
|
|
2178
|
+
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);
|
|
2179
|
+
dog.userData = { tail, dlegs };
|
|
2180
|
+
}
|
|
2181
|
+
root.traverse((o) => {
|
|
2182
|
+
if (o.isMesh) o.userData.idx = idx;
|
|
2183
|
+
});
|
|
2184
|
+
return { lightSources, 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 };
|
|
2185
|
+
}
|
|
2186
|
+
const MOVES_IDLE = MOVES.idle;
|
|
2187
|
+
function pose(c, z, W, t) {
|
|
2188
|
+
const p2 = c.ph + t, freq = z ? 3.4 : 5.6, ph = t * freq + c.ph;
|
|
2189
|
+
const breathe = reduce ? 0 : Math.sin(p2 * 1.6) * 0.012;
|
|
2190
|
+
c.upper.scale.y = 1 + breathe;
|
|
2191
|
+
const hunch = c.hunch + (z ? 0.38 : 0) + W * 0.05;
|
|
2192
|
+
c.upper.rotation.x = hunch;
|
|
2193
|
+
c.upper.rotation.y = W * Math.sin(ph) * 0.1;
|
|
2194
|
+
c.upper.rotation.z = z ? Math.sin(ph * 0.5) * 0.06 * W + 0.05 : 0;
|
|
2195
|
+
c.fig.position.y = -W * Math.abs(Math.sin(ph)) * 0.035;
|
|
2196
|
+
c.headG.rotation.y = reduce ? 0 : Math.sin(p2 * 0.5) * 0.25 * (z ? 0.3 : 1) * (W ? 0.4 : 1) * (t ? 1 : 0);
|
|
2197
|
+
c.headG.rotation.x = -hunch * 0.6 + (z ? 0.15 : 0);
|
|
2198
|
+
c.headG.rotation.z = z ? 0.22 : 0;
|
|
2199
|
+
c.legs.forEach((l, k) => {
|
|
2200
|
+
const phi = ph + (k ? Math.PI : 0);
|
|
2201
|
+
const amp = z && k === 0 ? 0.18 : c.skirt ? 0.3 : 0.42;
|
|
2202
|
+
l.g.rotation.x = -W * Math.sin(phi) * amp + (z && !W ? k ? -0.1 : 0.08 : 0);
|
|
2203
|
+
l.kn.rotation.x = W * (Math.max(0, Math.cos(phi)) * (z && k === 0 ? 0.25 : 0.85) + 0.06) + (W ? 0 : 0.03);
|
|
2204
|
+
});
|
|
2205
|
+
c.arms.forEach((a) => {
|
|
2206
|
+
const raise = z ? 1 : 0;
|
|
2207
|
+
const base = a.hold ? -0.2 : 0;
|
|
2208
|
+
const swing = -a.s * Math.sin(ph) * (z ? 0.12 : 0.42) * W * (a.grip ? 0 : 1);
|
|
2209
|
+
a.g.rotation.x = (a.grip && !z ? -0.75 : base - raise * (z ? 1.12 : 1.3)) + swing + (z && !reduce && t ? Math.sin(p2 * 3 + a.s * 2) * 0.08 : 0);
|
|
2210
|
+
a.g.rotation.z = a.s * (0.07 + (z ? 0.1 : 0)) + (a.grip && !z ? -a.s * 0.35 : 0);
|
|
2211
|
+
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));
|
|
2212
|
+
if (a.fingers) {
|
|
2213
|
+
const gr = (a.hold || a.grip ? 1.25 : 0.4) * (z ? 0.5 : 1);
|
|
2214
|
+
a.fingers.forEach((f, j) => f.rotation.z = -a.s * gr * (j % 2 ? 1.1 : 0.9));
|
|
2215
|
+
if (a.th) a.th.rotation.z = -a.s * (0.45 + gr * 0.25);
|
|
2216
|
+
a.hg.rotation.x = reduce ? 0 : -W * Math.sin(ph - 0.6) * 0.15 * a.s;
|
|
2217
|
+
}
|
|
2218
|
+
});
|
|
2219
|
+
if (c.dog && !reduce && t) {
|
|
2220
|
+
const d = c.dog.userData;
|
|
2221
|
+
d.tail.rotation.y = Math.sin(t * 9) * 0.5;
|
|
2222
|
+
d.dlegs.forEach((l) => {
|
|
2223
|
+
l.lg.rotation.x = W * Math.sin(t * 7 + l.ph) * 0.45;
|
|
2224
|
+
});
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
function zombify(c, cat) {
|
|
2228
|
+
c.skinM.color.copy(c.base).lerp(ZSKIN2, 1);
|
|
2229
|
+
c.eyeM.color.set(EYE[cat || "lento"] ?? EYE.lento);
|
|
2230
|
+
c.zb.forEach((b) => b.visible = true);
|
|
2231
|
+
c.mouth.visible = true;
|
|
2232
|
+
c.bags.material.opacity = 0.9;
|
|
2233
|
+
}
|
|
2234
|
+
function buildPerson(p2, zed) {
|
|
2235
|
+
const root = new THREE.Group(), chars = [];
|
|
2236
|
+
if (p2.b) {
|
|
2237
|
+
const c = build(p2.b, 0);
|
|
2238
|
+
chars.push(c);
|
|
2239
|
+
root.add(c.root);
|
|
2240
|
+
if (zed && !p2.kid) zombify(c, p2.zcat);
|
|
2241
|
+
c.z = !!(zed && !p2.kid);
|
|
2242
|
+
}
|
|
2243
|
+
(p2.kids || []).forEach((k) => {
|
|
2244
|
+
const c = build(k, 0);
|
|
2245
|
+
c.root.position.x = k.x;
|
|
2246
|
+
c.root.rotation.y = -k.x * 0.4;
|
|
2247
|
+
c.z = false;
|
|
2248
|
+
chars.push(c);
|
|
2249
|
+
root.add(c.root);
|
|
2250
|
+
});
|
|
2251
|
+
chars.forEach((c) => pose(c, !!c.z, 0, 0));
|
|
2252
|
+
return { root, chars };
|
|
2253
|
+
}
|
|
2254
|
+
const mark = (c) => {
|
|
2255
|
+
markKitOrigin2(c.root, { owner: "game-characters-three", kind: "person" });
|
|
2256
|
+
return c;
|
|
2257
|
+
};
|
|
2258
|
+
return { build: (sp, idx) => mark(build(sp, idx)), pose, zombify, buildPerson: (p2, zed) => mark(buildPerson(p2, zed)) };
|
|
2259
|
+
};
|
|
2260
|
+
|
|
2261
|
+
// src/wounds/fluids-bridge.ts
|
|
2262
|
+
var colorOf = (hex2) => Number.parseInt(hex2.replace(/^#|^0x/i, ""), 16);
|
|
2263
|
+
var bloodLookOfAge = (age, options = {}) => ({
|
|
2264
|
+
...age.blood < (options.thinBelow ?? 0.3) ? { liquid: options.thinLiquid ?? "old_blood" } : {},
|
|
2265
|
+
tint: { fresh: colorOf(age.freshBloodColor), dry: colorOf(age.dryBloodColor) },
|
|
2266
|
+
amount: age.blood
|
|
2267
|
+
});
|
|
2268
|
+
var BURST_LITERS = { severed: 0.35, destroyed: 0.6, split: 0.9 };
|
|
2269
|
+
var BURST_DROPS = { severed: 4, destroyed: 7, split: 10 };
|
|
2270
|
+
var createFluidsBridge = ({ sink, film, spray, burstDrops = BURST_DROPS, liquid = "blood", spurtSpeed = 1.4 }) => {
|
|
2271
|
+
const throwDrops = (point, wanted, look, direction, speed, radius) => {
|
|
2272
|
+
const amount = look?.amount ?? 1;
|
|
2273
|
+
const count = amount <= 0 ? 0 : Math.max(1, Math.round(wanted * Math.min(1, amount)));
|
|
2274
|
+
return film && count > 0 ? film.splat({
|
|
2275
|
+
fluid: look?.liquid ?? liquid,
|
|
2276
|
+
point,
|
|
2277
|
+
count,
|
|
2278
|
+
...direction ? { direction } : {},
|
|
2279
|
+
...speed !== void 0 ? { speed } : {},
|
|
2280
|
+
...radius !== void 0 ? { radius } : {},
|
|
2281
|
+
...look?.tint ? { tint: { fresh: look.tint.fresh, dry: look.tint.dry } } : {}
|
|
2282
|
+
}) : 0;
|
|
2283
|
+
};
|
|
2284
|
+
const bleed = (point, litersPerSecond, dt, direction = [0, -1, 0], pressurized = false) => {
|
|
2285
|
+
const liters = litersPerSecond * dt;
|
|
2286
|
+
if (liters <= 0) return;
|
|
2287
|
+
const speed = pressurized ? spurtSpeed : 0.15;
|
|
2288
|
+
sink.splash(liquid, point, liters, [direction[0] * speed, direction[1] * speed, direction[2] * speed]);
|
|
2289
|
+
};
|
|
2290
|
+
return {
|
|
2291
|
+
bleed,
|
|
2292
|
+
handle(event) {
|
|
2293
|
+
if (event.type === "leak" && event.fluid === "blood") bleed(event.point, event.rate, 1, event.direction, event.pressurized);
|
|
2294
|
+
},
|
|
2295
|
+
burst(change, point, look, direction) {
|
|
2296
|
+
sink.splash(look?.liquid ?? liquid, point, BURST_LITERS[change]);
|
|
2297
|
+
throwDrops(point, burstDrops[change], look, direction);
|
|
2298
|
+
},
|
|
2299
|
+
wound: (point, look, direction) => throwDrops(point, spray?.count ?? 3, look, direction, spray?.speed, spray?.radius)
|
|
2300
|
+
};
|
|
2301
|
+
};
|
|
2302
|
+
|
|
2303
|
+
// src/wounds/segment-nodes.ts
|
|
2304
|
+
var RADIUS = { head: 0.07, upperArm: 0.085, forearm: 0.065, hand: 0.05, thigh: 0.1, shin: 0.08 };
|
|
2305
|
+
var SIDE = (x) => x >= 0 ? "L" : "R";
|
|
2306
|
+
var woundBodyOf = (root) => {
|
|
2307
|
+
const rig = root.userData.rig;
|
|
2308
|
+
if (!rig) return void 0;
|
|
2309
|
+
const nodes = {};
|
|
2310
|
+
rig.arms.forEach((arm, index) => {
|
|
2311
|
+
const side = SIDE(arm.g.position.x);
|
|
2312
|
+
nodes[`upperArm${side}`] = { node: arm.g, radius: RADIUS.upperArm, arm: { index, joint: "shoulder" } };
|
|
2313
|
+
nodes[`forearm${side}`] = { node: arm.el, radius: RADIUS.forearm, arm: { index, joint: "elbow" } };
|
|
2314
|
+
});
|
|
2315
|
+
rig.legs.forEach((leg, index) => {
|
|
2316
|
+
const side = SIDE(leg.g.position.x);
|
|
2317
|
+
nodes[`thigh${side}`] = { node: leg.g, radius: RADIUS.thigh, leg: index };
|
|
2318
|
+
nodes[`shin${side}`] = { node: leg.kn, radius: RADIUS.shin, leg: index };
|
|
2319
|
+
});
|
|
2320
|
+
nodes.head = { node: rig.headG, radius: RADIUS.head, flip: true };
|
|
2321
|
+
return { root, rig, nodes };
|
|
2322
|
+
};
|
|
2323
|
+
|
|
2324
|
+
// src/wounds/wounds.ts
|
|
2325
|
+
import { segmentsBelow } from "@adatechnology/game-characters";
|
|
2326
|
+
|
|
2327
|
+
// src/wounds/cut.ts
|
|
2328
|
+
var blobRadii = (n, rough, seed, random) => {
|
|
2329
|
+
const phases = [seed * 1.7, seed * 3.1, seed * 5.3, seed * 7.9];
|
|
2330
|
+
const spikes = [];
|
|
2331
|
+
const spikeCount = rough > 0.5 ? Math.floor(random() * 4) : 0;
|
|
2332
|
+
for (let k = 0; k < spikeCount; k++) spikes.push([random() * 6.28, 0.45 + random() * 0.7]);
|
|
2333
|
+
const radii = [];
|
|
2334
|
+
for (let i = 0; i < n; i++) {
|
|
2335
|
+
const angle = i / n * Math.PI * 2;
|
|
2336
|
+
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);
|
|
2337
|
+
for (const [spikeAngle, length] of spikes) {
|
|
2338
|
+
const distance = Math.atan2(Math.sin(angle - spikeAngle), Math.cos(angle - spikeAngle));
|
|
2339
|
+
radius += length * Math.exp(-distance * distance * 90);
|
|
2340
|
+
}
|
|
2341
|
+
radii.push(Math.max(0.35, radius));
|
|
2342
|
+
}
|
|
2343
|
+
return radii;
|
|
2344
|
+
};
|
|
2345
|
+
var blobGeometry = (three, radii, rx, ry) => {
|
|
2346
|
+
const shape = new three.Shape();
|
|
2347
|
+
radii.forEach((radius, index) => {
|
|
2348
|
+
const angle = index / radii.length * Math.PI * 2;
|
|
2349
|
+
const x = Math.cos(angle) * radius * rx;
|
|
2350
|
+
const y = Math.sin(angle) * radius * ry;
|
|
2351
|
+
if (index) shape.lineTo(x, y);
|
|
2352
|
+
else shape.moveTo(x, y);
|
|
2353
|
+
});
|
|
2354
|
+
return new three.ShapeGeometry(shape);
|
|
2355
|
+
};
|
|
2356
|
+
var createWoundMaterials = (three) => {
|
|
2357
|
+
const wound = new three.MeshStandardMaterial({ color: 9046538, roughness: 0.18, metalness: 0.08 });
|
|
2358
|
+
const bone = new three.MeshStandardMaterial({ color: 15261900, roughness: 0.6 });
|
|
2359
|
+
const meat = new three.MeshStandardMaterial({ color: 4851206, roughness: 0.75, side: three.DoubleSide });
|
|
2360
|
+
const fat = new three.MeshStandardMaterial({ color: 12031338, roughness: 0.7 });
|
|
2361
|
+
const rim = new three.MeshStandardMaterial({ color: 9046538, roughness: 0.3, side: three.DoubleSide });
|
|
2362
|
+
const hole = new three.MeshStandardMaterial({ color: 1180676, roughness: 0.9 });
|
|
2363
|
+
const bruise = new three.MeshStandardMaterial({ color: 4857638, roughness: 0.6, transparent: true, opacity: 0.85 });
|
|
2364
|
+
const flesh = new three.MeshStandardMaterial({ color: 9316906, roughness: 0.55 });
|
|
2365
|
+
const all = [wound, bone, meat, fat, rim, hole, bruise, flesh];
|
|
2366
|
+
return { wound, bone, meat, fat, rim, hole, bruise, flesh, dispose: () => all.forEach((material) => material.dispose()) };
|
|
2367
|
+
};
|
|
2368
|
+
var makeCut = (three, materials, random, group, radius, up) => {
|
|
2369
|
+
const s = up ? 1 : -1;
|
|
2370
|
+
const { PI } = Math;
|
|
2371
|
+
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);
|
|
2372
|
+
rim.position.y = s * 1e-3;
|
|
2373
|
+
group.add(rim);
|
|
2374
|
+
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);
|
|
2375
|
+
meat.position.y = s * 4e-3;
|
|
2376
|
+
group.add(meat);
|
|
2377
|
+
const fat = new three.Mesh(new three.TorusGeometry(radius * 0.86, radius * 0.06, 6, 22), materials.fat);
|
|
2378
|
+
fat.rotation.x = PI / 2;
|
|
2379
|
+
fat.position.y = s * 5e-3;
|
|
2380
|
+
group.add(fat);
|
|
2381
|
+
const bone = new three.Mesh(new three.CylinderGeometry(radius * 0.26, radius * 0.3, radius * 0.7, 9), materials.bone);
|
|
2382
|
+
bone.position.set(radius * 0.12, s * radius * 0.3, 0);
|
|
2383
|
+
bone.rotation.z = 0.12;
|
|
2384
|
+
group.add(bone);
|
|
2385
|
+
const tip = new three.Mesh(new three.ConeGeometry(radius * 0.26, radius * 0.35, 7), materials.bone);
|
|
2386
|
+
tip.position.set(radius * 0.16, s * radius * 0.75, 0);
|
|
2387
|
+
tip.rotation.set(up ? 0 : PI, 0, 0.3);
|
|
2388
|
+
group.add(tip);
|
|
2389
|
+
for (let k = 0; k < 5; k++) {
|
|
2390
|
+
const angle = k / 5 * PI * 2 + random() * 0.6;
|
|
2391
|
+
const flap = new three.Mesh(new three.ConeGeometry(radius * 0.16, radius * (0.35 + random() * 0.35), 4), k % 2 ? materials.rim : materials.meat);
|
|
2392
|
+
flap.position.set(Math.cos(angle) * radius * 0.98, s * radius * 0.12, Math.sin(angle) * radius * 0.98);
|
|
2393
|
+
if (!up) flap.rotation.x = PI;
|
|
2394
|
+
group.add(flap);
|
|
2395
|
+
}
|
|
2396
|
+
const strands = [];
|
|
2397
|
+
for (let k = 0; k < 2; k++) {
|
|
2398
|
+
const angle = random() * PI * 2;
|
|
2399
|
+
const length = radius * (0.5 + random() * 0.6);
|
|
2400
|
+
const strand = new three.Mesh(new three.CylinderGeometry(radius * 0.05, radius * 0.03, length, 6).translate(0, -length / 2, 0), materials.rim);
|
|
2401
|
+
strand.position.set(Math.cos(angle) * radius, 0, Math.sin(angle) * radius);
|
|
2402
|
+
group.add(strand);
|
|
2403
|
+
const end = new three.Object3D();
|
|
2404
|
+
end.position.set(Math.cos(angle) * radius, -length, Math.sin(angle) * radius);
|
|
2405
|
+
group.add(end);
|
|
2406
|
+
strands.push(end);
|
|
2407
|
+
}
|
|
2408
|
+
group.traverse((object) => {
|
|
2409
|
+
object.userData.stump = 1;
|
|
2410
|
+
if (object.isMesh) object.castShadow = true;
|
|
2411
|
+
});
|
|
2412
|
+
return strands;
|
|
2413
|
+
};
|
|
2414
|
+
var makeStump = (three, materials, random, parent, position, radius, flip = false) => {
|
|
2415
|
+
const group = new three.Group();
|
|
2416
|
+
group.position.copy(position);
|
|
2417
|
+
if (flip) group.rotation.x = Math.PI;
|
|
2418
|
+
parent.add(group);
|
|
2419
|
+
return { group, strands: makeCut(three, materials, random, group, radius, false) };
|
|
2420
|
+
};
|
|
2421
|
+
var ringWound = (three, materials, parent, radius, y = 0) => {
|
|
2422
|
+
const group = new three.Group();
|
|
2423
|
+
group.position.y = y;
|
|
2424
|
+
parent.add(group);
|
|
2425
|
+
const torus = new three.Mesh(new three.TorusGeometry(radius, 0.022, 8, 18), materials.wound);
|
|
2426
|
+
torus.rotation.x = Math.PI / 2;
|
|
2427
|
+
group.add(torus);
|
|
2428
|
+
for (let k = 0; k < 3; k++) {
|
|
2429
|
+
const strip = new three.Mesh(new three.CylinderGeometry(8e-3, 8e-3, 0.12, 6), materials.wound);
|
|
2430
|
+
strip.position.set((k - 1) * 0.03, 0.03, 0.02);
|
|
2431
|
+
group.add(strip);
|
|
2432
|
+
}
|
|
2433
|
+
group.traverse((object) => object.userData.stump = 1);
|
|
2434
|
+
return group;
|
|
2435
|
+
};
|
|
2436
|
+
|
|
2437
|
+
// src/wounds/flying.ts
|
|
2438
|
+
import { discardObject3D } from "@adatechnology/game-shapes-three";
|
|
2439
|
+
var GRAVITY = 9.8;
|
|
2440
|
+
var FLOOR_OFFSET = 0.08;
|
|
2441
|
+
var BOUNCE_LIMIT = 1.2;
|
|
2442
|
+
var BOUNCE_VERTICAL = -0.25;
|
|
2443
|
+
var BOUNCE_HORIZONTAL = 0.5;
|
|
2444
|
+
var BOUNCE_SPIN = 0.4;
|
|
2445
|
+
var LAUNCH = { sideways: 1.6, up: 2.6, forward: 0.6, spin: [9, 6, 9] };
|
|
2446
|
+
var createFlyingPieces = (three, { random, groundAt = () => 0, onRest, max = 40 }) => {
|
|
2447
|
+
const pieces = [];
|
|
2448
|
+
const quaternion = new three.Quaternion();
|
|
2449
|
+
const euler = new three.Euler();
|
|
2450
|
+
const trim = () => {
|
|
2451
|
+
while (pieces.length > max) {
|
|
2452
|
+
const piece = pieces.shift();
|
|
2453
|
+
if (piece) discardObject3D(piece.object);
|
|
2454
|
+
}
|
|
2455
|
+
};
|
|
2456
|
+
const add = (object, velocity, spin, tag) => {
|
|
2457
|
+
const piece = { object, velocity, spin, rest: false, ...tag ? { tag } : {} };
|
|
2458
|
+
pieces.push(piece);
|
|
2459
|
+
trim();
|
|
2460
|
+
return piece;
|
|
2461
|
+
};
|
|
2462
|
+
return {
|
|
2463
|
+
pieces,
|
|
2464
|
+
launch(source, scene2, direction, dress, tag) {
|
|
2465
|
+
source.updateWorldMatrix(true, true);
|
|
2466
|
+
const clone = source.clone(true);
|
|
2467
|
+
source.matrixWorld.decompose(clone.position, clone.quaternion, clone.scale);
|
|
2468
|
+
scene2.add(clone);
|
|
2469
|
+
dress?.(clone);
|
|
2470
|
+
return add(
|
|
2471
|
+
clone,
|
|
2472
|
+
new three.Vector3(direction.x * LAUNCH.sideways, LAUNCH.up, direction.z * LAUNCH.sideways + LAUNCH.forward),
|
|
2473
|
+
new three.Vector3((random() - 0.5) * LAUNCH.spin[0], (random() - 0.5) * LAUNCH.spin[1], (random() - 0.5) * LAUNCH.spin[2]),
|
|
2474
|
+
tag
|
|
2475
|
+
);
|
|
2476
|
+
},
|
|
2477
|
+
throwObject: add,
|
|
2478
|
+
update(dt) {
|
|
2479
|
+
for (const piece of pieces) {
|
|
2480
|
+
if (piece.rest) continue;
|
|
2481
|
+
piece.velocity.y -= GRAVITY * dt;
|
|
2482
|
+
piece.object.position.addScaledVector(piece.velocity, dt);
|
|
2483
|
+
quaternion.setFromEuler(euler.set(piece.spin.x * dt, piece.spin.y * dt, piece.spin.z * dt));
|
|
2484
|
+
piece.object.quaternion.multiply(quaternion);
|
|
2485
|
+
const ground = groundAt(piece.object.position.x, piece.object.position.z) + FLOOR_OFFSET;
|
|
2486
|
+
if (piece.object.position.y >= ground) continue;
|
|
2487
|
+
piece.object.position.y = ground;
|
|
2488
|
+
if (Math.abs(piece.velocity.y) > BOUNCE_LIMIT) {
|
|
2489
|
+
piece.velocity.y *= BOUNCE_VERTICAL;
|
|
2490
|
+
piece.velocity.x *= BOUNCE_HORIZONTAL;
|
|
2491
|
+
piece.velocity.z *= BOUNCE_HORIZONTAL;
|
|
2492
|
+
piece.spin.multiplyScalar(BOUNCE_SPIN);
|
|
2493
|
+
} else {
|
|
2494
|
+
piece.rest = true;
|
|
2495
|
+
const yaw = euler.setFromQuaternion(piece.object.quaternion).y;
|
|
2496
|
+
piece.object.rotation.set(Math.PI / 2 * (random() < 0.5 ? 1 : -1), yaw, 0);
|
|
2497
|
+
piece.object.position.y = ground;
|
|
2498
|
+
onRest?.(piece, piece.object.position);
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
},
|
|
2502
|
+
clear() {
|
|
2503
|
+
discardObject3D(pieces.splice(0).map((piece) => piece.object));
|
|
2504
|
+
}
|
|
2505
|
+
};
|
|
2506
|
+
};
|
|
2507
|
+
|
|
2508
|
+
// src/wounds/marks.ts
|
|
2509
|
+
var starGeometry = (three, random, radius, points) => {
|
|
2510
|
+
const outline = [];
|
|
2511
|
+
for (let i = 0; i < points * 2; i++) {
|
|
2512
|
+
const angle = i / (points * 2) * Math.PI * 2 + (random() - 0.5) * 0.25;
|
|
2513
|
+
const r = i % 2 ? radius * (0.3 + random() * 0.2) : radius * (0.7 + random() * 0.6);
|
|
2514
|
+
outline.push(new three.Vector2(Math.cos(angle) * r, Math.sin(angle) * r));
|
|
2515
|
+
}
|
|
2516
|
+
return new three.ShapeGeometry(new three.Shape(outline));
|
|
2517
|
+
};
|
|
2518
|
+
var woundDecal = (three, materials, segment, point, normal, radius) => {
|
|
2519
|
+
const decal = new three.Mesh(new three.CircleGeometry(radius * 0.62, 14), materials.bruise);
|
|
2520
|
+
segment.add(decal);
|
|
2521
|
+
decal.position.copy(segment.worldToLocal(point.clone().addScaledVector(normal, 2e-3)));
|
|
2522
|
+
decal.lookAt(point.clone().addScaledVector(normal, 1));
|
|
2523
|
+
const rim = new three.Mesh(new three.RingGeometry(radius * 0.2, radius * 0.34, 12), materials.wound);
|
|
2524
|
+
rim.position.z = 3e-4;
|
|
2525
|
+
decal.add(rim);
|
|
2526
|
+
const hole = new three.Mesh(new three.CircleGeometry(radius * 0.22, 10), materials.hole);
|
|
2527
|
+
hole.position.z = 5e-4;
|
|
2528
|
+
decal.add(hole);
|
|
2529
|
+
decal.userData.wound = 1;
|
|
2530
|
+
return decal;
|
|
2531
|
+
};
|
|
2532
|
+
var exitWound = (three, materials, random, segment, point, direction, damage) => {
|
|
2533
|
+
const meshes = [];
|
|
2534
|
+
segment.traverse((object) => {
|
|
2535
|
+
if (object.isMesh && !object.userData.wound && !object.userData.stump) meshes.push(object);
|
|
2536
|
+
});
|
|
2537
|
+
const raycaster = new three.Raycaster(point.clone().addScaledVector(direction, 0.6), direction.clone().negate(), 0, 0.6);
|
|
2538
|
+
const hit = raycaster.intersectObjects(meshes)[0];
|
|
2539
|
+
if (!hit?.face) return void 0;
|
|
2540
|
+
const normal = hit.face.normal.clone().transformDirection(hit.object.matrixWorld);
|
|
2541
|
+
const radius = 0.012 + Math.min(0.035, damage * 28e-5);
|
|
2542
|
+
const star = new three.Mesh(starGeometry(three, random, radius, 5 + (Math.floor(random() * 3) | 0)), materials.flesh);
|
|
2543
|
+
segment.add(star);
|
|
2544
|
+
star.position.copy(segment.worldToLocal(hit.point.clone().addScaledVector(normal, 2e-3)));
|
|
2545
|
+
star.lookAt(hit.point.clone().addScaledVector(normal, 1));
|
|
2546
|
+
star.rotateZ(random() * 6);
|
|
2547
|
+
star.userData.wound = 1;
|
|
2548
|
+
const inner = new three.Mesh(starGeometry(three, random, radius * 0.45, 4), materials.hole);
|
|
2549
|
+
inner.position.z = 5e-4;
|
|
2550
|
+
star.add(inner);
|
|
2551
|
+
return { point: hit.point.clone(), normal, radius };
|
|
2552
|
+
};
|
|
2553
|
+
var gouge = (three, materials, random, segment, segmentKind, point, normal, direction, damage, fling) => {
|
|
2554
|
+
const count = damage >= 150 ? 7 : damage >= 55 ? 4 : 2;
|
|
2555
|
+
const chunkMaterials = [materials.flesh, materials.bruise, materials.flesh, materials.bone, materials.meat];
|
|
2556
|
+
for (let i = 0; i < count; i++) {
|
|
2557
|
+
const size = 0.012 + random() * (damage >= 150 ? 0.04 : 0.022);
|
|
2558
|
+
const chunk = new three.Mesh(new three.BoxGeometry(size, size * (0.6 + random() * 0.6), size * (0.6 + random() * 0.6)), chunkMaterials[i % 5]);
|
|
2559
|
+
chunk.position.copy(point);
|
|
2560
|
+
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));
|
|
2561
|
+
fling(chunk, velocity, new three.Vector3(random() * 14, random() * 14, random() * 14));
|
|
2562
|
+
}
|
|
2563
|
+
const radius = 0.018 + Math.min(0.04, damage * 25e-5);
|
|
2564
|
+
const crater = new three.Mesh(new three.CircleGeometry(radius, 12), materials.hole);
|
|
2565
|
+
segment.add(crater);
|
|
2566
|
+
crater.position.copy(segment.worldToLocal(point.clone().addScaledVector(normal, 3e-3)));
|
|
2567
|
+
crater.lookAt(point.clone().addScaledVector(normal, 1));
|
|
2568
|
+
const rim = new three.Mesh(new three.RingGeometry(radius, radius * 1.5, 12), materials.flesh);
|
|
2569
|
+
rim.position.z = -5e-4;
|
|
2570
|
+
crater.add(rim);
|
|
2571
|
+
if (["upperArm", "forearm", "thigh", "shin"].includes(segmentKind) && damage >= 55) {
|
|
2572
|
+
const splinter = new three.Mesh(new three.CylinderGeometry(6e-3, 6e-3, 0.03, 6), materials.bone);
|
|
2573
|
+
splinter.rotation.x = Math.PI / 2;
|
|
2574
|
+
splinter.position.z = -4e-3;
|
|
2575
|
+
crater.add(splinter);
|
|
2576
|
+
}
|
|
2577
|
+
crater.userData.wound = 1;
|
|
2578
|
+
return crater;
|
|
2579
|
+
};
|
|
2580
|
+
var explodeChunks = (three, materials, random, box, direction, fling) => {
|
|
2581
|
+
const centre = box.getCenter(new three.Vector3());
|
|
2582
|
+
const size = box.getSize(new three.Vector3()).length();
|
|
2583
|
+
const chunkMaterials = [materials.flesh, materials.bruise, materials.bone, materials.flesh, materials.meat];
|
|
2584
|
+
for (let i = 0; i < 14; i++) {
|
|
2585
|
+
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]);
|
|
2586
|
+
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));
|
|
2587
|
+
const velocity = direction.clone().multiplyScalar(1.5).add(new three.Vector3((random() - 0.5) * 3, random() * 2.5, (random() - 0.5) * 3));
|
|
2588
|
+
fling(chunk, velocity, new three.Vector3(random() * 15, random() * 15, random() * 15));
|
|
2589
|
+
}
|
|
2590
|
+
return centre;
|
|
2591
|
+
};
|
|
2592
|
+
|
|
2593
|
+
// src/wounds/pendulum.ts
|
|
2594
|
+
var PENDULUM = {
|
|
2595
|
+
/** Spring toward the pose of the body: `-26 (angle + base)`. */
|
|
2596
|
+
spring: 26,
|
|
2597
|
+
/** Damping: `-1.8 v`. */
|
|
2598
|
+
damping: 1.8,
|
|
2599
|
+
/** Angular speed the blow gives the arm. */
|
|
2600
|
+
kick: 6,
|
|
2601
|
+
/** Sway added while the body walks: `sin(2 phase) * 9`. */
|
|
2602
|
+
walkSway: 9,
|
|
2603
|
+
/** Sideways swing of the node: `sin(3 angle) * 0.12`; a shoulder also keeps its side * 0.12. */
|
|
2604
|
+
sideSwing: 0.12,
|
|
2605
|
+
/** The forearm of a broken shoulder hangs at this angle. */
|
|
2606
|
+
forearmHang: -0.05
|
|
2607
|
+
};
|
|
2608
|
+
var createPendulum = (node, kind, side, forearm) => ({
|
|
2609
|
+
node,
|
|
2610
|
+
kind,
|
|
2611
|
+
side,
|
|
2612
|
+
...forearm ? { forearm } : {},
|
|
2613
|
+
a: node.rotation.x,
|
|
2614
|
+
v: PENDULUM.kick
|
|
2615
|
+
});
|
|
2616
|
+
var stepPendulum = (pendulum, base, dt, walkPhase) => {
|
|
2617
|
+
const acceleration = -PENDULUM.spring * (pendulum.a + base) - PENDULUM.damping * pendulum.v + (walkPhase === void 0 ? 0 : Math.sin(walkPhase * 2) * PENDULUM.walkSway);
|
|
2618
|
+
pendulum.v += acceleration * dt;
|
|
2619
|
+
pendulum.a += pendulum.v * dt;
|
|
2620
|
+
pendulum.node.rotation.x = pendulum.a;
|
|
2621
|
+
pendulum.node.rotation.z = (pendulum.kind === "shoulder" ? pendulum.side * PENDULUM.sideSwing : 0) + Math.sin(pendulum.a * 3) * PENDULUM.sideSwing;
|
|
2622
|
+
if (pendulum.kind === "shoulder" && pendulum.forearm) pendulum.forearm.rotation.x = PENDULUM.forearmHang;
|
|
2623
|
+
};
|
|
2624
|
+
|
|
2625
|
+
// src/wounds/wounds.ts
|
|
2626
|
+
var worldPosition = (three, object) => object.getWorldPosition(new three.Vector3());
|
|
2627
|
+
var createWounds = (three, options) => {
|
|
2628
|
+
const { random, scene: scene2, bridge, sound } = options;
|
|
2629
|
+
const materials = options.materials ?? createWoundMaterials(three);
|
|
2630
|
+
const flying = createFlyingPieces(three, { random, ...options.groundAt ? { groundAt: options.groundAt } : {}, ...options.maxPieces ? { max: options.maxPieces } : {} });
|
|
2631
|
+
const states = /* @__PURE__ */ new Map();
|
|
2632
|
+
const stateOf = (body) => {
|
|
2633
|
+
const found = states.get(body);
|
|
2634
|
+
if (!found) throw new Error("game-characters-three: this body is not tracked by the wounds (call `track` first)");
|
|
2635
|
+
return found;
|
|
2636
|
+
};
|
|
2637
|
+
const burstBlood = (state, change, object, direction) => {
|
|
2638
|
+
const point = worldPosition(three, object);
|
|
2639
|
+
bridge?.burst(change, [point.x, point.y, point.z], state.body.blood, direction ? [direction.x, direction.y, direction.z] : void 0);
|
|
2640
|
+
};
|
|
2641
|
+
const sideOf = (object, body) => new three.Vector3(object.position.x >= 0 ? 1 : -1, 0, 0).applyQuaternion(body.root.quaternion);
|
|
2642
|
+
const hideBelow = (state, id) => {
|
|
2643
|
+
for (const below of segmentsBelow(id)) state.gone.add(below);
|
|
2644
|
+
};
|
|
2645
|
+
const sever = (state, id) => {
|
|
2646
|
+
const target = state.body.nodes[id];
|
|
2647
|
+
if (!target || state.gone.has(id)) {
|
|
2648
|
+
state.gone.add(id);
|
|
2649
|
+
hideBelow(state, id);
|
|
2650
|
+
return;
|
|
2651
|
+
}
|
|
2652
|
+
const { node, radius, flip } = target;
|
|
2653
|
+
const direction = sideOf(node, state.body);
|
|
2654
|
+
const fell = flying.launch(node, scene2, direction, (clone) => {
|
|
2655
|
+
makeCut(three, materials, random, clone, radius, true);
|
|
2656
|
+
}, id);
|
|
2657
|
+
void fell;
|
|
2658
|
+
node.visible = false;
|
|
2659
|
+
makeStump(three, materials, random, node.parent, node.position, radius, flip);
|
|
2660
|
+
state.gone.add(id);
|
|
2661
|
+
hideBelow(state, id);
|
|
2662
|
+
for (let i = state.pendulums.length - 1; i >= 0; i--) {
|
|
2663
|
+
const pendulum = state.pendulums[i];
|
|
2664
|
+
let shown = true;
|
|
2665
|
+
for (let o = pendulum.node; o; o = o.parent) if (!o.visible) shown = false;
|
|
2666
|
+
if (!shown) state.pendulums.splice(i, 1);
|
|
2667
|
+
}
|
|
2668
|
+
burstBlood(state, "severed", node, direction);
|
|
2669
|
+
if (id === "head") state.dead = true;
|
|
2670
|
+
};
|
|
2671
|
+
const destroy = (state, id, direction) => {
|
|
2672
|
+
const target = state.body.nodes[id];
|
|
2673
|
+
if (!target || state.gone.has(id)) return;
|
|
2674
|
+
const { node, radius, flip } = target;
|
|
2675
|
+
const box = new three.Box3().setFromObject(node);
|
|
2676
|
+
explodeChunks(three, materials, random, box, direction, (chunk, velocity, spin) => {
|
|
2677
|
+
scene2.add(chunk);
|
|
2678
|
+
flying.throwObject(chunk, velocity, spin, id);
|
|
2679
|
+
});
|
|
2680
|
+
node.visible = false;
|
|
2681
|
+
makeStump(three, materials, random, node.parent, node.position, radius, flip);
|
|
2682
|
+
state.gone.add(id);
|
|
2683
|
+
hideBelow(state, id);
|
|
2684
|
+
burstBlood(state, "destroyed", node, direction);
|
|
2685
|
+
if (id === "head") state.dead = true;
|
|
2686
|
+
};
|
|
2687
|
+
const breakBone = (state, id) => {
|
|
2688
|
+
const target = state.body.nodes[id];
|
|
2689
|
+
if (!target || state.gone.has(id)) return;
|
|
2690
|
+
if (target.arm) {
|
|
2691
|
+
const { index, joint } = target.arm;
|
|
2692
|
+
const arm = state.body.rig.arms[index];
|
|
2693
|
+
const node = joint === "shoulder" ? arm.g : arm.el;
|
|
2694
|
+
ringWound(three, materials, node, joint === "shoulder" ? 0.078 : 0.064, joint === "shoulder" ? -0.06 : -0.01);
|
|
2695
|
+
const kept = state.pendulums.filter((pendulum) => pendulum.node !== node);
|
|
2696
|
+
state.pendulums.length = 0;
|
|
2697
|
+
state.pendulums.push(...kept, createPendulum(node, joint, arm.s ?? 1, joint === "shoulder" ? arm.el : void 0));
|
|
2698
|
+
}
|
|
2699
|
+
if (target.leg !== void 0) state.limp[target.leg] = true;
|
|
2700
|
+
};
|
|
2701
|
+
const split = (state, direction) => {
|
|
2702
|
+
const upper = state.body.rig.upper;
|
|
2703
|
+
if (!upper.visible) return;
|
|
2704
|
+
flying.launch(upper, scene2, direction, (clone) => {
|
|
2705
|
+
makeCut(three, materials, random, clone, 0.16, false);
|
|
2706
|
+
}, "torso");
|
|
2707
|
+
upper.visible = false;
|
|
2708
|
+
makeStump(three, materials, random, upper.parent, upper.position, 0.16);
|
|
2709
|
+
for (const id of ["torso", "neck", "head", "jaw", "upperArmL", "upperArmR", "forearmL", "forearmR", "handL", "handR"]) state.gone.add(id);
|
|
2710
|
+
state.pendulums.length = 0;
|
|
2711
|
+
state.dead = true;
|
|
2712
|
+
burstBlood(state, "split", upper, direction);
|
|
2713
|
+
};
|
|
2714
|
+
const direction3 = (v, fallback) => v ? new three.Vector3(v[0], v[1], v[2]).normalize() : fallback;
|
|
2715
|
+
return {
|
|
2716
|
+
materials,
|
|
2717
|
+
flying,
|
|
2718
|
+
track(body) {
|
|
2719
|
+
states.set(body, { body, pendulums: [], limp: [false, false], gone: /* @__PURE__ */ new Set(), dead: false, crawling: false });
|
|
2720
|
+
return body;
|
|
2721
|
+
},
|
|
2722
|
+
untrack: (body) => void states.delete(body),
|
|
2723
|
+
handle(body, event) {
|
|
2724
|
+
const state = stateOf(body);
|
|
2725
|
+
if (event.type === "leak") {
|
|
2726
|
+
bridge?.handle(event);
|
|
2727
|
+
return;
|
|
2728
|
+
}
|
|
2729
|
+
if (event.type === "hole") {
|
|
2730
|
+
const target = body.nodes[event.part];
|
|
2731
|
+
const node = target?.node ?? body.root;
|
|
2732
|
+
const point = new three.Vector3(...event.worldPoint);
|
|
2733
|
+
const centre = worldPosition(three, node);
|
|
2734
|
+
const normal = point.clone().sub(centre).normalize();
|
|
2735
|
+
if (normal.lengthSq() < 0.5) normal.set(0, 0, 1);
|
|
2736
|
+
woundDecal(three, materials, node, point, normal, 0.014);
|
|
2737
|
+
if (event.exitPoint) {
|
|
2738
|
+
const exit = new three.Vector3(...event.exitPoint);
|
|
2739
|
+
const through = exit.clone().sub(point).normalize();
|
|
2740
|
+
exitWound(three, materials, random, node, exit, through, 40);
|
|
2741
|
+
}
|
|
2742
|
+
return;
|
|
2743
|
+
}
|
|
2744
|
+
if (event.type !== "body") return;
|
|
2745
|
+
const id = event.part;
|
|
2746
|
+
if (sound) {
|
|
2747
|
+
const heard = worldPosition(three, (id ? body.nodes[id]?.node : void 0) ?? body.root);
|
|
2748
|
+
sound.body(event, [heard.x, heard.y, heard.z]);
|
|
2749
|
+
}
|
|
2750
|
+
const fallback = new three.Vector3(0, 0.2, 1);
|
|
2751
|
+
const direction = direction3(event.exit, fallback);
|
|
2752
|
+
switch (event.change) {
|
|
2753
|
+
case "severed":
|
|
2754
|
+
if (id) sever(state, id);
|
|
2755
|
+
break;
|
|
2756
|
+
case "destroyed":
|
|
2757
|
+
if (id) destroy(state, id, direction);
|
|
2758
|
+
break;
|
|
2759
|
+
case "broken":
|
|
2760
|
+
if (id) breakBone(state, id);
|
|
2761
|
+
break;
|
|
2762
|
+
case "split":
|
|
2763
|
+
split(state, direction);
|
|
2764
|
+
break;
|
|
2765
|
+
case "limp":
|
|
2766
|
+
if (id) breakBone(state, id);
|
|
2767
|
+
break;
|
|
2768
|
+
case "crawl":
|
|
2769
|
+
state.crawling = true;
|
|
2770
|
+
break;
|
|
2771
|
+
case "killed":
|
|
2772
|
+
state.dead = true;
|
|
2773
|
+
break;
|
|
2774
|
+
case "wound": {
|
|
2775
|
+
const target = id ? body.nodes[id] : void 0;
|
|
2776
|
+
const at = worldPosition(three, target?.node ?? body.root);
|
|
2777
|
+
bridge?.wound([at.x, at.y, at.z], body.blood, event.exit ? [direction.x, direction.y, direction.z] : void 0);
|
|
2778
|
+
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) => {
|
|
2779
|
+
scene2.add(chunk);
|
|
2780
|
+
flying.throwObject(chunk, velocity, spin, id);
|
|
2781
|
+
});
|
|
2782
|
+
break;
|
|
2783
|
+
}
|
|
2784
|
+
default:
|
|
2785
|
+
break;
|
|
2786
|
+
}
|
|
2787
|
+
},
|
|
2788
|
+
setWalkPhase(body, phase) {
|
|
2789
|
+
const state = stateOf(body);
|
|
2790
|
+
if (phase === void 0) delete state.walkPhase;
|
|
2791
|
+
else state.walkPhase = phase;
|
|
2792
|
+
},
|
|
2793
|
+
goneOf: (body) => stateOf(body).gone,
|
|
2794
|
+
update(dt) {
|
|
2795
|
+
flying.update(dt);
|
|
2796
|
+
for (const state of states.values()) {
|
|
2797
|
+
const { rig } = state.body;
|
|
2798
|
+
for (const pendulum of state.pendulums) {
|
|
2799
|
+
const arm = rig.arms.find((candidate) => candidate.g === pendulum.node || candidate.el === pendulum.node);
|
|
2800
|
+
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;
|
|
2801
|
+
stepPendulum(pendulum, base, dt, state.walkPhase);
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
},
|
|
2805
|
+
applyLimp(body) {
|
|
2806
|
+
const state = stateOf(body);
|
|
2807
|
+
const bothLost = state.limp[0] && state.limp[1];
|
|
2808
|
+
state.limp.forEach((hurt, index) => {
|
|
2809
|
+
if (!hurt || bothLost) return;
|
|
2810
|
+
const leg = body.rig.legs[index];
|
|
2811
|
+
leg.g.rotation.x *= 0.3;
|
|
2812
|
+
leg.kn.rotation.x = 0.05;
|
|
2813
|
+
body.rig.upper.rotation.z += index ? -0.07 : 0.07;
|
|
2814
|
+
});
|
|
2815
|
+
},
|
|
2816
|
+
dispose() {
|
|
2817
|
+
flying.clear();
|
|
2818
|
+
materials.dispose();
|
|
2819
|
+
states.clear();
|
|
2820
|
+
}
|
|
2821
|
+
};
|
|
2822
|
+
};
|
|
2823
|
+
|
|
2824
|
+
// src/test-area/crowd.ts
|
|
2825
|
+
import { encodePoseHeight } from "@adatechnology/game-characters";
|
|
2826
|
+
|
|
2827
|
+
// src/instanced/lod.ts
|
|
2828
|
+
var LOD_SEGMENTS = [16, 10, 5];
|
|
2829
|
+
var LOD_BAKE = [1, 0.5, 0.28];
|
|
2830
|
+
var LOD_DISTANCES = [18, 55];
|
|
2831
|
+
var LOD_BATCH_CAPACITY = [160, 700, 2600];
|
|
2832
|
+
var LOD_CASTS_SHADOW = [true, true, false];
|
|
2833
|
+
var lodAt = (distance, distances = LOD_DISTANCES) => distance < distances[0] ? 0 : distance < distances[1] ? 1 : 2;
|
|
2834
|
+
|
|
2835
|
+
// src/instanced/variants.ts
|
|
2836
|
+
import { variantCountFor, variantDecay, variantSeed } from "@adatechnology/game-characters";
|
|
2837
|
+
|
|
2838
|
+
// src/instanced/bake.ts
|
|
2839
|
+
import { shapeGeometries as shapeGeometries5 } from "@adatechnology/game-shapes-three";
|
|
2840
|
+
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 };
|
|
2841
|
+
var BLOOD_LIFT_NEAR = 6e-3;
|
|
2842
|
+
var BLOOD_LIFT_FAR = 0.012;
|
|
2843
|
+
var FAR_LOD = 0.6;
|
|
2844
|
+
var EYE_SCALE = 2.2;
|
|
2845
|
+
var EYE_DEPTH_SCALE = 1.4;
|
|
2846
|
+
var bakeBody = (host, builders, spec, lod) => {
|
|
2847
|
+
const three = shapeGeometries5(host);
|
|
2848
|
+
const root = spec.retrato && builders.buildPortrait ? builders.buildPortrait(spec.retrato, lod) : builders.buildBest({ ...spec, pose: { ...BAKE_NEUTRAL_POSE, ...spec.pose ?? {} } }, lod);
|
|
2849
|
+
const rig = root.userData.rig;
|
|
2850
|
+
const P = rig.P;
|
|
2851
|
+
rig.fig.position.y = 0;
|
|
2852
|
+
rig.upper.rotation.set(0, 0, 0);
|
|
2853
|
+
rig.headG.rotation.set(0, 0, 0);
|
|
2854
|
+
for (const arm of rig.arms) {
|
|
2855
|
+
arm.g.rotation.set(0, 0, 0);
|
|
2856
|
+
arm.el.rotation.set(0, 0, 0);
|
|
2857
|
+
}
|
|
2858
|
+
for (const leg of rig.legs) {
|
|
2859
|
+
leg.g.rotation.set(0, 0, 0);
|
|
2860
|
+
leg.kn.rotation.set(0, 0, 0);
|
|
2861
|
+
}
|
|
2862
|
+
root.updateMatrixWorld(true);
|
|
2863
|
+
const worldPosition2 = (object) => object.getWorldPosition(new three.Vector3()).toArray();
|
|
2864
|
+
const bones = /* @__PURE__ */ new Map();
|
|
2865
|
+
bones.set(rig.headG, [1, worldPosition2(rig.headG), [0, 0, 0]]);
|
|
2866
|
+
for (const arm of rig.arms) {
|
|
2867
|
+
const shoulder = worldPosition2(arm.g);
|
|
2868
|
+
bones.set(arm.g, [arm.s < 0 ? 2 : 3, shoulder, [0, 0, 0]]);
|
|
2869
|
+
bones.set(arm.el, [arm.s < 0 ? 7 : 8, worldPosition2(arm.el), shoulder]);
|
|
2870
|
+
}
|
|
2871
|
+
for (const leg of rig.legs) {
|
|
2872
|
+
const hip = worldPosition2(leg.g);
|
|
2873
|
+
bones.set(leg.g, [leg.s < 0 ? 4 : 5, hip, [0, 0, 0]]);
|
|
2874
|
+
bones.set(leg.kn, [leg.s < 0 ? 9 : 10, worldPosition2(leg.kn), hip]);
|
|
2875
|
+
}
|
|
2876
|
+
const trunk = [0, [0, 0.9 * P.s, 0], [0, 0, 0]];
|
|
2877
|
+
const parts = [];
|
|
2878
|
+
const topMaterial = spec.retrato ? null : builders.topMaterial();
|
|
2879
|
+
root.traverse((object) => {
|
|
2880
|
+
const mesh = object;
|
|
2881
|
+
const material = mesh.material;
|
|
2882
|
+
if (!mesh.isMesh || !mesh.geometry || material.transparent) return;
|
|
2883
|
+
for (let ancestor = object; ancestor; ancestor = ancestor.parent) if (!ancestor.visible) return;
|
|
2884
|
+
let tag = trunk;
|
|
2885
|
+
for (let ancestor = object.parent; ancestor; ancestor = ancestor.parent) {
|
|
2886
|
+
const found = bones.get(ancestor);
|
|
2887
|
+
if (found) {
|
|
2888
|
+
tag = found;
|
|
2889
|
+
break;
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
let [bone, pivot, parentPivot] = tag;
|
|
2893
|
+
if (object.userData.bone === 11) {
|
|
2894
|
+
bone = 11;
|
|
2895
|
+
pivot = bones.get(rig.headG)[1];
|
|
2896
|
+
parentPivot = [0, 0, 0];
|
|
2897
|
+
}
|
|
2898
|
+
const isEye2 = !!object.userData.eye;
|
|
2899
|
+
const color = material.color;
|
|
2900
|
+
const materialId = isEye2 ? 3 : material === topMaterial ? 1 : 6;
|
|
2901
|
+
if (isEye2 && bone === 1) bone = 6;
|
|
2902
|
+
const geometry = mesh.geometry.clone();
|
|
2903
|
+
geometry.applyMatrix4(mesh.matrixWorld);
|
|
2904
|
+
const isBlood = material.polygonOffset && material.isMeshStandardMaterial;
|
|
2905
|
+
if (isBlood) liftAlongNormals(geometry, lod < FAR_LOD ? BLOOD_LIFT_FAR : BLOOD_LIFT_NEAR);
|
|
2906
|
+
if (isEye2) enlargeEye(three, geometry, lod < FAR_LOD ? 1 : 0.68);
|
|
2907
|
+
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];
|
|
2908
|
+
parts.push({ g: geometry, bone, pv: pivot, pp: parentPivot, col: colors });
|
|
2909
|
+
});
|
|
2910
|
+
return { geo: mergeBaked(three, parts), s: P.s, bulk: P.bulk, verts: parts.reduce((sum, part) => sum + part.g.attributes.position.count, 0) };
|
|
2911
|
+
};
|
|
2912
|
+
var liftAlongNormals = (geometry, lift) => {
|
|
2913
|
+
const positions = geometry.attributes.position;
|
|
2914
|
+
const normals = geometry.attributes.normal;
|
|
2915
|
+
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);
|
|
2916
|
+
};
|
|
2917
|
+
var enlargeEye = (three, geometry, factor) => {
|
|
2918
|
+
geometry.computeBoundingBox();
|
|
2919
|
+
const center = geometry.boundingBox.getCenter(new three.Vector3());
|
|
2920
|
+
geometry.translate(-center.x, -center.y, -center.z);
|
|
2921
|
+
geometry.scale(EYE_SCALE * factor, EYE_SCALE * factor, EYE_DEPTH_SCALE * factor);
|
|
2922
|
+
geometry.translate(center.x, center.y, center.z);
|
|
2923
|
+
};
|
|
2924
|
+
var mergeBaked = (three, parts) => {
|
|
2925
|
+
let vertexTotal = 0;
|
|
2926
|
+
let indexTotal = 0;
|
|
2927
|
+
for (const part of parts) {
|
|
2928
|
+
vertexTotal += part.g.attributes.position.count;
|
|
2929
|
+
indexTotal += part.g.index ? part.g.index.count : part.g.attributes.position.count;
|
|
2930
|
+
}
|
|
2931
|
+
const position = new Float32Array(vertexTotal * 3);
|
|
2932
|
+
const normal = new Float32Array(vertexTotal * 3);
|
|
2933
|
+
const color = new Float32Array(vertexTotal * 4);
|
|
2934
|
+
const bone = new Float32Array(vertexTotal);
|
|
2935
|
+
const pivot = new Float32Array(vertexTotal * 3);
|
|
2936
|
+
const parentPivot = new Float32Array(vertexTotal * 3);
|
|
2937
|
+
const index = vertexTotal > 65535 ? new Uint32Array(indexTotal) : new Uint16Array(indexTotal);
|
|
2938
|
+
let vertexOffset = 0;
|
|
2939
|
+
let indexOffset = 0;
|
|
2940
|
+
for (const part of parts) {
|
|
2941
|
+
const count = part.g.attributes.position.count;
|
|
2942
|
+
position.set(part.g.attributes.position.array, vertexOffset * 3);
|
|
2943
|
+
normal.set(part.g.attributes.normal.array, vertexOffset * 3);
|
|
2944
|
+
for (let vertex = 0; vertex < count; vertex++) {
|
|
2945
|
+
color.set(part.col, (vertexOffset + vertex) * 4);
|
|
2946
|
+
bone[vertexOffset + vertex] = part.bone;
|
|
2947
|
+
pivot.set(part.pv, (vertexOffset + vertex) * 3);
|
|
2948
|
+
parentPivot.set(part.pp, (vertexOffset + vertex) * 3);
|
|
2949
|
+
}
|
|
2950
|
+
if (part.g.index) {
|
|
2951
|
+
const source = part.g.index.array;
|
|
2952
|
+
for (let item = 0; item < source.length; item++) index[indexOffset++] = source[item] + vertexOffset;
|
|
2953
|
+
} else for (let vertex = 0; vertex < count; vertex++) index[indexOffset++] = vertexOffset + vertex;
|
|
2954
|
+
vertexOffset += count;
|
|
2955
|
+
part.g.dispose();
|
|
2956
|
+
}
|
|
2957
|
+
const out = new three.BufferGeometry();
|
|
2958
|
+
out.setAttribute("position", new three.BufferAttribute(position, 3));
|
|
2959
|
+
out.setAttribute("normal", new three.BufferAttribute(normal, 3));
|
|
2960
|
+
out.setAttribute("vcol", new three.BufferAttribute(color, 4));
|
|
2961
|
+
out.setAttribute("bone", new three.BufferAttribute(bone, 1));
|
|
2962
|
+
out.setAttribute("pivot", new three.BufferAttribute(pivot, 3));
|
|
2963
|
+
out.setAttribute("ppivot", new three.BufferAttribute(parentPivot, 3));
|
|
2964
|
+
out.setIndex(new three.BufferAttribute(index, 1));
|
|
2965
|
+
out.computeBoundingSphere();
|
|
2966
|
+
return out;
|
|
2967
|
+
};
|
|
2968
|
+
|
|
2969
|
+
// src/instanced/body-chunk.ts
|
|
2970
|
+
var BODY_CHUNK = `
|
|
2971
|
+
attribute float bone; attribute vec4 vcol; attribute vec3 pivot; attribute vec3 ppivot;
|
|
2972
|
+
attribute vec4 iA; attribute vec4 iB; attribute vec4 iC; attribute vec3 iCol; attribute vec3 iEye;
|
|
2973
|
+
uniform float uTime;
|
|
2974
|
+
varying vec3 vTint; varying float vGlow;
|
|
2975
|
+
mat3 rX(float a){ float c=cos(a), s=sin(a); return mat3(1.,0.,0., 0.,c,s, 0.,-s,c); }
|
|
2976
|
+
mat3 rY(float a){ float c=cos(a), s=sin(a); return mat3(c,0.,-s, 0.,1.,0., s,0.,c); }
|
|
2977
|
+
mat3 rZ(float a){ float c=cos(a), s=sin(a); return mat3(c,s,0., -s,c,0., 0.,0.,1.); }
|
|
2978
|
+
bool miss(float bit){ if (iC.z > 1.5) return false; return mod(floor(iC.x / bit), 2.) > 0.5; }
|
|
2979
|
+
vec3 bodyAnim(vec3 p, inout vec3 nrm){
|
|
2980
|
+
float ph = iA.w*6.2831, gait = iB.z, pose = iB.w - floor(iB.w/16.)*16., zomb = iC.z;
|
|
2981
|
+
float side = (bone==2.||bone==4.||bone==7.||bone==9.) ? -1. : 1.;
|
|
2982
|
+
// cascata: cada junta atrasa em rela\xE7\xE3o ao quadril (Besti\xE1rio 12.3)
|
|
2983
|
+
float lag = bone==1.||bone==6.||bone==11. ? 0.55 : (bone>=7.&&bone<=8. ? 0.35 : (bone==2.||bone==3. ? 0.2 : 0.));
|
|
2984
|
+
float sw = sin(ph - lag);
|
|
2985
|
+
mat3 Rp = mat3(1.), Rc = mat3(1.);
|
|
2986
|
+
bool isArm = bone==2.||bone==3., isFore = bone==7.||bone==8., isThigh = bone==4.||bone==5., isShin = bone==9.||bone==10.;
|
|
2987
|
+
float legS = (bone==4.||bone==9.) ? 1. : -1.;
|
|
2988
|
+
float walkS = sin(ph) * legS;
|
|
2989
|
+
if (isThigh || isShin) Rp = rX(-walkS*0.5*gait);
|
|
2990
|
+
if (isShin) Rc = rX(max(0., cos(ph + (legS>0.?0.:3.1416)))*0.85*gait);
|
|
2991
|
+
if (pose==6.) { if (isThigh) Rp = rX(-1.1); if (isShin) Rc = rX(1.4); }
|
|
2992
|
+
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
|
|
2993
|
+
if (pose==7.) { if (isThigh) Rp = rX(-1.15); if (isShin) Rc = rX(2.3); } // ajoelhado comendo
|
|
2994
|
+
if (pose==2. && isShin && fract(iA.w*3.)*legS > 0.3) Rc = rX(fract(iA.w*3.)*1.3); // corpo com um joelho dobrado
|
|
2995
|
+
float armS = sin(ph) * (side>0. ? 1. : -1.);
|
|
2996
|
+
if (isArm || isFore) {
|
|
2997
|
+
// zumbi: bra\xE7os ca\xEDdos vagando; levanta (iC.z = 1 + 0,4 \xD7 alcance) quando percebe algu\xE9m ou vai agarrar
|
|
2998
|
+
float raise = clamp((zomb - 1.)/0.4, 0., 1.);
|
|
2999
|
+
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;
|
|
3000
|
+
if (pose==3.) fwd = -1.55 + 0.35*sin(uTime*9.+iA.w);
|
|
3001
|
+
if (pose==4.) fwd = -2.6;
|
|
3002
|
+
if (pose==6.) fwd = -2.0;
|
|
3003
|
+
if (pose==2.) fwd = (fract(iA.w*5.) > 0.5 && side > 0.) ? -2.8 : -0.35 + 0.3*sin(iA.w*5. + side);
|
|
3004
|
+
if (pose==1.) fwd = -2.6 + 0.5*sin(ph)*side; // puxa o corpo com os bra\xE7os
|
|
3005
|
+
if (pose==7.) fwd = -0.9 + 0.3*sin(uTime*6.+iA.w);
|
|
3006
|
+
Rp = rX(fwd) * rZ(side*(pose==2. ? 0.5 + 0.3*sin(iA.w*3.) : 0.08));
|
|
3007
|
+
// armado (iC.z = 2 + pitch codificado): as duas m\xE3os na arma, bra\xE7os seguem a mira
|
|
3008
|
+
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
|
|
3009
|
+
}
|
|
3010
|
+
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);
|
|
3011
|
+
if (bone==11.) Rc = rX(pose==3.||pose==4. ? 0.35 + 0.2*sin(uTime*14.) : 0.05);
|
|
3012
|
+
vec3 q = p; vec3 n = nrm;
|
|
3013
|
+
if (isFore || isShin) { q = Rc*(q - pivot) + pivot; n = Rc*n; q = Rp*(q - ppivot) + ppivot; n = Rp*n; }
|
|
3014
|
+
else if (isArm || isThigh || bone==11.) { q = Rp*(q - pivot) + pivot; n = Rp*n; q = Rc*(q - pivot) + pivot; n = Rc*n; }
|
|
3015
|
+
// humano armado andando de lado: quadril e pernas giram para a dire\xE7\xE3o do passo (iC.x leva o \xE2ngulo)
|
|
3016
|
+
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; }
|
|
3017
|
+
// partes faltando: colapsa no piv\xF4
|
|
3018
|
+
if ((bone==1.||bone==6.||bone==11.) && miss(1.)) q = pivot;
|
|
3019
|
+
if (bone==11. && miss(512.)) q = pivot;
|
|
3020
|
+
if ((bone==2.||bone==7.) && miss(2.)) q = pivot; if ((bone==3.||bone==8.) && miss(4.)) q = pivot;
|
|
3021
|
+
if ((bone==4.||bone==9.) && miss(8.)) q = pivot; if ((bone==5.||bone==10.) && miss(16.)) q = pivot;
|
|
3022
|
+
if (bone==7. && miss(32.)) q = pivot; if (bone==8. && miss(64.)) q = pivot;
|
|
3023
|
+
if (bone==9. && miss(128.)) q = pivot; if (bone==10. && miss(256.)) q = pivot;
|
|
3024
|
+
// corpo: inclina\xE7\xE3o, balan\xE7o, agachar, rastejar, ca\xEDdo
|
|
3025
|
+
bool upper = !(isThigh || isShin);
|
|
3026
|
+
float zz = zomb > 0.5 && zomb < 1.5 ? 1. : 0.;
|
|
3027
|
+
float lean = zz > 0.5 ? 0.12 + 0.3*min(1., gait) : 0.04*gait;
|
|
3028
|
+
if (pose==3.) lean += 0.15;
|
|
3029
|
+
if (pose==5.) lean += 0.25;
|
|
3030
|
+
if (pose==7.) lean += 0.8;
|
|
3031
|
+
if (upper) { q -= vec3(0.,0.95,0.); q = rX(lean)*q; n = rX(lean)*n; q += vec3(0.,0.95,0.); }
|
|
3032
|
+
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
|
|
3033
|
+
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; }
|
|
3034
|
+
q.y += abs(sin(ph))*0.035*gait - (pose==5. ? 0.35 : pose==7. ? 0.5 : 0.);
|
|
3035
|
+
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
|
|
3036
|
+
if (pose==1.) { mat3 c = rX(1.42); q = c*(q - vec3(0.,0.95,0.)) + vec3(0.,0.2,0.); n = c*n; }
|
|
3037
|
+
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; }
|
|
3038
|
+
nrm = n;
|
|
3039
|
+
return q;
|
|
3040
|
+
}
|
|
3041
|
+
`;
|
|
3042
|
+
var HIP_PIVOT = "vec3(0.,0.95,0.)";
|
|
3043
|
+
var createBodyChunk = ({ hipHeight = 0.95 } = {}) => hipHeight === 0.95 ? BODY_CHUNK : BODY_CHUNK.replaceAll(HIP_PIVOT, `vec3(0.,${hipHeight},0.)`);
|
|
3044
|
+
|
|
3045
|
+
// src/instanced/impostor-pose.ts
|
|
3046
|
+
var rotateX = (angle) => {
|
|
3047
|
+
const c = Math.cos(angle);
|
|
3048
|
+
const s = Math.sin(angle);
|
|
3049
|
+
return ([x, y, z]) => [x, c * y - s * z, s * y + c * z];
|
|
3050
|
+
};
|
|
3051
|
+
var rotateZ = (angle) => {
|
|
3052
|
+
const c = Math.cos(angle);
|
|
3053
|
+
const s = Math.sin(angle);
|
|
3054
|
+
return ([x, y, z]) => [c * x - s * y, s * x + c * y, z];
|
|
3055
|
+
};
|
|
3056
|
+
var identity = (v) => v;
|
|
3057
|
+
var compose = (outer, inner) => (v) => outer(inner(v));
|
|
3058
|
+
var about = (matrix, center) => ([x, y, z]) => {
|
|
3059
|
+
const [rx, ry, rz] = matrix([x - center[0], y - center[1], z - center[2]]);
|
|
3060
|
+
return [rx + center[0], ry + center[1], rz + center[2]];
|
|
3061
|
+
};
|
|
3062
|
+
var HIP = [0, 0.95, 0];
|
|
3063
|
+
var TWO_PI = Math.PI * 2;
|
|
3064
|
+
var ARMS_RAISED = 0.5;
|
|
3065
|
+
var SIDE_BONES = /* @__PURE__ */ new Set([2, 4, 7, 9]);
|
|
3066
|
+
var walkPoseVertex = (position, normal, { bone, pivot, parentPivot }, step, steps) => {
|
|
3067
|
+
const ph = step / steps * TWO_PI;
|
|
3068
|
+
const gait = 1;
|
|
3069
|
+
const side = SIDE_BONES.has(bone) ? -1 : 1;
|
|
3070
|
+
const isThigh = bone === 4 || bone === 5;
|
|
3071
|
+
const isShin = bone === 9 || bone === 10;
|
|
3072
|
+
const isArm = bone === 2 || bone === 3;
|
|
3073
|
+
const isFore = bone === 7 || bone === 8;
|
|
3074
|
+
const legSign = bone === 4 || bone === 9 ? 1 : -1;
|
|
3075
|
+
const walkS = Math.sin(ph) * legSign;
|
|
3076
|
+
const armS = Math.sin(ph) * (side > 0 ? 1 : -1);
|
|
3077
|
+
let parent = identity;
|
|
3078
|
+
let child = identity;
|
|
3079
|
+
if (isThigh || isShin) parent = rotateX(-walkS * 0.5 * gait);
|
|
3080
|
+
if (isShin) child = rotateX(Math.max(0, Math.cos(ph + (legSign > 0 ? 0 : Math.PI))) * 0.85 * gait);
|
|
3081
|
+
if (isArm || isFore) {
|
|
3082
|
+
const fwd = (1 - ARMS_RAISED) * (-0.12 + armS * 0.3 * gait) + ARMS_RAISED * (-1.35 + armS * 0.18);
|
|
3083
|
+
parent = compose(rotateX(fwd), rotateZ(side * 0.08));
|
|
3084
|
+
}
|
|
3085
|
+
if (isFore) child = rotateX((1 - ARMS_RAISED) * (-0.3 - Math.max(0, armS) * 0.3 * gait) + ARMS_RAISED * -0.15);
|
|
3086
|
+
if (bone === 11) child = rotateX(0.05);
|
|
3087
|
+
let transform;
|
|
3088
|
+
if (isFore || isShin) transform = compose(about(parent, parentPivot), about(child, pivot));
|
|
3089
|
+
else if (isArm || isThigh || bone === 11) transform = compose(about(child, pivot), about(parent, pivot));
|
|
3090
|
+
else transform = identity;
|
|
3091
|
+
const lean = 0.12 + 0.3 * Math.min(1, gait);
|
|
3092
|
+
const upper = !(isThigh || isShin);
|
|
3093
|
+
const leanMatrix = upper ? about(rotateX(lean), HIP) : identity;
|
|
3094
|
+
const bounce = Math.abs(Math.sin(ph)) * 0.035 * gait;
|
|
3095
|
+
const [x, y, z] = leanMatrix(transform(position));
|
|
3096
|
+
const rotate = (v) => upper ? rotateX(lean)(v) : v;
|
|
3097
|
+
const turned = isFore || isShin ? parent(child(normal)) : child(parent(normal));
|
|
3098
|
+
return { position: [x, y + bounce, z], normal: rotate(turned) };
|
|
3099
|
+
};
|
|
3100
|
+
|
|
3101
|
+
// src/instanced/impostor-atlas.ts
|
|
3102
|
+
var IMPOSTOR_ANGLES = 8;
|
|
3103
|
+
var IMPOSTOR_FRAMES = 8;
|
|
3104
|
+
var IMPOSTOR_BONE_SCALE = 20;
|
|
3105
|
+
var IMPOSTOR_MATERIAL_SCALE = 30;
|
|
3106
|
+
var CELL_WIDTH = 32;
|
|
3107
|
+
var CELL_HEIGHT = 48;
|
|
3108
|
+
var COLUMNS = 32;
|
|
3109
|
+
var BOX_HALF_WIDTH = 0.41;
|
|
3110
|
+
var BOX_BOTTOM = -0.04;
|
|
3111
|
+
var BOX_HEIGHT = 1.14;
|
|
3112
|
+
var LIGHT = [-0.35, 0.75, 0.55];
|
|
3113
|
+
var LIGHT_LENGTH = Math.hypot(...LIGHT);
|
|
3114
|
+
var AMBIENT = 0.45;
|
|
3115
|
+
var COVERED = 255;
|
|
3116
|
+
var OWN_COLOR_MATERIAL = 6;
|
|
3117
|
+
var TROUSERS_CLASS = 2;
|
|
3118
|
+
var LEG_BONES = /* @__PURE__ */ new Set([4, 5, 9, 10]);
|
|
3119
|
+
var SHOE_HEIGHT = 0.1;
|
|
3120
|
+
var rasterise = (layers, stride, origin, cell, box, triangles) => {
|
|
3121
|
+
const depth = new Float32Array(cell.width * cell.height).fill(-Infinity);
|
|
3122
|
+
const toPixel = (u, v) => [(u + box.halfWidth) / (2 * box.halfWidth) * cell.width, (v - box.bottom) / box.height * cell.height];
|
|
3123
|
+
for (const triangle of triangles) {
|
|
3124
|
+
const screen = triangle.points.map((point) => toPixel(point[0], point[1]));
|
|
3125
|
+
const minX = Math.max(0, Math.floor(Math.min(...screen.map((p2) => p2[0]))));
|
|
3126
|
+
const maxX = Math.min(cell.width - 1, Math.ceil(Math.max(...screen.map((p2) => p2[0]))));
|
|
3127
|
+
const minY = Math.max(0, Math.floor(Math.min(...screen.map((p2) => p2[1]))));
|
|
3128
|
+
const maxY = Math.min(cell.height - 1, Math.ceil(Math.max(...screen.map((p2) => p2[1]))));
|
|
3129
|
+
const [a, b, c] = screen;
|
|
3130
|
+
const area = (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
|
|
3131
|
+
if (Math.abs(area) < 1e-6) continue;
|
|
3132
|
+
for (let y = minY; y <= maxY; y++) {
|
|
3133
|
+
for (let x = minX; x <= maxX; x++) {
|
|
3134
|
+
const px = x + 0.5;
|
|
3135
|
+
const py = y + 0.5;
|
|
3136
|
+
const w0 = ((b[0] - px) * (c[1] - py) - (b[1] - py) * (c[0] - px)) / area;
|
|
3137
|
+
const w1 = ((c[0] - px) * (a[1] - py) - (c[1] - py) * (a[0] - px)) / area;
|
|
3138
|
+
const w2 = 1 - w0 - w1;
|
|
3139
|
+
if (w0 < 0 || w1 < 0 || w2 < 0) continue;
|
|
3140
|
+
const z = w0 * triangle.points[0][2] + w1 * triangle.points[1][2] + w2 * triangle.points[2][2];
|
|
3141
|
+
const at = y * cell.width + x;
|
|
3142
|
+
if (z <= depth[at]) continue;
|
|
3143
|
+
depth[at] = z;
|
|
3144
|
+
const out = ((origin.y + y) * stride + origin.x + x) * 4;
|
|
3145
|
+
layers.pixels[out] = triangle.bone * IMPOSTOR_BONE_SCALE;
|
|
3146
|
+
layers.pixels[out + 1] = triangle.material * IMPOSTOR_MATERIAL_SCALE;
|
|
3147
|
+
layers.pixels[out + 2] = triangle.shade;
|
|
3148
|
+
layers.pixels[out + 3] = COVERED;
|
|
3149
|
+
layers.colors.set(triangle.color, out);
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
};
|
|
3154
|
+
var trianglesOf = (geometry, step, angle) => {
|
|
3155
|
+
const source = geometry.index ? geometry.toNonIndexed() : geometry;
|
|
3156
|
+
const position = source.attributes.position;
|
|
3157
|
+
const normal = source.attributes.normal;
|
|
3158
|
+
const bones = source.attributes.bone;
|
|
3159
|
+
const paint = source.attributes.vcol;
|
|
3160
|
+
const pivot = source.attributes.pivot;
|
|
3161
|
+
const parentPivot = source.attributes.ppivot;
|
|
3162
|
+
const azimuth = angle / IMPOSTOR_ANGLES * Math.PI * 2;
|
|
3163
|
+
const right = [Math.cos(azimuth), 0, -Math.sin(azimuth)];
|
|
3164
|
+
const toward = [Math.sin(azimuth), 0, Math.cos(azimuth)];
|
|
3165
|
+
const triangles = [];
|
|
3166
|
+
for (let first = 0; first + 2 < position.count; first += 3) {
|
|
3167
|
+
const points = [];
|
|
3168
|
+
let lambert = 0;
|
|
3169
|
+
for (let k = 0; k < 3; k++) {
|
|
3170
|
+
const index = first + k;
|
|
3171
|
+
const posed = walkPoseVertex([position.getX(index), position.getY(index), position.getZ(index)], [normal.getX(index), normal.getY(index), normal.getZ(index)], { bone: bones.getX(index), pivot: [pivot.getX(index), pivot.getY(index), pivot.getZ(index)], parentPivot: [parentPivot.getX(index), parentPivot.getY(index), parentPivot.getZ(index)] }, step, IMPOSTOR_FRAMES);
|
|
3172
|
+
const p2 = posed.position;
|
|
3173
|
+
points.push([p2[0] * right[0] + p2[2] * right[2], p2[1], p2[0] * toward[0] + p2[2] * toward[2]]);
|
|
3174
|
+
const n = posed.normal;
|
|
3175
|
+
const view = [n[0] * right[0] + n[2] * right[2], n[1], n[0] * toward[0] + n[2] * toward[2]];
|
|
3176
|
+
lambert += Math.max(0, (view[0] * LIGHT[0] + view[1] * LIGHT[1] + view[2] * LIGHT[2]) / LIGHT_LENGTH);
|
|
3177
|
+
}
|
|
3178
|
+
const bone = bones.getX(first);
|
|
3179
|
+
const rest = position.getY(first);
|
|
3180
|
+
const material = paint.getW(first) === OWN_COLOR_MATERIAL && LEG_BONES.has(bone) && rest > SHOE_HEIGHT ? TROUSERS_CLASS : paint.getW(first);
|
|
3181
|
+
const squareRoot = (value) => Math.round(Math.sqrt(Math.min(1, Math.max(0, value))) * 255);
|
|
3182
|
+
triangles.push({ points, bone, material, color: [squareRoot(paint.getX(first)), squareRoot(paint.getY(first)), squareRoot(paint.getZ(first)), 255], shade: Math.round((AMBIENT + (1 - AMBIENT) * (lambert / 3)) * 255) });
|
|
3183
|
+
}
|
|
3184
|
+
return triangles;
|
|
3185
|
+
};
|
|
3186
|
+
var topOf = (geometry) => {
|
|
3187
|
+
geometry.computeBoundingBox();
|
|
3188
|
+
return geometry.boundingBox.max.y;
|
|
3189
|
+
};
|
|
3190
|
+
var bakeImpostorAtlas = (three, bodies, options = {}) => {
|
|
3191
|
+
const cellWidth = options.cellWidth ?? CELL_WIDTH;
|
|
3192
|
+
const cellHeight = options.cellHeight ?? CELL_HEIGHT;
|
|
3193
|
+
const columns = options.columns ?? COLUMNS;
|
|
3194
|
+
const cells = bodies.length * IMPOSTOR_FRAMES * IMPOSTOR_ANGLES;
|
|
3195
|
+
const rows = Math.ceil(cells / columns);
|
|
3196
|
+
const stride = columns * cellWidth;
|
|
3197
|
+
const pixels = new Uint8Array(stride * rows * cellHeight * 4);
|
|
3198
|
+
const colorPixels = new Uint8Array(pixels.length);
|
|
3199
|
+
const boxes = bodies.map((body) => {
|
|
3200
|
+
const height = body.height ?? topOf(body.geometry);
|
|
3201
|
+
return { halfWidth: BOX_HALF_WIDTH * height, bottom: BOX_BOTTOM * height, height: BOX_HEIGHT * height };
|
|
3202
|
+
});
|
|
3203
|
+
bodies.forEach((body, silhouette) => {
|
|
3204
|
+
for (let frame = 0; frame < IMPOSTOR_FRAMES; frame++) {
|
|
3205
|
+
for (let angle = 0; angle < IMPOSTOR_ANGLES; angle++) {
|
|
3206
|
+
const cell = (silhouette * IMPOSTOR_FRAMES + frame) * IMPOSTOR_ANGLES + angle;
|
|
3207
|
+
rasterise({ pixels, colors: colorPixels }, stride, { x: cell % columns * cellWidth, y: Math.floor(cell / columns) * cellHeight }, { width: cellWidth, height: cellHeight }, boxes[silhouette], trianglesOf(body.geometry, frame, angle));
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
});
|
|
3211
|
+
const layer = (bytes) => {
|
|
3212
|
+
const texture2 = new three.DataTexture(bytes, stride, rows * cellHeight, three.RGBAFormat, three.UnsignedByteType);
|
|
3213
|
+
texture2.minFilter = three.NearestFilter;
|
|
3214
|
+
texture2.magFilter = three.NearestFilter;
|
|
3215
|
+
texture2.generateMipmaps = false;
|
|
3216
|
+
texture2.needsUpdate = true;
|
|
3217
|
+
return texture2;
|
|
3218
|
+
};
|
|
3219
|
+
const texture = layer(pixels);
|
|
3220
|
+
const colorTexture = layer(colorPixels);
|
|
3221
|
+
return { texture, colorTexture, silhouettes: bodies.length, cellWidth, cellHeight, columns, rows, boxes, pixels, colorPixels, dispose: () => (texture.dispose(), colorTexture.dispose()) };
|
|
3222
|
+
};
|
|
3223
|
+
|
|
3224
|
+
// src/instanced/identity-layout.ts
|
|
3225
|
+
var GAIT_STYLE = { NORMAL: 0, LIMP_LEFT: 1, LIMP_RIGHT: 2, DRAG: 3, LURCH: 4, SHAMBLE: 5 };
|
|
3226
|
+
var ACCESSORY_BIT = { CAP: 1, HELMET: 2, BACKPACK: 4, EXTRA: 8 };
|
|
3227
|
+
var STAGGER_POSE = 8;
|
|
3228
|
+
var TROUSER_PALETTE = [
|
|
3229
|
+
2832981,
|
|
3230
|
+
3821432,
|
|
3231
|
+
1842208,
|
|
3232
|
+
4868686,
|
|
3233
|
+
7035452,
|
|
3234
|
+
8022600,
|
|
3235
|
+
4147764,
|
|
3236
|
+
5913130,
|
|
3237
|
+
2764600,
|
|
3238
|
+
6120818,
|
|
3239
|
+
6958890,
|
|
3240
|
+
4864594,
|
|
3241
|
+
9077872,
|
|
3242
|
+
2372154,
|
|
3243
|
+
3485738,
|
|
3244
|
+
5660778
|
|
3245
|
+
];
|
|
3246
|
+
var CLIPS_BIT = 1048576;
|
|
3247
|
+
var CLIP_SPEED_MIN = 0.75;
|
|
3248
|
+
var CLIP_SPEED_SPAN = 0.5;
|
|
3249
|
+
var TWO_PI2 = Math.PI * 2;
|
|
3250
|
+
var packIdentity = (look) => {
|
|
3251
|
+
const x = (look.wounds & 4095) + 4096 * (look.freshness & 15);
|
|
3252
|
+
const y = (look.trousers & 15) + 16 * (look.gaitStyle & 7) + 128 * (look.accessories & 15);
|
|
3253
|
+
const blend = Math.round(Math.min(1, Math.max(0, look.clipBlend ?? 0)) * 255);
|
|
3254
|
+
const z = ((look.clipA ?? 0) & 15) + 16 * ((look.clipB ?? 0) & 15) + 256 * blend + 65536 * ((look.clipSpeed ?? 7) & 15) + (look.clips ? CLIPS_BIT : 0);
|
|
3255
|
+
const turns = ((look.leanAngle ?? 0) % TWO_PI2 + TWO_PI2) % TWO_PI2;
|
|
3256
|
+
const w = (Math.round(turns / TWO_PI2 * 256) & 255) + 256 * Math.round(Math.min(1, Math.max(0, look.leanAmount ?? 0)) * 255);
|
|
3257
|
+
return [x, y, z, w];
|
|
3258
|
+
};
|
|
3259
|
+
var unpackIdentity = ([x, y, z, w]) => ({
|
|
3260
|
+
wounds: x % 4096,
|
|
3261
|
+
freshness: Math.floor(x / 4096) % 16,
|
|
3262
|
+
trousers: y % 16,
|
|
3263
|
+
gaitStyle: Math.floor(y / 16) % 8,
|
|
3264
|
+
accessories: Math.floor(y / 128) % 16,
|
|
3265
|
+
clipA: z % 16,
|
|
3266
|
+
clipB: Math.floor(z / 16) % 16,
|
|
3267
|
+
clipBlend: Math.floor(z / 256) % 256 / 255,
|
|
3268
|
+
clipSpeed: Math.floor(z / 65536) % 16,
|
|
3269
|
+
clips: Math.floor(z / CLIPS_BIT) % 2 === 1,
|
|
3270
|
+
leanAngle: w % 256 / 256 * TWO_PI2,
|
|
3271
|
+
leanAmount: Math.floor(w / 256) % 256 / 255
|
|
3272
|
+
});
|
|
3273
|
+
var clipSpeedOf = (step) => CLIP_SPEED_MIN + step / 15 * CLIP_SPEED_SPAN;
|
|
3274
|
+
var glslColor = (hex2) => `vec3(${[hex2 >> 16 & 255, hex2 >> 8 & 255, hex2 & 255].map((channel) => (channel / 255).toFixed(4)).join(",")})`;
|
|
3275
|
+
|
|
3276
|
+
// src/instanced/body-chunk-ext.ts
|
|
3277
|
+
var HIP_PIVOT2 = "vec3(0.,0.95,0.)";
|
|
3278
|
+
var palette = `const vec3 TROUSERS[16] = vec3[16](${TROUSER_PALETTE.map(glslColor).join(",")});`;
|
|
3279
|
+
var CLIP_FUNCTIONS = `
|
|
3280
|
+
uniform sampler2D uClips; uniform float uClipStart[16]; uniform float uClipFrames[16]; uniform float uClipCycles[16];
|
|
3281
|
+
vec4 clipQuat(int clip, int boneIndex, int texel){
|
|
3282
|
+
float frames = uClipFrames[clip];
|
|
3283
|
+
float position = fract(iA.w / uClipCycles[clip]) * frames;
|
|
3284
|
+
float first = floor(position);
|
|
3285
|
+
float second = mod(first + 1., frames);
|
|
3286
|
+
vec4 a = texelFetch(uClips, ivec2(boneIndex*2 + texel, int(uClipStart[clip] + first)), 0);
|
|
3287
|
+
vec4 b = texelFetch(uClips, ivec2(boneIndex*2 + texel, int(uClipStart[clip] + second)), 0);
|
|
3288
|
+
if (dot(a, b) < 0. && texel == 0) b = -b;
|
|
3289
|
+
return mix(a, b, position - first);
|
|
3290
|
+
}
|
|
3291
|
+
vec4 clipBlend(int boneIndex, int texel){
|
|
3292
|
+
vec4 a = clipQuat(int(idField(iD.z, 1., 16.)), boneIndex, texel);
|
|
3293
|
+
float blend = idField(iD.z, 256., 256.) / 255.;
|
|
3294
|
+
if (blend > 0.002) { vec4 b = clipQuat(int(idField(iD.z, 16., 16.)), boneIndex, texel); if (dot(a, b) < 0. && texel == 0) b = -b; a = mix(a, b, blend); }
|
|
3295
|
+
return a;
|
|
3296
|
+
}
|
|
3297
|
+
vec4 boneQuat(int boneIndex){
|
|
3298
|
+
vec4 q = clipBlend(boneIndex, 0);
|
|
3299
|
+
return normalize(mix(vec4(0., 0., 0., 1.), q, clamp(iB.z, 0., 1.4)));
|
|
3300
|
+
}
|
|
3301
|
+
vec4 quatMul(vec4 a, vec4 b){ return vec4(a.w*b.xyz + b.w*a.xyz + cross(a.xyz, b.xyz), a.w*b.w - dot(a.xyz, b.xyz)); }
|
|
3302
|
+
vec3 qRot(vec4 q, vec3 v){ vec3 t = 2.*cross(q.xyz, v); return v + q.w*t + cross(q.xyz, t); }
|
|
3303
|
+
vec3 bodyAnimClip(vec3 p, inout vec3 nrm){
|
|
3304
|
+
int b = int(bone + 0.5);
|
|
3305
|
+
bool isShin = bone==9.||bone==10., isFore = bone==7.||bone==8.;
|
|
3306
|
+
bool isLimb = bone==2.||bone==3.||bone==4.||bone==5.||isFore||isShin;
|
|
3307
|
+
vec3 q = p; vec3 n = nrm;
|
|
3308
|
+
if (isLimb) {
|
|
3309
|
+
vec4 own = boneQuat(b);
|
|
3310
|
+
q = qRot(own, q - pivot) + pivot; n = qRot(own, n);
|
|
3311
|
+
if (isFore || isShin) { vec4 parent = boneQuat(b - 5); q = qRot(parent, q - ppivot) + ppivot; n = qRot(parent, n); }
|
|
3312
|
+
}
|
|
3313
|
+
if (bone==11.) { q = qRot(vec4(0.025, 0., 0., 0.9997), q - pivot) + pivot; }
|
|
3314
|
+
if ((bone==1.||bone==6.||bone==11.) && miss(1.)) q = pivot;
|
|
3315
|
+
if (bone==11. && miss(512.)) q = pivot;
|
|
3316
|
+
if ((bone==2.||bone==7.) && miss(2.)) q = pivot; if ((bone==3.||bone==8.) && miss(4.)) q = pivot;
|
|
3317
|
+
if ((bone==4.||bone==9.) && miss(8.)) q = pivot; if ((bone==5.||bone==10.) && miss(16.)) q = pivot;
|
|
3318
|
+
if (bone==7. && miss(32.)) q = pivot; if (bone==8. && miss(64.)) q = pivot;
|
|
3319
|
+
if (bone==9. && miss(128.)) q = pivot; if (bone==10. && miss(256.)) q = pivot;
|
|
3320
|
+
if (!(bone==4.||bone==5.||bone==9.||bone==10.)) {
|
|
3321
|
+
vec4 torso = normalize(mix(vec4(0., 0., 0., 1.), clipBlend(0, 0), clamp(iB.z, 0., 1.4)));
|
|
3322
|
+
vec4 whole = normalize(quatMul(torso, vec4(0.05996, 0., 0., 0.9982)));
|
|
3323
|
+
q = qRot(whole, q - ${HIP_PIVOT2}) + ${HIP_PIVOT2}; n = qRot(whole, n);
|
|
3324
|
+
}
|
|
3325
|
+
q.y += clipBlend(0, 1).y * clamp(iB.z, 0., 1.4);
|
|
3326
|
+
nrm = n;
|
|
3327
|
+
return q;
|
|
3328
|
+
}
|
|
3329
|
+
`;
|
|
3330
|
+
var createBodyChunkExt = ({ hipHeight = 0.95, segmentClips = false } = {}) => {
|
|
3331
|
+
const chunk = `
|
|
3332
|
+
attribute vec4 iD;
|
|
3333
|
+
varying vec4 vWound; varying vec3 vLocal;
|
|
3334
|
+
${palette}
|
|
3335
|
+
float idField(float value, float divisor, float modulus){ return mod(floor(value / divisor), modulus); }
|
|
3336
|
+
vec3 trousersOf(float code){ return TROUSERS[int(idField(code, 1., 16.))]; }
|
|
3337
|
+
mat3 rAxis(vec3 a, float t){ float c = cos(t), s = sin(t), k = 1. - c; return mat3(k*a.x*a.x + c, k*a.x*a.y + s*a.z, k*a.x*a.z - s*a.y, k*a.x*a.y - s*a.z, k*a.y*a.y + c, k*a.y*a.z + s*a.x, k*a.x*a.z + s*a.y, k*a.y*a.z - s*a.x, k*a.z*a.z + c); }
|
|
3338
|
+
float woundLevel(){ float region = (bone==1.||bone==6.||bone==11.) ? 0. : bone==0. ? 1. : (bone==2.||bone==7.) ? 2. : (bone==3.||bone==8.) ? 3. : (bone==4.||bone==9.) ? 4. : 5.; float divisor = region==0. ? 1. : region==1. ? 4. : region==2. ? 16. : region==3. ? 64. : region==4. ? 256. : 1024.; return idField(iD.x, divisor, 4.); }
|
|
3339
|
+
${segmentClips ? "CLIP_FUNCTIONS_HERE" : ""}
|
|
3340
|
+
vec3 bodyAnimX(vec3 p, inout vec3 nrm){
|
|
3341
|
+
float pose = iB.w - floor(iB.w/16.)*16.;
|
|
3342
|
+
${segmentClips ? "bool clipped = idField(iD.z, 1048576., 2.) > 0.5 && iC.z < 1.5 && (pose==0. || pose==8.);" : "bool clipped = false;"}
|
|
3343
|
+
vec3 q = ${segmentClips ? "clipped ? bodyAnimClip(p, nrm) : bodyAnim(p, nrm)" : "bodyAnim(p, nrm)"}; vec3 n = nrm;
|
|
3344
|
+
float ph = iA.w*6.2831, gait = iB.z;
|
|
3345
|
+
float style = clipped ? 0. : idField(iD.y, 16., 8.);
|
|
3346
|
+
bool isLeg = bone==4.||bone==5.||bone==9.||bone==10.;
|
|
3347
|
+
bool isThigh = bone==4.||bone==5., isArmTop = bone==2.||bone==3., isFore = bone==7.||bone==8.;
|
|
3348
|
+
vec3 hipJ = ${HIP_PIVOT2};
|
|
3349
|
+
// leg gait styles: the leg swings around its own hip
|
|
3350
|
+
vec3 legHip = isThigh ? pivot : ppivot;
|
|
3351
|
+
if (isLeg) {
|
|
3352
|
+
bool left = bone==4.||bone==9.;
|
|
3353
|
+
if (style==1. && left || style==2. && !left) { mat3 m = rX(-0.32*(0.5 + 0.5*sin(ph))); q = m*(q - legHip) + legHip; n = m*n; }
|
|
3354
|
+
if (style==3. && left) { mat3 m = rX(0.55); q = m*(q - legHip) + legHip; n = m*n; }
|
|
3355
|
+
}
|
|
3356
|
+
if (pose==8. && !clipped && (isArmTop || isFore)) { vec3 sh = isArmTop ? pivot : ppivot; mat3 m = rX(-0.95 + 0.25*sin(uTime*5. + iA.w*3.)); q = m*(q - sh) + sh; n = m*n; }
|
|
3357
|
+
if (!isLeg) {
|
|
3358
|
+
float bend = 0.;
|
|
3359
|
+
float sway = 0.;
|
|
3360
|
+
if (style==4.) bend += 0.22*pow(max(0., sin(ph*0.5)), 2.);
|
|
3361
|
+
if (style==5.) sway += 0.10*sin(ph);
|
|
3362
|
+
if (pose==8. && !clipped) { bend += 0.18 + 0.22*sin(uTime*6. + iA.w*7.); sway += 0.2*sin(uTime*4.1 + iA.w*5.); }
|
|
3363
|
+
if (bend != 0. || sway != 0.) { mat3 m = rX(bend)*rZ(sway); q = m*(q - hipJ) + hipJ; n = m*n; }
|
|
3364
|
+
}
|
|
3365
|
+
float leanAmount = idField(iD.w, 256., 256.) / 255.;
|
|
3366
|
+
if (leanAmount > 0.) {
|
|
3367
|
+
float leanAngle = idField(iD.w, 1., 256.) / 256.*6.2831853;
|
|
3368
|
+
mat3 m = rAxis(vec3(cos(leanAngle), 0., -sin(leanAngle)), leanAmount*1.45);
|
|
3369
|
+
q = m*q; n = m*n;
|
|
3370
|
+
}
|
|
3371
|
+
nrm = n;
|
|
3372
|
+
return q;
|
|
3373
|
+
}
|
|
3374
|
+
`;
|
|
3375
|
+
const full = chunk.replace("CLIP_FUNCTIONS_HERE", CLIP_FUNCTIONS);
|
|
3376
|
+
return hipHeight === 0.95 ? full : full.replaceAll(HIP_PIVOT2, `vec3(0.,${hipHeight},0.)`);
|
|
3377
|
+
};
|
|
3378
|
+
var ACCESSORY_PIVOT_DECLARATION = "attribute vec3 refPivot; vec3 pivot;";
|
|
3379
|
+
var FRAGMENT_EXT = `
|
|
3380
|
+
varying vec4 vWound; varying vec3 vLocal;
|
|
3381
|
+
float woundHash(vec3 p){ return fract(sin(dot(p, vec3(12.9898, 78.233, 37.719))) * 43758.5453); }
|
|
3382
|
+
float woundNoise(vec3 p){ vec3 i = floor(p), f = fract(p); f = f*f*(3. - 2.*f);
|
|
3383
|
+
return mix(mix(mix(woundHash(i), woundHash(i + vec3(1.,0.,0.)), f.x), mix(woundHash(i + vec3(0.,1.,0.)), woundHash(i + vec3(1.,1.,0.)), f.x), f.y),
|
|
3384
|
+
mix(mix(woundHash(i + vec3(0.,0.,1.)), woundHash(i + vec3(1.,0.,1.)), f.x), mix(woundHash(i + vec3(0.,1.,1.)), woundHash(i + vec3(1.,1.,1.)), f.x), f.y), f.z); }
|
|
3385
|
+
vec3 woundTint(vec3 base){
|
|
3386
|
+
if (vWound.x < 0.5) return base;
|
|
3387
|
+
float splash = woundNoise(vLocal*26. + vWound.z*17.);
|
|
3388
|
+
float cover = smoothstep(0.62 - 0.14*vWound.x, 0.82, splash);
|
|
3389
|
+
vec3 blood = mix(vec3(0.16,0.02,0.02), vec3(0.58,0.05,0.04), vWound.y);
|
|
3390
|
+
return mix(base, blood, clamp(cover*(0.45 + 0.3*vWound.x), 0., 0.9));
|
|
3391
|
+
}
|
|
3392
|
+
`;
|
|
3393
|
+
|
|
3394
|
+
// src/instanced/body-material.ts
|
|
3395
|
+
var PIVOT_DECLARATION = "attribute vec3 pivot;";
|
|
3396
|
+
var PANTS_LINE = "vec3 pants = iCol*0.45 + vec3(0.08,0.09,0.12);";
|
|
3397
|
+
var EYE_LINE = "if (mat==3. && vGlow < 0.5) vTint = vec3(0.08);";
|
|
3398
|
+
var VERTEX_INSTANCE_POSITION = "bodyAnim(position, _n)";
|
|
3399
|
+
var createBodyMaterial = (three, { depth = false, distance = false, time, chunk, identity: identity2 = false, accessory = false, segmentClips }) => {
|
|
3400
|
+
const extended = identity2 || accessory;
|
|
3401
|
+
const baseChunk = createBodyChunk(chunk);
|
|
3402
|
+
const bodyChunk = (accessory ? baseChunk.replace(PIVOT_DECLARATION, ACCESSORY_PIVOT_DECLARATION) : baseChunk) + (extended ? createBodyChunkExt({ ...chunk, segmentClips: segmentClips !== void 0 }) : "");
|
|
3403
|
+
const shadowPass = depth || distance;
|
|
3404
|
+
const material = distance ? new three.MeshDistanceMaterial() : depth ? new three.MeshDepthMaterial({ depthPacking: three.RGBADepthPacking }) : new three.MeshLambertMaterial({ color: 16777215 });
|
|
3405
|
+
material.onBeforeCompile = (shader) => {
|
|
3406
|
+
shader.uniforms.uTime = time;
|
|
3407
|
+
if (segmentClips) Object.assign(shader.uniforms, segmentClips);
|
|
3408
|
+
shader.vertexShader = shader.vertexShader.replace("#include <common>", "#include <common>\n" + bodyChunk).replace(
|
|
3409
|
+
"#include <begin_vertex>",
|
|
3410
|
+
`
|
|
3411
|
+
vec3 _n = normal;
|
|
3412
|
+
vec3 transformed = bodyAnim(position, _n);
|
|
3413
|
+
transformed *= vec3(iB.y, iB.x, iB.y);
|
|
3414
|
+
transformed = rY(iA.z)*transformed + vec3(iA.x, floor(iB.w/16.)/1000. - 2., iA.y);
|
|
3415
|
+
// pele: tons brasileiros variados, vai para cinza esverdeado (0x6f7a5c) com a infec\xE7\xE3o
|
|
3416
|
+
vec3 skin = mix(vec3(0.93,0.74,0.6), vec3(0.42,0.27,0.18), iC.w);
|
|
3417
|
+
skin = mix(skin, vec3(0.435,0.478,0.36), iC.y);
|
|
3418
|
+
vec3 pants = iCol*0.45 + vec3(0.08,0.09,0.12);
|
|
3419
|
+
float mat = vcol.w;
|
|
3420
|
+
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);
|
|
3421
|
+
vGlow = mat==3. && dot(iEye, iEye) > 0.01 ? 1. : 0.;
|
|
3422
|
+
if (mat==3. && vGlow < 0.5) vTint = vec3(0.08);
|
|
3423
|
+
`
|
|
3424
|
+
).replace("#include <beginnormal_vertex>", "#include <beginnormal_vertex>\n { vec3 _nn = objectNormal; bodyAnim(position, _nn); objectNormal = rY(iA.z)*_nn; }");
|
|
3425
|
+
if (!shadowPass) {
|
|
3426
|
+
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.);");
|
|
3427
|
+
shader.vertexShader = shader.vertexShader.replace("varying vec3 vTint;", "varying vec3 vTint; varying vec3 vEyeC;").replace("vGlow = mat==3.", "vEyeC = iEye; vGlow = mat==3.");
|
|
3428
|
+
shader.fragmentShader = shader.fragmentShader.replace("varying vec3 vTint;", "varying vec3 vTint; varying vec3 vEyeC;").replace("EYE", "vEyeC");
|
|
3429
|
+
}
|
|
3430
|
+
if (extended) extendShader(shader, { shadowPass, accessory });
|
|
3431
|
+
};
|
|
3432
|
+
if (extended) material.customProgramCacheKey = () => `|body-ext:${accessory ? "a" : "i"}${segmentClips ? "s" : ""}`;
|
|
3433
|
+
return material;
|
|
3434
|
+
};
|
|
3435
|
+
var extendShader = (shader, { shadowPass, accessory }) => {
|
|
3436
|
+
const accessoryHead = accessory ? "pivot = refPivot*iAcc.w + iAcc.xyz; vec3 _p = position*iAcc.w + iAcc.xyz;\n" : "";
|
|
3437
|
+
const source = accessory ? "_p" : "position";
|
|
3438
|
+
shader.vertexShader = shader.vertexShader.replace("#include <common>", accessory ? "#include <common>\nattribute vec4 iAcc;" : "#include <common>").replace(VERTEX_INSTANCE_POSITION, `bodyAnimX(${source}, _n)`).replace("vec3 _n = normal;", `vec3 _n = normal;
|
|
3439
|
+
${accessoryHead}`).replace("{ vec3 _nn = objectNormal; bodyAnim(position, _nn);", `{ ${accessoryHead.replace("vec3 _p", "vec3 _pn")} vec3 _nn = objectNormal; bodyAnimX(${accessory ? "_pn" : "position"}, _nn);`).replace(PANTS_LINE, "vec3 pants = trousersOf(iD.y);").replace(EYE_LINE, `${EYE_LINE}
|
|
3440
|
+
if (mat==6. && (bone==4.||bone==5.||bone==9.||bone==10.) && position.y > ${SHOE_HEIGHT}) vTint = mix(vTint, trousersOf(iD.y), 0.75);`);
|
|
3441
|
+
if (!shadowPass) {
|
|
3442
|
+
shader.vertexShader = shader.vertexShader.replace("vGlow = mat==3.", `vWound = vec4(${accessory ? "0." : "woundLevel()"}, idField(iD.x, 4096., 16.)/15., fract(iCol.r*7.31 + iCol.g*3.17 + iCol.b*5.93 + iD.x*0.0013), 0.); vLocal = position; vGlow = mat==3.`);
|
|
3443
|
+
shader.fragmentShader = shader.fragmentShader.replace("#include <common>", "#include <common>\n" + FRAGMENT_EXT).replace("diffuseColor.rgb = vTint;", "diffuseColor.rgb = woundTint(vTint);");
|
|
3444
|
+
}
|
|
3445
|
+
};
|
|
3446
|
+
|
|
3447
|
+
// src/instanced/body-batch.ts
|
|
3448
|
+
var ATTRIBUTES = [["iA", 4], ["iB", 4], ["iC", 4], ["iCol", 3], ["iEye", 3]];
|
|
3449
|
+
var IDENTITY_ATTRIBUTES = [["iD", 4]];
|
|
3450
|
+
var ACCESSORY_ATTRIBUTES = [["iAcc", 4]];
|
|
3451
|
+
var MIN_HEIGHT = -2;
|
|
3452
|
+
var MAX_HEIGHT = 8;
|
|
3453
|
+
var HEIGHT_STEPS = 1e3;
|
|
3454
|
+
var POSE_SLOTS = 16;
|
|
3455
|
+
var NO_IDENTITY = [0, 0, 0, 0];
|
|
3456
|
+
var NO_ACCESSORY = [0, 0, 0, 1];
|
|
3457
|
+
var PREPASS_RENDER_ORDER = -1;
|
|
3458
|
+
var createPrepass = (three, geometry, params) => {
|
|
3459
|
+
const material = createBodyMaterial(three, { depth: true, ...params });
|
|
3460
|
+
material.colorWrite = false;
|
|
3461
|
+
const mesh = new three.Mesh(geometry, material);
|
|
3462
|
+
mesh.frustumCulled = false;
|
|
3463
|
+
mesh.renderOrder = PREPASS_RENDER_ORDER;
|
|
3464
|
+
return mesh;
|
|
3465
|
+
};
|
|
3466
|
+
var createBodyBatch = (three, geometry, options) => {
|
|
3467
|
+
const { max, castShadow = true, time, accessory = false, segmentClips } = options;
|
|
3468
|
+
const identity2 = options.identity === true || accessory;
|
|
3469
|
+
const layout = [...ATTRIBUTES, ...identity2 ? IDENTITY_ATTRIBUTES : [], ...accessory ? ACCESSORY_ATTRIBUTES : []];
|
|
3470
|
+
const copy = new three.InstancedBufferGeometry().copy(geometry);
|
|
3471
|
+
copy.instanceCount = 0;
|
|
3472
|
+
const attributes = {};
|
|
3473
|
+
for (const [name, size] of layout) {
|
|
3474
|
+
const attribute = new three.InstancedBufferAttribute(new Float32Array(max * size), size);
|
|
3475
|
+
attribute.setUsage(three.DynamicDrawUsage);
|
|
3476
|
+
copy.setAttribute(name, attribute);
|
|
3477
|
+
attributes[name] = attribute;
|
|
3478
|
+
}
|
|
3479
|
+
const mesh = new three.Mesh(copy, createBodyMaterial(three, { time, identity: identity2, accessory, ...segmentClips ? { segmentClips } : {} }));
|
|
3480
|
+
Object.defineProperty(mesh, "count", {
|
|
3481
|
+
get: () => copy.instanceCount,
|
|
3482
|
+
set: (value) => {
|
|
3483
|
+
copy.instanceCount = value;
|
|
3484
|
+
}
|
|
3485
|
+
});
|
|
3486
|
+
mesh.customDepthMaterial = createBodyMaterial(three, { depth: true, time, identity: identity2, accessory, ...segmentClips ? { segmentClips } : {} });
|
|
3487
|
+
if (identity2) mesh.customDistanceMaterial = createBodyMaterial(three, { distance: true, time, identity: identity2, accessory, ...segmentClips ? { segmentClips } : {} });
|
|
3488
|
+
mesh.frustumCulled = false;
|
|
3489
|
+
mesh.castShadow = castShadow;
|
|
3490
|
+
mesh.receiveShadow = true;
|
|
3491
|
+
mesh.count = 0;
|
|
3492
|
+
const prepass = options.depthPrepass ? createPrepass(three, copy, { time, identity: identity2, accessory, ...segmentClips ? { segmentClips } : {} }) : void 0;
|
|
3493
|
+
let count = 0;
|
|
3494
|
+
return {
|
|
3495
|
+
mesh,
|
|
3496
|
+
...prepass ? { prepass } : {},
|
|
3497
|
+
max,
|
|
3498
|
+
get count() {
|
|
3499
|
+
return count;
|
|
3500
|
+
},
|
|
3501
|
+
begin() {
|
|
3502
|
+
count = 0;
|
|
3503
|
+
},
|
|
3504
|
+
push({ a, b, c, color, eye, y = 0, d, acc }) {
|
|
3505
|
+
if (count >= max) return;
|
|
3506
|
+
const index = count++;
|
|
3507
|
+
const fourOffset = index * 4;
|
|
3508
|
+
const threeOffset = index * 3;
|
|
3509
|
+
attributes.iA.array.set(a, fourOffset);
|
|
3510
|
+
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);
|
|
3511
|
+
attributes.iC.array.set(c, fourOffset);
|
|
3512
|
+
attributes.iCol.array.set([color.r, color.g, color.b], threeOffset);
|
|
3513
|
+
attributes.iEye.array.set(eye ? [eye.r, eye.g, eye.b] : [0, 0, 0], threeOffset);
|
|
3514
|
+
if (identity2) attributes.iD.array.set(d ?? NO_IDENTITY, fourOffset);
|
|
3515
|
+
if (accessory) attributes.iAcc.array.set(acc ?? NO_ACCESSORY, fourOffset);
|
|
3516
|
+
},
|
|
3517
|
+
end() {
|
|
3518
|
+
mesh.count = count;
|
|
3519
|
+
if (count === 0) return;
|
|
3520
|
+
for (const attribute of Object.values(attributes)) {
|
|
3521
|
+
attribute.clearUpdateRanges();
|
|
3522
|
+
attribute.addUpdateRange(0, count * attribute.itemSize);
|
|
3523
|
+
attribute.needsUpdate = true;
|
|
3524
|
+
}
|
|
3525
|
+
}
|
|
3526
|
+
};
|
|
3527
|
+
};
|
|
3528
|
+
|
|
3529
|
+
// src/instanced/variants.ts
|
|
3530
|
+
var bakeVariants = (three, builders, types) => {
|
|
3531
|
+
const variants = [];
|
|
3532
|
+
const byType = /* @__PURE__ */ new Map();
|
|
3533
|
+
for (const type of types) {
|
|
3534
|
+
const indexes = [];
|
|
3535
|
+
for (let variant = 0; variant < variantCountFor(type.id); variant++) {
|
|
3536
|
+
const decay = variantDecay(variant);
|
|
3537
|
+
const seed = variantSeed(type.index, variant);
|
|
3538
|
+
const baked = LOD_BAKE.map((lod) => bakeBody(three, builders, { type: type.name, decay, seed }, lod));
|
|
3539
|
+
indexes.push(variants.length);
|
|
3540
|
+
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) });
|
|
3541
|
+
}
|
|
3542
|
+
byType.set(type.id, indexes);
|
|
3543
|
+
}
|
|
3544
|
+
return { variants, byType };
|
|
3545
|
+
};
|
|
3546
|
+
var createVariantBatches = (three, variant, time, options = {}) => variant.geometries.map(
|
|
3547
|
+
(geometry, level) => createBodyBatch(three, geometry, { max: LOD_BATCH_CAPACITY[level], castShadow: (options.castsShadow ?? LOD_CASTS_SHADOW)[level], time, ...options.depthPrepass?.[level] ? { depthPrepass: true } : {}, ...options.identity ? { identity: true } : {}, ...options.segmentClips ? { segmentClips: options.segmentClips } : {} })
|
|
3548
|
+
);
|
|
3549
|
+
|
|
3550
|
+
// src/test-area/crowd.ts
|
|
3551
|
+
var FIELD = { x0: 12, x1: 30, z0: -16, z1: 16 };
|
|
3552
|
+
var STRIDE = 1;
|
|
3553
|
+
var createCrowd = (three, builders, types, eyes, random) => {
|
|
3554
|
+
const group = new three.Group();
|
|
3555
|
+
const time = { value: 0 };
|
|
3556
|
+
let variants = [];
|
|
3557
|
+
let byType = /* @__PURE__ */ new Map();
|
|
3558
|
+
let batches = [];
|
|
3559
|
+
let members = [];
|
|
3560
|
+
let bakeMs = 0;
|
|
3561
|
+
const ensure = () => {
|
|
3562
|
+
if (variants.length > 0) return;
|
|
3563
|
+
const started = typeof performance === "undefined" ? Date.now() : performance.now();
|
|
3564
|
+
const baked = bakeVariants(three, builders, types);
|
|
3565
|
+
bakeMs = (typeof performance === "undefined" ? Date.now() : performance.now()) - started;
|
|
3566
|
+
variants = baked.variants;
|
|
3567
|
+
byType = baked.byType;
|
|
3568
|
+
batches = variants.map((variant) => createVariantBatches(three, variant, time));
|
|
3569
|
+
for (const set of batches) for (const batch of set) group.add(batch.mesh);
|
|
3570
|
+
};
|
|
3571
|
+
const color = (hex2) => new three.Color(hex2);
|
|
3572
|
+
return {
|
|
3573
|
+
group,
|
|
3574
|
+
spawn(count) {
|
|
3575
|
+
ensure();
|
|
3576
|
+
for (let i = 0; i < count; i++) {
|
|
3577
|
+
const type = types[Math.floor(random() * types.length)];
|
|
3578
|
+
const pool = byType.get(type.id);
|
|
3579
|
+
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 });
|
|
3580
|
+
}
|
|
3581
|
+
},
|
|
3582
|
+
clear() {
|
|
3583
|
+
members = [];
|
|
3584
|
+
for (const set of batches) for (const batch of set) {
|
|
3585
|
+
batch.begin();
|
|
3586
|
+
batch.end();
|
|
3587
|
+
}
|
|
3588
|
+
},
|
|
3589
|
+
update(dt, camera) {
|
|
3590
|
+
time.value += dt;
|
|
3591
|
+
if (variants.length === 0) return;
|
|
3592
|
+
for (const set of batches) for (const batch of set) batch.begin();
|
|
3593
|
+
const pose = encodePoseHeight("walk", 0);
|
|
3594
|
+
for (const member of members) {
|
|
3595
|
+
member.x += Math.sin(member.yaw) * member.speed * dt;
|
|
3596
|
+
member.z += Math.cos(member.yaw) * member.speed * dt;
|
|
3597
|
+
if (member.x < FIELD.x0 || member.x > FIELD.x1) member.yaw = -member.yaw;
|
|
3598
|
+
if (member.z < FIELD.z0 || member.z > FIELD.z1) member.yaw = Math.PI - member.yaw;
|
|
3599
|
+
const variant = variants[member.variant];
|
|
3600
|
+
const distance = Math.hypot(member.x - camera.position.x, member.z - camera.position.z);
|
|
3601
|
+
const level = lodAt(distance, LOD_DISTANCES);
|
|
3602
|
+
const eye = color(eyes[variant.typeId] ?? 14926657);
|
|
3603
|
+
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 });
|
|
3604
|
+
}
|
|
3605
|
+
for (const set of batches) for (const batch of set) batch.end();
|
|
3606
|
+
},
|
|
3607
|
+
get count() {
|
|
3608
|
+
return members.length;
|
|
3609
|
+
},
|
|
3610
|
+
get bakeMs() {
|
|
3611
|
+
return bakeMs;
|
|
3612
|
+
},
|
|
3613
|
+
get drawCalls() {
|
|
3614
|
+
return batches.reduce((sum, set) => sum + set.filter((batch) => batch.count > 0).length, 0);
|
|
3615
|
+
},
|
|
3616
|
+
get triangles() {
|
|
3617
|
+
return batches.reduce((sum, set, index) => sum + set.reduce((inner, batch, level) => inner + batch.count * ((variants[index]?.verts[level] ?? 0) / 3), 0), 0);
|
|
3618
|
+
},
|
|
3619
|
+
trianglesPerLevel(typeId) {
|
|
3620
|
+
const first = byType.get(typeId)?.[0];
|
|
3621
|
+
return first === void 0 ? [] : variants[first].verts.map((verts) => verts / 3);
|
|
3622
|
+
}
|
|
3623
|
+
};
|
|
3624
|
+
};
|
|
3625
|
+
|
|
3626
|
+
// src/test-area/hits.ts
|
|
3627
|
+
var HIT_PRESETS = [
|
|
3628
|
+
{ id: "9x19", hit: { type: "ballistic", energy: 500, mass: 7.5, caliber: 9, caliberId: "9x19" } },
|
|
3629
|
+
{ id: "12ga_3", hit: { type: "ballistic", energy: 3e3, mass: 32, caliber: 8.4, pellets: 9, distance: 3, caliberId: "12ga_00buck" } },
|
|
3630
|
+
{ id: "7_62x39", hit: { type: "ballistic", energy: 2100, mass: 7.9, caliber: 7.62, caliberId: "7.62x39" } },
|
|
3631
|
+
{ id: "308", hit: { type: "ballistic", energy: 3504, mass: 9.7, caliber: 7.8, caliberId: "308" } },
|
|
3632
|
+
{ id: "5_56x45", hit: { type: "ballistic", energy: 1800, mass: 4, caliber: 5.56, caliberId: "5.56x45" } },
|
|
3633
|
+
{ id: "machete", hit: { type: "slash", energy: 120 } },
|
|
3634
|
+
{ id: "axe", hit: { type: "slash", energy: 220 } },
|
|
3635
|
+
{ id: "m67_1", hit: { type: "explosive", energy: 6e3, radius: 15 }, range: 1 },
|
|
3636
|
+
{ id: "m67_3", hit: { type: "explosive", energy: 6e3, radius: 15 }, range: 3 },
|
|
3637
|
+
{ id: "m67_6", hit: { type: "explosive", energy: 6e3, radius: 15 }, range: 6 }
|
|
3638
|
+
];
|
|
3639
|
+
var hitPresetOf = (id) => HIT_PRESETS.find((preset2) => preset2.id === id) ?? HIT_PRESETS[0];
|
|
3640
|
+
var hitFor = (preset2, origin, y, known) => {
|
|
3641
|
+
const { caliberId, ...rest } = preset2.hit;
|
|
3642
|
+
const withId = caliberId !== void 0 && known.has(caliberId) ? { ...rest, caliberId } : rest;
|
|
3643
|
+
if (preset2.hit.type === "explosive") return { ...withId, point: [origin[0] + (preset2.range ?? 3), origin[1] + 0.2, origin[2]] };
|
|
3644
|
+
return { ...withId, point: [origin[0], origin[1] + y, origin[2] + 0.4], direction: [0, 0, -1] };
|
|
3645
|
+
};
|
|
3646
|
+
|
|
3647
|
+
// src/test-area/person-spec.ts
|
|
3648
|
+
var hex = (value) => parseInt(value, 16);
|
|
3649
|
+
var personSpecOf = (profession) => {
|
|
3650
|
+
const { look } = profession;
|
|
3651
|
+
const ex = profession.accessories.map((accessory) => accessory.color ? [accessory.id, hex(accessory.color)] : [accessory.id]);
|
|
3652
|
+
return {
|
|
3653
|
+
skin: hex(look.skin),
|
|
3654
|
+
hair: { s: look.hair.style, c: hex(look.hair.color) },
|
|
3655
|
+
top: { c: hex(look.top.color), sl: look.top.sleeve, ...look.top.plaid ? { plaid: hex(look.top.plaid) } : {} },
|
|
3656
|
+
pants: { c: hex(look.pants.color) },
|
|
3657
|
+
shoes: hex(look.shoes),
|
|
3658
|
+
ex,
|
|
3659
|
+
...profession.female ? { fem: 1 } : {},
|
|
3660
|
+
...look.hat ? { hat: { k: look.hat.kind, c: hex(look.hat.color) } } : {},
|
|
3661
|
+
...look.face && look.face.length > 0 ? { face: [...look.face] } : {},
|
|
3662
|
+
...profession.hand ? { hand: profession.hand } : {},
|
|
3663
|
+
...profession.leftHand ? { lhand: profession.leftHand } : {}
|
|
3664
|
+
};
|
|
3665
|
+
};
|
|
3666
|
+
|
|
3667
|
+
// src/test-area/knock.ts
|
|
3668
|
+
import { bodyMass, KNOCK_CHEST, KNOCK_DOWN_SPEED, KNOCKED_DOWN_ANGLE, knockOf, stepKnock } from "@adatechnology/game-contracts";
|
|
3669
|
+
|
|
3670
|
+
// src/test-area/scenarios.ts
|
|
3671
|
+
import { SEGMENT_IDS } from "@adatechnology/game-characters";
|
|
3672
|
+
var LIMB_SEGMENTS = ["upperArmL", "forearmL", "handL", "upperArmR", "forearmR", "handR", "thighL", "shinL", "footL", "thighR", "shinR", "footR"];
|
|
3673
|
+
var BENCHMARK_BODIES = 1e3;
|
|
3674
|
+
var preset = (id) => HIT_PRESETS.find((entry) => entry.id === id);
|
|
3675
|
+
var bodyChanges = (events) => events.flatMap((event) => event.type === "body" ? [`${event.change}${event.part ? `:${event.part}` : ""}`] : []);
|
|
3676
|
+
var createScenarios = (deps) => {
|
|
3677
|
+
const person = (extra = {}, seed = 1) => deps.kit().create({ seed, kind: "person", ...extra });
|
|
3678
|
+
const shoot = (character, id, part) => {
|
|
3679
|
+
character.hit({ ...hitFor(preset(id), [0, 0, 0], 1.3, deps.kit().calibers), part });
|
|
3680
|
+
};
|
|
3681
|
+
const scenario = (id, run) => ({ id, name: deps.text[id] ?? id, run });
|
|
3682
|
+
return [
|
|
3683
|
+
scenario("region_x_caliber", ({ expect }) => {
|
|
3684
|
+
for (const ammo of ["9x19", "308"]) {
|
|
3685
|
+
for (const part of ["head", "torso", "upperArmL", "thighL"]) {
|
|
3686
|
+
const character = person();
|
|
3687
|
+
shoot(character, ammo, part);
|
|
3688
|
+
expect((character.lastHit?.points ?? 0) > 0, `${ammo} on ${part} does damage`, { points: character.lastHit?.points });
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3691
|
+
const head = person();
|
|
3692
|
+
shoot(head, "308", "head");
|
|
3693
|
+
expect(!head.alive, "a .308 to the head kills");
|
|
3694
|
+
}),
|
|
3695
|
+
scenario("cut_at_every_joint", ({ expect }) => {
|
|
3696
|
+
for (const part of LIMB_SEGMENTS) {
|
|
3697
|
+
const character = person();
|
|
3698
|
+
character.hit({ type: "slash", point: [0, 1, 0], part, points: 100 });
|
|
3699
|
+
const segment = character.segments.find((entry) => entry.id === part);
|
|
3700
|
+
expect(segment?.status === "severed" || segment?.status === "destroyed", `${part} is cut off`, { status: segment?.status });
|
|
3701
|
+
}
|
|
3702
|
+
expect(LIMB_SEGMENTS.every((part) => SEGMENT_IDS.includes(part)), "every joint is a segment of the body");
|
|
3703
|
+
}),
|
|
3704
|
+
scenario("broken_bone_dangles", ({ expect }) => {
|
|
3705
|
+
const character = person();
|
|
3706
|
+
const changes = [];
|
|
3707
|
+
for (let blow = 0; blow < 3; blow++) changes.push(...bodyChanges(character.hit({ type: "blunt", point: [0, 1.2, 0], part: "forearmL", points: 20 })));
|
|
3708
|
+
expect(changes.some((change) => change.startsWith("broken")), "three blows break the forearm", { changes });
|
|
3709
|
+
expect(character.segments.find((entry) => entry.id === "forearmL")?.attached === true, "the broken forearm stays attached (it dangles)");
|
|
3710
|
+
}),
|
|
3711
|
+
scenario("leg_limp_crawl", ({ expect }) => {
|
|
3712
|
+
const character = person();
|
|
3713
|
+
expect(bodyChanges(character.hit({ type: "blunt", point: [0, 0.5, 0], part: "thighL", points: 40 })).includes("limp"), "a hurt leg limps");
|
|
3714
|
+
expect(bodyChanges(character.hit({ type: "slash", point: [0, 0.5, 0], part: "thighL", points: 40 })).includes("crawl"), "a lost thigh crawls");
|
|
3715
|
+
}),
|
|
3716
|
+
scenario("head_kill", ({ expect }) => {
|
|
3717
|
+
const character = person();
|
|
3718
|
+
shoot(character, "9x19", "head");
|
|
3719
|
+
expect(!character.alive, "a 9x19 to the head kills");
|
|
3720
|
+
const torso = person();
|
|
3721
|
+
shoot(torso, "9x19", "torso");
|
|
3722
|
+
expect(torso.alive, "a 9x19 to the torso does not");
|
|
3723
|
+
}),
|
|
3724
|
+
scenario("grenade_by_radius", ({ expect }) => {
|
|
3725
|
+
const lost = (id) => {
|
|
3726
|
+
const character = person();
|
|
3727
|
+
character.hit(hitFor(preset(id), [0, 0, 0], 1, deps.kit().calibers));
|
|
3728
|
+
return character.segments.reduce((sum, segment) => sum + (segment.max - segment.hp), 0);
|
|
3729
|
+
};
|
|
3730
|
+
const [near, middle, far] = [lost("m67_1"), lost("m67_3"), lost("m67_6")];
|
|
3731
|
+
expect(near > middle && middle >= far, "the nearer the blast, the more it takes", { near, middle, far });
|
|
3732
|
+
}),
|
|
3733
|
+
scenario("penetration_line", ({ expect }) => {
|
|
3734
|
+
const kit = deps.kit();
|
|
3735
|
+
const targets = Array.from({ length: 5 }, (_, index) => ({ character: person({}, index + 1), part: "torso", distance: 5 + index * 1.2 }));
|
|
3736
|
+
const result = kit.penetrateLine({ energyAt: () => 3504, mass: 9.7, caliber: 7.8, caliberId: "308" }, targets);
|
|
3737
|
+
expect(result.hits.length >= 1, "the bullet hits the first body", { hits: result.hits.length });
|
|
3738
|
+
const energies = result.hits.map((hit) => hit.energy);
|
|
3739
|
+
expect(energies.every((energy, index) => index === 0 || energy <= energies[index - 1]), "it keeps less energy at each body", { energies });
|
|
3740
|
+
}),
|
|
3741
|
+
scenario("age_0_3", ({ expect }) => {
|
|
3742
|
+
const THREE = deps.three();
|
|
3743
|
+
if (!THREE) return expect(false, "the area is not attached to a room");
|
|
3744
|
+
deps.ensureBuilders(THREE);
|
|
3745
|
+
const colors = (decay) => {
|
|
3746
|
+
const root = deps.buildZombie(deps.typeNames()[0], decay);
|
|
3747
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3748
|
+
root.traverse((object) => {
|
|
3749
|
+
const material = object.material;
|
|
3750
|
+
if (material?.color) seen.add(material.color.getHexString());
|
|
3751
|
+
});
|
|
3752
|
+
return [...seen].sort().join(",");
|
|
3753
|
+
};
|
|
3754
|
+
const fresh = colors(0);
|
|
3755
|
+
expect(fresh.length > 0, "a fresh body is built");
|
|
3756
|
+
expect(colors(3) !== fresh, "a decrepit body looks different from a fresh one");
|
|
3757
|
+
}),
|
|
3758
|
+
scenario("vest_and_helmet", ({ expect }) => {
|
|
3759
|
+
const bare = person();
|
|
3760
|
+
const dressed = person({ protection: ["vest_police", "helmet_combat"] });
|
|
3761
|
+
shoot(bare, "9x19", "torso");
|
|
3762
|
+
shoot(dressed, "9x19", "torso");
|
|
3763
|
+
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 });
|
|
3764
|
+
shoot(dressed, "9x19", "head");
|
|
3765
|
+
expect(dressed.alive, "the helmet stops a 9x19 to the head", { absorbedBy: dressed.lastHit?.absorbedBy });
|
|
3766
|
+
}),
|
|
3767
|
+
scenario("bite_to_turn", ({ expect }) => {
|
|
3768
|
+
const undead = deps.undead();
|
|
3769
|
+
if (!undead?.infect) return expect(false, "no undead pack in this area");
|
|
3770
|
+
for (const age of ["adult", "child"]) {
|
|
3771
|
+
const victim = person({ age }, 9);
|
|
3772
|
+
const infection = undead.infect("bite", () => 0);
|
|
3773
|
+
expect(infection !== void 0, `a bite infects (${age})`);
|
|
3774
|
+
if (!infection) continue;
|
|
3775
|
+
infection.advance(1e3);
|
|
3776
|
+
expect(infection.reanimated, `the case ends in reanimation (${age})`);
|
|
3777
|
+
const turned = undead.turn(victim);
|
|
3778
|
+
expect(turned.alive && undead.traitsOf(turned) !== void 0, `the ${age} turns into a zombie`, { type: undead.traitsOf(turned)?.type });
|
|
3779
|
+
}
|
|
3780
|
+
}),
|
|
3781
|
+
scenario("benchmark_1000", ({ expect }) => {
|
|
3782
|
+
const THREE = deps.three();
|
|
3783
|
+
if (!THREE) return expect(false, "the area is not attached to a room");
|
|
3784
|
+
const crowd = deps.crowd();
|
|
3785
|
+
crowd.spawn(BENCHMARK_BODIES);
|
|
3786
|
+
expect(crowd.count >= BENCHMARK_BODIES, `${BENCHMARK_BODIES} bodies are in the field`, { count: crowd.count });
|
|
3787
|
+
const levels = crowd.trianglesPerLevel(deps.undead() ? "walker" : "");
|
|
3788
|
+
levels.forEach((triangles, level) => {
|
|
3789
|
+
const budget = deps.triangleBudget[level];
|
|
3790
|
+
if (level === levels.length - 1 && deps.triangleBudgetSoftFar) expect(true, `level ${level} body has ${Math.round(triangles)} triangles (budget ${budget})`, { triangles, budget, over: triangles > budget });
|
|
3791
|
+
else expect(triangles <= budget, `level ${level} body is inside ${budget} triangles`, { triangles });
|
|
3792
|
+
});
|
|
3793
|
+
crowd.clear();
|
|
3794
|
+
})
|
|
3795
|
+
];
|
|
3796
|
+
};
|
|
3797
|
+
|
|
3798
|
+
// src/test-area/text.ts
|
|
3799
|
+
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" };
|
|
3800
|
+
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" };
|
|
3801
|
+
var TEXT = {
|
|
3802
|
+
en: {
|
|
3803
|
+
group: "Characters",
|
|
3804
|
+
name: "Characters area",
|
|
3805
|
+
bodies: {},
|
|
3806
|
+
child: "Child",
|
|
3807
|
+
elder: "Elder",
|
|
3808
|
+
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" },
|
|
3809
|
+
hints: {
|
|
3810
|
+
decay: "0 fresh .. 3 decrepit: skin, blood and cloth of bodies built from now on",
|
|
3811
|
+
penetration: "physical: P = sqrt(E m / A). team-game: the points of the game. Applies to bodies created from now on",
|
|
3812
|
+
death_rule: "vitality-or-head (the game) or head-only (the golden rule of the pages)",
|
|
3813
|
+
detail: "segments: the 17 parts of the near path. regions: the 6 regions of the crowd",
|
|
3814
|
+
moment: "The speed of the selected zombie at that moment, in the readout",
|
|
3815
|
+
hit_preset: "What the Fire hit button does to the body you look at",
|
|
3816
|
+
protection: "Worn by the bodies created from now on"
|
|
3817
|
+
},
|
|
3818
|
+
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", voice: "Make the aimed zombie speak (groan, by the moment)", special_call: "Make the aimed zombie use its call (scream, leap)" },
|
|
3819
|
+
options: {
|
|
3820
|
+
penetration: { physical: "Physical", "team-game": "Team game" },
|
|
3821
|
+
death_rule: { "vitality-or-head": "Vitality or head", "head-only": "Head only" },
|
|
3822
|
+
detail: { segments: "Segments", regions: "Regions" },
|
|
3823
|
+
moment: { wander: "Wander", alert: "Alert", chase: "Chase", sprint: "Sprint", frenzy: "Frenzy", limp: "Limping", crawl: "Crawling" },
|
|
3824
|
+
pose: { walk: "Walk", stand: "Stand" },
|
|
3825
|
+
protection: { none: "None", vest_police: "Police vest", vest_military: "Military vest", helmet_combat: "Helmet", turnout_gear: "Firefighter gear", vest_helmet: "Vest and helmet" },
|
|
3826
|
+
path: { rich: "Rich", lod0: "Instanced LOD 0", lod1: "Instanced LOD 1", lod2: "Instanced LOD 2" },
|
|
3827
|
+
age: { child: "Child", young: "Young", adult: "Adult", elder: "Elder" },
|
|
3828
|
+
hit_preset: PRESETS_EN
|
|
3829
|
+
},
|
|
3830
|
+
layers: { capsules: "Hit capsules", joints: "Joints", bleed: "Bleed emitters", eyes_only: "Eyes only", speed: "Speed vectors" },
|
|
3831
|
+
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" },
|
|
3832
|
+
scenarios: {
|
|
3833
|
+
region_x_caliber: "Region x calibre",
|
|
3834
|
+
cut_at_every_joint: "A cut at every joint",
|
|
3835
|
+
broken_bone_dangles: "A broken bone dangles",
|
|
3836
|
+
leg_limp_crawl: "Leg: limp, then crawl",
|
|
3837
|
+
head_kill: "Head kills",
|
|
3838
|
+
grenade_by_radius: "Grenade by radius",
|
|
3839
|
+
penetration_line: "Penetration through a line of 5",
|
|
3840
|
+
age_0_3: "Decay 0 to 3",
|
|
3841
|
+
vest_and_helmet: "Vest and helmet",
|
|
3842
|
+
bite_to_turn: "Bite, fever, delirium, turn (child too)",
|
|
3843
|
+
benchmark_1000: "1000 bodies against the triangle budget"
|
|
3844
|
+
},
|
|
3845
|
+
log: { crossed: "Hit: {bodies} bodies crossed, {energy} of the energy kept", turned: "{from} turned into {to}" }
|
|
3846
|
+
},
|
|
3847
|
+
"pt-BR": {
|
|
3848
|
+
group: "Personagens",
|
|
3849
|
+
name: "\xC1rea de personagens",
|
|
3850
|
+
bodies: {},
|
|
3851
|
+
child: "Crian\xE7a",
|
|
3852
|
+
elder: "Idoso",
|
|
3853
|
+
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" },
|
|
3854
|
+
hints: {
|
|
3855
|
+
decay: "0 fresco .. 3 decr\xE9pito: pele, sangue e roupa dos corpos feitos daqui em diante",
|
|
3856
|
+
penetration: "physical: P = raiz(E m / A). team-game: os pontos do jogo. Vale para os corpos criados daqui em diante",
|
|
3857
|
+
death_rule: "vitality-or-head (o jogo) ou head-only (a regra de ouro das p\xE1ginas)",
|
|
3858
|
+
detail: "segments: as 17 partes do caminho de perto. regions: as 6 regi\xF5es da multid\xE3o",
|
|
3859
|
+
moment: "A velocidade do zumbi selecionado nesse momento, no mostrador",
|
|
3860
|
+
hit_preset: "O que o bot\xE3o Disparar faz no corpo para o qual voc\xEA olha",
|
|
3861
|
+
protection: "Vestida pelos corpos criados daqui em diante"
|
|
3862
|
+
},
|
|
3863
|
+
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", voice: "Fazer o zumbi mirado falar (gemido, pelo momento)", special_call: "Fazer o zumbi mirado usar o chamado (grito, salto)" },
|
|
3864
|
+
options: {
|
|
3865
|
+
penetration: { physical: "F\xEDsica", "team-game": "Do jogo" },
|
|
3866
|
+
death_rule: { "vitality-or-head": "Vitalidade ou cabe\xE7a", "head-only": "S\xF3 a cabe\xE7a" },
|
|
3867
|
+
detail: { segments: "Segmentos", regions: "Regi\xF5es" },
|
|
3868
|
+
moment: { wander: "Vagando", alert: "Alerta", chase: "Persegui\xE7\xE3o", sprint: "Disparada", frenzy: "Frenesi", limp: "Mancando", crawl: "Rastejando" },
|
|
3869
|
+
pose: { walk: "Andar", stand: "Parado" },
|
|
3870
|
+
protection: { none: "Nenhuma", vest_police: "Colete policial", vest_military: "Colete militar", helmet_combat: "Capacete", turnout_gear: "Roupa de bombeiro", vest_helmet: "Colete e capacete" },
|
|
3871
|
+
path: { rich: "Rico", lod0: "Instanciado LOD 0", lod1: "Instanciado LOD 1", lod2: "Instanciado LOD 2" },
|
|
3872
|
+
age: { child: "Crian\xE7a", young: "Jovem", adult: "Adulto", elder: "Idoso" },
|
|
3873
|
+
hit_preset: PRESETS_PT
|
|
3874
|
+
},
|
|
3875
|
+
layers: { capsules: "C\xE1psulas de acerto", joints: "Juntas", bleed: "Pontos de sangramento", eyes_only: "S\xF3 os olhos", speed: "Vetores de velocidade" },
|
|
3876
|
+
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" },
|
|
3877
|
+
scenarios: {
|
|
3878
|
+
region_x_caliber: "Regi\xE3o x calibre",
|
|
3879
|
+
cut_at_every_joint: "Um corte em cada junta",
|
|
3880
|
+
broken_bone_dangles: "Osso quebrado balan\xE7a",
|
|
3881
|
+
leg_limp_crawl: "Perna: manca, depois rasteja",
|
|
3882
|
+
head_kill: "A cabe\xE7a mata",
|
|
3883
|
+
grenade_by_radius: "Granada por raio",
|
|
3884
|
+
penetration_line: "Penetra\xE7\xE3o em fila de 5",
|
|
3885
|
+
age_0_3: "Decomposi\xE7\xE3o de 0 a 3",
|
|
3886
|
+
vest_and_helmet: "Colete e capacete",
|
|
3887
|
+
bite_to_turn: "Mordida, febre, del\xEDrio, vira (crian\xE7a tamb\xE9m)",
|
|
3888
|
+
benchmark_1000: "1000 corpos contra o or\xE7amento de tri\xE2ngulos"
|
|
3889
|
+
},
|
|
3890
|
+
log: { crossed: "Golpe: {bodies} corpos atravessados, {energy} da energia ficou", turned: "{from} virou {to}" }
|
|
3891
|
+
}
|
|
3892
|
+
};
|
|
3893
|
+
|
|
3894
|
+
// src/test-area/module.ts
|
|
3895
|
+
var CHARACTERS_AREA_ID = "characters_area";
|
|
3896
|
+
var ZOMBIE_ITEM_PREFIX = "zombie_";
|
|
3897
|
+
var PERSON_ITEM_PREFIX = "person_";
|
|
3898
|
+
var LANE_ITEM_COUNT = 5;
|
|
3899
|
+
var GAIT_MOMENT_STRIDE = 1.4;
|
|
3900
|
+
var DEFAULT_TRIANGLE_BUDGET = [2500, 800, 250];
|
|
3901
|
+
var BATCH_BUDGET_MS = 1;
|
|
3902
|
+
var MOMENTS_WITH_LIMP = /* @__PURE__ */ new Set(["limp", "crawl"]);
|
|
3903
|
+
function createCharactersTestModule(options = {}) {
|
|
3904
|
+
const locale = options.locale ?? "pt-BR";
|
|
3905
|
+
const text = TEXT[locale] ?? TEXT["pt-BR"];
|
|
3906
|
+
const undeadTypes = options.undeadTypes ?? [];
|
|
3907
|
+
const professions = options.professions ?? [];
|
|
3908
|
+
const settings = { decay: 0, age: "adult", penetration: "physical", deathRule: "vitality-or-head", detail: "segments", moment: "wander", pose: "walk", cascade: true, hit: "9x19", protection: "none" };
|
|
3909
|
+
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 } });
|
|
3910
|
+
let kit = makeKit();
|
|
3911
|
+
let undead = options.createUndead ? options.createUndead(kit) : null;
|
|
3912
|
+
let stream = createRng3(options.seed ?? 1);
|
|
3913
|
+
const random = () => stream();
|
|
3914
|
+
let seedCounter = 100;
|
|
3915
|
+
let host = null;
|
|
3916
|
+
let three = null;
|
|
3917
|
+
let best = null;
|
|
3918
|
+
let people = null;
|
|
3919
|
+
let wounds = null;
|
|
3920
|
+
let sound = null;
|
|
3921
|
+
let crowd = null;
|
|
3922
|
+
let selected = null;
|
|
3923
|
+
let time = 0;
|
|
3924
|
+
let lastCrossed = 0;
|
|
3925
|
+
let lastEnergy = 1;
|
|
3926
|
+
const entries = /* @__PURE__ */ new Map();
|
|
3927
|
+
const builtOf = /* @__PURE__ */ new WeakMap();
|
|
3928
|
+
const overlays = /* @__PURE__ */ new Map();
|
|
3929
|
+
const gaits = Object.fromEntries(undeadTypes.map((card) => [card.name["pt-BR"] ?? card.id, card.gait]));
|
|
3930
|
+
const animator = createRigAnimator({ gaits });
|
|
3931
|
+
const typeName = (card) => card.name["pt-BR"] ?? card.id;
|
|
3932
|
+
const eyeOf = (card) => parseInt(card.eye.color, 16);
|
|
3933
|
+
const ensureBuilders = (THREE) => {
|
|
3934
|
+
three = THREE;
|
|
3935
|
+
best ?? (best = createBestBuilder(THREE, { types: undeadTypes.map((card) => ({ name: typeName(card), eye: eyeOf(card) })), random }));
|
|
3936
|
+
people ?? (people = createPersonBuilder(THREE, {
|
|
3937
|
+
random,
|
|
3938
|
+
createCanvas: () => document.createElement("canvas"),
|
|
3939
|
+
// read at each build: the host may attach after the first one
|
|
3940
|
+
get lights() {
|
|
3941
|
+
return isLightBudget(host?.lights) ? host.lights : void 0;
|
|
3942
|
+
}
|
|
3943
|
+
}));
|
|
3944
|
+
};
|
|
3945
|
+
const nextSeed = () => seedCounter += 1;
|
|
3946
|
+
const protectionList = () => settings.protection === "none" ? [] : settings.protection === "vest_helmet" ? ["vest_police", "helmet_combat"] : [settings.protection];
|
|
3947
|
+
const createCharacter = (kind, cardId, id) => {
|
|
3948
|
+
const seed = nextSeed();
|
|
3949
|
+
const common = { seed, id, age: settings.age, protection: protectionList(), detail: settings.detail };
|
|
3950
|
+
if (kind === "zombie" && undead) return undead.create(common, { type: cardId });
|
|
3951
|
+
return kit.create({ ...common, kind: kind === "zombie" ? cardId : "person", ...kind === "person" ? { preset: cardId } : {} });
|
|
3952
|
+
};
|
|
3953
|
+
const trackEntry = (entry) => {
|
|
3954
|
+
if (entry.body) wounds?.track(entry.body);
|
|
3955
|
+
};
|
|
3956
|
+
const wrap = (entry, character) => {
|
|
3957
|
+
const syncTransform = () => {
|
|
3958
|
+
const root = entry.root;
|
|
3959
|
+
character.setTransform([root.position.x, root.position.y, root.position.z], [root.quaternion.x, root.quaternion.y, root.quaternion.z, root.quaternion.w]);
|
|
3960
|
+
};
|
|
3961
|
+
const draw = (events) => {
|
|
3962
|
+
if (entry.body) for (const event of events) wounds?.handle(entry.body, event);
|
|
3963
|
+
};
|
|
3964
|
+
return Object.create(character, {
|
|
3965
|
+
hit: {
|
|
3966
|
+
value: (hit) => {
|
|
3967
|
+
syncTransform();
|
|
3968
|
+
selected = entry;
|
|
3969
|
+
const events = character.hit(hit);
|
|
3970
|
+
draw(events);
|
|
3971
|
+
const report = character.lastHit;
|
|
3972
|
+
lastCrossed = report?.through ? 2 : 1;
|
|
3973
|
+
lastEnergy = report && "exitEnergy" in report ? report.exitEnergy : lastEnergy;
|
|
3974
|
+
return events;
|
|
3975
|
+
}
|
|
3976
|
+
},
|
|
3977
|
+
update: {
|
|
3978
|
+
value: (dt) => {
|
|
3979
|
+
syncTransform();
|
|
3980
|
+
const events = character.update(dt);
|
|
3981
|
+
draw(events);
|
|
3982
|
+
return events;
|
|
3983
|
+
}
|
|
3984
|
+
},
|
|
3985
|
+
reset: {
|
|
3986
|
+
value: () => {
|
|
3987
|
+
character.reset();
|
|
3988
|
+
entry.root.visible = true;
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
});
|
|
3992
|
+
};
|
|
3993
|
+
const makeItem = (kind, cardId, name) => ({
|
|
3994
|
+
id: `${kind === "zombie" ? ZOMBIE_ITEM_PREFIX : PERSON_ITEM_PREFIX}${cardId}`,
|
|
3995
|
+
name,
|
|
3996
|
+
movable: false,
|
|
3997
|
+
build: (THREE) => {
|
|
3998
|
+
ensureBuilders(THREE);
|
|
3999
|
+
const seed = nextSeed();
|
|
4000
|
+
if (kind === "zombie") {
|
|
4001
|
+
const card = undeadTypes.find((entry) => entry.id === cardId);
|
|
4002
|
+
const root = best.build({ type: typeName(card), seed, decay: settings.decay });
|
|
4003
|
+
builtOf.set(root, { kind, cardId, built: root });
|
|
4004
|
+
return root;
|
|
4005
|
+
}
|
|
4006
|
+
const profession = professions.find((entry) => entry.id === cardId);
|
|
4007
|
+
const built = people.build(profession ? personSpecOf(profession) : { skin: 12882032, hair: { s: "short", c: 2236962 }, top: { c: 4478310, sl: "long" }, pants: { c: 3355443 }, shoes: 1118481 });
|
|
4008
|
+
if (!built.root.userData.rig) built.root.userData.rig = built;
|
|
4009
|
+
builtOf.set(built.root, { kind, cardId, built });
|
|
4010
|
+
return built.root;
|
|
4011
|
+
},
|
|
4012
|
+
impl: (root, id) => {
|
|
4013
|
+
const object = root;
|
|
4014
|
+
const made = builtOf.get(object);
|
|
4015
|
+
const character = createCharacter(made.kind, made.cardId, id);
|
|
4016
|
+
const scale = character.spec.height / kit.body.heightMetres;
|
|
4017
|
+
if (Math.abs(scale - 1) > 0.01) object.scale.setScalar(scale);
|
|
4018
|
+
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 };
|
|
4019
|
+
entry.character = wrap(entry, character);
|
|
4020
|
+
const age = made.kind === "zombie" ? undead?.traitsOf(character)?.age : void 0;
|
|
4021
|
+
const blood = age !== void 0 ? options.bloodOf?.(age) : void 0;
|
|
4022
|
+
if (entry.body && blood) entry.body.blood = blood;
|
|
4023
|
+
if (three) entry.batcher = createStaticBatcher(three, object, { followMeshes: true, budgetMs: BATCH_BUDGET_MS });
|
|
4024
|
+
entries.set(id, entry);
|
|
4025
|
+
selected ?? (selected = entry);
|
|
4026
|
+
trackEntry(entry);
|
|
4027
|
+
return entry.character;
|
|
4028
|
+
}
|
|
4029
|
+
});
|
|
4030
|
+
const speedOf = (entry) => {
|
|
4031
|
+
const traits = undead?.traitsOf(entry.character);
|
|
4032
|
+
if (!traits || !undead) return entry.kind === "person" ? 1.4 : 0;
|
|
4033
|
+
const moment = settings.moment;
|
|
4034
|
+
return undead.speedFor(traits, { moment: MOMENTS_WITH_LIMP.has(moment) ? "chase" : moment, limp: moment === "limp", crawling: moment === "crawl" || entry.character.limping.length > 1 });
|
|
4035
|
+
};
|
|
4036
|
+
const clearOverlays = () => {
|
|
4037
|
+
for (const object of overlays.values()) object.parent?.remove(object);
|
|
4038
|
+
overlays.clear();
|
|
4039
|
+
};
|
|
4040
|
+
const overlayFor = (layer, entry) => {
|
|
4041
|
+
if (!three) return null;
|
|
4042
|
+
const group = new three.Group();
|
|
4043
|
+
const material = new three.MeshBasicMaterial({ color: layer === "capsules" ? 4513279 : layer === "joints" ? 16772676 : 16729190, wireframe: layer === "capsules", depthTest: false, transparent: true, opacity: 0.8 });
|
|
4044
|
+
if (layer === "capsules") {
|
|
4045
|
+
for (const part of entry.character.describe().parts) {
|
|
4046
|
+
const shape = part.shape;
|
|
4047
|
+
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);
|
|
4048
|
+
const mesh = new three.Mesh(geometry, material);
|
|
4049
|
+
mesh.position.set(...part.position);
|
|
4050
|
+
if (part.rotation) mesh.rotation.set(...part.rotation);
|
|
4051
|
+
group.add(mesh);
|
|
4052
|
+
}
|
|
4053
|
+
} else if (layer === "joints") {
|
|
4054
|
+
for (const joint of Object.values(entry.character.rig.joints)) {
|
|
4055
|
+
const mesh = new three.Mesh(new three.SphereGeometry(0.025, 6, 4), material);
|
|
4056
|
+
mesh.position.set(...joint);
|
|
4057
|
+
group.add(mesh);
|
|
4058
|
+
}
|
|
4059
|
+
} else if (layer === "speed") {
|
|
4060
|
+
const length = Math.max(0.1, speedOf(entry));
|
|
4061
|
+
const arrow = new three.ArrowHelper(new three.Vector3(0, 0, 1), new three.Vector3(0, 2.1, 0), length, 6750054);
|
|
4062
|
+
group.add(arrow);
|
|
4063
|
+
} else return null;
|
|
4064
|
+
group.renderOrder = 10;
|
|
4065
|
+
return group;
|
|
4066
|
+
};
|
|
4067
|
+
const refreshLayers = () => {
|
|
4068
|
+
clearOverlays();
|
|
4069
|
+
if (!host?.bench) return;
|
|
4070
|
+
for (const layer of ["capsules", "joints", "speed"]) {
|
|
4071
|
+
if (!host.bench.layer(layer)) continue;
|
|
4072
|
+
for (const entry of entries.values()) {
|
|
4073
|
+
const overlay = overlayFor(layer, entry);
|
|
4074
|
+
if (!overlay) continue;
|
|
4075
|
+
entry.root.add(overlay);
|
|
4076
|
+
overlays.set(`${layer}:${entry.id}`, overlay);
|
|
4077
|
+
}
|
|
4078
|
+
}
|
|
4079
|
+
const eyesOnly = host.bench.layer("eyes_only");
|
|
4080
|
+
for (const entry of entries.values()) entry.eyes?.isolate(eyesOnly);
|
|
4081
|
+
};
|
|
4082
|
+
const animate = (entry, dt) => {
|
|
4083
|
+
const { root, character } = entry;
|
|
4084
|
+
if (entry.knock) {
|
|
4085
|
+
const rig = root.userData.rig;
|
|
4086
|
+
const ground = (x, z) => host?.groundAt(x, z) ?? 0;
|
|
4087
|
+
const done = stepKnock(entry.knock, root.position, dt, ground, character.alive);
|
|
4088
|
+
if (done) entry.knock = void 0;
|
|
4089
|
+
if (character.alive && rig) rig.fig.rotation.x = -(entry.knock?.lying ?? 0) * KNOCKED_DOWN_ANGLE;
|
|
4090
|
+
if (entry.knock && entry.knock.lying > 0 && character.alive) {
|
|
4091
|
+
entry.eyes?.off();
|
|
4092
|
+
return;
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
4095
|
+
if (!character.alive) {
|
|
4096
|
+
entry.tilt = Math.min(1, entry.tilt + dt * 2);
|
|
4097
|
+
const rig = root.userData.rig;
|
|
4098
|
+
if (rig) rig.fig.rotation.x = -entry.tilt * 1.35;
|
|
4099
|
+
entry.eyes?.off();
|
|
4100
|
+
return;
|
|
4101
|
+
}
|
|
4102
|
+
const speed = settings.pose === "walk" ? speedOf(entry) : 0;
|
|
4103
|
+
entry.walk += dt * speed;
|
|
4104
|
+
if (entry.kind === "person") people.pose(entry.built, false, settings.pose === "walk" ? 1 : 0, time);
|
|
4105
|
+
else if (settings.pose === "walk") {
|
|
4106
|
+
animator.animate(root, time, { speed: speed || GAIT_MOMENT_STRIDE });
|
|
4107
|
+
if (settings.cascade) animator.cascade(root.userData.rig, dt);
|
|
4108
|
+
}
|
|
4109
|
+
if (entry.body) {
|
|
4110
|
+
wounds?.setWalkPhase(entry.body, settings.pose === "walk" ? entry.walk : void 0);
|
|
4111
|
+
wounds?.applyLimp(entry.body);
|
|
4112
|
+
}
|
|
4113
|
+
};
|
|
4114
|
+
const selectedOrFirst = () => selected ?? entries.values().next().value ?? null;
|
|
4115
|
+
const fireOn = (entry) => {
|
|
4116
|
+
const preset2 = hitPresetOf(settings.hit);
|
|
4117
|
+
const p2 = entry.root.position;
|
|
4118
|
+
entry.character.hit(hitFor(preset2, [p2.x, p2.y, p2.z], 1.3, kit.calibers));
|
|
4119
|
+
};
|
|
4120
|
+
const ensureCrowd = () => {
|
|
4121
|
+
if (crowd) return crowd;
|
|
4122
|
+
const built = three;
|
|
4123
|
+
const builders = {
|
|
4124
|
+
buildBest: (spec, lod) => {
|
|
4125
|
+
;
|
|
4126
|
+
best.setLOD(lod);
|
|
4127
|
+
return best.build(spec);
|
|
4128
|
+
},
|
|
4129
|
+
topMaterial: () => best.topM()
|
|
4130
|
+
};
|
|
4131
|
+
const wanted = options.crowdTypes ?? ["walker", "runner"];
|
|
4132
|
+
const variantTypes = undeadTypes.map((card, index) => ({ id: card.id, name: typeName(card), index })).filter((type) => wanted.includes(type.id));
|
|
4133
|
+
crowd = createCrowd(built, builders, variantTypes, Object.fromEntries(undeadTypes.map((card) => [card.id, eyeOf(card)])), random);
|
|
4134
|
+
if (host) host.scene.add(crowd.group);
|
|
4135
|
+
return crowd;
|
|
4136
|
+
};
|
|
4137
|
+
const onChange = (change) => {
|
|
4138
|
+
if (change.kind === "layer") {
|
|
4139
|
+
refreshLayers();
|
|
4140
|
+
return;
|
|
4141
|
+
}
|
|
4142
|
+
if (change.kind === "action") {
|
|
4143
|
+
const target = selectedOrFirst();
|
|
4144
|
+
if (change.id === "heal") target?.character.reset();
|
|
4145
|
+
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 });
|
|
4146
|
+
else if (change.id === "infect" && target && undead?.infect) {
|
|
4147
|
+
const infection = undead.infect("bite", () => 0);
|
|
4148
|
+
if (infection) {
|
|
4149
|
+
infection.advance(1e3);
|
|
4150
|
+
if (infection.reanimated) target.character = wrap(target, undead.turn(target.character));
|
|
4151
|
+
}
|
|
4152
|
+
} else if (change.id === "fire_preset" && target) fireOn(target);
|
|
4153
|
+
else if ((change.id === "voice" || change.id === "special_call") && sound) {
|
|
4154
|
+
const speaker = target?.kind === "zombie" ? target : [...entries.values()].find((entry) => entry.kind === "zombie");
|
|
4155
|
+
if (speaker) {
|
|
4156
|
+
const at = speaker.root.getWorldPosition(new host.THREE.Vector3());
|
|
4157
|
+
const hurried = settings.moment === "alert" || settings.moment === "chase" || settings.moment === "sprint";
|
|
4158
|
+
sound.voice(speaker.cardId, change.id === "special_call" ? "special" : hurried ? "alert" : "idle", [at.x, at.y + 1.5, at.z]);
|
|
4159
|
+
}
|
|
4160
|
+
} else if (change.id === "spawn_100" || change.id === "spawn_1000") {
|
|
4161
|
+
ensureBuilders(host?.THREE ?? three);
|
|
4162
|
+
ensureCrowd().spawn(change.id === "spawn_100" ? 100 : 1e3);
|
|
4163
|
+
}
|
|
4164
|
+
return;
|
|
4165
|
+
}
|
|
4166
|
+
const value = change.value;
|
|
4167
|
+
switch (change.id) {
|
|
4168
|
+
case "decay":
|
|
4169
|
+
settings.decay = Number(value);
|
|
4170
|
+
break;
|
|
4171
|
+
case "age":
|
|
4172
|
+
settings.age = String(value);
|
|
4173
|
+
break;
|
|
4174
|
+
case "penetration":
|
|
4175
|
+
settings.penetration = String(value);
|
|
4176
|
+
kit = makeKit();
|
|
4177
|
+
undead = options.createUndead ? options.createUndead(kit) : null;
|
|
4178
|
+
break;
|
|
4179
|
+
case "death_rule":
|
|
4180
|
+
settings.deathRule = String(value);
|
|
4181
|
+
kit = makeKit();
|
|
4182
|
+
undead = options.createUndead ? options.createUndead(kit) : null;
|
|
4183
|
+
break;
|
|
4184
|
+
case "detail":
|
|
4185
|
+
settings.detail = String(value);
|
|
4186
|
+
break;
|
|
4187
|
+
case "moment":
|
|
4188
|
+
settings.moment = String(value);
|
|
4189
|
+
refreshLayers();
|
|
4190
|
+
break;
|
|
4191
|
+
case "pose":
|
|
4192
|
+
settings.pose = String(value);
|
|
4193
|
+
break;
|
|
4194
|
+
case "cascade":
|
|
4195
|
+
settings.cascade = Boolean(value);
|
|
4196
|
+
break;
|
|
4197
|
+
case "hit_preset":
|
|
4198
|
+
settings.hit = String(value);
|
|
4199
|
+
break;
|
|
4200
|
+
case "protection":
|
|
4201
|
+
settings.protection = String(value);
|
|
4202
|
+
break;
|
|
4203
|
+
default:
|
|
4204
|
+
break;
|
|
4205
|
+
}
|
|
4206
|
+
};
|
|
4207
|
+
const choice = (id, ids, fallback) => ({
|
|
4208
|
+
kind: "choice",
|
|
4209
|
+
id,
|
|
4210
|
+
name: text.controls[id],
|
|
4211
|
+
options: ids.map((optionId) => ({ id: optionId, name: text.options[id][optionId] ?? optionId })),
|
|
4212
|
+
default: fallback
|
|
4213
|
+
});
|
|
4214
|
+
const withHint = (control, hint) => hint ? { ...control, hint } : control;
|
|
4215
|
+
const scenarioDeps = {
|
|
4216
|
+
text: text.scenarios,
|
|
4217
|
+
kit: () => kit,
|
|
4218
|
+
undead: () => undead,
|
|
4219
|
+
three: () => three ?? host?.THREE,
|
|
4220
|
+
ensureBuilders,
|
|
4221
|
+
buildZombie: (name, decay) => best.build({ type: name, seed: 7, decay }),
|
|
4222
|
+
typeNames: () => undeadTypes.map(typeName),
|
|
4223
|
+
crowd: () => {
|
|
4224
|
+
ensureBuilders(three ?? host?.THREE);
|
|
4225
|
+
return ensureCrowd();
|
|
4226
|
+
},
|
|
4227
|
+
random,
|
|
4228
|
+
triangleBudget: options.triangleBudget ?? DEFAULT_TRIANGLE_BUDGET,
|
|
4229
|
+
triangleBudgetSoftFar: options.triangleBudget === void 0
|
|
4230
|
+
};
|
|
4231
|
+
const lane = () => professions.slice(0, LANE_ITEM_COUNT).map((profession) => `${PERSON_ITEM_PREFIX}${profession.id}`);
|
|
4232
|
+
return {
|
|
4233
|
+
id: CHARACTERS_AREA_ID,
|
|
4234
|
+
name: text.name,
|
|
4235
|
+
ground: { surface: "concrete_slab" },
|
|
4236
|
+
group: text.group,
|
|
4237
|
+
get entries() {
|
|
4238
|
+
return entries;
|
|
4239
|
+
},
|
|
4240
|
+
get crowd() {
|
|
4241
|
+
return crowd;
|
|
4242
|
+
},
|
|
4243
|
+
items: () => [
|
|
4244
|
+
...undeadTypes.map((card) => makeItem("zombie", card.id, card.name[locale] ?? card.id)),
|
|
4245
|
+
...professions.map((profession) => makeItem("person", profession.id, profession.name[locale] ?? profession.id))
|
|
4246
|
+
],
|
|
4247
|
+
area: () => {
|
|
4248
|
+
const placements = [];
|
|
4249
|
+
undeadTypes.forEach((card, index) => placements.push({ item: `${ZOMBIE_ITEM_PREFIX}${card.id}`, position: [-6 + index * 1.1, 0, 3], rotationY: Math.PI }));
|
|
4250
|
+
professions.forEach((profession, index) => placements.push({ item: `${PERSON_ITEM_PREFIX}${profession.id}`, position: [-5.45 + index * 1.1, 0, 7], rotationY: Math.PI }));
|
|
4251
|
+
lane().forEach((item, index) => placements.push({ item, position: [0, 0, -3 - index * 1.4], rotationY: 0 }));
|
|
4252
|
+
return { placements, view: { eye: [0, 5.5, 13], target: [0, 0.6, 3] } };
|
|
4253
|
+
},
|
|
4254
|
+
controls: () => [
|
|
4255
|
+
withHint({ kind: "number", id: "decay", name: text.controls.decay, min: 0, max: 3, step: 1, default: 0 }, text.hints.decay),
|
|
4256
|
+
choice("age", ["child", "young", "adult", "elder"], "adult"),
|
|
4257
|
+
withHint(choice("penetration", ["physical", "team-game"], "physical"), text.hints.penetration),
|
|
4258
|
+
withHint(choice("death_rule", ["vitality-or-head", "head-only"], "vitality-or-head"), text.hints.death_rule),
|
|
4259
|
+
withHint(choice("detail", ["segments", "regions"], "segments"), text.hints.detail),
|
|
4260
|
+
withHint(choice("moment", ["wander", "alert", "chase", "sprint", "frenzy", "limp", "crawl"], "wander"), text.hints.moment),
|
|
4261
|
+
choice("pose", ["walk", "stand"], "walk"),
|
|
4262
|
+
{ kind: "boolean", id: "cascade", name: text.controls.cascade, default: true },
|
|
4263
|
+
withHint(choice("hit_preset", HIT_PRESETS.map((preset2) => preset2.id), "9x19"), text.hints.hit_preset),
|
|
4264
|
+
withHint(choice("protection", ["none", "vest_police", "vest_military", "helmet_combat", "turnout_gear", "vest_helmet"], "none"), text.hints.protection),
|
|
4265
|
+
{ kind: "action", id: "fire_preset", name: text.actions.fire_preset },
|
|
4266
|
+
{ kind: "action", id: "voice", name: text.actions.voice },
|
|
4267
|
+
{ kind: "action", id: "special_call", name: text.actions.special_call },
|
|
4268
|
+
{ kind: "action", id: "heal", name: text.actions.heal },
|
|
4269
|
+
{ kind: "action", id: "kill", name: text.actions.kill },
|
|
4270
|
+
{ kind: "action", id: "infect", name: text.actions.infect },
|
|
4271
|
+
{ kind: "action", id: "spawn_100", name: text.actions.spawn_100 },
|
|
4272
|
+
{ kind: "action", id: "spawn_1000", name: text.actions.spawn_1000 }
|
|
4273
|
+
],
|
|
4274
|
+
layers: () => ["capsules", "joints", "bleed", "eyes_only", "speed"].map((id) => ({ id, name: text.layers[id], default: false })),
|
|
4275
|
+
metrics: () => [
|
|
4276
|
+
{ id: "bodies", name: text.metrics.bodies, read: () => entries.size },
|
|
4277
|
+
{ id: "instances", name: text.metrics.instances, read: () => crowd?.count ?? 0 },
|
|
4278
|
+
{ id: "triangles", name: text.metrics.triangles, max: 25e4, read: () => Math.round(crowd?.triangles ?? 0) },
|
|
4279
|
+
{ id: "draw_calls", name: text.metrics.draw_calls, read: () => crowd?.drawCalls ?? 0 },
|
|
4280
|
+
{ id: "bake_ms", name: text.metrics.bake_ms, unit: "ms", digits: 0, read: () => crowd?.bakeMs ?? 0 },
|
|
4281
|
+
{ id: "speed_now", name: text.metrics.speed, unit: "m/s", digits: 2, read: () => {
|
|
4282
|
+
const entry = selectedOrFirst();
|
|
4283
|
+
return entry ? speedOf(entry) : 0;
|
|
4284
|
+
} },
|
|
4285
|
+
{ id: "vitality", name: text.metrics.vitality, read: () => selectedOrFirst()?.character.state().body?.vitality ?? 0 },
|
|
4286
|
+
{ id: "bleed_rate", name: text.metrics.bleed, digits: 3, read: () => selectedOrFirst()?.character.bleeders().reduce((sum, bleeder) => sum + (bleeder.rate ?? 0), 0) ?? 0 },
|
|
4287
|
+
{ id: "segments_hp", name: text.metrics.segments_hp, read: () => Math.round(selectedOrFirst()?.character.segments.reduce((sum, segment) => sum + segment.hp, 0) ?? 0) },
|
|
4288
|
+
{ id: "last_crossed", name: text.metrics.last_crossed, read: () => lastCrossed },
|
|
4289
|
+
{ id: "last_energy", name: text.metrics.last_energy, digits: 2, read: () => lastEnergy }
|
|
4290
|
+
],
|
|
4291
|
+
scenarios: () => createScenarios(scenarioDeps),
|
|
4292
|
+
attach(lent) {
|
|
4293
|
+
host = lent;
|
|
4294
|
+
ensureBuilders(lent.THREE);
|
|
4295
|
+
const scene2 = lent.scene;
|
|
4296
|
+
const bridge = options.fluids ? createFluidsBridge({ sink: options.fluids, ...options.film ? { film: options.film } : {} }) : void 0;
|
|
4297
|
+
sound = lent.audio && options.sound !== false ? createCharacterSound(lent.audio, options.sound ?? {}) : null;
|
|
4298
|
+
wounds = createWounds(lent.THREE, { random, scene: scene2, groundAt: (x, z) => lent.groundAt(x, z), ...bridge ? { bridge } : {}, ...sound ? { sound } : {} });
|
|
4299
|
+
for (const entry of entries.values()) trackEntry(entry);
|
|
4300
|
+
const stopChange = lent.bench?.onChange(onChange);
|
|
4301
|
+
const stopReset = lent.onReset?.(() => {
|
|
4302
|
+
stream = createRng3(options.seed ?? 1);
|
|
4303
|
+
seedCounter = 100;
|
|
4304
|
+
time = 0;
|
|
4305
|
+
selected = null;
|
|
4306
|
+
lastCrossed = 0;
|
|
4307
|
+
lastEnergy = 1;
|
|
4308
|
+
});
|
|
4309
|
+
const stopEvents = lent.on((event) => {
|
|
4310
|
+
if (event.type !== "explosion") return;
|
|
4311
|
+
for (const entry of entries.values()) {
|
|
4312
|
+
if (!entry.root.parent) continue;
|
|
4313
|
+
const p2 = entry.root.position;
|
|
4314
|
+
const reach = lent.blastReach?.(event.point, [p2.x, p2.y + KNOCK_CHEST, p2.z], [event.source, entry.id]) ?? 1;
|
|
4315
|
+
const knock = knockOf({ ...event, reach }, [p2.x, p2.y, p2.z], bodyMass(entry.character.spec.height, entry.character.spec.build), lent.blastShove);
|
|
4316
|
+
if (knock) entry.knock = entry.knock ? { ...knock, lying: entry.knock.lying } : knock;
|
|
4317
|
+
}
|
|
4318
|
+
});
|
|
4319
|
+
const stopFrame = lent.onFrame((dt) => {
|
|
4320
|
+
time += dt;
|
|
4321
|
+
for (const [id, entry] of entries) {
|
|
4322
|
+
if (!entry.root.parent) {
|
|
4323
|
+
entry.batcher?.dispose();
|
|
4324
|
+
if (entry.body) wounds?.untrack(entry.body);
|
|
4325
|
+
if (selected === entry) selected = null;
|
|
4326
|
+
entries.delete(id);
|
|
4327
|
+
continue;
|
|
4328
|
+
}
|
|
4329
|
+
animate(entry, dt);
|
|
4330
|
+
entry.batcher?.update();
|
|
4331
|
+
}
|
|
4332
|
+
wounds?.update(dt);
|
|
4333
|
+
sound?.update(dt);
|
|
4334
|
+
crowd?.update(dt, lent.camera);
|
|
4335
|
+
});
|
|
4336
|
+
return () => {
|
|
4337
|
+
stopChange?.();
|
|
4338
|
+
stopReset?.();
|
|
4339
|
+
stopEvents();
|
|
4340
|
+
stopFrame();
|
|
4341
|
+
clearOverlays();
|
|
4342
|
+
wounds?.dispose();
|
|
4343
|
+
wounds = null;
|
|
4344
|
+
sound?.dispose();
|
|
4345
|
+
sound = null;
|
|
4346
|
+
if (crowd) {
|
|
4347
|
+
crowd.group.parent?.remove(crowd.group);
|
|
4348
|
+
crowd = null;
|
|
4349
|
+
}
|
|
4350
|
+
host = null;
|
|
4351
|
+
};
|
|
4352
|
+
}
|
|
4353
|
+
};
|
|
4354
|
+
}
|
|
4355
|
+
|
|
4356
|
+
// src/test-area/material.ts
|
|
4357
|
+
var BODY_MATERIAL_DEF = {
|
|
4358
|
+
id: "flesh",
|
|
4359
|
+
density: 1050,
|
|
4360
|
+
hpPerKg: 15,
|
|
4361
|
+
penetration: 1.2,
|
|
4362
|
+
ductility: 0.8,
|
|
4363
|
+
fracture: "tear",
|
|
4364
|
+
fragmentSize: [0.03, 0.1],
|
|
4365
|
+
damageScale: { slash: 1.6 },
|
|
4366
|
+
hitDebris: "drips",
|
|
4367
|
+
breakDebris: "drips",
|
|
4368
|
+
thermal: { ignitionC: 260, flameC: 700, fuelFraction: 0.25, burnRate: 4e-3, conductivity: 0.5, heatCapacity: 3.5, smoke: "grey" },
|
|
4369
|
+
tags: ["organic", "body"],
|
|
4370
|
+
compressive: 1e3
|
|
4371
|
+
};
|
|
4372
|
+
var registerBodyMaterial = (world) => {
|
|
4373
|
+
if (!world.materials.has(BODY_MATERIAL_DEF.id)) world.materials.set(BODY_MATERIAL_DEF.id, BODY_MATERIAL_DEF);
|
|
4374
|
+
};
|
|
4375
|
+
|
|
4376
|
+
// src/test-area/avatar-module.ts
|
|
4377
|
+
import { POSE_CLIPS, POSE_CLIP_LOCALES } from "@adatechnology/game-characters";
|
|
4378
|
+
|
|
4379
|
+
// src/test-area/avatar-text.ts
|
|
4380
|
+
var AVATAR_TEXT = {
|
|
4381
|
+
en: {
|
|
4382
|
+
group: "Avatar",
|
|
4383
|
+
name: "Player body",
|
|
4384
|
+
actions: {
|
|
4385
|
+
camera_first: "Camera: 1st person",
|
|
4386
|
+
camera_third: "Camera: 3rd person",
|
|
4387
|
+
crouch: "Crouch",
|
|
4388
|
+
stand: "Stand up",
|
|
4389
|
+
fall: "Fall down",
|
|
4390
|
+
get_up: "Get up",
|
|
4391
|
+
armed_on: "Armed: on",
|
|
4392
|
+
armed_off: "Armed: off",
|
|
4393
|
+
stop_clip: "Stop clip"
|
|
4394
|
+
},
|
|
4395
|
+
play: "Play",
|
|
4396
|
+
metrics: { camera: "Camera", clip: "Clip playing", last_event: "Last clip event", state: "State", joints: "Joints" },
|
|
4397
|
+
none: "none"
|
|
4398
|
+
},
|
|
4399
|
+
"pt-BR": {
|
|
4400
|
+
group: "Avatar",
|
|
4401
|
+
name: "Corpo do jogador",
|
|
4402
|
+
actions: {
|
|
4403
|
+
camera_first: "C\xE2mera: 1\xAA pessoa",
|
|
4404
|
+
camera_third: "C\xE2mera: 3\xAA pessoa",
|
|
4405
|
+
crouch: "Agachar",
|
|
4406
|
+
stand: "Levantar",
|
|
4407
|
+
fall: "Cair",
|
|
4408
|
+
get_up: "Levantar do ch\xE3o",
|
|
4409
|
+
armed_on: "Armado: ligado",
|
|
4410
|
+
armed_off: "Armado: desligado",
|
|
4411
|
+
stop_clip: "Parar clipe"
|
|
4412
|
+
},
|
|
4413
|
+
play: "Tocar",
|
|
4414
|
+
metrics: { camera: "C\xE2mera", clip: "Clipe tocando", last_event: "\xDAltimo evento do clipe", state: "Estado", joints: "Juntas" },
|
|
4415
|
+
none: "nenhum"
|
|
4416
|
+
}
|
|
4417
|
+
};
|
|
4418
|
+
|
|
4419
|
+
// src/test-area/avatar-module.ts
|
|
4420
|
+
var AVATAR_AREA_ID = "avatar_area";
|
|
4421
|
+
var round = (value) => (Math.round(value * 100) / 100).toString();
|
|
4422
|
+
function createAvatarTestModule(options) {
|
|
4423
|
+
const clips = options.clips ?? POSE_CLIPS;
|
|
4424
|
+
const textOf = (locale) => AVATAR_TEXT[locale ?? options.locale ?? "pt-BR"] ?? AVATAR_TEXT["pt-BR"];
|
|
4425
|
+
const phaseLabel = (label, locale) => {
|
|
4426
|
+
const id = locale ?? options.locale ?? "pt-BR";
|
|
4427
|
+
const table = { ...POSE_CLIP_LOCALES[id] ?? POSE_CLIP_LOCALES["pt-BR"], ...options.clipLocales?.[id] ?? options.clipLocales?.["pt-BR"] };
|
|
4428
|
+
return typeof label === "string" ? table[label] ?? label : "";
|
|
4429
|
+
};
|
|
4430
|
+
const state = { crouch: false, down: false, armed: null };
|
|
4431
|
+
let host = null;
|
|
4432
|
+
let lastEvent = "";
|
|
4433
|
+
const log = (action, extra = {}) => {
|
|
4434
|
+
const entry = { type: "avatar", action, ...extra };
|
|
4435
|
+
host?.log(JSON.stringify(entry), entry);
|
|
4436
|
+
};
|
|
4437
|
+
const push = (action, patch, extra = {}) => {
|
|
4438
|
+
Object.assign(state, patch);
|
|
4439
|
+
options.state?.set(patch);
|
|
4440
|
+
log(action, { crouch: state.crouch, down: state.down, armed: state.armed, ...extra });
|
|
4441
|
+
};
|
|
4442
|
+
const setCamera = (mode) => {
|
|
4443
|
+
options.camera?.set(mode);
|
|
4444
|
+
log(`camera_${mode}`, { mode: options.camera?.mode() ?? null });
|
|
4445
|
+
};
|
|
4446
|
+
const onChange = (change) => {
|
|
4447
|
+
if (change.kind !== "action") return;
|
|
4448
|
+
switch (change.id) {
|
|
4449
|
+
case "camera_first":
|
|
4450
|
+
return setCamera("first");
|
|
4451
|
+
case "camera_third":
|
|
4452
|
+
return setCamera("third");
|
|
4453
|
+
case "crouch":
|
|
4454
|
+
return push("crouch", { crouch: true });
|
|
4455
|
+
case "stand":
|
|
4456
|
+
return push("stand", { crouch: false });
|
|
4457
|
+
case "fall":
|
|
4458
|
+
return push("fall", { down: true });
|
|
4459
|
+
case "get_up":
|
|
4460
|
+
return push("get_up", { down: false });
|
|
4461
|
+
case "armed_on":
|
|
4462
|
+
return push("armed_on", { armed: true });
|
|
4463
|
+
case "armed_off":
|
|
4464
|
+
return push("armed_off", { armed: false });
|
|
4465
|
+
case "stop_clip":
|
|
4466
|
+
options.avatar.stop();
|
|
4467
|
+
return log("stop_clip", { playing: options.avatar.playing });
|
|
4468
|
+
default: {
|
|
4469
|
+
const clip = clips.find((item) => `play_${item.id}` === change.id);
|
|
4470
|
+
if (!clip) return;
|
|
4471
|
+
options.avatar.play(clip);
|
|
4472
|
+
log("play_clip", { clip: clip.id, duration: clip.duration, loop: clip.loop ?? false, channels: clip.channels ?? "full" });
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4475
|
+
};
|
|
4476
|
+
const joints = () => {
|
|
4477
|
+
const rig = options.avatar.rig;
|
|
4478
|
+
const [armA, armB] = rig.arms;
|
|
4479
|
+
const [legA, legB] = rig.legs;
|
|
4480
|
+
return `arms ${round(armA.g.rotation.x)}/${round(armB.g.rotation.x)} elbows ${round(armA.el.rotation.x)}/${round(armB.el.rotation.x)} legs ${round(legA.g.rotation.x)}/${round(legB.g.rotation.x)} knees ${round(legA.kn.rotation.x)}/${round(legB.kn.rotation.x)} lean ${round(rig.upper.rotation.x)}`;
|
|
4481
|
+
};
|
|
4482
|
+
return {
|
|
4483
|
+
id: AVATAR_AREA_ID,
|
|
4484
|
+
name: textOf().name,
|
|
4485
|
+
ground: { surface: "concrete_slab" },
|
|
4486
|
+
group: textOf().group,
|
|
4487
|
+
state,
|
|
4488
|
+
items: () => [],
|
|
4489
|
+
area: () => ({ placements: [], view: { eye: [0, 1.7, 6], target: [0, 1.2, 0] } }),
|
|
4490
|
+
controls: (locale) => {
|
|
4491
|
+
const text = textOf(locale);
|
|
4492
|
+
const action = (id, name, hint) => ({ kind: "action", id, name, ...hint ? { hint } : {} });
|
|
4493
|
+
return [
|
|
4494
|
+
action("camera_first", text.actions.camera_first),
|
|
4495
|
+
action("camera_third", text.actions.camera_third),
|
|
4496
|
+
action("crouch", text.actions.crouch),
|
|
4497
|
+
action("stand", text.actions.stand),
|
|
4498
|
+
action("fall", text.actions.fall),
|
|
4499
|
+
action("get_up", text.actions.get_up),
|
|
4500
|
+
action("armed_on", text.actions.armed_on),
|
|
4501
|
+
action("armed_off", text.actions.armed_off),
|
|
4502
|
+
...clips.map((clip) => action(`play_${clip.id}`, `${text.play}: ${clip.id}`)),
|
|
4503
|
+
action("stop_clip", text.actions.stop_clip)
|
|
4504
|
+
];
|
|
4505
|
+
},
|
|
4506
|
+
metrics: (locale) => {
|
|
4507
|
+
const text = textOf(locale);
|
|
4508
|
+
return [
|
|
4509
|
+
{ id: "camera", name: text.metrics.camera, read: () => options.camera?.mode() ?? text.none },
|
|
4510
|
+
{ id: "clip", name: text.metrics.clip, read: () => options.avatar.playing ?? text.none },
|
|
4511
|
+
{ id: "last_event", name: text.metrics.last_event, read: () => lastEvent || text.none },
|
|
4512
|
+
{ id: "state", name: text.metrics.state, read: () => `${state.crouch ? "crouch " : ""}${state.down ? "down " : ""}${state.armed === null ? "" : state.armed ? "armed" : "unarmed"}`.trim() || text.none },
|
|
4513
|
+
{ id: "joints", name: text.metrics.joints, read: joints }
|
|
4514
|
+
];
|
|
4515
|
+
},
|
|
4516
|
+
attach(lent) {
|
|
4517
|
+
host = lent;
|
|
4518
|
+
const stopChange = lent.bench?.onChange(onChange);
|
|
4519
|
+
const stopEvents = options.avatar.on("event", (event) => {
|
|
4520
|
+
lastEvent = `${event.clip} ${event.type} ${round(event.t)}s${event.data && "label" in event.data ? ` ${phaseLabel(event.data.label)}` : ""}`;
|
|
4521
|
+
log("clip_event", { clip: event.clip, event: event.type, t: event.t, data: event.data ?? null });
|
|
4522
|
+
if (event.type === "impact") log("impact", { clip: event.clip, t: event.t, kind: event.data?.kind ?? null });
|
|
4523
|
+
});
|
|
4524
|
+
return () => {
|
|
4525
|
+
stopChange?.();
|
|
4526
|
+
stopEvents();
|
|
4527
|
+
host = null;
|
|
4528
|
+
};
|
|
4529
|
+
}
|
|
4530
|
+
};
|
|
4531
|
+
}
|
|
4532
|
+
|
|
4533
|
+
export {
|
|
4534
|
+
BODY_CHUNK,
|
|
4535
|
+
createBodyChunk,
|
|
4536
|
+
walkPoseVertex,
|
|
4537
|
+
IMPOSTOR_ANGLES,
|
|
4538
|
+
IMPOSTOR_FRAMES,
|
|
4539
|
+
IMPOSTOR_BONE_SCALE,
|
|
4540
|
+
IMPOSTOR_MATERIAL_SCALE,
|
|
4541
|
+
bakeImpostorAtlas,
|
|
4542
|
+
GAIT_STYLE,
|
|
4543
|
+
ACCESSORY_BIT,
|
|
4544
|
+
STAGGER_POSE,
|
|
4545
|
+
TROUSER_PALETTE,
|
|
4546
|
+
CLIPS_BIT,
|
|
4547
|
+
packIdentity,
|
|
4548
|
+
unpackIdentity,
|
|
4549
|
+
clipSpeedOf,
|
|
4550
|
+
glslColor,
|
|
4551
|
+
createBodyChunkExt,
|
|
4552
|
+
createBodyMaterial,
|
|
4553
|
+
createBodyBatch,
|
|
4554
|
+
EXTRAS,
|
|
4555
|
+
HATS,
|
|
4556
|
+
createPersonKit,
|
|
4557
|
+
LOD_SEGMENTS,
|
|
4558
|
+
LOD_BAKE,
|
|
4559
|
+
LOD_DISTANCES,
|
|
4560
|
+
LOD_BATCH_CAPACITY,
|
|
4561
|
+
LOD_CASTS_SHADOW,
|
|
4562
|
+
lodAt,
|
|
4563
|
+
BAKE_NEUTRAL_POSE,
|
|
4564
|
+
bakeBody,
|
|
4565
|
+
bakeVariants,
|
|
4566
|
+
createVariantBatches,
|
|
4567
|
+
createRigAnimator,
|
|
4568
|
+
createBestBuilder,
|
|
4569
|
+
FACE_FEATURES,
|
|
4570
|
+
buildHand,
|
|
4571
|
+
HAND_ITEMS,
|
|
4572
|
+
createPersonBuilder,
|
|
4573
|
+
createGlowTexture,
|
|
4574
|
+
createEye,
|
|
4575
|
+
findEyes,
|
|
4576
|
+
PENDULUM,
|
|
4577
|
+
createPendulum,
|
|
4578
|
+
stepPendulum,
|
|
4579
|
+
blobRadii,
|
|
4580
|
+
blobGeometry,
|
|
4581
|
+
createWoundMaterials,
|
|
4582
|
+
makeCut,
|
|
4583
|
+
makeStump,
|
|
4584
|
+
ringWound,
|
|
4585
|
+
createFlyingPieces,
|
|
4586
|
+
starGeometry,
|
|
4587
|
+
woundDecal,
|
|
4588
|
+
exitWound,
|
|
4589
|
+
gouge,
|
|
4590
|
+
explodeChunks,
|
|
4591
|
+
woundBodyOf,
|
|
4592
|
+
createWounds,
|
|
4593
|
+
bloodLookOfAge,
|
|
4594
|
+
createFluidsBridge,
|
|
4595
|
+
UNDEAD_VOICE_GROUP,
|
|
4596
|
+
CHARACTER_SOUNDS,
|
|
4597
|
+
CHARACTER_SOUND_BANK,
|
|
4598
|
+
UNDEAD_VOICES,
|
|
4599
|
+
UNDEAD_VOICE_SOURCES,
|
|
4600
|
+
BODY_SOUNDS,
|
|
4601
|
+
PLAYER_SOUNDS,
|
|
4602
|
+
createCharacterSound,
|
|
4603
|
+
HIT_PRESETS,
|
|
4604
|
+
personSpecOf,
|
|
4605
|
+
CHARACTERS_AREA_ID,
|
|
4606
|
+
ZOMBIE_ITEM_PREFIX,
|
|
4607
|
+
PERSON_ITEM_PREFIX,
|
|
4608
|
+
createCharactersTestModule,
|
|
4609
|
+
BODY_MATERIAL_DEF,
|
|
4610
|
+
registerBodyMaterial,
|
|
4611
|
+
AVATAR_AREA_ID,
|
|
4612
|
+
createAvatarTestModule
|
|
4613
|
+
};
|
|
4614
|
+
//# sourceMappingURL=chunk-XSMP7T6O.js.map
|