texture wip

This commit is contained in:
Robin Appelman 2023-12-10 00:43:19 +01:00
commit 7948e6ba5b
8 changed files with 1180 additions and 67 deletions

898
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -19,6 +19,12 @@ three-d = { version = "0.14.0", features = ["egui-gui"] }
miette = { version = "5.5.0", features = ["fancy"] }
criterion = "0.4.0"
iai = "0.1"
steamlocate = "1.2.1"
vbsp = { version = "0.2.0", git = "https://github.com/icewind1991/vbsp" }
vtf = { version = "0.1.5", git = "https://github.com/roman901/vtf-rs", rev = "a346cb6d85c2766fe7e30b0a70b6f38e67c3a6c9" }
vpk = { version = "0.1.4", git = "https://github.com/icewind1991/vpk-rs", branch = "perf" }
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
[[bench]]
name = "parse"

View file

@ -16,7 +16,15 @@ fn main() -> Result<(), vmdl::ModelError> {
let _vtx = Vtx::read(&data)?;
let data = fs::read(path.with_extension("vvd"))?;
let _vvd = Vvd::read(&data)?;
dbg!(mdl.textures, mdl.texture_paths);
let models = mdl
.body_parts
.iter()
.flat_map(|part| part.models.iter())
.flat_map(|model| model.meshes.iter())
.map(|mesh| mesh.material)
.collect::<Vec<_>>();
dbg!(mdl.textures, models, mdl.texture_paths);
// let model = Model::from_parts(mdl, vtx, vvd);
// for strip in model.vertex_strips() {

97
examples/view/loader.rs Normal file
View file

@ -0,0 +1,97 @@
use std::fmt::{Debug, Formatter};
use std::fs;
use std::path::PathBuf;
use steamlocate::SteamDir;
use thiserror::Error;
use tracing::{debug, error, info};
use vpk::VPK;
#[derive(Debug, Error)]
pub enum LoadError {
#[error("{0}")]
Other(&'static str),
#[error(transparent)]
IO(#[from] std::io::Error),
}
impl From<&'static str> for LoadError {
fn from(e: &'static str) -> Self {
LoadError::Other(e)
}
}
pub struct Loader {
tf_dir: PathBuf,
download: PathBuf,
vpks: Vec<VPK>,
}
impl Debug for Loader {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Loader")
.field("tf_dir", &self.tf_dir)
.finish_non_exhaustive()
}
}
impl Loader {
pub fn new() -> Result<Self, LoadError> {
let tf_dir = SteamDir::locate()
.ok_or("Can't find steam directory")?
.app(&440)
.ok_or("Can't find tf2 directory")?
.path
.join("tf");
let download = tf_dir.join("download");
let vpks = tf_dir
.read_dir()?
.filter_map(|item| item.ok())
.filter_map(|item| Some(item.path().to_str()?.to_string()))
.filter(|path| path.ends_with("dir.vpk"))
.map(|path| vpk::from_path(&path))
.filter_map(|res| res.ok())
.collect();
Ok(Loader {
tf_dir,
download,
vpks,
})
}
#[tracing::instrument]
pub fn load(&self, name: &str) -> Result<Vec<u8>, LoadError> {
debug!("loading {}", name);
if name.ends_with("bsp") {
let path = self.tf_dir.join(name);
if path.exists() {
debug!("found in tf2 dir");
return Ok(fs::read(path)?);
}
let path = self.download.join(name);
if path.exists() {
debug!("found in download dir");
return Ok(fs::read(path)?);
}
}
for vpk in self.vpks.iter() {
if let Some(entry) = vpk.tree.get(name) {
let data = entry.get()?.into_owned();
debug!("got {} bytes from vpk", data.len());
return Ok(data);
}
}
info!("Failed to find {} in vpk", name);
Err(LoadError::Other("Can't find file in vpks"))
}
pub fn load_from_paths(&self, name: &str, paths: &[String]) -> Result<Vec<u8>, LoadError> {
for path in paths {
if let Ok(data) = self.load(&format!("{}{}", path, name)) {
return Ok(data);
}
}
error!("Failed to find {} in vpk paths: {}", name, paths.join(", "));
Err(LoadError::Other("Can't find file in vpks"))
}
}

View file

@ -1,12 +1,17 @@
mod loader;
use crate::loader::{LoadError, Loader};
use std::env::args_os;
use std::fs;
use std::path::{Path, PathBuf};
use thiserror::Error;
use three_d::*;
use tracing::error;
use vmdl::mdl::Mdl;
use vmdl::vtx::Vtx;
use vmdl::vvd::Vvd;
use vmdl::{Model, Vector};
use vtf::vtf::VTF;
#[derive(Debug, Error)]
enum Error {
@ -18,6 +23,10 @@ enum Error {
IO(#[from] std::io::Error),
#[error(transparent)]
Render(#[from] RendererError),
#[error(transparent)]
Loader(#[from] LoadError),
#[error(transparent)]
Vtf(#[from] vtf::Error),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -34,6 +43,7 @@ pub enum DebugType {
fn main() -> Result<(), Error> {
miette::set_panic_hook();
tracing_subscriber::fmt::init();
let mut args = args_os();
let _ = args.next();
@ -62,7 +72,9 @@ fn main() -> Result<(), Error> {
let mut control = OrbitControl::new(*camera.target(), 1.0, 100.0);
let mut gui = three_d::GUI::new(&context);
let cpu_mesh = model_to_mesh(&model);
let loader = Loader::new().expect("loader");
let cpu_model = model_to_model(&model, &loader);
let ph_material = PhysicalMaterial {
albedo: Color {
r: 128,
@ -72,23 +84,8 @@ fn main() -> Result<(), Error> {
},
..Default::default()
};
let material = CpuMaterial {
albedo: Color {
r: 128,
g: 128,
b: 128,
a: 255,
},
..Default::default()
};
let model: three_d::Model<PhysicalMaterial> = three_d::Model::new(
&context,
&CpuModel {
materials: vec![material],
geometries: vec![cpu_mesh],
},
)?;
let model: three_d::Model<PhysicalMaterial> = three_d::Model::new(&context, &cpu_model)?;
let mut directional = [
DirectionalLight::new(&context, 1.0, Color::WHITE, &vec3(1.0, -1.0, 0.0)),
@ -253,7 +250,7 @@ fn load(path: &Path) -> Result<Model, vmdl::ModelError> {
// 1 hammer unit is ~1.905cm
const UNIT_SCALE: f32 = 1.0 / (1.905 * 100.0);
fn model_to_mesh(model: &Model) -> CpuMesh {
fn model_to_model(model: &Model, loader: &Loader) -> CpuModel {
let offset = model
.vertices()
.iter()
@ -276,17 +273,89 @@ fn model_to_mesh(model: &Model) -> CpuMesh {
.iter()
.map(|vertex| vertex.normal.into())
.collect();
let uvs: Vec<Vec2> = model
.vertices()
.iter()
.map(|vertex| vertex.texture_coordinates.into())
.collect();
let texture_names = model.textures();
let geometries = model
.meshes()
.map(|mesh| {
let indices = Indices::U32(
model
.vertex_strip_indices()
mesh.vertex_strip_indices()
.flat_map(|strip| strip.map(|index| index as u32))
.collect(),
);
CpuMesh {
positions: Positions::F32(positions),
normals: Some(normals),
positions: Positions::F32(positions.clone()),
normals: Some(normals.clone()),
uvs: Some(uvs.clone()),
material_name: Some(
texture_names
.get(mesh.material_index() as usize)
.expect("texture out of bounds")
.name
.clone(),
),
indices,
..Default::default()
}
})
.collect();
let materials = model
.textures()
.iter()
.map(|texture| {
let dirs = model.texture_directories();
match load_texture(&texture.name, dirs, loader) {
Ok(texture) => CpuMaterial {
albedo: Color::default(),
name: texture.name.clone(),
albedo_texture: Some(texture),
..Default::default()
},
Err(e) => {
error!("{:#}", e);
CpuMaterial {
albedo: Color {
r: 255,
g: 0,
b: 255,
a: 255,
},
name: texture.name.clone(),
..Default::default()
}
}
}
})
.collect();
CpuModel {
materials,
geometries,
}
}
fn load_texture(name: &str, dirs: &[String], loader: &Loader) -> Result<CpuTexture, Error> {
let dirs = dirs
.iter()
.map(|dir| format!("materials/{}", dir))
.collect::<Vec<_>>();
let path = format!("{}.vtf", name);
let mut raw = loader.load_from_paths(&path, &dirs)?;
let vtf = VTF::read(&mut raw)?;
let image = vtf.highres_image.decode(0)?;
Ok(CpuTexture {
name: name.into(),
data: TextureData::RgbaU8(image.into_rgba8().pixels().map(|pixel| pixel.0).collect()),
height: vtf.header.height as u32,
width: vtf.header.width as u32,
..CpuTexture::default()
})
}

View file

@ -6,6 +6,7 @@ pub mod vtx;
pub mod vvd;
pub use crate::mdl::Mdl;
use crate::mdl::TextureInfo;
pub use crate::vtx::Vtx;
use crate::vvd::Vertex;
pub use crate::vvd::Vvd;
@ -37,6 +38,44 @@ impl Model {
&self.vvd.vertices
}
pub fn texture_directories(&self) -> &[String] {
&self.mdl.texture_paths
}
pub fn textures(&self) -> &[TextureInfo] {
&self.mdl.textures
}
pub fn meshes(&self) -> impl Iterator<Item = Mesh> {
let mdl_meshes = self
.mdl
.body_parts
.iter()
.flat_map(|part| part.models.iter())
.flat_map(|model| {
model
.meshes
.iter()
.map(|mesh| (mesh, model.vertex_offset as usize))
});
let vtx_meshes = self
.vtx
.body_parts
.iter()
.flat_map(|part| part.models.iter())
.flat_map(|model| model.lods.first())
.flat_map(|lod| lod.meshes.iter());
mdl_meshes
.zip(vtx_meshes)
.map(|((mdl, model_vertex_offset), vtx)| Mesh {
model_vertex_offset,
mdl,
vtx,
})
}
pub fn vertex_strip_indices(&self) -> impl Iterator<Item = impl Iterator<Item = usize> + '_> {
let mesh_vertex_offsets = self
.mdl
@ -81,6 +120,32 @@ impl Model {
}
}
pub struct Mesh<'a> {
model_vertex_offset: usize,
mdl: &'a mdl::Mesh,
vtx: &'a vtx::Mesh,
}
impl<'a> Mesh<'a> {
pub fn vertex_strip_indices(&self) -> impl Iterator<Item = impl Iterator<Item = usize> + '_> {
let mdl_offset = self.mdl.vertex_offset as usize + self.model_vertex_offset;
self.vtx.strip_groups.iter().flat_map(move |strip_group| {
let group_indices = &strip_group.indices;
let vertices = &strip_group.vertices;
strip_group.strips.iter().map(move |strip| {
strip
.indices()
.map(move |index| group_indices[index] as usize)
.map(move |index| vertices[index].original_mesh_vertex_id as usize + mdl_offset)
})
})
}
pub fn material_index(&self) -> i32 {
self.mdl.material
}
}
fn read_indexes<I: Iterator<Item = usize> + 'static, T: Readable>(
indexes: I,
data: &[u8],

View file

@ -29,10 +29,12 @@ impl Mdl {
.collect::<Result<Vec<TextureInfo>>>()?;
let texture_dirs_indexes =
read_relative_iter(data, header.texture_dir_indexes()).collect::<Result<Vec<u32>>>()?;
let texture_paths = read_relative::<String, _>(
let texture_paths = read_relative_iter::<String, _>(
data,
texture_dirs_indexes.into_iter().map(|index| index as usize),
)?;
)
.map(|path| path.map(|path| path.replace('\\', "/")))
.collect::<Result<Vec<_>>>()?;
let bones = read_indexes(header.bone_indexes(), data).collect::<Result<_>>()?;
Ok(Mdl {
@ -98,6 +100,7 @@ impl ReadRelative for Model {
#[derive(Debug, Clone)]
pub struct Mesh {
pub material: i32,
pub vertex_offset: i32,
}
@ -106,6 +109,7 @@ impl ReadRelative for Mesh {
fn read(_data: &[u8], header: Self::Header) -> Result<Self> {
Ok(Mesh {
material: header.material,
vertex_offset: header.vertex_index,
})
}
@ -121,7 +125,7 @@ impl ReadRelative for TextureInfo {
fn read(data: &[u8], header: Self::Header) -> Result<Self> {
Ok(TextureInfo {
name: String::read(&data[header.name_index as usize..], ())?,
name: String::read(&data[header.name_index as usize..], ())?.replace('\\', "/"),
})
}
}

View file

@ -124,7 +124,7 @@ pub struct ModelVertexData {
#[repr(C)]
#[allow(dead_code)]
pub struct MeshHeader {
material: i32,
pub material: i32,
model_index: i32,
vertex_count: i32,
pub vertex_index: i32,