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