mirror of
https://github.com/PrimeDecomp/retrotool.git
synced 2026-07-12 18:18:58 -07:00
Add material data viewer for CMDL
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
|
||||
+192
-12
@@ -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<ModelAsset>,
|
||||
pub loaded: Option<LoadedModel>,
|
||||
pub selected_lod: usize,
|
||||
pub selected_material: Option<usize>,
|
||||
pub camera: ModelCamera,
|
||||
pub diffuse_map: Handle<Image>,
|
||||
pub specular_map: Handle<Image>,
|
||||
pub egui_textures: HashMap<Uuid, UiTexture>,
|
||||
}
|
||||
|
||||
impl ModelTab {
|
||||
@@ -74,6 +85,7 @@ impl SystemTab for ModelTab {
|
||||
SResMut<Assets<TextureAsset>>,
|
||||
SResMut<Assets<Image>>,
|
||||
SResMut<AssetServer>,
|
||||
SResMut<EguiUserTextures>,
|
||||
);
|
||||
type UiParam = (SCommands, SRes<AssetServer>, SRes<Assets<ModelAsset>>);
|
||||
|
||||
@@ -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<Uuid, UiTexture>,
|
||||
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<Uuid, UiTexture>,
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,6 +35,43 @@ impl Default for TextureTab {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UiTexture {
|
||||
_image: Handle<Image>,
|
||||
texture_id: egui::TextureId,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
impl UiTexture {
|
||||
pub fn new(
|
||||
image: Image,
|
||||
images: &mut Assets<Image>,
|
||||
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<Assets<TextureAsset>>, SResMut<Assets<Image>>, SResMut<EguiUserTextures>);
|
||||
|
||||
@@ -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)),
|
||||
|
||||
Reference in New Issue
Block a user