From cccc796d31a10ddf907578476e51524fb4ee7cfb Mon Sep 17 00:00:00 2001 From: Aidar Shaikhiev Date: Thu, 15 Dec 2022 16:19:49 +0000 Subject: [PATCH] Items images export for wiki --- .cargo/config | 1 + .gitignore | 1 + CHANGELOG.md | 2 + Cargo.lock | 1 + voxygen/Cargo.toml | 8 ++- voxygen/src/bin/img-export.rs | 129 ++++++++++++++++++++++++++++++++++ voxygen/src/hud/item_imgs.rs | 4 +- voxygen/src/ui/graphic/mod.rs | 3 +- voxygen/src/ui/mod.rs | 2 +- 9 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 voxygen/src/bin/img-export.rs diff --git a/.cargo/config b/.cargo/config index c25fcc783b..34ee985132 100644 --- a/.cargo/config +++ b/.cargo/config @@ -16,6 +16,7 @@ csv-export = "run --manifest-path common/Cargo.toml --features=bin_csv --bin csv csv-import = "run --manifest-path common/Cargo.toml --features=bin_csv --bin csv_import" dot-recipes = "run --manifest-path common/Cargo.toml --features=bin_graphviz --bin recipe_graphviz" dot-skills = "run --manifest-path common/Cargo.toml --features=bin_graphviz --bin skill_graphviz" +img-export = "run --manifest-path voxygen/Cargo.toml --features=bin_img-export --bin img-export" # server-cli server = "run --bin veloren-server-cli" test-server = "run --bin veloren-server-cli --no-default-features --features simd" diff --git a/.gitignore b/.gitignore index b1c32082f9..e7bbbdc07d 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ wgpu-trace/ # Export data *.csv +img-export/**/*.png # Game data *.sqlite diff --git a/CHANGELOG.md b/CHANGELOG.md index b470bba410..981538ffc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - NPCs now move to their target's last known position. - Experience bar below the hotbar - Bridges. +- Tool for exporting PNG images of all in-game models (`cargo img-export`) ### Changed @@ -78,6 +79,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Camera jittering in third person has been significantly reduced - Many water shader issues have been fixed - Flee if attacked even if attacker is not close. +- `/time` command will never rewind time, only advance it to not break rtsim ## [0.13.0] - 2022-07-23 diff --git a/Cargo.lock b/Cargo.lock index a9f58613c1..0ec17a5cf6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7104,6 +7104,7 @@ dependencies = [ "veloren-client", "veloren-client-i18n", "veloren-common", + "veloren-common-assets", "veloren-common-base", "veloren-common-ecs", "veloren-common-frontend", diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index f3f99314d0..6090035710 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -31,6 +31,7 @@ plugins = ["client/plugins"] egui-ui = ["voxygen-egui", "egui", "egui_wgpu_backend", "egui_winit_platform"] shaderc-from-source = ["shaderc/build-from-source"] discord = ["discord-sdk"] +bin_img-export = ["common-assets"] # We don't ship egui with published release builds so a separate feature is required that excludes it. default-publish = ["singleplayer", "native-dialog", "plugins", "discord", "simd"] @@ -41,12 +42,13 @@ default = ["default-no-egui", "egui-ui"] [dependencies] client = {package = "veloren-client", path = "../client"} common = {package = "veloren-common", path = "../common"} +common-assets = {package = "veloren-common-assets", path = "../common/assets", optional = true} # img-export common-base = {package = "veloren-common-base", path = "../common/base"} common-ecs = {package = "veloren-common-ecs", path = "../common/ecs"} common-frontend = {package = "veloren-common-frontend", path = "../common/frontend"} common-net = {package = "veloren-common-net", path = "../common/net"} -common-systems = {package = "veloren-common-systems", path = "../common/systems"} common-state = {package = "veloren-common-state", path = "../common/state"} +common-systems = {package = "veloren-common-systems", path = "../common/systems"} anim = {package = "veloren-voxygen-anim", path = "anim"} i18n = {package = "veloren-client-i18n", path = "../client/i18n"} @@ -160,3 +162,7 @@ rayon = "1.5.0" [[bench]] harness = false name = "meshing_benchmark" + +[[bin]] +name = "img-export" +required-features = ["bin_img-export"] diff --git a/voxygen/src/bin/img-export.rs b/voxygen/src/bin/img-export.rs new file mode 100644 index 0000000000..6c20a3f9ce --- /dev/null +++ b/voxygen/src/bin/img-export.rs @@ -0,0 +1,129 @@ +use clap::Parser; +use common::figure::Segment; +use common_assets::{AssetExt, DotVoxAsset}; +use std::{fs, path::Path}; +use vek::{Mat4, Quaternion, Vec2, Vec3, Vec4}; +use veloren_voxygen::{ + hud::item_imgs::{ImageSpec, ItemImagesSpec}, + ui::graphic::renderer::{draw_vox, SampleStrat, Transform}, +}; + +#[derive(Parser)] +struct Cli { + ///Optional width and height scaling + #[clap(default_value_t = 20)] + scale: u32, +} + +pub fn main() { + let args = Cli::parse(); + let manifest = ItemImagesSpec::load_expect("voxygen.item_image_manifest"); + for (_, spec) in manifest.read().0.iter() { + match spec { + ImageSpec::Vox(specifier) => voxel_to_png(&specifier, Transform::default(), args.scale), + ImageSpec::VoxTrans(specifier, offset, [rot_x, rot_y, rot_z], zoom) => voxel_to_png( + &specifier, + Transform { + ori: Quaternion::rotation_x(rot_x * std::f32::consts::PI / 180.0) + .rotated_y(rot_y * std::f32::consts::PI / 180.0) + .rotated_z(rot_z * std::f32::consts::PI / 180.0), + offset: Vec3::from(*offset), + /* FIXME: This is a dirty workaround to not cut off the edges of some objects + * like ./img-export/weapon/component/axe/poleaxe/bronze.vox + * more details here: https://gitlab.com/veloren/veloren/-/merge_requests/3494#note_1205030803 */ + zoom: *zoom * 0.8, + orth: true, + stretch: false, + }, + args.scale, + ), + ImageSpec::Png(specifier) => { + println!("Skip png image {}", specifier); + continue; + }, + } + } +} + +fn voxel_to_png(specifier: &String, transform: Transform, scale: u32) { + let voxel = match DotVoxAsset::load(&format!("voxygen.{}", specifier)) { + Ok(dot_vox) => dot_vox, + Err(err) => { + println!("Coudn't load voxel: {}", err); + return; + }, + }; + let dot_vox_data = &voxel.read().0; + let model_size = dot_vox_data + .models + .get(0) + .expect("Error getting model from voxel") + .size; + let ori_mat = Mat4::from(transform.ori); + let aabb_size = Vec3::new(model_size.x, model_size.y, model_size.z); + //TODO: skip dims transformation if transform is default(), instead use + // model_size + let rotated_size = calc_rotated_size(&ori_mat, &aabb_size); + let projection_size = Vec2 { + x: ((rotated_size.y as u32) * scale) as u16, + y: ((rotated_size.z as u32) * scale) as u16, + }; + let segment = Segment::from_vox(dot_vox_data, false); + let path = format!("img-export/{}.png", &specifier_to_path(specifier)); + let folder_path = path.rsplit_once('/').expect("Invalid path").0; + let full_path = Path::new(&path); + if let Err(e) = fs::create_dir_all(Path::new(folder_path)) { + println!("{}", e); + return; + } + + draw_vox(&segment, projection_size, transform, SampleStrat::None) + .save(full_path) + .unwrap_or_else(|_| panic!("Can't save file {}", full_path.to_str().expect(""))); +} + +fn calc_rotated_size(ori_mat: &Mat4, aabb_size: &Vec3) -> Vec3 { + let aabb_min = Vec3 { + x: 0f32, + y: 0f32, + z: 0f32, + }; + let aabb_max = Vec3 { + x: aabb_size.y as f32, + y: aabb_size.z as f32, + z: aabb_size.x as f32, + }; + let aabb_vertices: [Vec3; 8] = [ + Vec3::new(aabb_min.x, aabb_min.y, aabb_min.z), + Vec3::new(aabb_max.x, aabb_min.y, aabb_min.z), + Vec3::new(aabb_max.x, aabb_max.y, aabb_min.z), + Vec3::new(aabb_min.x, aabb_max.y, aabb_min.z), + Vec3::new(aabb_min.x, aabb_min.y, aabb_max.z), + Vec3::new(aabb_max.x, aabb_min.y, aabb_max.z), + Vec3::new(aabb_max.x, aabb_max.y, aabb_max.z), + Vec3::new(aabb_min.x, aabb_max.y, aabb_max.z), + ]; + let rotated_vertices = aabb_vertices.map(|c| (Vec4::::from(c) * *ori_mat).xyz()); + let max_xyz = rotated_vertices + .iter() + .copied() + .reduce(|acc, corner| Vec3::::partial_max(acc, corner)) + .expect("Failed find maximum"); + let min_xyz = rotated_vertices + .iter() + .copied() + .reduce(|acc, vertex| Vec3::::partial_min(acc, vertex)) + .expect("Failed find minimum"); + Vec3 { + x: max_xyz.x - min_xyz.x, + y: max_xyz.y - min_xyz.y, + z: max_xyz.z - min_xyz.z, + } +} + +fn specifier_to_path(specifier: &String) -> String { + specifier + .strip_prefix("voxel.") + .unwrap_or_else(|| panic!("There was no prefix in {}", specifier)) + .replace('.', "/") +} diff --git a/voxygen/src/hud/item_imgs.rs b/voxygen/src/hud/item_imgs.rs index ef0e05f5f8..1c160a0e11 100644 --- a/voxygen/src/hud/item_imgs.rs +++ b/voxygen/src/hud/item_imgs.rs @@ -18,7 +18,7 @@ pub fn animate_by_pulse(ids: &[Id], pulse: f32) -> Id { } #[derive(Serialize, Deserialize)] -enum ImageSpec { +pub enum ImageSpec { Png(String), Vox(String), // (specifier, offset, (axis, 2 * angle / pi), zoom) @@ -54,7 +54,7 @@ impl ImageSpec { } #[derive(Serialize, Deserialize)] -struct ItemImagesSpec(HashMap); +pub struct ItemImagesSpec(pub HashMap); impl assets::Asset for ItemImagesSpec { type Loader = assets::RonLoader; diff --git a/voxygen/src/ui/graphic/mod.rs b/voxygen/src/ui/graphic/mod.rs index deedb9d45b..5bf35e870b 100644 --- a/voxygen/src/ui/graphic/mod.rs +++ b/voxygen/src/ui/graphic/mod.rs @@ -1,5 +1,6 @@ mod pixel_art; -mod renderer; +pub mod renderer; + pub use renderer::{SampleStrat, Transform}; use crate::{ diff --git a/voxygen/src/ui/mod.rs b/voxygen/src/ui/mod.rs index 5a5aaabf84..535ec2e3ad 100644 --- a/voxygen/src/ui/mod.rs +++ b/voxygen/src/ui/mod.rs @@ -1,6 +1,6 @@ mod cache; mod event; -mod graphic; +pub mod graphic; mod scale; mod widgets; #[macro_use]