mirror of
https://codeberg.org/icewind/vbspview.git
synced 2026-06-03 18:24:09 +02:00
merge props and optimizate material handling
This commit is contained in:
parent
8fb1ccb9b1
commit
77bcd106f9
4 changed files with 102 additions and 58 deletions
|
|
@ -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.
|
||||
|
||||

|
||||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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| {
|
||||
pub fn load_material(path: &str, loader: &Loader) -> Result<MaterialData, Error> {
|
||||
let path = if path.starts_with("materials/") {
|
||||
path.to_string()
|
||||
} else {
|
||||
format!(
|
||||
"materials/{}",
|
||||
dir.to_ascii_lowercase().trim_start_matches('/')
|
||||
"materials/{}.vmt",
|
||||
path.to_ascii_lowercase().trim_end_matches(".vmt")
|
||||
)
|
||||
})
|
||||
.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))?;
|
||||
};
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
50
src/prop.rs
50
src/prop.rs
|
|
@ -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,
|
||||
}
|
||||
});
|
||||
})
|
||||
.collect();
|
||||
|
||||
props
|
||||
.map(|prop| {
|
||||
let geometries: Vec<_> = prop_to_meshes(&prop, show_textures).collect();
|
||||
let materials: Vec<_> = if show_textures {
|
||||
prop.model
|
||||
.textures()
|
||||
let used_materials = MaterialSet::new(loader);
|
||||
|
||||
let geometries = props
|
||||
.iter()
|
||||
.map(|tex| prop_texture_to_material(tex, loader))
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
.flat_map(|prop| prop_to_meshes(prop, &used_materials, show_textures))
|
||||
.collect();
|
||||
|
||||
Ok(CpuModel {
|
||||
name: prop.name.into(),
|
||||
let materials = used_materials
|
||||
.into_iter()
|
||||
.map(|mat| prop_texture_to_material(&mat, loader))
|
||||
.collect();
|
||||
|
||||
Ok(vec![CpuModel {
|
||||
name: "props".into(),
|
||||
geometries,
|
||||
materials,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}])
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue