img_export/
img_export.rs

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    ///Optional width and height scaling
53    #[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                // TODO: Could have args te specify calendar too.
117                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(), // Objects do not support mounting
565        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}