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