mirror of
https://codeberg.org/icewind/vbsp-to-gltf.git
synced 2026-06-03 18:24:07 +02:00
add convert options
This commit is contained in:
parent
1161fcf2ad
commit
16d20a5faf
11 changed files with 171 additions and 41 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -2181,6 +2181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8277e703c934b9693d0773d5749faacc6366b3d81d012da556a4cfd4ab87f336"
|
checksum = "8277e703c934b9693d0773d5749faacc6366b3d81d012da556a4cfd4ab87f336"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libm",
|
"libm",
|
||||||
|
"rayon",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2610,6 +2611,7 @@ dependencies = [
|
||||||
name = "vbsp-to-gltf"
|
name = "vbsp-to-gltf"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
"async-tempfile",
|
"async-tempfile",
|
||||||
"axum",
|
"axum",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
|
@ -2622,6 +2624,7 @@ dependencies = [
|
||||||
"miette",
|
"miette",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
"texpresso",
|
||||||
"tf-asset-loader",
|
"tf-asset-loader",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
|
||||||
12
Cargo.toml
12
Cargo.toml
|
|
@ -34,9 +34,10 @@ gltf-json = { version = "1.4.0", features = ["KHR_texture_transform"] }
|
||||||
gltf = "1.4.0"
|
gltf = "1.4.0"
|
||||||
cgmath = "0.18.0"
|
cgmath = "0.18.0"
|
||||||
bytemuck = { version = "1.14.0", features = ["derive"] }
|
bytemuck = { version = "1.14.0", features = ["derive"] }
|
||||||
|
texpresso = { version = "2.0.1", features = ["rayon"] }
|
||||||
|
serde = "1.0.193"
|
||||||
|
|
||||||
url = { version = "2.5.0", optional = true, features = ["serde"] }
|
url = { version = "2.5.0", optional = true, features = ["serde"] }
|
||||||
serde = { version = "1.0.193", optional = true }
|
|
||||||
toml = { version = "0.8.8", optional = true }
|
toml = { version = "0.8.8", optional = true }
|
||||||
axum = { version = "0.7.2", optional = true, features = ["macros"] }
|
axum = { version = "0.7.2", optional = true, features = ["macros"] }
|
||||||
tokio = { version = "1.35.1", features = ["full"], optional = true }
|
tokio = { version = "1.35.1", features = ["full"], optional = true }
|
||||||
|
|
@ -44,9 +45,14 @@ reqwest = { version = "0.11.23", optional = true, default-features = false, feat
|
||||||
async-tempfile = { version = "0.5.0", optional = true }
|
async-tempfile = { version = "0.5.0", optional = true }
|
||||||
tower-http = { version = "0.5.0", optional = true, features = ["cors"] }
|
tower-http = { version = "0.5.0", optional = true, features = ["cors"] }
|
||||||
http = { version = "1.0.0", optional = true }
|
http = { version = "1.0.0", optional = true }
|
||||||
|
ahash = { version = "0.8.6", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
server = ["url", "serde", "toml", "axum", "tokio", "reqwest", "async-tempfile", "tower-http", "http"]
|
server = ["url", "toml", "axum", "tokio", "reqwest", "async-tempfile", "tower-http", "http", "ahash"]
|
||||||
|
|
||||||
[profile.dev.package."*"]
|
[profile.dev.package."*"]
|
||||||
opt-level = 2
|
opt-level = 2
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
codegen-units = 1
|
||||||
|
lto = true
|
||||||
19
src/bsp.rs
19
src/bsp.rs
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::convert::map_coords;
|
use crate::convert::map_coords;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::gltf_builder::push_or_get_material;
|
use crate::gltf_builder::push_or_get_material;
|
||||||
|
use crate::ConvertOptions;
|
||||||
use bytemuck::{offset_of, Pod, Zeroable};
|
use bytemuck::{offset_of, Pod, Zeroable};
|
||||||
use gltf_json::accessor::{ComponentType, GenericComponentType, Type};
|
use gltf_json::accessor::{ComponentType, GenericComponentType, Type};
|
||||||
use gltf_json::buffer::{Stride, Target, View};
|
use gltf_json::buffer::{Stride, Target, View};
|
||||||
|
|
@ -73,11 +74,12 @@ pub fn push_bsp_model(
|
||||||
loader: &Loader,
|
loader: &Loader,
|
||||||
model: &Handle<Model>,
|
model: &Handle<Model>,
|
||||||
offset: Vector,
|
offset: Vector,
|
||||||
|
options: &ConvertOptions,
|
||||||
) -> Node {
|
) -> Node {
|
||||||
let primitives = model
|
let primitives = model
|
||||||
.faces()
|
.faces()
|
||||||
.filter(|face| face.is_visible())
|
.filter(|face| face.is_visible())
|
||||||
.map(|face| push_bsp_face(buffer, gltf, loader, &face))
|
.map(|face| push_bsp_face(buffer, gltf, loader, &face, options))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mesh = Mesh {
|
let mesh = Mesh {
|
||||||
|
|
@ -112,6 +114,7 @@ pub fn push_bsp_face(
|
||||||
gltf: &mut Root,
|
gltf: &mut Root,
|
||||||
loader: &Loader,
|
loader: &Loader,
|
||||||
face: &Handle<Face>,
|
face: &Handle<Face>,
|
||||||
|
options: &ConvertOptions,
|
||||||
) -> Primitive {
|
) -> Primitive {
|
||||||
let vertex_count = face.vertex_positions().count() as u64;
|
let vertex_count = face.vertex_positions().count() as u64;
|
||||||
|
|
||||||
|
|
@ -175,7 +178,17 @@ pub fn push_bsp_face(
|
||||||
gltf.accessors.push(positions);
|
gltf.accessors.push(positions);
|
||||||
gltf.accessors.push(uvs);
|
gltf.accessors.push(uvs);
|
||||||
|
|
||||||
let material_index = push_or_get_material(buffer, gltf, loader, face.texture().name());
|
let material_index = if options.textures {
|
||||||
|
Some(push_or_get_material(
|
||||||
|
buffer,
|
||||||
|
gltf,
|
||||||
|
loader,
|
||||||
|
face.texture().name(),
|
||||||
|
options,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
Primitive {
|
Primitive {
|
||||||
attributes: {
|
attributes: {
|
||||||
|
|
@ -190,7 +203,7 @@ pub fn push_bsp_face(
|
||||||
extensions: Default::default(),
|
extensions: Default::default(),
|
||||||
extras: Default::default(),
|
extras: Default::default(),
|
||||||
indices: None,
|
indices: None,
|
||||||
material: Some(material_index),
|
material: material_index,
|
||||||
mode: Valid(Mode::Triangles),
|
mode: Valid(Mode::Triangles),
|
||||||
targets: None,
|
targets: None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use tracing_subscriber::util::SubscriberInitExt;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
use tracing_tree::HierarchicalLayer;
|
use tracing_tree::HierarchicalLayer;
|
||||||
use vbsp::Bsp;
|
use vbsp::Bsp;
|
||||||
use vbsp_to_gltf::{export, Error};
|
use vbsp_to_gltf::{export, ConvertOptions, Error};
|
||||||
|
|
||||||
fn setup() {
|
fn setup() {
|
||||||
miette::set_panic_hook();
|
miette::set_panic_hook();
|
||||||
|
|
@ -43,7 +43,7 @@ fn main() -> miette::Result<()> {
|
||||||
let map = Bsp::read(&data).map_err(Error::from)?;
|
let map = Bsp::read(&data).map_err(Error::from)?;
|
||||||
loader.add_source(map.pack.clone().into_zip());
|
loader.add_source(map.pack.clone().into_zip());
|
||||||
|
|
||||||
let glb = export(map, &loader)?;
|
let glb = export(map, &loader, ConvertOptions::default())?;
|
||||||
|
|
||||||
let writer = File::create(&args.target)
|
let writer = File::create(&args.target)
|
||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use gltf_json as json;
|
||||||
|
|
||||||
use crate::bsp::{bsp_models, push_bsp_model};
|
use crate::bsp::{bsp_models, push_bsp_model};
|
||||||
use crate::prop::push_or_get_model;
|
use crate::prop::push_or_get_model;
|
||||||
use crate::Error;
|
use crate::{ConvertOptions, Error};
|
||||||
use cgmath::{Deg, Quaternion, Rotation3};
|
use cgmath::{Deg, Quaternion, Rotation3};
|
||||||
use gltf::Glb;
|
use gltf::Glb;
|
||||||
use gltf_json::scene::UnitQuaternion;
|
use gltf_json::scene::UnitQuaternion;
|
||||||
|
|
@ -12,18 +12,25 @@ use std::borrow::Cow;
|
||||||
use tf_asset_loader::Loader;
|
use tf_asset_loader::Loader;
|
||||||
use vbsp::Bsp;
|
use vbsp::Bsp;
|
||||||
|
|
||||||
pub fn export(bsp: Bsp, loader: &Loader) -> 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();
|
||||||
|
|
||||||
let mut root = Root::default();
|
let mut root = Root::default();
|
||||||
|
|
||||||
for (model, offset) in bsp_models(&bsp)? {
|
for (model, offset) in bsp_models(&bsp)? {
|
||||||
let node = push_bsp_model(&mut buffer, &mut root, loader, &model, offset);
|
let node = push_bsp_model(&mut buffer, &mut root, loader, &model, offset, &options);
|
||||||
root.nodes.push(node);
|
root.nodes.push(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
for prop in bsp.static_props() {
|
for prop in bsp.static_props() {
|
||||||
let mesh = push_or_get_model(&mut buffer, &mut root, loader, prop.model(), prop.skin);
|
let mesh = push_or_get_model(
|
||||||
|
&mut buffer,
|
||||||
|
&mut root,
|
||||||
|
loader,
|
||||||
|
prop.model(),
|
||||||
|
prop.skin,
|
||||||
|
&options,
|
||||||
|
);
|
||||||
let rotation = prop.rotation();
|
let rotation = prop.rotation();
|
||||||
|
|
||||||
let node = Node {
|
let node = Node {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::convert::pad_byte_vector;
|
use crate::convert::pad_byte_vector;
|
||||||
use crate::materials::{load_material_fallback, MaterialData, TextureData};
|
use crate::materials::{load_material_fallback, MaterialData, TextureData};
|
||||||
|
use crate::ConvertOptions;
|
||||||
use gltf_json::buffer::View;
|
use gltf_json::buffer::View;
|
||||||
use gltf_json::extensions::texture::{
|
use gltf_json::extensions::texture::{
|
||||||
TextureTransform, TextureTransformOffset, TextureTransformRotation, TextureTransformScale,
|
TextureTransform, TextureTransformOffset, TextureTransformRotation, TextureTransformScale,
|
||||||
|
|
@ -20,12 +21,13 @@ pub fn push_or_get_material(
|
||||||
gltf: &mut Root,
|
gltf: &mut Root,
|
||||||
loader: &Loader,
|
loader: &Loader,
|
||||||
material: &str,
|
material: &str,
|
||||||
|
options: &ConvertOptions,
|
||||||
) -> Index<Material> {
|
) -> Index<Material> {
|
||||||
let material = material.to_ascii_lowercase();
|
let material = material.to_ascii_lowercase();
|
||||||
match get_material_index(&gltf.materials, &material) {
|
match get_material_index(&gltf.materials, &material) {
|
||||||
Some(index) => index,
|
Some(index) => index,
|
||||||
None => {
|
None => {
|
||||||
let material = load_material_fallback(&material, &[String::new()], loader);
|
let material = load_material_fallback(&material, &[String::new()], loader, options);
|
||||||
let index = gltf.materials.len() as u32;
|
let index = gltf.materials.len() as u32;
|
||||||
let material = push_material(buffer, gltf, material);
|
let material = push_material(buffer, gltf, material);
|
||||||
gltf.materials.push(material);
|
gltf.materials.push(material);
|
||||||
|
|
|
||||||
37
src/lib.rs
37
src/lib.rs
|
|
@ -5,5 +5,42 @@ pub mod gltf_builder;
|
||||||
mod materials;
|
mod materials;
|
||||||
mod prop;
|
mod prop;
|
||||||
|
|
||||||
|
use ahash::RandomState;
|
||||||
pub use convert::export;
|
pub use convert::export;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::hash::{BuildHasher, Hash, Hasher};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct ConvertOptions {
|
||||||
|
#[serde(default = "default_enable")]
|
||||||
|
pub textures: bool,
|
||||||
|
#[serde(default = "default_scale")]
|
||||||
|
pub texture_scale: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConvertOptions {
|
||||||
|
pub fn key(&self) -> u64 {
|
||||||
|
let mut hasher = RandomState::with_seeds(1, 2, 3, 4).build_hasher();
|
||||||
|
self.textures.hash(&mut hasher);
|
||||||
|
self.texture_scale.to_le_bytes().hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ConvertOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
ConvertOptions {
|
||||||
|
textures: true,
|
||||||
|
texture_scale: 1.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_enable() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_scale() -> f32 {
|
||||||
|
1.0
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,19 @@
|
||||||
use crate::Error;
|
use crate::{ConvertOptions, Error};
|
||||||
use image::DynamicImage;
|
use image::imageops::FilterType;
|
||||||
|
use image::{DynamicImage, GenericImageView};
|
||||||
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};
|
||||||
use vmt_parser::{from_str, TextureTransform};
|
use vmt_parser::{from_str, TextureTransform};
|
||||||
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(
|
||||||
match load_material(name, search_dirs, loader) {
|
name: &str,
|
||||||
|
search_dirs: &[String],
|
||||||
|
loader: &Loader,
|
||||||
|
options: &ConvertOptions,
|
||||||
|
) -> MaterialData {
|
||||||
|
match load_material(name, search_dirs, loader, options) {
|
||||||
Ok(mat) => mat,
|
Ok(mat) => mat,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(error = ?e, "failed to load material");
|
error!(error = ?e, "failed to load material");
|
||||||
|
|
@ -44,6 +50,7 @@ pub fn load_material(
|
||||||
name: &str,
|
name: &str,
|
||||||
search_dirs: &[String],
|
search_dirs: &[String],
|
||||||
loader: &Loader,
|
loader: &Loader,
|
||||||
|
options: &ConvertOptions,
|
||||||
) -> Result<MaterialData, Error> {
|
) -> Result<MaterialData, Error> {
|
||||||
let dirs = search_dirs
|
let dirs = search_dirs
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -94,11 +101,11 @@ pub fn load_material(
|
||||||
let translucent = material.translucent();
|
let translucent = material.translucent();
|
||||||
let glass = material.surface_prop() == Some("glass");
|
let glass = material.surface_prop() == Some("glass");
|
||||||
let alpha_test = material.alpha_test();
|
let alpha_test = material.alpha_test();
|
||||||
let texture = load_texture(base_texture, loader)?;
|
let texture = load_texture(base_texture, loader, options)?;
|
||||||
|
|
||||||
let bump_map = material.bump_map().and_then(|path| {
|
let bump_map = material.bump_map().and_then(|path| {
|
||||||
Some(TextureData {
|
Some(TextureData {
|
||||||
image: load_texture(path, loader).ok()?,
|
image: load_texture(path, loader, options).ok()?,
|
||||||
name: path.into(),
|
name: path.into(),
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
@ -124,7 +131,11 @@ pub fn load_material(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_texture(name: &str, loader: &Loader) -> Result<DynamicImage, Error> {
|
fn load_texture(
|
||||||
|
name: &str,
|
||||||
|
loader: &Loader,
|
||||||
|
options: &ConvertOptions,
|
||||||
|
) -> Result<DynamicImage, Error> {
|
||||||
let path = format!(
|
let path = format!(
|
||||||
"materials/{}.vtf",
|
"materials/{}.vtf",
|
||||||
name.trim_end_matches(".vtf").trim_start_matches('/')
|
name.trim_end_matches(".vtf").trim_start_matches('/')
|
||||||
|
|
@ -134,5 +145,13 @@ fn load_texture(name: &str, loader: &Loader) -> Result<DynamicImage, Error> {
|
||||||
.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(&mut raw)?;
|
||||||
let image = vtf.highres_image.decode(0)?;
|
let image = vtf.highres_image.decode(0)?;
|
||||||
Ok(image)
|
if options.texture_scale != 1.0 {
|
||||||
|
Ok(image.resize(
|
||||||
|
(image.width() as f32 * options.texture_scale) as u32,
|
||||||
|
(image.height() as f32 * options.texture_scale) as u32,
|
||||||
|
FilterType::CatmullRom,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(image)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
43
src/prop.rs
43
src/prop.rs
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::convert::map_coords;
|
use crate::convert::map_coords;
|
||||||
use crate::gltf_builder::push_or_get_material;
|
use crate::gltf_builder::push_or_get_material;
|
||||||
use crate::Error;
|
use crate::{ConvertOptions, Error};
|
||||||
use bytemuck::{offset_of, Pod, Zeroable};
|
use bytemuck::{offset_of, Pod, Zeroable};
|
||||||
use gltf_json::accessor::{ComponentType, GenericComponentType, Type};
|
use gltf_json::accessor::{ComponentType, GenericComponentType, Type};
|
||||||
use gltf_json::buffer::{Stride, Target, View};
|
use gltf_json::buffer::{Stride, Target, View};
|
||||||
|
|
@ -125,6 +125,7 @@ pub fn push_or_get_model(
|
||||||
loader: &Loader,
|
loader: &Loader,
|
||||||
model: &str,
|
model: &str,
|
||||||
skin: i32,
|
skin: i32,
|
||||||
|
options: &ConvertOptions,
|
||||||
) -> Index<Mesh> {
|
) -> 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) {
|
||||||
|
|
@ -132,7 +133,7 @@ pub fn push_or_get_model(
|
||||||
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;
|
let index = gltf.meshes.len() as u32;
|
||||||
let material = push_model(buffer, gltf, loader, &prop, skin, skinned_name);
|
let material = push_model(buffer, gltf, loader, &prop, skin, skinned_name, options);
|
||||||
gltf.meshes.push(material);
|
gltf.meshes.push(material);
|
||||||
Index::new(index)
|
Index::new(index)
|
||||||
}
|
}
|
||||||
|
|
@ -154,6 +155,7 @@ pub fn push_model(
|
||||||
model: &Model,
|
model: &Model,
|
||||||
skin: i32,
|
skin: i32,
|
||||||
skinned_name: String,
|
skinned_name: String,
|
||||||
|
options: &ConvertOptions,
|
||||||
) -> Mesh {
|
) -> Mesh {
|
||||||
let accessor_start = gltf.accessors.len() as u32;
|
let accessor_start = gltf.accessors.len() as u32;
|
||||||
push_vertices(buffer, gltf, model);
|
push_vertices(buffer, gltf, model);
|
||||||
|
|
@ -164,7 +166,17 @@ pub fn push_model(
|
||||||
|
|
||||||
let primitives = model
|
let primitives = model
|
||||||
.meshes()
|
.meshes()
|
||||||
.map(|mesh| push_primitive(buffer, gltf, loader, &mesh, accessor_start, &skin_table))
|
.map(|mesh| {
|
||||||
|
push_primitive(
|
||||||
|
buffer,
|
||||||
|
gltf,
|
||||||
|
loader,
|
||||||
|
&mesh,
|
||||||
|
accessor_start,
|
||||||
|
&skin_table,
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Mesh {
|
Mesh {
|
||||||
|
|
@ -183,6 +195,7 @@ pub fn push_primitive(
|
||||||
mesh: &vmdl::Mesh,
|
mesh: &vmdl::Mesh,
|
||||||
vertex_accessor_start: u32,
|
vertex_accessor_start: u32,
|
||||||
skin: &SkinTable,
|
skin: &SkinTable,
|
||||||
|
options: &ConvertOptions,
|
||||||
) -> Primitive {
|
) -> Primitive {
|
||||||
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;
|
||||||
|
|
@ -224,12 +237,22 @@ pub fn push_primitive(
|
||||||
};
|
};
|
||||||
gltf.accessors.push(accessor);
|
gltf.accessors.push(accessor);
|
||||||
|
|
||||||
let texture = skin
|
let material = if options.textures {
|
||||||
.texture_info(mesh.material_index())
|
let texture = skin
|
||||||
.expect("mat out of bounds");
|
.texture_info(mesh.material_index())
|
||||||
let texture_path = find_material(&texture.name, &texture.search_paths, loader)
|
.expect("mat out of bounds");
|
||||||
.expect("failed to find texture");
|
let texture_path = find_material(&texture.name, &texture.search_paths, loader)
|
||||||
let material_index = push_or_get_material(buffer, gltf, loader, &texture_path);
|
.expect("failed to find texture");
|
||||||
|
Some(push_or_get_material(
|
||||||
|
buffer,
|
||||||
|
gltf,
|
||||||
|
loader,
|
||||||
|
&texture_path,
|
||||||
|
options,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
Primitive {
|
Primitive {
|
||||||
attributes: {
|
attributes: {
|
||||||
|
|
@ -251,7 +274,7 @@ pub fn push_primitive(
|
||||||
extensions: Default::default(),
|
extensions: Default::default(),
|
||||||
extras: Default::default(),
|
extras: Default::default(),
|
||||||
indices: Some(Index::new(accessor_start)),
|
indices: Some(Index::new(accessor_start)),
|
||||||
material: Some(material_index),
|
material,
|
||||||
mode: Valid(Mode::Triangles),
|
mode: Valid(Mode::Triangles),
|
||||||
targets: None,
|
targets: None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,5 +20,13 @@
|
||||||
<aside>
|
<aside>
|
||||||
Note that map conversion can take 10 to 30 seconds, once a map has been converted once it is cached for subsequent requests.
|
Note that map conversion can take 10 to 30 seconds, once a map has been converted once it is cached for subsequent requests.
|
||||||
</aside>
|
</aside>
|
||||||
|
<h2>Options</h2>
|
||||||
|
<p>
|
||||||
|
The following options can be set as query parameters
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>textures=false</code>: disable textures</li>
|
||||||
|
<li><code>texture_scale=<scale></code>: scale textures, lower scale for significantly smaller filesizes</li>
|
||||||
|
</ul>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
mod pack;
|
mod pack;
|
||||||
|
|
||||||
use crate::pack::pack;
|
use crate::pack::pack;
|
||||||
use axum::extract::{Path, State};
|
use axum::extract::{Path, Query, State};
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum::response::{Html, IntoResponse, Response};
|
use axum::response::{Html, IntoResponse, Response};
|
||||||
use axum::routing::get;
|
use axum::routing::get;
|
||||||
|
|
@ -27,7 +27,7 @@ use tracing_subscriber::EnvFilter;
|
||||||
use tracing_tree::HierarchicalLayer;
|
use tracing_tree::HierarchicalLayer;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use vbsp::{Bsp, BspError};
|
use vbsp::{Bsp, BspError};
|
||||||
use vbsp_to_gltf::{export, Error};
|
use vbsp_to_gltf::{export, ConvertOptions, Error};
|
||||||
|
|
||||||
type Result<T, E = ServerError> = std::result::Result<T, E>;
|
type Result<T, E = ServerError> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
|
@ -163,8 +163,12 @@ struct App {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn cached(&self, map: &str) -> Result<Option<Vec<u8>>> {
|
fn cache_path(&self, map: &str, options_key: u64) -> PathBuf {
|
||||||
let path = self.cache_dir.join(map);
|
self.cache_dir.join(format!("{options_key:016x}_{map}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cached(&self, map: &str, options_key: u64) -> Result<Option<Vec<u8>>> {
|
||||||
|
let path = self.cache_path(map, options_key);
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
Ok(Some(read(path)?))
|
Ok(Some(read(path)?))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -172,8 +176,8 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cache(&self, map: &str, data: &[u8]) -> Result<()> {
|
fn cache(&self, map: &str, data: &[u8], options_key: u64) -> Result<()> {
|
||||||
let path = self.cache_dir.join(map);
|
let path = self.cache_path(map, options_key);
|
||||||
Ok(write(path, data)?)
|
Ok(write(path, data)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -198,11 +202,19 @@ async fn index() -> impl IntoResponse {
|
||||||
Html(include_str!("./index.html"))
|
Html(include_str!("./index.html"))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn convert(State(app): State<Arc<App>>, Path(map): Path<String>) -> impl IntoResponse {
|
async fn convert(
|
||||||
|
State(app): State<Arc<App>>,
|
||||||
|
Path(map): Path<String>,
|
||||||
|
Query(mut options): Query<ConvertOptions>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
if options.texture_scale > 1.0 {
|
||||||
|
options.texture_scale = 1.0;
|
||||||
|
}
|
||||||
|
let options_key = options.key();
|
||||||
if !map.is_ascii() || map.contains('/') || !map.ends_with(".glb") {
|
if !map.is_ascii() || map.contains('/') || !map.ends_with(".glb") {
|
||||||
return Err(ServerError::InvalidMapName(map));
|
return Err(ServerError::InvalidMapName(map));
|
||||||
}
|
}
|
||||||
if let Some(cached) = app.cached(&map)? {
|
if let Some(cached) = app.cached(&map, options_key)? {
|
||||||
info!(map = map, "serving cached model");
|
info!(map = map, "serving cached model");
|
||||||
return Ok(cached);
|
return Ok(cached);
|
||||||
}
|
}
|
||||||
|
|
@ -215,7 +227,7 @@ async fn convert(State(app): State<Arc<App>>, Path(map): Path<String>) -> impl I
|
||||||
let bsp = Bsp::read(&bsp_data).map_err(Error::from)?;
|
let bsp = Bsp::read(&bsp_data).map_err(Error::from)?;
|
||||||
loader.add_source(bsp.pack.clone().into_zip());
|
loader.add_source(bsp.pack.clone().into_zip());
|
||||||
|
|
||||||
let glb = export(bsp, &loader)?;
|
let glb = export(bsp, &loader, options)?;
|
||||||
let glb = glb.to_vec().map_err(Error::from)?;
|
let glb = glb.to_vec().map_err(Error::from)?;
|
||||||
let packed = pack(&map, &glb).await?;
|
let packed = pack(&map, &glb).await?;
|
||||||
|
|
||||||
|
|
@ -226,7 +238,7 @@ async fn convert(State(app): State<Arc<App>>, Path(map): Path<String>) -> impl I
|
||||||
"optimized model"
|
"optimized model"
|
||||||
);
|
);
|
||||||
|
|
||||||
app.cache(&map, &packed)?;
|
app.cache(&map, &packed, options_key)?;
|
||||||
|
|
||||||
Ok(packed)
|
Ok(packed)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue