mirror of
https://github.com/PrimeDecomp/retrotool.git
synced 2026-07-12 18:18:58 -07:00
Rework model loader; start supporting alpha blend materials
This commit is contained in:
Generated
+13
-3
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<Uuid, Handle<TextureAsset>>,
|
||||
}
|
||||
|
||||
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<Uuid, Handle<Image>>,
|
||||
pub materials: HashMap<MaterialKey, Handle<CustomMaterial>>,
|
||||
}
|
||||
|
||||
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<TextureAsset>,
|
||||
) -> 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<TextureAsset>,
|
||||
images: &mut Assets<Image>,
|
||||
) {
|
||||
// Build sampler descriptors
|
||||
let mut sampler_descriptors = HashMap::<Uuid, SamplerDescriptor>::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<CustomMaterial>,
|
||||
) -> Result<Handle<CustomMaterial>> {
|
||||
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<Uuid, Handle<Image>>,
|
||||
) -> Result<CustomMaterial> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 => {}
|
||||
|
||||
@@ -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<Face>,
|
||||
#[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<Handle<Image>>,
|
||||
@@ -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<Self>,
|
||||
descriptor: &mut RenderPipelineDescriptor,
|
||||
|
||||
@@ -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<Mesh>,
|
||||
pub material: Handle<CustomMaterial>,
|
||||
pub mirrored_material: Handle<CustomMaterial>,
|
||||
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<BuiltMesh>,
|
||||
pub lod: Vec<ModelLod>,
|
||||
pub materials: Vec<CMaterialCache>,
|
||||
pub aabb: Aabb,
|
||||
}
|
||||
|
||||
pub fn load_model(
|
||||
asset: &ModelAsset,
|
||||
_commands: &mut Commands,
|
||||
// server: &AssetServer,
|
||||
texture_assets: &Assets<TextureAsset>,
|
||||
images: &mut Assets<Image>,
|
||||
materials: &mut Assets<CustomMaterial>,
|
||||
meshes: &mut Assets<Mesh>,
|
||||
// center: bool,
|
||||
) -> Result<BuiltModel> {
|
||||
pub fn load_model(asset: &ModelAsset, meshes: &mut Assets<Mesh>) -> Result<BuiltModel> {
|
||||
let ModelAsset {
|
||||
inner: ModelData { head, mtrl, mesh, vbuf, ibuf, vtx_buffers, idx_buffers },
|
||||
textures,
|
||||
..
|
||||
} = asset;
|
||||
|
||||
// Build sampler descriptors
|
||||
let mut sampler_descriptors = HashMap::<Uuid, SamplerDescriptor>::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::<Uuid, Handle<Image>>::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<VertexBufferInfo> = 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<const N: usize> {
|
||||
fn to_f32_array(self) -> [f32; N];
|
||||
}
|
||||
|
||||
impl<const N: usize> HalfArray<N> for [u16; N] {
|
||||
fn to_f32_array(self) -> [f32; N] {
|
||||
let mut dst = [0f32; N];
|
||||
self.reinterpret_cast::<f16>().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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<project::ProjectTab>),
|
||||
Texture(Box<texture::TextureTab>),
|
||||
Model(Box<model::ModelTab>),
|
||||
ModCon(Box<modcon::ModConTab>),
|
||||
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,
|
||||
|
||||
@@ -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::<CustomMaterial> {
|
||||
mesh: mesh.mesh.clone(),
|
||||
material: if is_mirrored {
|
||||
mesh.mirrored_material.clone()
|
||||
} else {
|
||||
mesh.material.clone()
|
||||
},
|
||||
material,
|
||||
..default()
|
||||
},
|
||||
RaycastMesh::<ModConRaycastSet>::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() {
|
||||
|
||||
@@ -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<LoadedMesh>,
|
||||
pub lod: Vec<ModelLod>,
|
||||
pub materials: Vec<CMaterialCache>,
|
||||
}
|
||||
|
||||
#[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::<CustomMaterial> {
|
||||
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((
|
||||
|
||||
@@ -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::<ModelAsset, _>(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()
|
||||
}));
|
||||
})));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user