From ecb9e1906fb8471a21f27fb82bf361028ae9272e Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 19 Mar 2023 17:48:45 -0400 Subject: [PATCH] Add material data viewer for CMDL --- lib/src/format/cmdl.rs | 8 +- lib/src/format/mod.rs | 5 + retrotool-gui/src/loaders/model.rs | 38 +++--- retrotool-gui/src/tabs/model.rs | 204 +++++++++++++++++++++++++++-- retrotool-gui/src/tabs/texture.rs | 37 ++++++ retrotool/src/cmd/cmdl.rs | 4 +- 6 files changed, 262 insertions(+), 34 deletions(-) diff --git a/lib/src/format/cmdl.rs b/lib/src/format/cmdl.rs index b085c02..062c70a 100644 --- a/lib/src/format/cmdl.rs +++ b/lib/src/format/cmdl.rs @@ -457,10 +457,10 @@ pub struct CLayeredTextureData { #[derive(Clone, Debug)] pub struct STextureUsageInfo { pub tex_coord: u32, - pub filter: u32, - pub wrap_x: u32, - pub wrap_y: u32, - pub wrap_z: u32, + pub filter: i32, + pub wrap_x: i32, + pub wrap_y: i32, + pub wrap_z: i32, } #[binrw] diff --git a/lib/src/format/mod.rs b/lib/src/format/mod.rs index 4c50c67..c67b4e1 100644 --- a/lib/src/format/mod.rs +++ b/lib/src/format/mod.rs @@ -101,6 +101,11 @@ pub struct CVector4i { pub w: i32, } +impl CVector4i { + #[inline] + pub fn to_array(self) -> [i32; 4] { [self.x, self.y, self.z, self.w] } +} + #[binrw] #[derive(Clone, Debug)] pub struct CMatrix4f { diff --git a/retrotool-gui/src/loaders/model.rs b/retrotool-gui/src/loaders/model.rs index a54dd00..957ec04 100644 --- a/retrotool-gui/src/loaders/model.rs +++ b/retrotool-gui/src/loaders/model.rs @@ -18,6 +18,7 @@ use retrolib::format::{ ETextureAnisotropicRatio, ETextureFilter, ETextureMipFilter, ETextureWrap, STextureSamplerData, }, + CColor4f, }; use uuid::Uuid; use wgpu_types::{AddressMode, Face, FilterMode}; @@ -191,6 +192,11 @@ impl ModelAsset { } } +#[inline] +fn convert_color(value: &CColor4f) -> Color { + Color::rgba_linear(value.r, value.g, value.b, value.a) +} + fn build_material( key: &MaterialKey, materials: &[CMaterialCache], @@ -220,9 +226,9 @@ fn build_material( }, EMaterialDataId::BCRL => match &data.data { CMaterialDataInner::LayeredTexture(layers) => { - out_mat.base_color_l0 = layers.base.colors[0].to_array().into(); - out_mat.base_color_l1 = layers.base.colors[1].to_array().into(); - out_mat.base_color_l2 = layers.base.colors[2].to_array().into(); + out_mat.base_color_l0 = convert_color(&layers.base.colors[0]); + out_mat.base_color_l1 = convert_color(&layers.base.colors[1]); + out_mat.base_color_l2 = convert_color(&layers.base.colors[2]); out_mat.base_color_texture_0 = texture_images.get(&layers.textures[0].id).cloned(); out_mat.base_color_texture_1 = @@ -242,7 +248,7 @@ fn build_material( }, EMaterialDataId::DIFC => match &data.data { CMaterialDataInner::Color(color) => { - out_mat.base_color = Color::rgba(color.r, color.g, color.b, color.a); + out_mat.base_color = convert_color(color); } _ => log::warn!("Unsupported material data type for DIFC {:?}", data.data_type), }, @@ -256,7 +262,7 @@ fn build_material( }, EMaterialDataId::ICNC => match &data.data { CMaterialDataInner::Color(color) => { - out_mat.emissive_color = Color::rgba(color.r, color.g, color.b, color.a); + out_mat.emissive_color = convert_color(color); } _ => log::warn!("Unsupported material data type for ICNC {:?}", data.data_type), }, @@ -272,9 +278,9 @@ fn build_material( }, EMaterialDataId::NRML => match &data.data { CMaterialDataInner::LayeredTexture(layers) => { - out_mat.normal_map_l0 = layers.base.colors[0].to_array().into(); - out_mat.normal_map_l1 = layers.base.colors[1].to_array().into(); - out_mat.normal_map_l2 = layers.base.colors[2].to_array().into(); + out_mat.normal_map_l0 = convert_color(&layers.base.colors[0]); + out_mat.normal_map_l1 = convert_color(&layers.base.colors[1]); + out_mat.normal_map_l2 = convert_color(&layers.base.colors[2]); out_mat.normal_map_texture_0 = texture_images.get(&layers.textures[0].id).cloned(); out_mat.normal_map_texture_1 = @@ -304,9 +310,9 @@ fn build_material( }, EMaterialDataId::MTLL => match &data.data { CMaterialDataInner::LayeredTexture(layers) => { - out_mat.metallic_map_l0 = layers.base.colors[0].to_array().into(); - out_mat.metallic_map_l1 = layers.base.colors[1].to_array().into(); - out_mat.metallic_map_l2 = layers.base.colors[2].to_array().into(); + out_mat.metallic_map_l0 = convert_color(&layers.base.colors[0]); + out_mat.metallic_map_l1 = convert_color(&layers.base.colors[1]); + out_mat.metallic_map_l2 = convert_color(&layers.base.colors[2]); out_mat.metallic_map_texture_0 = texture_images.get(&layers.textures[0].id).cloned(); out_mat.metallic_map_texture_1 = @@ -356,7 +362,7 @@ fn sampler_descriptor_from_usage<'desc>( 3 => todo!("Mirror clamp"), 4 => AddressMode::ClampToBorder, 5 => todo!("Clamp"), - u32::MAX => data.map_or(AddressMode::Repeat, |d| texture_wrap(d.wrap_x)), + -1 => data.map_or(AddressMode::Repeat, |d| texture_wrap(d.wrap_x)), n => todo!("wrap {n}"), }, address_mode_v: match usage.wrap_y { @@ -366,7 +372,7 @@ fn sampler_descriptor_from_usage<'desc>( 3 => todo!("Mirror clamp"), 4 => AddressMode::ClampToBorder, 5 => todo!("Clamp"), - u32::MAX => data.map_or(AddressMode::Repeat, |d| texture_wrap(d.wrap_y)), + -1 => data.map_or(AddressMode::Repeat, |d| texture_wrap(d.wrap_y)), n => todo!("wrap {n}"), }, address_mode_w: match usage.wrap_z { @@ -376,13 +382,13 @@ fn sampler_descriptor_from_usage<'desc>( 3 => todo!("Mirror clamp"), 4 => AddressMode::ClampToBorder, 5 => todo!("Clamp"), - u32::MAX => data.map_or(AddressMode::Repeat, |d| texture_wrap(d.wrap_z)), + -1 => data.map_or(AddressMode::Repeat, |d| texture_wrap(d.wrap_z)), n => todo!("wrap {n}"), }, mag_filter: match usage.filter { 0 => FilterMode::Nearest, 1 => FilterMode::Linear, - u32::MAX => data.map_or(FilterMode::Nearest, |d| match d.filter { + -1 => data.map_or(FilterMode::Nearest, |d| match d.filter { ETextureFilter::Nearest => FilterMode::Nearest, ETextureFilter::Linear => FilterMode::Linear, }), @@ -391,7 +397,7 @@ fn sampler_descriptor_from_usage<'desc>( min_filter: match usage.filter { 0 => FilterMode::Nearest, 1 => FilterMode::Linear, - u32::MAX => data.map_or(FilterMode::Nearest, |d| match d.filter { + -1 => data.map_or(FilterMode::Nearest, |d| match d.filter { ETextureFilter::Nearest => FilterMode::Nearest, ETextureFilter::Linear => FilterMode::Linear, }), diff --git a/retrotool-gui/src/tabs/model.rs b/retrotool-gui/src/tabs/model.rs index 4a27897..ca4b9e4 100644 --- a/retrotool-gui/src/tabs/model.rs +++ b/retrotool-gui/src/tabs/model.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use bevy::{ asset::LoadState, core_pipeline::{clear_color::ClearColorConfig, tonemapping::Tonemapping}, @@ -5,9 +7,13 @@ use bevy::{ prelude::*, render::{camera::Viewport, view::RenderLayers}, }; -use bevy_egui::EguiContext; -use egui::{Sense, Widget}; -use retrolib::format::cmdl::CMaterialCache; +use bevy_egui::{EguiContext, EguiUserTextures}; +use egui::{Color32, Sense, Widget}; +use retrolib::format::{ + cmdl::{CMaterialCache, CMaterialDataInner, CMaterialTextureTokenData}, + txtr::K_FORM_TXTR, +}; +use uuid::Uuid; use crate::{ icon, @@ -21,7 +27,10 @@ use crate::{ model::{convert_aabb, load_model, ModelLod}, TemporaryLabel, }, - tabs::SystemTab, + tabs::{ + texture::{TextureTab, UiTexture}, + SystemTab, TabType, + }, AssetRef, TabState, }; @@ -45,9 +54,11 @@ pub struct ModelTab { pub handle: Handle, pub loaded: Option, pub selected_lod: usize, + pub selected_material: Option, pub camera: ModelCamera, pub diffuse_map: Handle, pub specular_map: Handle, + pub egui_textures: HashMap, } impl ModelTab { @@ -74,6 +85,7 @@ impl SystemTab for ModelTab { SResMut>, SResMut>, SResMut, + SResMut, ); type UiParam = (SCommands, SRes, SRes>); @@ -86,6 +98,7 @@ impl SystemTab for ModelTab { texture_assets, mut images, server, + mut egui_textures, ) = query; if let Some(loaded) = &self.loaded { for mesh in &loaded.meshes { @@ -151,10 +164,21 @@ impl SystemTab for ModelTab { self.camera.init(&convert_aabb(&asset.inner.head.bounds), false); self.diffuse_map = server.load("papermill_diffuse_rgb9e5_zstd.ktx2"); self.specular_map = server.load("papermill_specular_rgb9e5_zstd.ktx2"); + + // Build egui textures + for (texture_id, texture_handle) in &asset.textures { + let texture = texture_assets.get(texture_handle).unwrap(); + let ui_texture = UiTexture::new( + texture.slices[0][0].clone(), + images.as_mut(), + egui_textures.as_mut(), + ); + self.egui_textures.insert(*texture_id, ui_texture); + } } fn close(&mut self, query: SystemParamItem<'_, '_, Self::LoadParam>) { - let (mut commands, _, _, _, _, _, _) = query; + let (mut commands, _, _, _, _, _, _, _) = query; if let Some(loaded) = &self.loaded { for mesh in &loaded.meshes { if let Some(commands) = commands.get_entity(mesh.entity) { @@ -239,13 +263,28 @@ impl SystemTab for ModelTab { } for idx in loaded.lod[self.selected_lod].meshes.iter() { let mesh = &mut loaded.meshes[idx]; - ui.checkbox( - &mut mesh.visible, - format!( - "Mesh {idx} ({}, {}, {})", - mesh.unk_c, mesh.unk_e, loaded.materials[mesh.material_idx].name - ), - ); + ui.horizontal(|ui| { + ui.checkbox( + &mut mesh.visible, + format!( + "Mesh {idx} ({})", + loaded.materials[mesh.material_idx].name + ), + ); + if !matches!(mesh.unk_c, 0 | 1) { + ui.colored_label(Color32::RED, format!("(unk_c: {})", mesh.unk_c)); + } + if mesh.unk_e != 64 { + ui.colored_label(Color32::RED, format!("(unk_e: {})", mesh.unk_e)); + } + if ui + .small_button(format!("{}", icon::MATERIAL_DATA)) + .on_hover_text_at_pointer("View material") + .clicked() + { + self.selected_material = Some(mesh.material_idx); + } + }); if let Some(mut commands) = commands.get_entity(mesh.entity) { commands.insert(( if mesh.visible { Visibility::Visible } else { Visibility::Hidden }, @@ -255,6 +294,33 @@ impl SystemTab for ModelTab { } }); }); + if let Some(material_idx) = self.selected_material { + ui.push_id(format!("material_{}", material_idx), |ui| { + egui::Frame::group(ui.style()).fill(Color32::from_black_alpha(200)).show( + ui, + |ui| { + egui::ScrollArea::vertical() + // .max_height(rect.height() * 0.25) + .show(ui, |ui| { + if ui + .small_button(format!("{}", icon::PANEL_CLOSE)) + .on_hover_text_at_pointer("Close") + .clicked() + { + self.selected_material = None; + } + material_ui( + ui, + &loaded.materials[material_idx], + &self.egui_textures, + state, + server.as_ref(), + ); + }); + }, + ); + }); + } state.render_layer += 1; } else { ui.centered_and_justified(|ui| { @@ -275,3 +341,117 @@ impl SystemTab for ModelTab { fn id(&self) -> String { format!("{} {}", self.asset_ref.kind, self.asset_ref.id) } } + +fn property_with_value(ui: &mut egui::Ui, name: &str, value: String) { + ui.horizontal(|ui| { + ui.label(format!("{}:", name)); + if egui::Label::new(&value) + .sense(Sense::click()) + .ui(ui) + .on_hover_text_at_pointer("Click to copy") + .clicked() + { + ui.output_mut(|out| out.copied_text = value); + } + }); +} + +fn texture_ui( + ui: &mut egui::Ui, + texture: &CMaterialTextureTokenData, + textures: &HashMap, + state: &mut TabState, + server: &AssetServer, +) { + property_with_value(ui, "Texture ID", format!("{}", texture.id)); + if let Some(ui_texture) = textures.get(&texture.id) { + if ui_texture + .image_scaled(200.0) + .sense(Sense::click()) + .ui(ui) + .on_hover_cursor(egui::CursorIcon::PointingHand) + .clicked() + { + state.open_tab = Some(TabType::Texture(Box::new(TextureTab { + asset_ref: AssetRef { id: texture.id, kind: K_FORM_TXTR }, + handle: server.load(format!("{}.{}", texture.id, K_FORM_TXTR)), + ..default() + }))); + } + } + if let Some(usage) = &texture.usage { + property_with_value(ui, "Tex coord", format!("{}", usage.tex_coord)); + property_with_value(ui, "Filter", format!("{}", usage.filter)); + property_with_value(ui, "Wrap X", format!("{}", usage.wrap_x)); + property_with_value(ui, "Wrap Y", format!("{}", usage.wrap_y)); + property_with_value(ui, "Wrap Z", format!("{}", usage.wrap_z)); + } +} + +fn material_ui( + ui: &mut egui::Ui, + mat: &CMaterialCache, + textures: &HashMap, + state: &mut TabState, + server: &AssetServer, +) { + property_with_value(ui, "Material", mat.name.clone()); + property_with_value(ui, "Shader ID", format!("{}", mat.shader_id)); + property_with_value(ui, "Unk ID", format!("{}", mat.unk_guid)); + property_with_value(ui, "Flags", format!("{:032b}", mat.unk1)); + property_with_value(ui, "Unk", format!("{}", mat.unk2)); + ui.collapsing(format!("Render types: {}", mat.render_types.len()), |ui| { + for render_type in &mat.render_types { + ui.group(|ui| { + property_with_value(ui, "Data ID", format!("{}", render_type.data_id)); + property_with_value(ui, "Data type", format!("{}", render_type.data_type)); + property_with_value(ui, "Flag 1", format!("{}", render_type.flag1)); + property_with_value(ui, "Flag 2", format!("{}", render_type.flag2)); + }); + } + }); + ui.collapsing(format!("Data: {}", mat.data.len()), |ui| { + for material_data in &mat.data { + ui.group(|ui| { + property_with_value(ui, "Data ID", format!("{:?}", material_data.data_id)); + property_with_value(ui, "Data type", format!("{:?}", material_data.data_type)); + match &material_data.data { + CMaterialDataInner::Texture(texture) => { + texture_ui(ui, texture, textures, state, server); + } + CMaterialDataInner::Color(color) => { + property_with_value(ui, "Color", format!("{:?}", color.to_array())); + } + CMaterialDataInner::Scalar(scalar) => { + property_with_value(ui, "Scalar", format!("{}", scalar)); + } + CMaterialDataInner::Int1(int) => { + property_with_value(ui, "Int", format!("{}", int)); + } + CMaterialDataInner::Int4(int4) => { + property_with_value(ui, "Int4", format!("{:?}", int4.to_array())); + } + CMaterialDataInner::Mat4(mat4) => { + property_with_value(ui, "Mat4", format!("{:?}", mat4)); + } + CMaterialDataInner::LayeredTexture(layers) => { + for (idx, color) in layers.base.colors.iter().enumerate() { + property_with_value( + ui, + &format!("Color {idx}"), + format!("{:?}", color.to_array()), + ); + } + property_with_value(ui, "Flags", format!("{}", layers.base.flags)); + property_with_value(ui, "Unk", format!("{}", layers.base.unk)); + for texture in &layers.textures { + ui.group(|ui| { + texture_ui(ui, texture, textures, state, server); + }); + } + } + } + }); + } + }); +} diff --git a/retrotool-gui/src/tabs/texture.rs b/retrotool-gui/src/tabs/texture.rs index 70b6c82..dd37760 100644 --- a/retrotool-gui/src/tabs/texture.rs +++ b/retrotool-gui/src/tabs/texture.rs @@ -35,6 +35,43 @@ impl Default for TextureTab { } } +pub struct UiTexture { + _image: Handle, + texture_id: egui::TextureId, + width: u32, + height: u32, +} + +impl UiTexture { + pub fn new( + image: Image, + images: &mut Assets, + egui_textures: &mut EguiUserTextures, + ) -> Self { + let width = image.texture_descriptor.size.width; + let height = image.texture_descriptor.size.height; + let handle = images.add(image); + let weak_handle = handle.clone_weak(); + Self { _image: handle, texture_id: egui_textures.add_image(weak_handle), width, height } + } + + #[allow(dead_code)] + pub fn image(&self) -> egui::Image { + egui::Image::new(self.texture_id, egui::Vec2::new(self.width as f32, self.height as f32)) + } + + pub fn image_scaled(&self, max_size: f32) -> egui::Image { + let size = if self.height > self.width { + let ratio = max_size / self.height as f32; + egui::Vec2::new(self.width as f32 * ratio, max_size) + } else { + let ratio = max_size / self.width as f32; + egui::Vec2::new(max_size, self.height as f32 * ratio) + }; + egui::Image::new(self.texture_id, size) + } +} + impl SystemTab for TextureTab { type LoadParam = (SRes>, SResMut>, SResMut); diff --git a/retrotool/src/cmd/cmdl.rs b/retrotool/src/cmd/cmdl.rs index 00fe291..df8a2e1 100644 --- a/retrotool/src/cmd/cmdl.rs +++ b/retrotool/src/cmd/cmdl.rs @@ -488,13 +488,13 @@ fn convert(args: ConvertArgs) -> Result<()> { mag_filter: match usage.filter { 0 => Some(Valid(json::texture::MagFilter::Nearest)), 1 => Some(Valid(json::texture::MagFilter::Linear)), - u32::MAX => None, + -1 => None, filter => todo!("Filter {filter}"), }, min_filter: match usage.filter { 0 => Some(Valid(json::texture::MinFilter::Nearest)), 1 => Some(Valid(json::texture::MinFilter::Linear)), - u32::MAX => None, + -1 => None, filter => todo!("Filter {filter}"), }, name: Some(format!("{} sampler", texture.id)),