mirror of
https://codeberg.org/icewind/vbsp-to-gltf.git
synced 2026-06-03 18:24:07 +02:00
updates
This commit is contained in:
parent
e393b88ece
commit
14d12fea4c
8 changed files with 1241 additions and 870 deletions
1950
Cargo.lock
generated
1950
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
50
Cargo.toml
50
Cargo.toml
|
|
@ -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
46
flake.lock
generated
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
28
src/prop.rs
28
src/prop.rs
|
|
@ -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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue