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, object::ObjectSkeleton,
9 quadruped_low::QuadrupedLowSkeleton, quadruped_medium::QuadrupedMediumSkeleton,
10 quadruped_small::QuadrupedSmallSkeleton, 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 offsets = 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
433 skel.compute_matrices(base_mat, &mut buf, body)
434 },
435 Body::QuadrupedSmall(body) => {
436 let skel = anim::quadruped_small::IdleAnimation::update_skeleton(
437 &QuadrupedSmallSkeleton::default(),
438 time,
439 0.0,
440 &mut 1.0,
441 &anim::quadruped_small::SkeletonAttr::from(&body),
442 );
443
444 skel.compute_matrices(base_mat, &mut buf, body)
445 },
446 Body::QuadrupedMedium(body) => {
447 let skel = anim::quadruped_medium::IdleAnimation::update_skeleton(
448 &QuadrupedMediumSkeleton::default(),
449 time,
450 0.0,
451 &mut 1.0,
452 &anim::quadruped_medium::SkeletonAttr::from(&body),
453 );
454 skel.compute_matrices(base_mat, &mut buf, body)
455 },
456 Body::BirdMedium(body) => {
457 let skel = anim::bird_medium::IdleAnimation::update_skeleton(
458 &BirdMediumSkeleton::default(),
459 time,
460 0.0,
461 &mut 1.0,
462 &anim::bird_medium::SkeletonAttr::from(&body),
463 );
464 skel.compute_matrices(base_mat, &mut buf, body)
465 },
466 Body::FishMedium(body) => {
467 let skel = anim::fish_medium::IdleAnimation::update_skeleton(
468 &FishMediumSkeleton::default(),
469 (
470 Vec3::zero(),
471 anim::vek::Vec3::<f32>::unit_y(),
472 anim::vek::Vec3::<f32>::unit_y(),
473 time,
474 Vec3::zero(),
475 ),
476 0.0,
477 &mut 1.0,
478 &anim::fish_medium::SkeletonAttr::from(&body),
479 );
480 skel.compute_matrices(base_mat, &mut buf, body)
481 },
482 Body::Dragon(body) => {
483 let skel = anim::dragon::IdleAnimation::update_skeleton(
484 &DragonSkeleton::default(),
485 time,
486 0.0,
487 &mut 1.0,
488 &anim::dragon::SkeletonAttr::from(&body),
489 );
490 skel.compute_matrices(base_mat, &mut buf, body)
491 },
492 Body::BirdLarge(body) => {
493 let skel = anim::bird_large::IdleAnimation::update_skeleton(
494 &BirdLargeSkeleton::default(),
495 time,
496 0.0,
497 &mut 1.0,
498 &anim::bird_large::SkeletonAttr::from(&body),
499 );
500 skel.compute_matrices(base_mat, &mut buf, body)
501 },
502 Body::FishSmall(body) => {
503 let skel = anim::fish_small::IdleAnimation::update_skeleton(
504 &FishSmallSkeleton::default(),
505 (
506 Vec3::zero(),
507 anim::vek::Vec3::<f32>::unit_y(),
508 anim::vek::Vec3::<f32>::unit_y(),
509 time,
510 Vec3::zero(),
511 ),
512 0.0,
513 &mut 1.0,
514 &anim::fish_small::SkeletonAttr::from(&body),
515 );
516 skel.compute_matrices(base_mat, &mut buf, body)
517 },
518 Body::BipedLarge(body) => {
519 let skel = anim::biped_large::IdleAnimation::update_skeleton(
520 &BipedLargeSkeleton::default(),
521 (active_tool_kind, second_tool_kind, time),
522 0.0,
523 &mut 1.0,
524 &anim::biped_large::SkeletonAttr::from(&body),
525 );
526 skel.compute_matrices(base_mat, &mut buf, body)
527 },
528 Body::BipedSmall(body) => {
529 let skel = anim::biped_small::IdleAnimation::update_skeleton(
530 &BipedSmallSkeleton::default(),
531 (
532 Vec3::zero(),
533 anim::vek::Vec3::<f32>::unit_y(),
534 anim::vek::Vec3::<f32>::unit_y(),
535 time,
536 Vec3::zero(),
537 ),
538 0.0,
539 &mut 1.0,
540 &anim::biped_small::SkeletonAttr::from(&body),
541 );
542 skel.compute_matrices(base_mat, &mut buf, body)
543 },
544 Body::Object(body) => {
545 let skel = anim::object::IdleAnimation::update_skeleton(
546 &ObjectSkeleton::default(),
547 (active_tool_kind, second_tool_kind, time),
548 0.0,
549 &mut 1.0,
550 &anim::object::SkeletonAttr::from(&body),
551 );
552 skel.compute_matrices(base_mat, &mut buf, body)
553 },
554 Body::Golem(body) => {
555 let skel = anim::golem::IdleAnimation::update_skeleton(
556 &GolemSkeleton::default(),
557 time,
558 0.0,
559 &mut 1.0,
560 &anim::golem::SkeletonAttr::from(&body),
561 );
562 skel.compute_matrices(base_mat, &mut buf, body)
563 },
564 Body::Theropod(body) => {
565 let skel = anim::theropod::IdleAnimation::update_skeleton(
566 &TheropodSkeleton::default(),
567 time,
568 0.0,
569 &mut 1.0,
570 &anim::theropod::SkeletonAttr::from(&body),
571 );
572 skel.compute_matrices(base_mat, &mut buf, body)
573 },
574 Body::QuadrupedLow(body) => {
575 let skel = anim::quadruped_low::IdleAnimation::update_skeleton(
576 &QuadrupedLowSkeleton::default(),
577 (time, [
578 HeadState::Attached,
579 HeadState::Attached,
580 HeadState::Attached,
581 ]),
582 0.0,
583 &mut 1.0,
584 &anim::quadruped_low::SkeletonAttr::from(&body),
585 );
586 skel.compute_matrices(base_mat, &mut buf, body)
587 },
588 Body::Arthropod(body) => {
589 let skel = anim::arthropod::IdleAnimation::update_skeleton(
590 &ArthropodSkeleton::default(),
591 time,
592 0.0,
593 &mut 1.0,
594 &anim::arthropod::SkeletonAttr::from(&body),
595 );
596 skel.compute_matrices(base_mat, &mut buf, body)
597 },
598 Body::Crustacean(body) => {
599 let skel = anim::crustacean::IdleAnimation::update_skeleton(
600 &CrustaceanSkeleton::default(),
601 time,
602 0.0,
603 &mut 1.0,
604 &anim::crustacean::SkeletonAttr::from(&body),
605 );
606 skel.compute_matrices(base_mat, &mut buf, body)
607 },
608 Body::Item(_) => panic!("Item bodies aren't supported"),
609 Body::Ship(_) => panic!("Ship bodies aren't supported"),
610 Body::Plugin(_) => panic!("Plugin bodies aren't supported"),
611 };
612
613 let mut bones: Vec<_> = bone_segments
614 .into_iter()
615 .zip(buf)
616 .filter_map(|(segment, bone)| {
617 let (segment, offset) = segment?;
618
619 Some((
620 Mat4::from_col_arrays(bone.0) * Mat4::translation_3d(offset),
621 segment,
622 ))
623 })
624 .collect();
625
626 if let Some(rider) = info.rider {
627 bones.extend(load_npc_bones(
628 *rider,
629 manifests,
630 Mat4::from(offsets.mount_bone),
631 ));
632 }
633
634 bones
635}