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
|
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.
|
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
|
let materials: Vec<_> = textures
|
||||||
.iter()
|
.iter()
|
||||||
.map(|texture| load_material_fallback(texture, &["".into()], loader))
|
.map(|texture| load_material_fallback(texture, loader))
|
||||||
.map(convert_material)
|
.map(convert_material)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,22 @@
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
use image::{DynamicImage, GenericImageView};
|
use image::{DynamicImage, GenericImageView};
|
||||||
|
use std::cell::RefCell;
|
||||||
use tf_asset_loader::Loader;
|
use tf_asset_loader::Loader;
|
||||||
use three_d::{CpuMaterial, CpuTexture};
|
use three_d::{CpuMaterial, CpuTexture};
|
||||||
use three_d_asset::Srgba;
|
use three_d_asset::Srgba;
|
||||||
use tracing::{error, instrument};
|
use tracing::{error, instrument};
|
||||||
|
use vmdl::mdl::TextureInfo;
|
||||||
use vmt_parser::from_str;
|
use vmt_parser::from_str;
|
||||||
use vmt_parser::material::{Material, WaterMaterial};
|
use vmt_parser::material::{Material, WaterMaterial};
|
||||||
use vtf::vtf::VTF;
|
use vtf::vtf::VTF;
|
||||||
|
|
||||||
pub fn load_material_fallback(name: &str, search_dirs: &[String], loader: &Loader) -> MaterialData {
|
pub fn load_material_fallback(name: &str, loader: &Loader) -> MaterialData {
|
||||||
match load_material(name, search_dirs, loader) {
|
match load_material(name, loader) {
|
||||||
Ok(mat) => mat,
|
Ok(mat) => mat,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(error = ?e, material = name, "failed to load material");
|
error!(error = ?e, material = name, "failed to load material");
|
||||||
MaterialData {
|
MaterialData {
|
||||||
name: name.into(),
|
path: name.into(),
|
||||||
color: [255, 0, 255, 255],
|
color: [255, 0, 255, 255],
|
||||||
..MaterialData::default()
|
..MaterialData::default()
|
||||||
}
|
}
|
||||||
|
|
@ -24,7 +26,6 @@ pub fn load_material_fallback(name: &str, search_dirs: &[String], loader: &Loade
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct MaterialData {
|
pub struct MaterialData {
|
||||||
pub name: String,
|
|
||||||
pub path: String,
|
pub path: String,
|
||||||
pub color: [u8; 4],
|
pub color: [u8; 4],
|
||||||
pub texture: Option<TextureData>,
|
pub texture: Option<TextureData>,
|
||||||
|
|
@ -40,24 +41,15 @@ pub struct TextureData {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(loader))]
|
#[instrument(skip(loader))]
|
||||||
pub fn load_material(
|
pub fn load_material(path: &str, loader: &Loader) -> Result<MaterialData, Error> {
|
||||||
name: &str,
|
let path = if path.starts_with("materials/") {
|
||||||
search_dirs: &[String],
|
path.to_string()
|
||||||
loader: &Loader,
|
} else {
|
||||||
) -> Result<MaterialData, Error> {
|
|
||||||
let dirs = search_dirs
|
|
||||||
.iter()
|
|
||||||
.map(|dir| {
|
|
||||||
format!(
|
format!(
|
||||||
"materials/{}",
|
"materials/{}.vmt",
|
||||||
dir.to_ascii_lowercase().trim_start_matches('/')
|
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
|
let raw = loader
|
||||||
.load(&path)?
|
.load(&path)?
|
||||||
.ok_or_else(|| Error::ResourceNotFound(path.clone()))?;
|
.ok_or_else(|| Error::ResourceNotFound(path.clone()))?;
|
||||||
|
|
@ -82,7 +74,6 @@ pub fn load_material(
|
||||||
{
|
{
|
||||||
return Ok(MaterialData {
|
return Ok(MaterialData {
|
||||||
color: [82, 180, 217, 128],
|
color: [82, 180, 217, 128],
|
||||||
name: name.into(),
|
|
||||||
path,
|
path,
|
||||||
texture: None,
|
texture: None,
|
||||||
bump_map: None,
|
bump_map: None,
|
||||||
|
|
@ -107,7 +98,6 @@ pub fn load_material(
|
||||||
|
|
||||||
Ok(MaterialData {
|
Ok(MaterialData {
|
||||||
color: [255; 4],
|
color: [255; 4],
|
||||||
name: name.into(),
|
|
||||||
path,
|
path,
|
||||||
texture: Some(TextureData {
|
texture: Some(TextureData {
|
||||||
name: base_texture.into(),
|
name: base_texture.into(),
|
||||||
|
|
@ -138,7 +128,7 @@ pub fn convert_material(material: MaterialData) -> CpuMaterial {
|
||||||
material.color[2],
|
material.color[2],
|
||||||
material.color[3],
|
material.color[3],
|
||||||
),
|
),
|
||||||
name: material.name,
|
name: material.path,
|
||||||
albedo_texture: material
|
albedo_texture: material
|
||||||
.texture
|
.texture
|
||||||
.map(|tex| convert_texture(tex, material.translucent | material.alpha_test.is_some())),
|
.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()
|
..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::bsp::map_coords;
|
||||||
use crate::material::{convert_material, load_material_fallback};
|
use crate::material::{convert_material, load_material_fallback, MaterialSet};
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
use tf_asset_loader::Loader;
|
use tf_asset_loader::Loader;
|
||||||
use three_d::{CpuMaterial, CpuModel, Mat4, Positions, Vec2, Vec3, Vec4};
|
use three_d::{CpuMaterial, CpuModel, Mat4, Positions, Vec2, Vec3, Vec4};
|
||||||
use three_d_asset::{Geometry, Primitive, TriMesh};
|
use three_d_asset::{Geometry, Primitive, TriMesh};
|
||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
use vbsp::{Handle, StaticPropLump};
|
use vbsp::{Handle, StaticPropLump};
|
||||||
use vmdl::mdl::{Mdl, TextureInfo};
|
use vmdl::mdl::Mdl;
|
||||||
use vmdl::vtx::Vtx;
|
use vmdl::vtx::Vtx;
|
||||||
use vmdl::vvd::Vvd;
|
use vmdl::vvd::Vvd;
|
||||||
|
|
||||||
|
|
@ -28,7 +28,7 @@ pub fn load_props<'a, I: Iterator<Item = Handle<'a, StaticPropLump>>>(
|
||||||
props: I,
|
props: I,
|
||||||
show_textures: bool,
|
show_textures: bool,
|
||||||
) -> Result<Vec<CpuModel>, Error> {
|
) -> Result<Vec<CpuModel>, Error> {
|
||||||
let props = props
|
let props: Vec<_> = props
|
||||||
.filter_map(|prop| match load_prop(loader, prop.model()) {
|
.filter_map(|prop| match load_prop(loader, prop.model()) {
|
||||||
Ok(model) => Some((prop, model)),
|
Ok(model) => Some((prop, model)),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
@ -45,28 +45,26 @@ pub fn load_props<'a, I: Iterator<Item = Handle<'a, StaticPropLump>>>(
|
||||||
transform,
|
transform,
|
||||||
skin: prop.skin,
|
skin: prop.skin,
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
props
|
let used_materials = MaterialSet::new(loader);
|
||||||
.map(|prop| {
|
|
||||||
let geometries: Vec<_> = prop_to_meshes(&prop, show_textures).collect();
|
let geometries = props
|
||||||
let materials: Vec<_> = if show_textures {
|
|
||||||
prop.model
|
|
||||||
.textures()
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|tex| prop_texture_to_material(tex, loader))
|
.flat_map(|prop| prop_to_meshes(prop, &used_materials, show_textures))
|
||||||
.collect()
|
.collect();
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(CpuModel {
|
let materials = used_materials
|
||||||
name: prop.name.into(),
|
.into_iter()
|
||||||
|
.map(|mat| prop_texture_to_material(&mat, loader))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(vec![CpuModel {
|
||||||
|
name: "props".into(),
|
||||||
geometries,
|
geometries,
|
||||||
materials,
|
materials,
|
||||||
})
|
}])
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PropData<'a> {
|
struct PropData<'a> {
|
||||||
|
|
@ -78,6 +76,7 @@ struct PropData<'a> {
|
||||||
|
|
||||||
fn prop_to_meshes<'a>(
|
fn prop_to_meshes<'a>(
|
||||||
prop: &'a PropData,
|
prop: &'a PropData,
|
||||||
|
used_materials: &'a MaterialSet<'a>,
|
||||||
show_textures: bool,
|
show_textures: bool,
|
||||||
) -> impl Iterator<Item = Primitive> + 'a {
|
) -> impl Iterator<Item = Primitive> + 'a {
|
||||||
let transform = prop.transform;
|
let transform = prop.transform;
|
||||||
|
|
@ -93,7 +92,8 @@ fn prop_to_meshes<'a>(
|
||||||
|
|
||||||
model.meshes().map(move |mesh| {
|
model.meshes().map(move |mesh| {
|
||||||
let material_index = if show_textures {
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
@ -131,10 +131,6 @@ fn prop_to_meshes<'a>(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prop_texture_to_material(texture: &TextureInfo, loader: &Loader) -> CpuMaterial {
|
fn prop_texture_to_material(texture: &str, loader: &Loader) -> CpuMaterial {
|
||||||
convert_material(load_material_fallback(
|
convert_material(load_material_fallback(&texture, loader))
|
||||||
&texture.name,
|
|
||||||
&texture.search_paths,
|
|
||||||
loader,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue