This commit is contained in:
Robin Appelman 2024-08-29 15:02:25 +02:00
commit 14d12fea4c
8 changed files with 1241 additions and 870 deletions

1950
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -18,34 +18,34 @@ path = "src/server/server.rs"
required-features = ["server"] required-features = ["server"]
[dependencies] [dependencies]
miette = { version = "5.5.0", features = ["fancy"] } miette = { version = "7.2.0", features = ["fancy"] }
vbsp = "0.4.1" vbsp = "0.6.0"
thiserror = "1.0.37" thiserror = "1.0.63"
tracing = "0.1.37" tracing = "0.1.40"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
tracing-tree = "0.3.0" tracing-tree = "0.4.0"
vtf = "0.1.6" vtf = "0.3.0"
vmt-parser = "0.1.1" vmt-parser = "0.2.0"
image = "0.23.14" image = "0.25.2"
tf-asset-loader = { version = "0.1.4", features = ["zip"] } tf-asset-loader = { version = "0.1.5", features = ["zip"] }
vmdl = "0.1" vmdl = "0.2"
clap = { version = "4.0.29", features = ["derive"] } clap = { version = "4.4.18", features = ["derive"] }
gltf-json = { version = "1.4.0", features = ["KHR_texture_transform"] } gltf-json = { version = "1.4.1", features = ["KHR_texture_transform"] }
gltf = "1.4.0" gltf = "1.4.1"
cgmath = "0.18.0" cgmath = "0.18.0"
bytemuck = { version = "1.14.0", features = ["derive"] } bytemuck = { version = "1.17.1", features = ["derive"] }
texpresso = { version = "2.0.1", features = ["rayon"] } texpresso = { version = "2.0.1", features = ["rayon"] }
serde = "1.0.193" serde = "1.0.209"
ahash = "0.8.6" ahash = "0.8.11"
url = { version = "2.5.0", optional = true, features = ["serde"] } url = { version = "2.5.2", optional = true, features = ["serde"] }
toml = { version = "0.8.8", optional = true } toml = { version = "0.8.19", optional = true }
axum = { version = "0.7.2", optional = true, features = ["macros"] } axum = { version = "0.7.5", optional = true, features = ["macros"] }
tokio = { version = "1.35.1", features = ["full"], optional = true } tokio = { version = "1.39.3", features = ["full"], optional = true }
reqwest = { version = "0.11.23", optional = true, default-features = false, features = ["rustls-tls-webpki-roots"] } reqwest = { version = "0.12.7", optional = true, default-features = false, features = ["rustls-tls-webpki-roots"] }
async-tempfile = { version = "0.5.0", optional = true } async-tempfile = { version = "0.6.0", optional = true }
tower-http = { version = "0.5.0", optional = true, features = ["cors"] } tower-http = { version = "0.5.2", optional = true, features = ["cors"] }
http = { version = "1.0.0", optional = true } http = { version = "1.1.0", optional = true }
[features] [features]
server = ["url", "toml", "axum", "tokio", "reqwest", "async-tempfile", "tower-http", "http"] server = ["url", "toml", "axum", "tokio", "reqwest", "async-tempfile", "tower-http", "http"]

46
flake.lock generated
View file

@ -10,11 +10,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1689107909, "lastModified": 1717704286,
"narHash": "sha256-fb+zxf7AWesECHx1foXOM3NcKHLrdeXzGb6s2AhT6pE=", "narHash": "sha256-zrLB/FTKODEAlJjgO8TwbK7teTseYbjLESp8QJ/FJYc=",
"owner": "icewind1991", "owner": "icewind1991",
"repo": "cross-naersk", "repo": "cross-naersk",
"rev": "51de54599de569e6faa2ee33dd659c5c028d9911", "rev": "9068daceb8f0d248dcf629944f60e92b81391bdb",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -30,11 +30,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1698420672, "lastModified": 1721727458,
"narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=", "narHash": "sha256-r/xppY958gmZ4oTfLiHN0ZGuQ+RSTijDblVgVLFi1mw=",
"owner": "nix-community", "owner": "nix-community",
"repo": "naersk", "repo": "naersk",
"rev": "aeb58d5e8faead8980a807c840232697982d47b9", "rev": "3fb418eaf352498f6b6c30592e3beb63df42ef11",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -45,16 +45,16 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1702346276, "lastModified": 1724727824,
"narHash": "sha256-eAQgwIWApFQ40ipeOjVSoK4TEHVd6nbSd9fApiHIw5A=", "narHash": "sha256-0XH9MJk54imJm+RHOLTUJ7e+ponLW00tw5ke4MTVa1Y=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "cf28ee258fd5f9a52de6b9865cdb93a1f96d09b7", "rev": "36bae45077667aff5720e5b3f1a5458f51cf0776",
"type": "github" "type": "github"
}, },
"original": { "original": {
"id": "nixpkgs", "id": "nixpkgs",
"ref": "nixos-23.11", "ref": "nixos-24.05",
"type": "indirect" "type": "indirect"
} }
}, },
@ -70,19 +70,16 @@
}, },
"rust-overlay": { "rust-overlay": {
"inputs": { "inputs": {
"flake-utils": [
"utils"
],
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1702520151, "lastModified": 1724898214,
"narHash": "sha256-jxJWosN7hgcW+dFT8V3EBDCYUOjv5tpjEBRmlakS7tU=", "narHash": "sha256-4yMO9+Lsr3zqTf4clAGGag/bfNTmc/ITOXbJQcOEok4=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "d6a1d8f80dbcda4c13993b859a3574c3dde61072", "rev": "0bc2c784e3a6ce30a2ab1b9f47325ccbed13039f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -98,16 +95,15 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1703615423, "lastModified": 1714795926,
"narHash": "sha256-LIK2g0Qo3+ykC3J8x4b/rvxoTVsJ/WA9HzfIVIHvdRE=", "narHash": "sha256-PkgC9jqoN6cJ8XYzTA2PlrWs7aPJkM3BGiTxNqax0cA=",
"owner": "icewind1991", "owner": "nix-community",
"repo": "steam-fetcher", "repo": "steam-fetcher",
"rev": "0eba31348de19e91ffaf375467b448f6549a7eb3", "rev": "12f66eafb7862d91b3e30c14035f96a21941bd9c",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "icewind1991", "owner": "nix-community",
"ref": "filelist",
"repo": "steam-fetcher", "repo": "steam-fetcher",
"type": "github" "type": "github"
} }
@ -132,11 +128,11 @@
"systems": "systems" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1701680307, "lastModified": 1710146030,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725", "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -1,6 +1,6 @@
{ {
inputs = { inputs = {
nixpkgs.url = "nixpkgs/nixos-23.11"; nixpkgs.url = "nixpkgs/nixos-24.05";
utils.url = "github:numtide/flake-utils"; utils.url = "github:numtide/flake-utils";
naersk.url = "github:nix-community/naersk"; naersk.url = "github:nix-community/naersk";
naersk.inputs.nixpkgs.follows = "nixpkgs"; naersk.inputs.nixpkgs.follows = "nixpkgs";
@ -11,8 +11,7 @@
cross-naersk.inputs.nixpkgs.follows = "nixpkgs"; cross-naersk.inputs.nixpkgs.follows = "nixpkgs";
cross-naersk.inputs.naersk.follows = "naersk"; cross-naersk.inputs.naersk.follows = "naersk";
steam-fetcher = { steam-fetcher = {
# url = "github:nix-community/steam-fetcher"; url = "github:nix-community/steam-fetcher";
url = "github:icewind1991/steam-fetcher/filelist";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
}; };

View file

@ -10,7 +10,7 @@ use gltf_json::validation::USize64;
use gltf_json::{Buffer, Index, Node, Root, Scene}; use gltf_json::{Buffer, Index, Node, Root, Scene};
use std::borrow::Cow; use std::borrow::Cow;
use tf_asset_loader::Loader; use tf_asset_loader::Loader;
use vbsp::Bsp; use vbsp::{Bsp, Entity};
pub fn export(bsp: Bsp, loader: &Loader, options: ConvertOptions) -> Result<Glb<'static>, Error> { pub fn export(bsp: Bsp, loader: &Loader, options: ConvertOptions) -> Result<Glb<'static>, Error> {
let mut buffer = Vec::new(); let mut buffer = Vec::new();
@ -22,37 +22,49 @@ pub fn export(bsp: Bsp, loader: &Loader, options: ConvertOptions) -> Result<Glb<
root.nodes.push(node); root.nodes.push(node);
} }
for prop in bsp.static_props() { let entity_props =
let mesh = push_or_get_model( bsp.entities
.iter()
.flat_map(|ent| ent.parse())
.filter_map(|ent| match ent {
Entity::PropDynamic(prop) => Some(prop.as_prop_placement()),
Entity::PropPhysics(prop) => Some(prop.as_prop_placement()),
Entity::PropDynamicOverride(prop) => Some(prop.as_prop_placement()),
_ => None,
});
let static_props = bsp.static_props().map(|prop| prop.as_prop_placement());
for prop in static_props.chain(entity_props) {
if let Some(mesh) = push_or_get_model(
&mut buffer, &mut buffer,
&mut root, &mut root,
loader, loader,
prop.model(), prop.model,
prop.skin, prop.skin,
&options, &options,
); ) {
let rotation = prop.rotation(); let rotation = prop.rotation;
let node = Node { let node = Node {
camera: None, camera: None,
children: None, children: None,
extensions: Default::default(), extensions: Default::default(),
extras: Default::default(), extras: Default::default(),
matrix: None, matrix: None,
mesh: Some(mesh), mesh: Some(mesh),
name: Some(prop.model().into()), name: Some(prop.model.into()),
rotation: Some(UnitQuaternion([ rotation: Some(UnitQuaternion([
rotation.v.x, rotation.v.x,
rotation.v.y, rotation.v.y,
rotation.v.z, rotation.v.z,
rotation.s, rotation.s,
])), ])),
scale: None, scale: None,
translation: Some(map_coords(prop.origin)), translation: Some(map_coords(prop.origin)),
skin: None, skin: None,
weights: None, weights: None,
}; };
root.nodes.push(node); root.nodes.push(node);
}
} }
let node_indices = 0..root.nodes.len(); let node_indices = 0..root.nodes.len();

View file

@ -11,8 +11,8 @@ use gltf_json::texture::Info;
use gltf_json::validation::Checked::Valid; use gltf_json::validation::Checked::Valid;
use gltf_json::validation::USize64; use gltf_json::validation::USize64;
use gltf_json::{Extras, Image, Index, Material, Root, Texture}; use gltf_json::{Extras, Image, Index, Material, Root, Texture};
use image::png::PngEncoder; use image::codecs::png::PngEncoder;
use image::{ColorType, DynamicImage, GenericImageView}; use image::{ColorType, DynamicImage, ImageEncoder};
use std::f32::consts::PI; use std::f32::consts::PI;
use tf_asset_loader::Loader; use tf_asset_loader::Loader;
@ -125,15 +125,17 @@ fn push_texture(buffer: &mut Vec<u8>, gltf: &mut Root, texture: TextureData) ->
let buffer_start = buffer.len() as u64; let buffer_start = buffer.len() as u64;
let view_start = gltf.buffer_views.len() as u32; let view_start = gltf.buffer_views.len() as u32;
let image_start = gltf.images.len() as u32; let image_start = gltf.images.len() as u32;
let image_buffer_size =
(image.color().bits_per_pixel() / 8) as u32 * image.width() * image.height();
let mut png_buffer = Vec::new(); let mut png_buffer = Vec::new();
let encoder = PngEncoder::new(&mut png_buffer); let encoder = PngEncoder::new(&mut png_buffer);
encoder encoder
.encode( .write_image(
image.as_bytes(), &image.as_bytes()[0..image_buffer_size as usize],
image.width(), image.width(),
image.height(), image.height(),
image.color(), image.color().into(),
) )
.expect("failed to encode"); .expect("failed to encode");

View file

@ -1,6 +1,6 @@
use crate::{ConvertOptions, Error}; use crate::{ConvertOptions, Error};
use image::imageops::FilterType; use image::imageops::FilterType;
use image::{DynamicImage, GenericImageView}; use image::DynamicImage;
use tf_asset_loader::Loader; use tf_asset_loader::Loader;
use tracing::{error, instrument}; use tracing::{error, instrument};
use vmt_parser::material::{Material, WaterMaterial}; use vmt_parser::material::{Material, WaterMaterial};
@ -140,10 +140,10 @@ fn load_texture(
"materials/{}.vtf", "materials/{}.vtf",
name.trim_end_matches(".vtf").trim_start_matches('/') name.trim_end_matches(".vtf").trim_start_matches('/')
); );
let mut raw = loader let raw = loader
.load(&path)? .load(&path)?
.ok_or(Error::Other(format!("Can't find file {}", path)))?; .ok_or(Error::Other(format!("Can't find file {}", path)))?;
let vtf = VTF::read(&mut raw)?; let vtf = VTF::read(&raw)?;
let image = vtf.highres_image.decode(0)?; let image = vtf.highres_image.decode(0)?;
if options.texture_scale != 1.0 { if options.texture_scale != 1.0 {
Ok(image.resize( Ok(image.resize(

View file

@ -20,10 +20,10 @@ pub struct ModelVertex {
uv: [f32; 2], uv: [f32; 2],
} }
impl From<&vmdl::vvd::Vertex> for ModelVertex { impl ModelVertex {
fn from(vertex: &vmdl::vvd::Vertex) -> Self { fn from(vertex: &vmdl::vvd::Vertex, model: &Model) -> Self {
ModelVertex { ModelVertex {
position: map_coords(vertex.position), position: map_coords(model.apply_root_transform(vertex.position)),
uv: vertex.texture_coordinates, uv: vertex.texture_coordinates,
normal: vertex.normal.into(), normal: vertex.normal.into(),
} }
@ -36,13 +36,13 @@ fn push_vertices(buffer: &mut Vec<u8>, gltf: &mut Root, model: &Model) {
let vertex_count = model.vertices().len() as u64; let vertex_count = model.vertices().len() as u64;
let (min, max) = model.bounding_box(); let (min, max) = model.bounding_box();
let min = map_coords(min); let min = map_coords(model.apply_root_transform(min));
let max = map_coords(max); let max = map_coords(model.apply_root_transform(max));
let vertex_data = model let vertex_data = model
.vertices() .vertices()
.iter() .iter()
.map(ModelVertex::from) .map(|vert| ModelVertex::from(vert, model))
.flat_map(bytemuck::cast::<_, [u8; size_of::<ModelVertex>()]>); .flat_map(bytemuck::cast::<_, [u8; size_of::<ModelVertex>()]>);
buffer.extend(vertex_data); buffer.extend(vertex_data);
@ -126,16 +126,20 @@ pub fn push_or_get_model(
model: &str, model: &str,
skin: i32, skin: i32,
options: &ConvertOptions, options: &ConvertOptions,
) -> Index<Mesh> { ) -> Option<Index<Mesh>> {
let skinned_name = format!("{model}_{skin}"); let skinned_name = format!("{model}_{skin}");
match get_mesh_index(&gltf.meshes, &skinned_name) { match get_mesh_index(&gltf.meshes, &skinned_name) {
Some(index) => index, Some(index) => Some(index),
None => { None => {
let prop = load_prop(loader, model).expect("failed to load prop"); let prop = load_prop(loader, model).expect("failed to load prop");
let index = gltf.meshes.len() as u32; if prop.vertices().is_empty() {
let material = push_model(buffer, gltf, loader, &prop, skin, skinned_name, options); None
gltf.meshes.push(material); } else {
Index::new(index) let index = gltf.meshes.len() as u32;
let material = push_model(buffer, gltf, loader, &prop, skin, skinned_name, options);
gltf.meshes.push(material);
Some(Index::new(index))
}
} }
} }
} }