From 4e89ad99accb6b717eda19f7f18ec5f16a778454 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 19 Mar 2023 12:24:00 -0400 Subject: [PATCH] Rework model loader; start supporting alpha blend materials --- Cargo.lock | 16 +- retrotool-gui/Cargo.toml | 5 +- retrotool-gui/assets/custom_material.wgsl | 2 +- retrotool-gui/src/loaders/model.rs | 350 ++++++++++++++++++- retrotool-gui/src/main.rs | 8 +- retrotool-gui/src/material.rs | 20 +- retrotool-gui/src/render/model.rs | 406 ++++------------------ retrotool-gui/src/tabs/mod.rs | 22 +- retrotool-gui/src/tabs/modcon.rs | 42 ++- retrotool-gui/src/tabs/model.rs | 80 +++-- retrotool-gui/src/tabs/project.rs | 12 +- retrotool/Cargo.toml | 6 +- 12 files changed, 536 insertions(+), 433 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd13efa..f858028 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1575,7 +1575,7 @@ checksum = "e8af5ef47e2ed89d23d0ecbc1b681b30390069de70260937877514377fc24feb" dependencies = [ "bit_field", "flume", - "half", + "half 2.2.1", "lebe", "miniz_oxide", "smallvec", @@ -1849,6 +1849,16 @@ dependencies = [ "crunchy", ] +[[package]] +name = "half" +version = "3.0.0-dev" +source = "git+https://github.com/encounter/half-rs?branch=inline#807f00f6b83419b20942ed240f46ceb36d7de162" +dependencies = [ + "bytemuck", + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -2844,7 +2854,7 @@ dependencies = [ "ddsfile", "env_logger", "gltf-json", - "half", + "half 3.0.0-dev", "image", "log", "memmap2", @@ -2870,7 +2880,7 @@ dependencies = [ "bytemuck", "egui", "egui_dock", - "half", + "half 3.0.0-dev", "image", "log", "num-traits", diff --git a/retrotool-gui/Cargo.toml b/retrotool-gui/Cargo.toml index 94036ef..be2c9fe 100644 --- a/retrotool-gui/Cargo.toml +++ b/retrotool-gui/Cargo.toml @@ -13,6 +13,7 @@ readme = "README.md" default = [] dynamic = ["bevy/dynamic_linking"] embed = ["bevy_embedded_assets"] +nightly = ["half/use-intrinsics"] [dependencies] anyhow = "1.0.69" @@ -22,10 +23,10 @@ bevy_embedded_assets = { version = "0.7.0", optional = true } bevy_mod_raycast = { git = "https://github.com/encounter/bevy_mod_raycast", branch = "updates" } binrw = "0.11.1" bit-set = "0.5.3" -bytemuck = "1.13.0" +bytemuck = { version = "1.13.0", features = ["min_const_generics"] } egui = "0.21.0" egui_dock = "0.4.0" -half = "2.2.1" +half = { git = "https://github.com/encounter/half-rs", branch = "inline", features = ["bytemuck"] } image = "0.24.5" log = "0.4.17" num-traits = "0.2.15" diff --git a/retrotool-gui/assets/custom_material.wgsl b/retrotool-gui/assets/custom_material.wgsl index 6126088..346da9c 100644 --- a/retrotool-gui/assets/custom_material.wgsl +++ b/retrotool-gui/assets/custom_material.wgsl @@ -434,7 +434,7 @@ fn pbr( let occlusion = in.occlusion; // output_color = alpha_discard(in.material, output_color); - output_color.a = 1.0; + // output_color.a = 1.0; // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886" let NdotV = max(dot(in.N, in.V), 0.0001); diff --git a/retrotool-gui/src/loaders/model.rs b/retrotool-gui/src/loaders/model.rs index 819823e..a54dd00 100644 --- a/retrotool-gui/src/loaders/model.rs +++ b/retrotool-gui/src/loaders/model.rs @@ -1,19 +1,38 @@ -use std::path::PathBuf; +use std::{num::NonZeroU8, path::PathBuf}; -use anyhow::Error; +use anyhow::{Error, Result}; use bevy::{ asset::{AssetLoader, AssetPath, BoxedFuture, LoadContext, LoadState, LoadedAsset}, prelude::*, - utils::HashMap, + render::{render_resource::SamplerDescriptor, texture::ImageSampler}, + utils::{hashbrown::hash_map::Entry, HashMap}, }; use binrw::Endian; use retrolib::format::{ - cmdl::{CMaterialDataInner, ModelData, K_FORM_CMDL}, + cmdl::{ + CMaterialCache, CMaterialDataInner, EMaterialDataId, ModelData, STextureUsageInfo, + K_FORM_CMDL, + }, foot::{locate_asset_id, locate_meta}, + txtr::{ + ETextureAnisotropicRatio, ETextureFilter, ETextureMipFilter, ETextureWrap, + STextureSamplerData, + }, }; use uuid::Uuid; +use wgpu_types::{AddressMode, Face, FilterMode}; -use crate::{loaders::texture::TextureAsset, AssetRef}; +use crate::{ + loaders::texture::TextureAsset, material::CustomMaterial, render::model::MESH_FLAG_OPAQUE, + AssetRef, +}; + +#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)] +pub struct MaterialKey { + pub material_idx: usize, + pub mesh_flags: u16, + pub mesh_mirrored: bool, +} #[derive(Debug, Clone, bevy::reflect::TypeUuid)] #[uuid = "83269869-1209-408e-8835-bc6f2496e829"] @@ -21,12 +40,8 @@ pub struct ModelAsset { pub asset_ref: AssetRef, pub inner: ModelData, pub textures: HashMap>, -} - -impl ModelAsset { - pub fn get_load_state(&self, server: &AssetServer) -> LoadState { - server.get_group_load_state(self.textures.values().map(|h| h.id())) - } + pub texture_images: HashMap>, + pub materials: HashMap>, } pub struct ModelAssetLoader; @@ -40,7 +55,7 @@ impl AssetLoader for ModelAssetLoader { &'a self, bytes: &'a [u8], load_context: &'a mut LoadContext, - ) -> BoxedFuture<'a, anyhow::Result<(), Error>> { + ) -> BoxedFuture<'a, Result<(), Error>> { Box::pin(async move { let id = locate_asset_id(bytes, Endian::Little)?; let meta = locate_meta(bytes, Endian::Little)?; @@ -84,6 +99,8 @@ impl AssetLoader for ModelAssetLoader { asset_ref: AssetRef { id, kind: K_FORM_CMDL }, inner: data, textures, + texture_images: default(), + materials: default(), }) .with_dependencies(dependencies.into_values().collect()), ); @@ -93,3 +110,312 @@ impl AssetLoader for ModelAssetLoader { fn extensions(&self) -> &[&str] { &["cmdl", "smdl", "wmdl"] } } + +impl ModelAsset { + pub fn get_load_state(&self, server: &AssetServer) -> LoadState { + server.get_group_load_state(self.textures.values().map(|h| h.id())) + } + + pub fn sampler_data<'asset>( + &self, + texture_id: &Uuid, + texture_assets: &'asset Assets, + ) -> Option<&'asset STextureSamplerData> { + self.textures + .get(texture_id) + .and_then(|handle| texture_assets.get(handle)) + .map(|txtr| &txtr.inner.head.sampler_data) + } + + pub fn build_texture_images( + &mut self, + texture_assets: &Assets, + images: &mut Assets, + ) { + // Build sampler descriptors + let mut sampler_descriptors = HashMap::::new(); + for mat in &self.inner.mtrl.materials { + for data in &mat.data { + match &data.data { + CMaterialDataInner::Texture(texture) => { + if let Some(usage) = &texture.usage { + let sampler_data = self.sampler_data(&texture.id, texture_assets); + sampler_descriptors.insert( + texture.id, + sampler_descriptor_from_usage(usage, sampler_data), + ); + } + } + CMaterialDataInner::LayeredTexture(layers) => { + for texture in &layers.textures { + if let Some(usage) = &texture.usage { + let sampler_data = self.sampler_data(&texture.id, texture_assets); + sampler_descriptors.insert( + texture.id, + sampler_descriptor_from_usage(usage, sampler_data), + ); + } + } + } + _ => continue, + } + } + } + + // Build texture images + for (id, handle) in &self.textures { + let asset = texture_assets.get(handle).unwrap(); + let mut image = asset.texture.clone(); + if let Some(desc) = sampler_descriptors.get(id) { + image.sampler_descriptor = ImageSampler::Descriptor(desc.clone()); + } + self.texture_images.insert(*id, images.add(image)); + } + } + + pub fn material( + &mut self, + key: &MaterialKey, + assets: &mut Assets, + ) -> Result> { + Ok(match self.materials.entry(*key) { + Entry::Occupied(e) => e.get().clone(), + Entry::Vacant(e) => { + let material = + build_material(key, &self.inner.mtrl.materials, &self.texture_images)?; + let handle = assets.add(material); + e.insert(handle.clone()); + handle + } + }) + } +} + +fn build_material( + key: &MaterialKey, + materials: &[CMaterialCache], + texture_images: &HashMap>, +) -> Result { + let mut out_mat = CustomMaterial { + alpha_mode: if key.mesh_flags & MESH_FLAG_OPAQUE != 0 { + AlphaMode::Opaque + } else { + AlphaMode::Blend + }, + cull_mode: Some(if key.mesh_mirrored { Face::Front } else { Face::Back }), + ..default() + }; + for data in &materials[key.material_idx].data { + match data.data_id { + EMaterialDataId::DIFT | EMaterialDataId::BCLR => match &data.data { + CMaterialDataInner::Texture(texture) => { + out_mat.base_color_l0 = Color::WHITE; + out_mat.base_color_texture_0 = texture_images.get(&texture.id).cloned(); + out_mat.base_color_uv_0 = + texture.usage.as_ref().map(|u| u.tex_coord).unwrap_or_default(); + } + _ => { + log::warn!("Unsupported material data type for DIFT {:?}", data.data_type); + } + }, + 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_texture_0 = + texture_images.get(&layers.textures[0].id).cloned(); + out_mat.base_color_texture_1 = + texture_images.get(&layers.textures[1].id).cloned(); + out_mat.base_color_texture_2 = + texture_images.get(&layers.textures[2].id).cloned(); + out_mat.base_color_uv_0 = + layers.textures[0].usage.as_ref().map(|u| u.tex_coord).unwrap_or_default(); + out_mat.base_color_uv_1 = + layers.textures[1].usage.as_ref().map(|u| u.tex_coord).unwrap_or_default(); + out_mat.base_color_uv_2 = + layers.textures[2].usage.as_ref().map(|u| u.tex_coord).unwrap_or_default(); + } + _ => { + log::warn!("Unsupported material data type for BCRL {:?}", data.data_type); + } + }, + EMaterialDataId::DIFC => match &data.data { + CMaterialDataInner::Color(color) => { + out_mat.base_color = Color::rgba(color.r, color.g, color.b, color.a); + } + _ => log::warn!("Unsupported material data type for DIFC {:?}", data.data_type), + }, + EMaterialDataId::ICAN => match &data.data { + CMaterialDataInner::Texture(texture) => { + out_mat.emissive_texture = texture_images.get(&texture.id).cloned(); + out_mat.emissive_uv = + texture.usage.as_ref().map(|u| u.tex_coord).unwrap_or_default(); + } + _ => log::warn!("Unsupported material data type for ICAN {:?}", data.data_type), + }, + EMaterialDataId::ICNC => match &data.data { + CMaterialDataInner::Color(color) => { + out_mat.emissive_color = Color::rgba(color.r, color.g, color.b, color.a); + } + _ => log::warn!("Unsupported material data type for ICNC {:?}", data.data_type), + }, + EMaterialDataId::NMAP => match &data.data { + CMaterialDataInner::Texture(texture) => { + out_mat.normal_map_l0 = Color::WHITE; + out_mat.normal_map_texture_0 = texture_images.get(&texture.id).cloned(); + out_mat.normal_map_uv_0 = texture.usage.as_ref().unwrap().tex_coord; + } + _ => { + log::warn!("Unsupported material data type for NMAP {:?}", data.data_type); + } + }, + 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_texture_0 = + texture_images.get(&layers.textures[0].id).cloned(); + out_mat.normal_map_texture_1 = + texture_images.get(&layers.textures[1].id).cloned(); + out_mat.normal_map_texture_2 = + texture_images.get(&layers.textures[2].id).cloned(); + out_mat.normal_map_uv_0 = + layers.textures[0].usage.as_ref().map(|u| u.tex_coord).unwrap_or_default(); + out_mat.normal_map_uv_1 = + layers.textures[1].usage.as_ref().map(|u| u.tex_coord).unwrap_or_default(); + out_mat.normal_map_uv_2 = + layers.textures[2].usage.as_ref().map(|u| u.tex_coord).unwrap_or_default(); + } + _ => { + log::warn!("Unsupported material data type for NRML {:?}", data.data_type); + } + }, + EMaterialDataId::METL => match &data.data { + CMaterialDataInner::Texture(texture) => { + out_mat.metallic_map_l0 = Color::WHITE; + out_mat.metallic_map_texture_0 = texture_images.get(&texture.id).cloned(); + out_mat.metallic_map_uv_0 = texture.usage.as_ref().unwrap().tex_coord; + } + _ => { + log::warn!("Unsupported material data type for METL {:?}", data.data_type); + } + }, + 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_texture_0 = + texture_images.get(&layers.textures[0].id).cloned(); + out_mat.metallic_map_texture_1 = + texture_images.get(&layers.textures[1].id).cloned(); + out_mat.metallic_map_texture_2 = + texture_images.get(&layers.textures[2].id).cloned(); + out_mat.metallic_map_uv_0 = + layers.textures[0].usage.as_ref().map(|u| u.tex_coord).unwrap_or_default(); + out_mat.metallic_map_uv_1 = + layers.textures[1].usage.as_ref().map(|u| u.tex_coord).unwrap_or_default(); + out_mat.metallic_map_uv_2 = + layers.textures[2].usage.as_ref().map(|u| u.tex_coord).unwrap_or_default(); + } + _ => { + log::warn!("Unsupported material data type for MTLL {:?}", data.data_type); + } + }, + id => { + log::warn!("Unsupported material data ID {id:?}"); + } + } + } + Ok(out_mat) +} + +fn texture_wrap(wrap: ETextureWrap) -> AddressMode { + match wrap { + ETextureWrap::ClampToEdge => AddressMode::ClampToEdge, + ETextureWrap::Repeat => AddressMode::Repeat, + ETextureWrap::MirroredRepeat => AddressMode::MirrorRepeat, + ETextureWrap::MirrorClamp => todo!("Mirror clamp"), + ETextureWrap::ClampToBorder => AddressMode::ClampToBorder, + ETextureWrap::Clamp => todo!("Clamp"), + } +} + +fn sampler_descriptor_from_usage<'desc>( + usage: &STextureUsageInfo, + data: Option<&STextureSamplerData>, +) -> SamplerDescriptor<'desc> { + SamplerDescriptor { + label: None, + address_mode_u: match usage.wrap_x { + 0 => AddressMode::ClampToEdge, + 1 => AddressMode::Repeat, + 2 => AddressMode::MirrorRepeat, + 3 => todo!("Mirror clamp"), + 4 => AddressMode::ClampToBorder, + 5 => todo!("Clamp"), + u32::MAX => data.map_or(AddressMode::Repeat, |d| texture_wrap(d.wrap_x)), + n => todo!("wrap {n}"), + }, + address_mode_v: match usage.wrap_y { + 0 => AddressMode::ClampToEdge, + 1 => AddressMode::Repeat, + 2 => AddressMode::MirrorRepeat, + 3 => todo!("Mirror clamp"), + 4 => AddressMode::ClampToBorder, + 5 => todo!("Clamp"), + u32::MAX => data.map_or(AddressMode::Repeat, |d| texture_wrap(d.wrap_y)), + n => todo!("wrap {n}"), + }, + address_mode_w: match usage.wrap_z { + 0 => AddressMode::ClampToEdge, + 1 => AddressMode::Repeat, + 2 => AddressMode::MirrorRepeat, + 3 => todo!("Mirror clamp"), + 4 => AddressMode::ClampToBorder, + 5 => todo!("Clamp"), + u32::MAX => 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 { + ETextureFilter::Nearest => FilterMode::Nearest, + ETextureFilter::Linear => FilterMode::Linear, + }), + n => todo!("Filter {n}"), + }, + min_filter: match usage.filter { + 0 => FilterMode::Nearest, + 1 => FilterMode::Linear, + u32::MAX => data.map_or(FilterMode::Nearest, |d| match d.filter { + ETextureFilter::Nearest => FilterMode::Nearest, + ETextureFilter::Linear => FilterMode::Linear, + }), + n => todo!("Filter {n}"), + }, + mipmap_filter: data.map_or(FilterMode::Nearest, |d| match d.mip_filter { + ETextureMipFilter::Nearest => FilterMode::Nearest, + ETextureMipFilter::Linear => FilterMode::Linear, + }), + lod_min_clamp: 0.0, + lod_max_clamp: f32::MAX, + compare: None, + anisotropy_clamp: NonZeroU8::new( + data.map(|d| match d.aniso { + ETextureAnisotropicRatio::None => 0, + ETextureAnisotropicRatio::Ratio1 => 1, + ETextureAnisotropicRatio::Ratio2 => 2, + ETextureAnisotropicRatio::Ratio4 => 4, + ETextureAnisotropicRatio::Ratio8 => 8, + ETextureAnisotropicRatio::Ratio16 => 16, + }) + .unwrap_or_default(), + ), + // anisotropy_clamp: NonZeroU8::new(8), + border_color: None, + } +} diff --git a/retrotool-gui/src/main.rs b/retrotool-gui/src/main.rs index d933683..d6005f4 100644 --- a/retrotool-gui/src/main.rs +++ b/retrotool-gui/src/main.rs @@ -208,18 +208,18 @@ fn ui_system(world: &mut World) { for tab in tabs { match tab { TabType::Project(tab) => { - load_tab(world, &mut ctx, tab); + load_tab(world, &mut ctx, tab.as_mut()); } TabType::Texture(tab) => { - load_tab(world, &mut ctx, tab); + load_tab(world, &mut ctx, tab.as_mut()); tab_assets.push(tab.asset_ref); } TabType::Model(tab) => { - load_tab(world, &mut ctx, tab); + load_tab(world, &mut ctx, tab.as_mut()); tab_assets.push(tab.asset_ref); } TabType::ModCon(tab) => { - load_tab(world, &mut ctx, tab); + load_tab(world, &mut ctx, tab.as_mut()); tab_assets.push(tab.asset_ref); } TabType::Empty => {} diff --git a/retrotool-gui/src/material.rs b/retrotool-gui/src/material.rs index 8ea268c..2cda9d4 100644 --- a/retrotool-gui/src/material.rs +++ b/retrotool-gui/src/material.rs @@ -7,15 +7,15 @@ use bevy::{ // A "high" random id should be used for custom attributes to ensure consistent sorting and avoid collisions with other attributes. // See the MeshVertexAttribute docs for more info. -const ATTRIBUTE_UV_1: MeshVertexAttribute = +pub const ATTRIBUTE_UV_1: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_Uv_1", 988540917, VertexFormat::Float32x2); -const ATTRIBUTE_UV_2: MeshVertexAttribute = +pub const ATTRIBUTE_UV_2: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_Uv_2", 988540918, VertexFormat::Float32x2); -const ATTRIBUTE_UV_3: MeshVertexAttribute = +pub const ATTRIBUTE_UV_3: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_Uv_3", 988540919, VertexFormat::Float32x2); -const ATTRIBUTE_TANGENT_1: MeshVertexAttribute = +pub const ATTRIBUTE_TANGENT_1: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_Tangent_1", 988540920, VertexFormat::Float32x4); -const ATTRIBUTE_TANGENT_2: MeshVertexAttribute = +pub const ATTRIBUTE_TANGENT_2: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_Tangent_2", 988540921, VertexFormat::Float32x4); // This is the struct that will be passed to your shader @@ -26,6 +26,8 @@ const ATTRIBUTE_TANGENT_2: MeshVertexAttribute = pub struct CustomMaterial { #[reflect(ignore)] pub cull_mode: Option, + #[reflect(ignore)] + pub alpha_mode: AlphaMode, #[uniform(0)] pub base_color: Color, #[texture(1)] @@ -49,8 +51,6 @@ pub struct CustomMaterial { pub base_color_l1: Color, #[uniform(0)] pub base_color_l2: Color, - // pub ican_color: Color, - // pub ican_unmasked_color: Color, #[texture(7)] #[sampler(8)] pub normal_map_texture_0: Option>, @@ -106,6 +106,7 @@ impl Default for CustomMaterial { fn default() -> Self { Self { cull_mode: Some(Face::Back), + alpha_mode: AlphaMode::Opaque, base_color: Color::WHITE, base_color_texture_0: None, base_color_texture_1: None, @@ -136,7 +137,7 @@ impl Default for CustomMaterial { metallic_map_l2: Color::NONE, emissive_texture: None, emissive_uv: 0, - emissive_color: Color::BLACK, + emissive_color: Color::NONE, } } } @@ -155,6 +156,9 @@ impl Material for CustomMaterial { fn fragment_shader() -> ShaderRef { "custom_material.wgsl".into() } + #[inline] + fn alpha_mode(&self) -> AlphaMode { self.alpha_mode } + fn specialize( _pipeline: &MaterialPipeline, descriptor: &mut RenderPipelineDescriptor, diff --git a/retrotool-gui/src/render/model.rs b/retrotool-gui/src/render/model.rs index de914c8..71d3347 100644 --- a/retrotool-gui/src/render/model.rs +++ b/retrotool-gui/src/render/model.rs @@ -11,32 +11,40 @@ use bevy::{ }, }; use bit_set::BitSet; -use half::f16; -use retrolib::format::{ - cmdl::{ - CMaterialDataInner, EBufferType, EMaterialDataId, EVertexComponent, EVertexDataFormat, - ModelData, STextureUsageInfo, SVertexDataComponent, +use half::prelude::*; +use retrolib::{ + array_ref, + format::{ + cmdl::{ + CMaterialCache, CMaterialDataInner, EBufferType, EMaterialDataId, EVertexComponent, + EVertexDataFormat, ModelData, STextureUsageInfo, SVertexDataComponent, + }, + txtr::{ + ETextureAnisotropicRatio, ETextureFilter, ETextureMipFilter, ETextureWrap, + STextureSamplerData, + }, + CAABox, CTransform4f, }, - txtr::{ - ETextureAnisotropicRatio, ETextureFilter, ETextureMipFilter, ETextureWrap, - STextureSamplerData, - }, - CAABox, CTransform4f, }; use uuid::Uuid; use wgpu_types::{AddressMode, Face, FilterMode, PrimitiveTopology}; use crate::{ loaders::{model::ModelAsset, texture::TextureAsset}, - material::CustomMaterial, + material::{ + CustomMaterial, ATTRIBUTE_TANGENT_1, ATTRIBUTE_TANGENT_2, ATTRIBUTE_UV_1, ATTRIBUTE_UV_2, + ATTRIBUTE_UV_3, + }, }; +pub const MESH_FLAG_OPAQUE: u16 = 1; + pub struct BuiltMesh { pub mesh: Handle, - pub material: Handle, - pub mirrored_material: Handle, - pub material_name: String, + pub material_idx: usize, pub visible: bool, + pub flags: u16, + pub unk_e: u16, } pub struct ModelLod { @@ -47,70 +55,16 @@ pub struct ModelLod { pub struct BuiltModel { pub meshes: Vec, pub lod: Vec, + pub materials: Vec, pub aabb: Aabb, } -pub fn load_model( - asset: &ModelAsset, - _commands: &mut Commands, - // server: &AssetServer, - texture_assets: &Assets, - images: &mut Assets, - materials: &mut Assets, - meshes: &mut Assets, - // center: bool, -) -> Result { +pub fn load_model(asset: &ModelAsset, meshes: &mut Assets) -> Result { let ModelAsset { inner: ModelData { head, mtrl, mesh, vbuf, ibuf, vtx_buffers, idx_buffers }, - textures, .. } = asset; - // Build sampler descriptors - let mut sampler_descriptors = HashMap::::new(); - for mat in &mtrl.materials { - for data in &mat.data { - match &data.data { - CMaterialDataInner::Texture(texture) => { - if let Some(usage) = &texture.usage { - let sampler_data = textures - .get(&texture.id) - .and_then(|handle| texture_assets.get(handle)) - .map(|txtr| &txtr.inner.head.sampler_data); - sampler_descriptors - .insert(texture.id, sampler_descriptor_from_usage(usage, sampler_data)); - } - } - CMaterialDataInner::LayeredTexture(layers) => { - for texture in &layers.textures { - if let Some(usage) = &texture.usage { - let sampler_data = textures - .get(&texture.id) - .and_then(|handle| texture_assets.get(handle)) - .map(|txtr| &txtr.inner.head.sampler_data); - sampler_descriptors.insert( - texture.id, - sampler_descriptor_from_usage(usage, sampler_data), - ); - } - } - } - _ => continue, - } - } - } - - // Build texture images - let mut texture_handles = HashMap::>::new(); - for (id, handle) in textures { - let asset = texture_assets.get(handle).unwrap(); - let mut image = asset.texture.clone(); - if let Some(desc) = sampler_descriptors.get(id) { - image.sampler_descriptor = ImageSampler::Descriptor(desc.clone()); - } - texture_handles.insert(*id, images.add(image)); - } - // Build vertex buffers let mut buf_infos: Vec = Vec::with_capacity(vtx_buffers.len()); let mut cur_buf = 0usize; @@ -141,168 +95,6 @@ pub fn load_model( index_buffers.push(out); } - // Build materials - let mut material_handles = Vec::with_capacity(mtrl.materials.len()); - let mut material_handles_mirrored = Vec::with_capacity(mtrl.materials.len()); - for mat in &mtrl.materials { - let mut out_mat = CustomMaterial::default(); - // log::info!("Shader {}, unk {}", mat.shader_id, mat.unk_guid); - for data in &mat.data { - match data.data_id { - EMaterialDataId::DIFT | EMaterialDataId::BCLR => match &data.data { - CMaterialDataInner::Texture(texture) => { - out_mat.base_color_l0 = Color::WHITE; - out_mat.base_color_texture_0 = texture_handles.get(&texture.id).cloned(); - out_mat.base_color_uv_0 = - texture.usage.as_ref().map(|u| u.tex_coord).unwrap_or_default(); - } - _ => { - log::warn!("Unsupported material data type for DIFT {:?}", data.data_type); - } - }, - 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_texture_0 = - texture_handles.get(&layers.textures[0].id).cloned(); - out_mat.base_color_texture_1 = - texture_handles.get(&layers.textures[1].id).cloned(); - out_mat.base_color_texture_2 = - texture_handles.get(&layers.textures[2].id).cloned(); - out_mat.base_color_uv_0 = layers.textures[0] - .usage - .as_ref() - .map(|u| u.tex_coord) - .unwrap_or_default(); - out_mat.base_color_uv_1 = layers.textures[1] - .usage - .as_ref() - .map(|u| u.tex_coord) - .unwrap_or_default(); - out_mat.base_color_uv_2 = layers.textures[2] - .usage - .as_ref() - .map(|u| u.tex_coord) - .unwrap_or_default(); - } - _ => { - log::warn!("Unsupported material data type for BCRL {:?}", data.data_type); - } - }, - EMaterialDataId::DIFC => match &data.data { - CMaterialDataInner::Color(color) => { - out_mat.base_color = Color::rgba(color.r, color.g, color.b, color.a); - } - _ => log::warn!("Unsupported material data type for DIFC {:?}", data.data_type), - }, - EMaterialDataId::ICAN => match &data.data { - CMaterialDataInner::Texture(texture) => { - out_mat.emissive_texture = texture_handles.get(&texture.id).cloned(); - out_mat.emissive_uv = - texture.usage.as_ref().map(|u| u.tex_coord).unwrap_or_default(); - } - _ => log::warn!("Unsupported material data type for ICAN {:?}", data.data_type), - }, - EMaterialDataId::ICNC => match &data.data { - CMaterialDataInner::Color(color) => { - out_mat.emissive_color = Color::rgba(color.r, color.g, color.b, color.a); - } - _ => log::warn!("Unsupported material data type for ICNC {:?}", data.data_type), - }, - EMaterialDataId::NMAP => match &data.data { - CMaterialDataInner::Texture(texture) => { - out_mat.normal_map_l0 = Color::WHITE; - out_mat.normal_map_texture_0 = texture_handles.get(&texture.id).cloned(); - out_mat.normal_map_uv_0 = texture.usage.as_ref().unwrap().tex_coord; - } - _ => { - log::warn!("Unsupported material data type for NMAP {:?}", data.data_type); - } - }, - 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_texture_0 = - texture_handles.get(&layers.textures[0].id).cloned(); - out_mat.normal_map_texture_1 = - texture_handles.get(&layers.textures[1].id).cloned(); - out_mat.normal_map_texture_2 = - texture_handles.get(&layers.textures[2].id).cloned(); - out_mat.normal_map_uv_0 = layers.textures[0] - .usage - .as_ref() - .map(|u| u.tex_coord) - .unwrap_or_default(); - out_mat.normal_map_uv_1 = layers.textures[1] - .usage - .as_ref() - .map(|u| u.tex_coord) - .unwrap_or_default(); - out_mat.normal_map_uv_2 = layers.textures[2] - .usage - .as_ref() - .map(|u| u.tex_coord) - .unwrap_or_default(); - } - _ => { - log::warn!("Unsupported material data type for NRML {:?}", data.data_type); - } - }, - EMaterialDataId::METL => match &data.data { - CMaterialDataInner::Texture(texture) => { - out_mat.metallic_map_l0 = Color::WHITE; - out_mat.metallic_map_texture_0 = texture_handles.get(&texture.id).cloned(); - out_mat.metallic_map_uv_0 = texture.usage.as_ref().unwrap().tex_coord; - } - _ => { - log::warn!("Unsupported material data type for METL {:?}", data.data_type); - } - }, - 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_texture_0 = - texture_handles.get(&layers.textures[0].id).cloned(); - out_mat.metallic_map_texture_1 = - texture_handles.get(&layers.textures[1].id).cloned(); - out_mat.metallic_map_texture_2 = - texture_handles.get(&layers.textures[2].id).cloned(); - out_mat.metallic_map_uv_0 = layers.textures[0] - .usage - .as_ref() - .map(|u| u.tex_coord) - .unwrap_or_default(); - out_mat.metallic_map_uv_1 = layers.textures[1] - .usage - .as_ref() - .map(|u| u.tex_coord) - .unwrap_or_default(); - out_mat.metallic_map_uv_2 = layers.textures[2] - .usage - .as_ref() - .map(|u| u.tex_coord) - .unwrap_or_default(); - } - _ => { - log::warn!("Unsupported material data type for MTLL {:?}", data.data_type); - } - }, - id => { - log::warn!("Unsupported material data ID {id:?}"); - } - } - } - material_handles.push(materials.add(out_mat.clone())); - out_mat.cull_mode = Some(Face::Front); - material_handles_mirrored.push(materials.add(out_mat)); - } - // Process meshes let aabb = Aabb::from_min_max( Vec3::new(head.bounds.min.x, head.bounds.min.y, head.bounds.min.z), @@ -330,10 +122,10 @@ pub fn load_model( } out_meshes.push(BuiltMesh { mesh: meshes.add(out_mesh), - material: material_handles[in_mesh.material_idx as usize].clone(), - mirrored_material: material_handles_mirrored[in_mesh.material_idx as usize].clone(), - material_name: mtrl.materials[in_mesh.material_idx as usize].name.clone(), + material_idx: in_mesh.material_idx as usize, visible: true, + flags: in_mesh.unk_c, + unk_e: in_mesh.unk_e, }); } @@ -348,7 +140,7 @@ pub fn load_model( lod.push(ModelLod { meshes: visible, distance: mesh.lod_rules.get(idx).map(|r| r.value) }); } - Ok(BuiltModel { meshes: out_meshes, lod, aabb }) + Ok(BuiltModel { meshes: out_meshes, lod, materials: mtrl.materials.clone(), aabb }) } pub fn convert_aabb(aabb: &CAABox) -> Aabb { @@ -397,6 +189,18 @@ where out } +trait HalfArray { + fn to_f32_array(self) -> [f32; N]; +} + +impl HalfArray for [u16; N] { + fn to_f32_array(self) -> [f32; N] { + let mut dst = [0f32; N]; + self.reinterpret_cast::().convert_to_f32_slice(&mut dst); + dst + } +} + fn convert_component( input: &[u8], component: &SVertexDataComponent, @@ -408,11 +212,19 @@ fn convert_component( Position => Mesh::ATTRIBUTE_POSITION, Normal => Mesh::ATTRIBUTE_NORMAL, Tangent0 => Mesh::ATTRIBUTE_TANGENT, + Tangent1 => ATTRIBUTE_TANGENT_1, + Tangent2 => ATTRIBUTE_TANGENT_2, TexCoord0 => Mesh::ATTRIBUTE_UV_0, + TexCoord1 => ATTRIBUTE_UV_1, + TexCoord2 => ATTRIBUTE_UV_2, + TexCoord3 => ATTRIBUTE_UV_3, Color => Mesh::ATTRIBUTE_COLOR, // BoneIndices => Mesh::ATTRIBUTE_JOINT_INDEX, // BoneWeights => Mesh::ATTRIBUTE_JOINT_WEIGHT, - _ => return None, + _ => { + log::info!("Skipping attribute {:?}", component.component); + return None; + } }; let values = match component.format { Rg8Unorm => Unorm8x2(copy_direct(input, component)), @@ -426,9 +238,7 @@ fn convert_component( Rg16Uint => Uint16x2(copy_direct(input, component)), Rg16Snorm => Snorm16x2(copy_direct(input, component)), Rg16Sint => Sint16x2(copy_direct(input, component)), - Rg16Float => Float32x2(copy_converting(input, component, |v: [u16; 2]| { - v.map(|u| f16::from_bits(u).to_f32()) - })), + Rg16Float => Float32x2(copy_converting(input, component, |v: [u16; 2]| v.to_f32_array())), Rgba8Unorm => match component.component { Color => Float32x4(copy_converting(input, component, |v: [u8; 4]| { v.map(|u| u as f32 * 255.0) @@ -446,15 +256,23 @@ fn convert_component( Rgba16Snorm => Snorm16x4(copy_direct(input, component)), Rgba16Sint => Sint16x4(copy_direct(input, component)), Rgba16Float => match component.component { - Position | Normal => Float32x3(copy_converting(input, component, |v: [u16; 3]| { - v.map(|u| f16::from_bits(u).to_f32()) - })), - TexCoord0 => Float32x2(copy_converting(input, component, |v: [u16; 2]| { - v.map(|u| f16::from_bits(u).to_f32()) - })), - _ => Float32x4(copy_converting(input, component, |v: [u16; 4]| { - v.map(|u| f16::from_bits(u).to_f32()) + Position | Normal => Float32x3(copy_converting(input, component, |v: [u16; 4]| { + debug_assert_eq!(v[3], 1); + *array_ref!(v.to_f32_array(), 0, 3) })), + TexCoord0 | TexCoord1 | TexCoord2 | TexCoord3 => { + Float32x2(copy_converting(input, component, |v: [u16; 4]| { + let dst = v.to_f32_array(); + if component.component == TexCoord1 { + // println!("UV 1: {:?}", values); + // ??? + *array_ref!(dst, 2, 2) + } else { + *array_ref!(dst, 0, 2) + } + })) + } + _ => Float32x4(copy_converting(input, component, |v: [u16; 4]| v.to_f32_array())), }, Rgb32Uint => Uint32x3(copy_direct(input, component)), Rgb32Sint => Sint32x3(copy_direct(input, component)), @@ -463,7 +281,9 @@ fn convert_component( Rgba32Sint => Sint32x4(copy_direct(input, component)), Rgba32Float => match component.component { Position | Normal => Float32x3(copy_direct(input, component)), - TexCoord0 => Float32x2(copy_direct(input, component)), + TexCoord0 | TexCoord1 | TexCoord2 | TexCoord3 => { + Float32x2(copy_direct(input, component)) + } _ => Float32x4(copy_direct(input, component)), }, R16Uint => Uint32(copy_converting(input, component, |v: u16| v as u32)), @@ -532,91 +352,3 @@ enum IndicesSlice<'a> { U16(Cow<'a, [u16]>), U32(Cow<'a, [u32]>), } - -fn texture_wrap(wrap: ETextureWrap) -> AddressMode { - match wrap { - ETextureWrap::ClampToEdge => AddressMode::ClampToEdge, - ETextureWrap::Repeat => AddressMode::Repeat, - ETextureWrap::MirroredRepeat => AddressMode::MirrorRepeat, - ETextureWrap::MirrorClamp => todo!("Mirror clamp"), - ETextureWrap::ClampToBorder => AddressMode::ClampToBorder, - ETextureWrap::Clamp => todo!("Clamp"), - } -} - -fn sampler_descriptor_from_usage<'a>( - usage: &STextureUsageInfo, - data: Option<&STextureSamplerData>, -) -> SamplerDescriptor<'a> { - SamplerDescriptor { - label: None, - address_mode_u: match usage.wrap_x { - 0 => AddressMode::ClampToEdge, - 1 => AddressMode::Repeat, - 2 => AddressMode::MirrorRepeat, - 3 => todo!("Mirror clamp"), - 4 => AddressMode::ClampToBorder, - 5 => todo!("Clamp"), - u32::MAX => data.map_or(AddressMode::Repeat, |d| texture_wrap(d.wrap_x)), - n => todo!("wrap {n}"), - }, - address_mode_v: match usage.wrap_y { - 0 => AddressMode::ClampToEdge, - 1 => AddressMode::Repeat, - 2 => AddressMode::MirrorRepeat, - 3 => todo!("Mirror clamp"), - 4 => AddressMode::ClampToBorder, - 5 => todo!("Clamp"), - u32::MAX => data.map_or(AddressMode::Repeat, |d| texture_wrap(d.wrap_y)), - n => todo!("wrap {n}"), - }, - address_mode_w: match usage.wrap_z { - 0 => AddressMode::ClampToEdge, - 1 => AddressMode::Repeat, - 2 => AddressMode::MirrorRepeat, - 3 => todo!("Mirror clamp"), - 4 => AddressMode::ClampToBorder, - 5 => todo!("Clamp"), - u32::MAX => 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 { - ETextureFilter::Nearest => FilterMode::Nearest, - ETextureFilter::Linear => FilterMode::Linear, - }), - n => todo!("Filter {n}"), - }, - min_filter: match usage.filter { - 0 => FilterMode::Nearest, - 1 => FilterMode::Linear, - u32::MAX => data.map_or(FilterMode::Nearest, |d| match d.filter { - ETextureFilter::Nearest => FilterMode::Nearest, - ETextureFilter::Linear => FilterMode::Linear, - }), - n => todo!("Filter {n}"), - }, - mipmap_filter: data.map_or(FilterMode::Nearest, |d| match d.mip_filter { - ETextureMipFilter::Nearest => FilterMode::Nearest, - ETextureMipFilter::Linear => FilterMode::Linear, - }), - lod_min_clamp: 0.0, - lod_max_clamp: f32::MAX, - compare: None, - anisotropy_clamp: NonZeroU8::new( - data.map(|d| match d.aniso { - ETextureAnisotropicRatio::None => 0, - ETextureAnisotropicRatio::Ratio1 => 1, - ETextureAnisotropicRatio::Ratio2 => 2, - ETextureAnisotropicRatio::Ratio4 => 4, - ETextureAnisotropicRatio::Ratio8 => 8, - ETextureAnisotropicRatio::Ratio16 => 16, - }) - .unwrap_or_default(), - ), - // anisotropy_clamp: NonZeroU8::new(8), - border_color: None, - } -} diff --git a/retrotool-gui/src/tabs/mod.rs b/retrotool-gui/src/tabs/mod.rs index 2093e12..0193c98 100644 --- a/retrotool-gui/src/tabs/mod.rs +++ b/retrotool-gui/src/tabs/mod.rs @@ -9,10 +9,10 @@ use bevy_egui::EguiContext; use crate::AssetRef; pub enum TabType { - Project(project::ProjectTab), - Texture(texture::TextureTab), - Model(model::ModelTab), - ModCon(modcon::ModConTab), + Project(Box), + Texture(Box), + Model(Box), + ModCon(Box), Empty, } @@ -78,10 +78,10 @@ impl egui_dock::TabViewer for TabViewer<'_> { fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) { match tab { - TabType::Project(tab) => render_tab(self.world, ui, tab, &mut self.state), - TabType::Texture(tab) => render_tab(self.world, ui, tab, &mut self.state), - TabType::Model(tab) => render_tab(self.world, ui, tab, &mut self.state), - TabType::ModCon(tab) => render_tab(self.world, ui, tab, &mut self.state), + TabType::Project(tab) => render_tab(self.world, ui, tab.as_mut(), &mut self.state), + TabType::Texture(tab) => render_tab(self.world, ui, tab.as_mut(), &mut self.state), + TabType::Model(tab) => render_tab(self.world, ui, tab.as_mut(), &mut self.state), + TabType::ModCon(tab) => render_tab(self.world, ui, tab.as_mut(), &mut self.state), TabType::Empty => {} } } @@ -100,15 +100,15 @@ impl egui_dock::TabViewer for TabViewer<'_> { match tab { TabType::Project(_) => false, TabType::Texture(tab) => { - close_tab(self.world, tab); + close_tab(self.world, tab.as_mut()); true } TabType::Model(tab) => { - close_tab(self.world, tab); + close_tab(self.world, tab.as_mut()); true } TabType::ModCon(tab) => { - close_tab(self.world, tab); + close_tab(self.world, tab.as_mut()); true } TabType::Empty => false, diff --git a/retrotool-gui/src/tabs/modcon.rs b/retrotool-gui/src/tabs/modcon.rs index 576fbad..a4f7c8a 100644 --- a/retrotool-gui/src/tabs/modcon.rs +++ b/retrotool-gui/src/tabs/modcon.rs @@ -12,7 +12,11 @@ use egui::{Sense, Widget}; use crate::{ icon, - loaders::{modcon::ModConAsset, model::ModelAsset, texture::TextureAsset}, + loaders::{ + modcon::ModConAsset, + model::{MaterialKey, ModelAsset}, + texture::TextureAsset, + }, material::CustomMaterial, render::{ camera::ModelCamera, @@ -175,14 +179,8 @@ impl SystemTab for ModConTab { _ => continue, } - let result = load_model( - asset, - &mut commands, - &texture_assets, - &mut images, - &mut materials, - &mut meshes, - ); + asset.build_texture_images(&texture_assets, &mut images); + let result = load_model(asset, &mut meshes); let built = match result { Ok(value) => value, Err(e) => { @@ -202,14 +200,24 @@ impl SystemTab for ModConTab { .with_children(|builder| { for idx in built.lod[0].meshes.iter() { let mesh = &built.meshes[idx]; + let material = match asset.material( + &MaterialKey { + material_idx: mesh.material_idx, + mesh_flags: mesh.flags, + mesh_mirrored: is_mirrored, + }, + &mut materials, + ) { + Ok(handle) => handle, + Err(e) => { + log::warn!("Failed to build material: {:?}", e); + continue; + } + }; builder.spawn(( - MaterialMeshBundle { + MaterialMeshBundle:: { mesh: mesh.mesh.clone(), - material: if is_mirrored { - mesh.mirrored_material.clone() - } else { - mesh.material.clone() - }, + material, ..default() }, RaycastMesh::::default(), @@ -305,11 +313,11 @@ impl SystemTab for ModConTab { if ui.button("Open in new tab").clicked() { let handle = server .load(format!("{}.{}", selected.asset_ref.id, selected.asset_ref.kind)); - state.open_tab = Some(TabType::Model(ModelTab { + state.open_tab = Some(TabType::Model(Box::new(ModelTab { asset_ref: selected.asset_ref, handle, ..default() - })); + }))); ui.close_menu(); } if ui.button("Copy GUID").clicked() { diff --git a/retrotool-gui/src/tabs/model.rs b/retrotool-gui/src/tabs/model.rs index cef712f..4a27897 100644 --- a/retrotool-gui/src/tabs/model.rs +++ b/retrotool-gui/src/tabs/model.rs @@ -7,10 +7,14 @@ use bevy::{ }; use bevy_egui::EguiContext; use egui::{Sense, Widget}; +use retrolib::format::cmdl::CMaterialCache; use crate::{ icon, - loaders::{model::ModelAsset, texture::TextureAsset}, + loaders::{ + model::{MaterialKey, ModelAsset}, + texture::TextureAsset, + }, material::CustomMaterial, render::{ camera::ModelCamera, @@ -23,13 +27,16 @@ use crate::{ pub struct LoadedMesh { pub entity: Entity, - pub material_name: String, + pub material_idx: usize, pub visible: bool, + pub unk_c: u16, + pub unk_e: u16, } pub struct LoadedModel { pub meshes: Vec, pub lod: Vec, + pub materials: Vec, } #[derive(Default)] @@ -99,14 +106,8 @@ impl SystemTab for ModelTab { _ => return, } - let result = load_model( - asset, - &mut commands, - &texture_assets, - &mut images, - &mut materials, - &mut meshes, - ); + asset.build_texture_images(&texture_assets, &mut images); + let result = load_model(asset, &mut meshes); let built = match result { Ok(value) => value, Err(e) => { @@ -114,25 +115,39 @@ impl SystemTab for ModelTab { return; } }; - self.loaded = Some(LoadedModel { - meshes: built - .meshes - .into_iter() - .map(|mesh| LoadedMesh { - entity: commands - .spawn(MaterialMeshBundle { - mesh: mesh.mesh, - material: mesh.material, - transform: Transform::from_translation((-built.aabb.center).into()), - ..default() - }) - .id(), - material_name: mesh.material_name, - visible: mesh.visible, + let mut meshes = Vec::with_capacity(built.meshes.len()); + for mesh in built.meshes { + let material = match asset.material( + &MaterialKey { + material_idx: mesh.material_idx, + mesh_flags: mesh.flags, + mesh_mirrored: false, + }, + &mut materials, + ) { + Ok(handle) => handle, + Err(e) => { + log::warn!("Failed to build material: {:?}", e); + continue; + } + }; + let entity = commands + .spawn(MaterialMeshBundle:: { + mesh: mesh.mesh, + material, + transform: Transform::from_translation((-built.aabb.center).into()), + ..default() }) - .collect(), - lod: built.lod, - }); + .id(); + meshes.push(LoadedMesh { + entity, + material_idx: mesh.material_idx, + visible: mesh.visible, + unk_c: mesh.flags, + unk_e: mesh.unk_e, + }); + } + self.loaded = Some(LoadedModel { meshes, lod: built.lod, materials: built.materials }); 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"); @@ -160,8 +175,8 @@ impl SystemTab for ModelTab { let left_top = rect.left_top().to_vec2() * scale; let size = rect.size() * scale; let viewport = Viewport { - physical_position: UVec2 { x: left_top.x as u32, y: left_top.y as u32 }, - physical_size: UVec2 { x: size.x as u32, y: size.y as u32 }, + physical_position: UVec2::new(left_top.x as u32, left_top.y as u32), + physical_size: UVec2::new(size.x as u32, size.y as u32), depth: 0.0..1.0, }; let response = @@ -226,7 +241,10 @@ impl SystemTab for ModelTab { let mesh = &mut loaded.meshes[idx]; ui.checkbox( &mut mesh.visible, - format!("Mesh {idx} ({})", mesh.material_name), + format!( + "Mesh {idx} ({}, {}, {})", + mesh.unk_c, mesh.unk_e, loaded.materials[mesh.material_idx].name + ), ); if let Some(mut commands) = commands.get_entity(mesh.entity) { commands.insert(( diff --git a/retrotool-gui/src/tabs/project.rs b/retrotool-gui/src/tabs/project.rs index 90729da..4732fe5 100644 --- a/retrotool-gui/src/tabs/project.rs +++ b/retrotool-gui/src/tabs/project.rs @@ -216,28 +216,28 @@ impl SystemTab for ProjectTab { "{}.{}", entry.id, entry.kind )); - state.open_tab = Some(TabType::Texture(TextureTab { + state.open_tab = Some(TabType::Texture(Box::new(TextureTab { asset_ref, handle, ..default() - })); + }))); } K_FORM_CMDL | K_FORM_SMDL | K_FORM_WMDL => { let handle = server .load::(format!("{}.{}", entry.id, entry.kind)); - state.open_tab = Some(TabType::Model(ModelTab { + state.open_tab = Some(TabType::Model(Box::new(ModelTab { asset_ref, handle, ..default() - })); + }))); } K_FORM_MCON => { let handle = server.load(format!("{}.{}", entry.id, entry.kind)); - state.open_tab = Some(TabType::ModCon(ModConTab { + state.open_tab = Some(TabType::ModCon(Box::new(ModConTab { asset_ref, handle, ..default() - })); + }))); } _ => {} } diff --git a/retrotool/Cargo.toml b/retrotool/Cargo.toml index 6b35325..1f6321e 100644 --- a/retrotool/Cargo.toml +++ b/retrotool/Cargo.toml @@ -10,6 +10,10 @@ repository = "https://github.com/PrimeDecomp/retrotool" readme = "README.md" categories = ["command-line-utilities"] +[features] +default = [] +nightly = ["half/use-intrinsics"] + [dependencies] anyhow = "1.0.69" argh = "0.1.10" @@ -18,7 +22,7 @@ binrw_derive = "0.11.1" ddsfile = { git = "https://github.com/encounter/ddsfile", rev = "880f04c1dffa680eab0e9e09cfa58591fe186a31" } env_logger = "0.10.0" gltf-json = { version = "1.1.0", features = ["names", "extras"] } -half = "2.2.1" +half = { git = "https://github.com/encounter/half-rs", branch = "inline" } image = "0.24.5" log = "0.4.17" memmap2 = "0.5.8"