1use std::{fs, path::Path, sync::Arc};
2
3use anim::{
4 Animation, FigureBoneData, Skeleton, arthropod::ArthropodSkeleton,
5 biped_large::BipedLargeSkeleton, biped_small::BipedSmallSkeleton,
6 bird_large::BirdLargeSkeleton, bird_medium::BirdMediumSkeleton, character::CharacterSkeleton,
7 crustacean::CrustaceanSkeleton, dragon::DragonSkeleton, fish_medium::FishMediumSkeleton,
8 fish_small::FishSmallSkeleton, golem::GolemSkeleton, quadruped_low::QuadrupedLowSkeleton,
9 quadruped_medium::QuadrupedMediumSkeleton, quadruped_small::QuadrupedSmallSkeleton,
10 theropod::TheropodSkeleton,
11};
12use clap::Parser;
13use common::{
14 comp::{
15 self, Body, CharacterState, Inventory,
16 body::parts::HeadState,
17 item::{ItemKind, armor::ArmorKind},
18 slot::{ArmorSlot, EquipSlot},
19 },
20 figure::Segment,
21 generation::{EntityInfo, try_all_entity_configs},
22};
23use image::RgbaImage;
24use rand::{Rng, SeedableRng};
25use rand_chacha::ChaChaRng;
26use vek::{Mat4, Quaternion, Vec2, Vec3};
27
28use common::assets::{AssetExt, AssetHandle};
29use veloren_voxygen::{
30 hud::item_imgs::{ImageSpec, ItemImagesSpec},
31 scene::{
32 CameraMode,
33 figure::{
34 cache::{CharacterCacheKey, FigureKey},
35 load::{
36 ArthropodSpec, BipedLargeSpec, BipedSmallSpec, BirdLargeSpec, BirdMediumSpec,
37 BodySpec, CrustaceanSpec, DragonSpec, FishMediumSpec, FishSmallSpec, GolemSpec,
38 HumSpec, ObjectSpec, QuadrupedLowSpec, QuadrupedMediumSpec, QuadrupedSmallSpec,
39 TheropodSpec,
40 },
41 },
42 },
43 ui::{
44 Graphic,
45 graphic::renderer::{draw_vox, draw_voxes},
46 },
47};
48
49#[derive(Parser)]
50struct Cli {
51 #[clap(default_value_t = 20)]
53 scale: u32,
54
55 #[clap(long = "all-items")]
56 all_items: bool,
57
58 #[clap(long = "all-npcs")]
59 all_npcs: bool,
60
61 #[clap(long)]
62 filter: Option<String>,
63
64 #[clap(long)]
65 seed: Option<u128>,
66}
67
68pub fn main() {
69 let args = Cli::parse();
70
71 if !args.all_items && !args.all_npcs {
72 println!("Nothing to do, to see arguments use `--help`.")
73 } else {
74 let image_size = Vec2 {
75 x: (10_u32 * args.scale) as u16,
76 y: (10_u32 * args.scale) as u16,
77 };
78 let mut img_count = 0;
79 if args.all_items {
80 let manifest = ItemImagesSpec::load_expect("voxygen.item_image_manifest");
81 for (_, spec) in manifest.read().0.iter() {
82 let specifier = match spec {
83 ImageSpec::Vox(specifier, _, _) => specifier,
84 ImageSpec::VoxTrans(specifier, _, _, _, _, _) => specifier,
85 _ => continue,
86 };
87 if args.filter.as_ref().is_some_and(|f| !specifier.contains(f)) {
88 continue;
89 }
90 let graphic = spec.create_graphic();
91 let img = match graphic {
92 Graphic::Voxel(segment, trans, sample_strat) => {
93 draw_vox(&segment, image_size, trans, sample_strat)
94 },
95 _ => continue,
96 };
97 save_img(specifier, img);
98 img_count += 1;
99 }
100 }
101 if args.all_npcs {
102 let manifests = Manifests::load().expect("This should work");
103 for specifier in try_all_entity_configs().expect("Couldn't load npcs").iter() {
104 if args.filter.as_ref().is_some_and(|f| !specifier.contains(f)) {
105 continue;
106 }
107 let mut rng = ChaChaRng::from_seed(
108 args.seed
109 .map(|s| {
110 let b = s.to_le_bytes();
111 std::array::from_fn(|i| b[i % b.len()])
112 })
113 .unwrap_or(rand::thread_rng().gen()),
114 );
115 let info =
117 EntityInfo::at(Vec3::zero()).with_asset_expect(specifier, &mut rng, None);
118 let bones = load_npc_bones(info, &manifests, Mat4::identity());
119 if bones.is_empty() {
120 continue;
121 };
122 let bones: Vec<_> = bones.iter().map(|(t, s)| (*t, s)).collect();
123 let img = draw_voxes(
124 &bones,
125 image_size,
126 veloren_voxygen::ui::Transform {
127 ori: Quaternion::rotation_x(-90.0 * std::f32::consts::PI / 180.0)
128 .rotated_y(180.0 * std::f32::consts::PI / 180.0)
129 .rotated_z(0.0 * std::f32::consts::PI / 180.0),
130 offset: Vec3::new(0.0, 0.0, 0.0),
131 zoom: 0.9,
132 orth: true,
133 stretch: false,
134 },
135 veloren_voxygen::ui::SampleStrat::None,
136 Vec3::new(0.0, 1.0, 1.0),
137 );
138
139 save_img(specifier, img);
140 img_count += 1;
141 }
142 }
143
144 println!("Exported {img_count} images!");
145 }
146}
147
148fn save_img(specifier: &str, img: RgbaImage) {
149 let path = specifier_to_path(specifier);
150 let folder_path = path.rsplit_once('/').expect("Invalid path").0;
151 let full_path = Path::new(&path);
152 if let Err(e) = fs::create_dir_all(Path::new(folder_path)) {
153 println!("{}", e);
154 return;
155 }
156
157 img.save(full_path)
158 .unwrap_or_else(|_| panic!("Can't save file {}", full_path.to_str().expect("")));
159}
160
161fn specifier_to_path(specifier: &str) -> String {
162 let inner = specifier.replace('.', "/");
163
164 format!("img-export/{inner}.png")
165}
166
167struct Manifests {
168 humanoid: AssetHandle<HumSpec>,
169 quadruped_small: AssetHandle<QuadrupedSmallSpec>,
170 quadruped_medium: AssetHandle<QuadrupedMediumSpec>,
171 bird_medium: AssetHandle<BirdMediumSpec>,
172 fish_medium: AssetHandle<FishMediumSpec>,
173 dragon: AssetHandle<DragonSpec>,
174 bird_large: AssetHandle<BirdLargeSpec>,
175 fish_small: AssetHandle<FishSmallSpec>,
176 biped_large: AssetHandle<BipedLargeSpec>,
177 biped_small: AssetHandle<BipedSmallSpec>,
178 object: AssetHandle<ObjectSpec>,
179 golem: AssetHandle<GolemSpec>,
180 theropod: AssetHandle<TheropodSpec>,
181 quadruped_low: AssetHandle<QuadrupedLowSpec>,
182 arthropod: AssetHandle<ArthropodSpec>,
183 crustacean: AssetHandle<CrustaceanSpec>,
184}
185
186impl Manifests {
187 fn load() -> Result<Self, common::assets::BoxedError> {
188 Ok(Self {
189 humanoid: AssetExt::load("")?,
190 quadruped_small: AssetExt::load("")?,
191 quadruped_medium: AssetExt::load("")?,
192 bird_medium: AssetExt::load("")?,
193 fish_medium: AssetExt::load("")?,
194 dragon: AssetExt::load("")?,
195 bird_large: AssetExt::load("")?,
196 fish_small: AssetExt::load("")?,
197 biped_large: AssetExt::load("")?,
198 biped_small: AssetExt::load("")?,
199 object: AssetExt::load("")?,
200 golem: AssetExt::load("")?,
201 theropod: AssetExt::load("")?,
202 quadruped_low: AssetExt::load("")?,
203 arthropod: AssetExt::load("")?,
204 crustacean: AssetExt::load("")?,
205 })
206 }
207}
208
209fn load_npc_bones(
210 info: EntityInfo,
211 manifests: &Manifests,
212 mut base_mat: Mat4<f32>,
213) -> Vec<(Mat4<f32>, Segment)> {
214 base_mat *= Mat4::scaling_3d(info.scale);
215 let loadout = info.loadout.build();
216
217 let inventory = Inventory::with_loadout(loadout, info.body);
218
219 let state = CharacterState::Idle(Default::default());
220
221 let extra = Some(Arc::new(CharacterCacheKey::from(
222 Some(&state),
223 CameraMode::ThirdPerson,
224 &inventory,
225 )));
226
227 let bone_segments = match info.body {
228 Body::Humanoid(body) => comp::humanoid::Body::bone_meshes(
229 &FigureKey {
230 body,
231 item_key: None,
232 extra,
233 },
234 &manifests.humanoid,
235 (),
236 ),
237 Body::QuadrupedSmall(body) => comp::quadruped_small::Body::bone_meshes(
238 &FigureKey {
239 body,
240 item_key: None,
241 extra,
242 },
243 &manifests.quadruped_small,
244 (),
245 ),
246 Body::QuadrupedMedium(body) => comp::quadruped_medium::Body::bone_meshes(
247 &FigureKey {
248 body,
249 item_key: None,
250 extra,
251 },
252 &manifests.quadruped_medium,
253 (),
254 ),
255 Body::BirdMedium(body) => comp::bird_medium::Body::bone_meshes(
256 &FigureKey {
257 body,
258 item_key: None,
259 extra,
260 },
261 &manifests.bird_medium,
262 (),
263 ),
264 Body::FishMedium(body) => comp::fish_medium::Body::bone_meshes(
265 &FigureKey {
266 body,
267 item_key: None,
268 extra,
269 },
270 &manifests.fish_medium,
271 (),
272 ),
273 Body::Dragon(body) => comp::dragon::Body::bone_meshes(
274 &FigureKey {
275 body,
276 item_key: None,
277 extra,
278 },
279 &manifests.dragon,
280 (),
281 ),
282 Body::BirdLarge(body) => comp::bird_large::Body::bone_meshes(
283 &FigureKey {
284 body,
285 item_key: None,
286 extra,
287 },
288 &manifests.bird_large,
289 (),
290 ),
291 Body::FishSmall(body) => comp::fish_small::Body::bone_meshes(
292 &FigureKey {
293 body,
294 item_key: None,
295 extra,
296 },
297 &manifests.fish_small,
298 (),
299 ),
300 Body::BipedLarge(body) => comp::biped_large::Body::bone_meshes(
301 &FigureKey {
302 body,
303 item_key: None,
304 extra,
305 },
306 &manifests.biped_large,
307 (),
308 ),
309 Body::BipedSmall(body) => comp::biped_small::Body::bone_meshes(
310 &FigureKey {
311 body,
312 item_key: None,
313 extra,
314 },
315 &manifests.biped_small,
316 (),
317 ),
318 Body::Object(body) => comp::object::Body::bone_meshes(
319 &FigureKey {
320 body,
321 item_key: None,
322 extra,
323 },
324 &manifests.object,
325 (),
326 ),
327 Body::Golem(body) => comp::golem::Body::bone_meshes(
328 &FigureKey {
329 body,
330 item_key: None,
331 extra,
332 },
333 &manifests.golem,
334 (),
335 ),
336 Body::Theropod(body) => comp::theropod::Body::bone_meshes(
337 &FigureKey {
338 body,
339 item_key: None,
340 extra,
341 },
342 &manifests.theropod,
343 (),
344 ),
345 Body::QuadrupedLow(body) => comp::quadruped_low::Body::bone_meshes(
346 &FigureKey {
347 body,
348 item_key: None,
349 extra,
350 },
351 &manifests.quadruped_low,
352 (),
353 ),
354 Body::Arthropod(body) => comp::arthropod::Body::bone_meshes(
355 &FigureKey {
356 body,
357 item_key: None,
358 extra,
359 },
360 &manifests.arthropod,
361 (),
362 ),
363 Body::Crustacean(body) => comp::crustacean::Body::bone_meshes(
364 &FigureKey {
365 body,
366 item_key: None,
367 extra,
368 },
369 &manifests.crustacean,
370 (),
371 ),
372 Body::Item(_) => panic!("Item bodies aren't supported"),
373 Body::Ship(_) => panic!("Ship bodies aren't supported"),
374 Body::Plugin(_) => panic!("Plugin bodies aren't supported"),
375 };
376
377 let tool_info = |equip_slot| {
378 inventory
379 .equipped(equip_slot)
380 .map(|i| {
381 if let ItemKind::Tool(tool) = &*i.kind() {
382 (Some(tool.kind), Some(tool.hands), i.ability_spec())
383 } else {
384 (None, None, None)
385 }
386 })
387 .unwrap_or((None, None, None))
388 };
389
390 let (active_tool_kind, active_tool_hand, active_tool_spec) =
391 tool_info(EquipSlot::ActiveMainhand);
392 let _active_tool_spec = active_tool_spec.as_deref();
393 let (second_tool_kind, second_tool_hand, second_tool_spec) =
394 tool_info(EquipSlot::ActiveOffhand);
395 let _second_tool_spec = second_tool_spec.as_deref();
396 let hands = (active_tool_hand, second_tool_hand);
397 let time = 0.0;
398
399 let mut buf = [FigureBoneData::default(); 16];
400 let mount_mat = match info.body {
401 Body::Humanoid(body) => {
402 let back_carry_offset = inventory
403 .equipped(EquipSlot::Armor(ArmorSlot::Back))
404 .and_then(|i| {
405 if let ItemKind::Armor(armor) = i.kind().as_ref() {
406 match &armor.kind {
407 ArmorKind::Backpack => Some(4.0),
408 ArmorKind::Back => Some(1.5),
409 _ => None,
410 }
411 } else {
412 None
413 }
414 })
415 .unwrap_or(0.0);
416
417 let skel = anim::character::StandAnimation::update_skeleton(
418 &CharacterSkeleton::new(false, back_carry_offset),
419 (
420 active_tool_kind,
421 second_tool_kind,
422 hands,
423 anim::vek::Vec3::<f32>::unit_y(),
424 anim::vek::Vec3::<f32>::unit_y(),
425 time,
426 Vec3::zero(),
427 ),
428 0.0,
429 &mut 1.0,
430 &anim::character::SkeletonAttr::from(&body),
431 );
432 let computed_skel = skel.compute_matrices(base_mat, &mut buf, body);
433
434 anim::character::mount_mat(&computed_skel, &skel).0
435 },
436 Body::QuadrupedSmall(body) => {
437 let skel = anim::quadruped_small::IdleAnimation::update_skeleton(
438 &QuadrupedSmallSkeleton::default(),
439 time,
440 0.0,
441 &mut 1.0,
442 &anim::quadruped_small::SkeletonAttr::from(&body),
443 );
444 let computed_skel = skel.compute_matrices(base_mat, &mut buf, body);
445
446 anim::quadruped_small::mount_mat(&computed_skel, &skel).0
447 },
448 Body::QuadrupedMedium(body) => {
449 let skel = anim::quadruped_medium::IdleAnimation::update_skeleton(
450 &QuadrupedMediumSkeleton::default(),
451 time,
452 0.0,
453 &mut 1.0,
454 &anim::quadruped_medium::SkeletonAttr::from(&body),
455 );
456 let computed_skel = skel.compute_matrices(base_mat, &mut buf, body);
457
458 anim::quadruped_medium::mount_mat(&body, &computed_skel, &skel).0
459 },
460 Body::BirdMedium(body) => {
461 let skel = anim::bird_medium::IdleAnimation::update_skeleton(
462 &BirdMediumSkeleton::default(),
463 time,
464 0.0,
465 &mut 1.0,
466 &anim::bird_medium::SkeletonAttr::from(&body),
467 );
468 let computed_skel = skel.compute_matrices(base_mat, &mut buf, body);
469
470 anim::bird_medium::mount_mat(&computed_skel, &skel).0
471 },
472 Body::FishMedium(body) => {
473 let skel = anim::fish_medium::IdleAnimation::update_skeleton(
474 &FishMediumSkeleton::default(),
475 (
476 Vec3::zero(),
477 anim::vek::Vec3::<f32>::unit_y(),
478 anim::vek::Vec3::<f32>::unit_y(),
479 time,
480 Vec3::zero(),
481 ),
482 0.0,
483 &mut 1.0,
484 &anim::fish_medium::SkeletonAttr::from(&body),
485 );
486 let computed_skel = skel.compute_matrices(base_mat, &mut buf, body);
487
488 anim::fish_medium::mount_mat(&computed_skel, &skel).0
489 },
490 Body::Dragon(body) => {
491 let skel = anim::dragon::IdleAnimation::update_skeleton(
492 &DragonSkeleton::default(),
493 time,
494 0.0,
495 &mut 1.0,
496 &anim::dragon::SkeletonAttr::from(&body),
497 );
498 let computed_skel = skel.compute_matrices(base_mat, &mut buf, body);
499
500 anim::dragon::mount_mat(&computed_skel, &skel).0
501 },
502 Body::BirdLarge(body) => {
503 let skel = anim::bird_large::IdleAnimation::update_skeleton(
504 &BirdLargeSkeleton::default(),
505 time,
506 0.0,
507 &mut 1.0,
508 &anim::bird_large::SkeletonAttr::from(&body),
509 );
510 let computed_skel = skel.compute_matrices(base_mat, &mut buf, body);
511
512 anim::bird_large::mount_mat(&body, &computed_skel, &skel).0
513 },
514 Body::FishSmall(body) => {
515 let skel = anim::fish_small::IdleAnimation::update_skeleton(
516 &FishSmallSkeleton::default(),
517 (
518 Vec3::zero(),
519 anim::vek::Vec3::<f32>::unit_y(),
520 anim::vek::Vec3::<f32>::unit_y(),
521 time,
522 Vec3::zero(),
523 ),
524 0.0,
525 &mut 1.0,
526 &anim::fish_small::SkeletonAttr::from(&body),
527 );
528 let computed_skel = skel.compute_matrices(base_mat, &mut buf, body);
529
530 anim::fish_small::mount_mat(&computed_skel, &skel).0
531 },
532 Body::BipedLarge(body) => {
533 let skel = anim::biped_large::IdleAnimation::update_skeleton(
534 &BipedLargeSkeleton::default(),
535 (active_tool_kind, second_tool_kind, time),
536 0.0,
537 &mut 1.0,
538 &anim::biped_large::SkeletonAttr::from(&body),
539 );
540 let computed_skel = skel.compute_matrices(base_mat, &mut buf, body);
541
542 anim::biped_large::mount_mat(&body, &computed_skel, &skel).0
543 },
544 Body::BipedSmall(body) => {
545 let skel = anim::biped_small::IdleAnimation::update_skeleton(
546 &BipedSmallSkeleton::default(),
547 (
548 Vec3::zero(),
549 anim::vek::Vec3::<f32>::unit_y(),
550 anim::vek::Vec3::<f32>::unit_y(),
551 time,
552 Vec3::zero(),
553 ),
554 0.0,
555 &mut 1.0,
556 &anim::biped_small::SkeletonAttr::from(&body),
557 );
558 let computed_skel = skel.compute_matrices(base_mat, &mut buf, body);
559
560 anim::biped_small::mount_mat(&body, &computed_skel, &skel).0
561 },
562 Body::Object(_) => Mat4::identity(), Body::Golem(body) => {
564 let skel = anim::golem::IdleAnimation::update_skeleton(
565 &GolemSkeleton::default(),
566 time,
567 0.0,
568 &mut 1.0,
569 &anim::golem::SkeletonAttr::from(&body),
570 );
571 let computed_skel = skel.compute_matrices(base_mat, &mut buf, body);
572
573 anim::golem::mount_mat(&computed_skel, &skel).0
574 },
575 Body::Theropod(body) => {
576 let skel = anim::theropod::IdleAnimation::update_skeleton(
577 &TheropodSkeleton::default(),
578 time,
579 0.0,
580 &mut 1.0,
581 &anim::theropod::SkeletonAttr::from(&body),
582 );
583 let computed_skel = skel.compute_matrices(base_mat, &mut buf, body);
584
585 anim::theropod::mount_mat(&body, &computed_skel, &skel).0
586 },
587 Body::QuadrupedLow(body) => {
588 let skel = anim::quadruped_low::IdleAnimation::update_skeleton(
589 &QuadrupedLowSkeleton::default(),
590 (time, [
591 HeadState::Attached,
592 HeadState::Attached,
593 HeadState::Attached,
594 ]),
595 0.0,
596 &mut 1.0,
597 &anim::quadruped_low::SkeletonAttr::from(&body),
598 );
599 let computed_skel = skel.compute_matrices(base_mat, &mut buf, body);
600
601 anim::quadruped_low::mount_mat(&body, &computed_skel, &skel).0
602 },
603 Body::Arthropod(body) => {
604 let skel = anim::arthropod::IdleAnimation::update_skeleton(
605 &ArthropodSkeleton::default(),
606 time,
607 0.0,
608 &mut 1.0,
609 &anim::arthropod::SkeletonAttr::from(&body),
610 );
611 let computed_skel = skel.compute_matrices(base_mat, &mut buf, body);
612
613 anim::arthropod::mount_mat(&body, &computed_skel, &skel).0
614 },
615 Body::Crustacean(body) => {
616 let skel = anim::crustacean::IdleAnimation::update_skeleton(
617 &CrustaceanSkeleton::default(),
618 time,
619 0.0,
620 &mut 1.0,
621 &anim::crustacean::SkeletonAttr::from(&body),
622 );
623 let computed_skel = skel.compute_matrices(base_mat, &mut buf, body);
624
625 anim::crustacean::mount_mat(&computed_skel, &skel).0
626 },
627 Body::Item(_) => panic!("Item bodies aren't supported"),
628 Body::Ship(_) => panic!("Ship bodies aren't supported"),
629 Body::Plugin(_) => panic!("Plugin bodies aren't supported"),
630 };
631
632 let mut bones: Vec<_> = bone_segments
633 .into_iter()
634 .zip(buf)
635 .filter_map(|(segment, bone)| {
636 let (segment, offset) = segment?;
637
638 Some((
639 Mat4::from_col_arrays(bone.0) * Mat4::translation_3d(offset),
640 segment,
641 ))
642 })
643 .collect();
644
645 if let Some(rider) = info.rider {
646 bones.extend(load_npc_bones(*rider, manifests, mount_mat));
647 }
648
649 bones
650}