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, 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    ///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 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}