merge props and optimizate material handling

This commit is contained in:
Robin Appelman 2023-12-26 16:14:25 +01:00
commit 77bcd106f9
4 changed files with 102 additions and 58 deletions

View file

@ -8,8 +8,6 @@ tf2 map viewer based on [vbsp](https://github.com/icewind1991/vbsp)
cargo run --release -- /path/to/map.bsp
```
Note that asset loading isn't very well optimized so loading maps can take a while.
In order to load the assets referenced by the map, TF2 needs to be installed locally.
![pl_badwater as rendered by the viewer](screenshots/badwater.png)

View file

@ -105,7 +105,7 @@ fn model_to_model(
let materials: Vec<_> = textures
.iter()
.map(|texture| load_material_fallback(texture, &["".into()], loader))
.map(|texture| load_material_fallback(texture, loader))
.map(convert_material)
.collect();

View file

@ -1,20 +1,22 @@
use crate::Error;
use image::{DynamicImage, GenericImageView};
use std::cell::RefCell;
use tf_asset_loader::Loader;
use three_d::{CpuMaterial, CpuTexture};
use three_d_asset::Srgba;
use tracing::{error, instrument};
use vmdl::mdl::TextureInfo;
use vmt_parser::from_str;
use vmt_parser::material::{Material, WaterMaterial};
use vtf::vtf::VTF;
pub fn load_material_fallback(name: &str, search_dirs: &[String], loader: &Loader) -> MaterialData {
match load_material(name, search_dirs, loader) {
pub fn load_material_fallback(name: &str, loader: &Loader) -> MaterialData {
match load_material(name, loader) {
Ok(mat) => mat,
Err(e) => {
error!(error = ?e, material = name, "failed to load material");
MaterialData {
name: name.into(),
path: name.into(),
color: [255, 0, 255, 255],
..MaterialData::default()
}
@ -24,7 +26,6 @@ pub fn load_material_fallback(name: &str, search_dirs: &[String], loader: &Loade
#[derive(Default, Debug)]
pub struct MaterialData {
pub name: String,
pub path: String,
pub color: [u8; 4],
pub texture: Option<TextureData>,
@ -40,24 +41,15 @@ pub struct TextureData {
}
#[instrument(skip(loader))]
pub fn load_material(
name: &str,
search_dirs: &[String],
loader: &Loader,
) -> Result<MaterialData, Error> {
let dirs = search_dirs
.iter()
.map(|dir| {
format!(
"materials/{}",
dir.to_ascii_lowercase().trim_start_matches('/')
)
})
.collect::<Vec<_>>();
let path = format!("{}.vmt", name.to_ascii_lowercase().trim_end_matches(".vmt"));
let path = loader
.find_in_paths(&path, &dirs)
.ok_or(Error::ResourceNotFound(path))?;
pub fn load_material(path: &str, loader: &Loader) -> Result<MaterialData, Error> {
let path = if path.starts_with("materials/") {
path.to_string()
} else {
format!(
"materials/{}.vmt",
path.to_ascii_lowercase().trim_end_matches(".vmt")
)
};
let raw = loader
.load(&path)?
.ok_or_else(|| Error::ResourceNotFound(path.clone()))?;
@ -82,7 +74,6 @@ pub fn load_material(
{
return Ok(MaterialData {
color: [82, 180, 217, 128],
name: name.into(),
path,
texture: None,
bump_map: None,
@ -107,7 +98,6 @@ pub fn load_material(
Ok(MaterialData {
color: [255; 4],
name: name.into(),
path,
texture: Some(TextureData {
name: base_texture.into(),
@ -138,7 +128,7 @@ pub fn convert_material(material: MaterialData) -> CpuMaterial {
material.color[2],
material.color[3],
),
name: material.name,
name: material.path,
albedo_texture: material
.texture
.map(|tex| convert_texture(tex, material.translucent | material.alpha_test.is_some())),
@ -177,3 +167,63 @@ pub fn convert_texture(texture: TextureData, keep_alpha: bool) -> CpuTexture {
..CpuTexture::default()
}
}
#[derive(Debug)]
pub struct MaterialSet<'a> {
loader: &'a Loader,
materials: RefCell<Vec<String>>,
}
impl<'s> MaterialSet<'s> {
pub fn new(loader: &'s Loader) -> Self {
MaterialSet {
loader,
materials: RefCell::default(),
}
}
pub fn get_index(&self, material: &TextureInfo) -> usize {
let search_path = material
.search_paths
.iter()
.map(|dir| {
format!(
"materials/{}",
dir.to_ascii_lowercase().trim_start_matches('/')
)
})
.collect::<Vec<_>>();
let material = format!(
"{}.vmt",
material.name.to_ascii_lowercase().trim_end_matches(".vmt")
);
let material = if search_path.is_empty() {
material.to_string()
} else {
self.loader
.find_in_paths(&material, &search_path)
.unwrap_or(material.into())
};
let mut materials = self.materials.borrow_mut();
match materials
.iter()
.enumerate()
.find_map(|(i, name)| (*name == material).then_some(i))
{
Some(i) => i,
None => {
let i = materials.len();
materials.push(material);
i
}
}
}
pub fn into_iter(self) -> impl Iterator<Item = String> {
self.materials.into_inner().into_iter()
}
}

View file

@ -1,12 +1,12 @@
use crate::bsp::map_coords;
use crate::material::{convert_material, load_material_fallback};
use crate::material::{convert_material, load_material_fallback, MaterialSet};
use crate::Error;
use tf_asset_loader::Loader;
use three_d::{CpuMaterial, CpuModel, Mat4, Positions, Vec2, Vec3, Vec4};
use three_d_asset::{Geometry, Primitive, TriMesh};
use tracing::{error, warn};
use vbsp::{Handle, StaticPropLump};
use vmdl::mdl::{Mdl, TextureInfo};
use vmdl::mdl::Mdl;
use vmdl::vtx::Vtx;
use vmdl::vvd::Vvd;
@ -28,7 +28,7 @@ pub fn load_props<'a, I: Iterator<Item = Handle<'a, StaticPropLump>>>(
props: I,
show_textures: bool,
) -> Result<Vec<CpuModel>, Error> {
let props = props
let props: Vec<_> = props
.filter_map(|prop| match load_prop(loader, prop.model()) {
Ok(model) => Some((prop, model)),
Err(e) => {
@ -45,28 +45,26 @@ pub fn load_props<'a, I: Iterator<Item = Handle<'a, StaticPropLump>>>(
transform,
skin: prop.skin,
}
});
props
.map(|prop| {
let geometries: Vec<_> = prop_to_meshes(&prop, show_textures).collect();
let materials: Vec<_> = if show_textures {
prop.model
.textures()
.iter()
.map(|tex| prop_texture_to_material(tex, loader))
.collect()
} else {
Vec::new()
};
Ok(CpuModel {
name: prop.name.into(),
geometries,
materials,
})
})
.collect()
.collect();
let used_materials = MaterialSet::new(loader);
let geometries = props
.iter()
.flat_map(|prop| prop_to_meshes(prop, &used_materials, show_textures))
.collect();
let materials = used_materials
.into_iter()
.map(|mat| prop_texture_to_material(&mat, loader))
.collect();
Ok(vec![CpuModel {
name: "props".into(),
geometries,
materials,
}])
}
struct PropData<'a> {
@ -78,6 +76,7 @@ struct PropData<'a> {
fn prop_to_meshes<'a>(
prop: &'a PropData,
used_materials: &'a MaterialSet<'a>,
show_textures: bool,
) -> impl Iterator<Item = Primitive> + 'a {
let transform = prop.transform;
@ -93,7 +92,8 @@ fn prop_to_meshes<'a>(
model.meshes().map(move |mesh| {
let material_index = if show_textures {
skin.texture_index(mesh.material_index())
skin.texture_info(mesh.material_index())
.map(|mat| used_materials.get_index(mat))
} else {
None
};
@ -131,10 +131,6 @@ fn prop_to_meshes<'a>(
})
}
fn prop_texture_to_material(texture: &TextureInfo, loader: &Loader) -> CpuMaterial {
convert_material(load_material_fallback(
&texture.name,
&texture.search_paths,
loader,
))
fn prop_texture_to_material(texture: &str, loader: &Loader) -> CpuMaterial {
convert_material(load_material_fallback(&texture, loader))
}