mirror of
https://codeberg.org/icewind/vmdl.git
synced 2026-06-03 08:34:23 +02:00
gltf exporter wip
This commit is contained in:
parent
ab311b7f60
commit
30acc4b093
8 changed files with 475 additions and 7 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
/target
|
/target
|
||||||
.direnv
|
.direnv
|
||||||
result
|
result
|
||||||
|
*.glb
|
||||||
98
Cargo.lock
generated
98
Cargo.lock
generated
|
|
@ -147,6 +147,12 @@ dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "binrw"
|
name = "binrw"
|
||||||
version = "0.13.3"
|
version = "0.13.3"
|
||||||
|
|
@ -947,6 +953,44 @@ dependencies = [
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gltf"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad2dcfb6dd7a66f9eb3d181a29dcfb22d146b0bcdc2e1ed1713cbf03939a88ea"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"byteorder",
|
||||||
|
"gltf-json",
|
||||||
|
"image 0.24.7",
|
||||||
|
"lazy_static",
|
||||||
|
"urlencoding",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gltf-derive"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2cbcea5dd47e7ad4e9ee6f040384fcd7204bbf671aa4f9e7ca7dfc9bfa1de20"
|
||||||
|
dependencies = [
|
||||||
|
"inflections",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.40",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gltf-json"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d5b810806b78dde4b71a95cc0e6fdcab34c4c617da3574df166f9987be97d03"
|
||||||
|
dependencies = [
|
||||||
|
"gltf-derive",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glutin"
|
name = "glutin"
|
||||||
version = "0.29.1"
|
version = "0.29.1"
|
||||||
|
|
@ -1079,15 +1123,30 @@ dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"color_quant",
|
"color_quant",
|
||||||
"gif",
|
"gif",
|
||||||
"jpeg-decoder",
|
"jpeg-decoder 0.1.22",
|
||||||
"num-iter",
|
"num-iter",
|
||||||
"num-rational",
|
"num-rational 0.3.2",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"png 0.16.8",
|
"png 0.16.8",
|
||||||
"scoped_threadpool",
|
"scoped_threadpool",
|
||||||
"tiff",
|
"tiff",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image"
|
||||||
|
version = "0.24.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"byteorder",
|
||||||
|
"color_quant",
|
||||||
|
"jpeg-decoder 0.3.0",
|
||||||
|
"num-rational 0.4.1",
|
||||||
|
"num-traits",
|
||||||
|
"png 0.17.10",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.3"
|
version = "1.9.3"
|
||||||
|
|
@ -1108,6 +1167,12 @@ dependencies = [
|
||||||
"hashbrown 0.14.3",
|
"hashbrown 0.14.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inflections"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instant"
|
name = "instant"
|
||||||
version = "0.1.12"
|
version = "0.1.12"
|
||||||
|
|
@ -1176,6 +1241,12 @@ dependencies = [
|
||||||
"rayon",
|
"rayon",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jpeg-decoder"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.66"
|
version = "0.3.66"
|
||||||
|
|
@ -1549,6 +1620,17 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
|
|
@ -2432,7 +2514,7 @@ version = "0.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437"
|
checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jpeg-decoder",
|
"jpeg-decoder 0.1.22",
|
||||||
"miniz_oxide 0.4.4",
|
"miniz_oxide 0.4.4",
|
||||||
"weezl",
|
"weezl",
|
||||||
]
|
]
|
||||||
|
|
@ -2599,6 +2681,12 @@ version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urlencoding"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -2626,6 +2714,8 @@ dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cgmath",
|
"cgmath",
|
||||||
"criterion",
|
"criterion",
|
||||||
|
"gltf",
|
||||||
|
"gltf-json",
|
||||||
"iai",
|
"iai",
|
||||||
"itertools 0.12.0",
|
"itertools 0.12.0",
|
||||||
"miette",
|
"miette",
|
||||||
|
|
@ -2659,7 +2749,7 @@ checksum = "ebc3592bc888a6a3df8543143370f0617339b6c336f9691b2437307256ecdb0a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"err-derive",
|
"err-derive",
|
||||||
"image",
|
"image 0.23.14",
|
||||||
"num_enum 0.7.1",
|
"num_enum 0.7.1",
|
||||||
"parse-display",
|
"parse-display",
|
||||||
"texpresso",
|
"texpresso",
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ vpk = "0.2.0"
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
tracing-subscriber = "0.3.18"
|
tracing-subscriber = "0.3.18"
|
||||||
steamy-vdf = { version = "0.3.0", git = "https://github.com/icewind1991/steamy", branch = "nom7" }
|
steamy-vdf = { version = "0.3.0", git = "https://github.com/icewind1991/steamy", branch = "nom7" }
|
||||||
|
gltf-json = "1.3.0"
|
||||||
|
gltf = "1.3.0"
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "parse"
|
name = "parse"
|
||||||
|
|
|
||||||
14
examples/gltf/convert.rs
Normal file
14
examples/gltf/convert.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
use crate::Vertex;
|
||||||
|
use vmdl::Model;
|
||||||
|
|
||||||
|
pub fn model_to_vertices(model: &Model) -> Vec<Vertex> {
|
||||||
|
model
|
||||||
|
.meshes()
|
||||||
|
.flat_map(|mesh| mesh.vertices())
|
||||||
|
.map(|vertex| Vertex {
|
||||||
|
position: vertex.position.into(),
|
||||||
|
uv: vertex.texture_coordinates.into(),
|
||||||
|
normal: vertex.normal.into(),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
18
examples/gltf/error.rs
Normal file
18
examples/gltf/error.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
use crate::loader::LoadError;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error(transparent)]
|
||||||
|
Three(#[from] Box<dyn std::error::Error>),
|
||||||
|
#[error(transparent)]
|
||||||
|
Mdl(#[from] vmdl::ModelError),
|
||||||
|
#[error(transparent)]
|
||||||
|
IO(#[from] std::io::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
Loader(#[from] LoadError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Vtf(#[from] vtf::Error),
|
||||||
|
#[error("{0}")]
|
||||||
|
Other(&'static str),
|
||||||
|
}
|
||||||
97
examples/gltf/loader.rs
Normal file
97
examples/gltf/loader.rs
Normal 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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
246
examples/gltf/main.rs
Normal file
246
examples/gltf/main.rs
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
mod convert;
|
||||||
|
mod error;
|
||||||
|
mod loader;
|
||||||
|
|
||||||
|
use gltf_json as json;
|
||||||
|
|
||||||
|
use std::{fs, mem};
|
||||||
|
|
||||||
|
use crate::convert::model_to_vertices;
|
||||||
|
pub use error::Error;
|
||||||
|
use json::validation::Checked::Valid;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::env::args_os;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use vmdl::{Mdl, Model, Vtx, Vvd};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
|
enum Output {
|
||||||
|
/// Output standard glTF.
|
||||||
|
Standard,
|
||||||
|
|
||||||
|
/// Output binary glTF.
|
||||||
|
Binary,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Vertex {
|
||||||
|
position: [f32; 3],
|
||||||
|
uv: [f32; 2],
|
||||||
|
normal: [f32; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate bounding coordinates of a list of vertices, used for the clipping distance of the model
|
||||||
|
fn bounding_coords(points: &[Vertex]) -> ([f32; 3], [f32; 3]) {
|
||||||
|
let mut min = [f32::MAX, f32::MAX, f32::MAX];
|
||||||
|
let mut max = [f32::MIN, f32::MIN, f32::MIN];
|
||||||
|
|
||||||
|
for point in points {
|
||||||
|
let p = point.position;
|
||||||
|
for i in 0..3 {
|
||||||
|
min[i] = f32::min(min[i], p[i]);
|
||||||
|
max[i] = f32::max(max[i], p[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(min, max)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn align_to_multiple_of_four(n: &mut u32) {
|
||||||
|
*n = (*n + 3) & !3;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_padded_byte_vector<T>(vec: Vec<T>) -> Vec<u8> {
|
||||||
|
let byte_length = vec.len() * mem::size_of::<T>();
|
||||||
|
let byte_capacity = vec.capacity() * mem::size_of::<T>();
|
||||||
|
let alloc = vec.into_boxed_slice();
|
||||||
|
let ptr = Box::<[T]>::into_raw(alloc) as *mut u8;
|
||||||
|
let mut new_vec = unsafe { Vec::from_raw_parts(ptr, byte_length, byte_capacity) };
|
||||||
|
while new_vec.len() % 4 != 0 {
|
||||||
|
new_vec.push(0); // pad to multiple of four bytes
|
||||||
|
}
|
||||||
|
new_vec
|
||||||
|
}
|
||||||
|
|
||||||
|
fn export(model: Model, output: Output) {
|
||||||
|
let vertices = model_to_vertices(&model);
|
||||||
|
|
||||||
|
let (min, max) = bounding_coords(&vertices);
|
||||||
|
|
||||||
|
let buffer_length = (vertices.len() * mem::size_of::<Vertex>()) as u32;
|
||||||
|
let buffer = json::Buffer {
|
||||||
|
byte_length: buffer_length,
|
||||||
|
extensions: Default::default(),
|
||||||
|
extras: Default::default(),
|
||||||
|
name: None,
|
||||||
|
uri: if output == Output::Standard {
|
||||||
|
Some("buffer0.bin".into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let buffer_view = json::buffer::View {
|
||||||
|
buffer: json::Index::new(0),
|
||||||
|
byte_length: buffer.byte_length,
|
||||||
|
byte_offset: None,
|
||||||
|
byte_stride: Some(mem::size_of::<Vertex>() as u32),
|
||||||
|
extensions: Default::default(),
|
||||||
|
extras: Default::default(),
|
||||||
|
name: None,
|
||||||
|
target: Some(Valid(json::buffer::Target::ArrayBuffer)),
|
||||||
|
};
|
||||||
|
let positions = json::Accessor {
|
||||||
|
buffer_view: Some(json::Index::new(0)),
|
||||||
|
byte_offset: Some(0),
|
||||||
|
count: vertices.len() as u32,
|
||||||
|
component_type: Valid(json::accessor::GenericComponentType(
|
||||||
|
json::accessor::ComponentType::F32,
|
||||||
|
)),
|
||||||
|
extensions: Default::default(),
|
||||||
|
extras: Default::default(),
|
||||||
|
type_: Valid(json::accessor::Type::Vec3),
|
||||||
|
min: Some(json::Value::from(Vec::from(min))),
|
||||||
|
max: Some(json::Value::from(Vec::from(max))),
|
||||||
|
name: None,
|
||||||
|
normalized: false,
|
||||||
|
sparse: None,
|
||||||
|
};
|
||||||
|
let uvs = json::Accessor {
|
||||||
|
buffer_view: Some(json::Index::new(0)),
|
||||||
|
byte_offset: Some((3 * mem::size_of::<f32>()) as u32),
|
||||||
|
count: vertices.len() as u32,
|
||||||
|
component_type: Valid(json::accessor::GenericComponentType(
|
||||||
|
json::accessor::ComponentType::F32,
|
||||||
|
)),
|
||||||
|
extensions: Default::default(),
|
||||||
|
extras: Default::default(),
|
||||||
|
type_: Valid(json::accessor::Type::Vec3),
|
||||||
|
min: None,
|
||||||
|
max: None,
|
||||||
|
name: None,
|
||||||
|
normalized: false,
|
||||||
|
sparse: None,
|
||||||
|
};
|
||||||
|
let normals = json::Accessor {
|
||||||
|
buffer_view: Some(json::Index::new(0)),
|
||||||
|
byte_offset: Some((5 * mem::size_of::<f32>()) as u32),
|
||||||
|
count: vertices.len() as u32,
|
||||||
|
component_type: Valid(json::accessor::GenericComponentType(
|
||||||
|
json::accessor::ComponentType::F32,
|
||||||
|
)),
|
||||||
|
extensions: Default::default(),
|
||||||
|
extras: Default::default(),
|
||||||
|
type_: Valid(json::accessor::Type::Vec3),
|
||||||
|
min: None,
|
||||||
|
max: None,
|
||||||
|
name: None,
|
||||||
|
normalized: false,
|
||||||
|
sparse: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let primitive = json::mesh::Primitive {
|
||||||
|
attributes: {
|
||||||
|
let mut map = std::collections::BTreeMap::new();
|
||||||
|
map.insert(Valid(json::mesh::Semantic::Positions), json::Index::new(0));
|
||||||
|
map.insert(
|
||||||
|
Valid(json::mesh::Semantic::TexCoords(0)),
|
||||||
|
json::Index::new(1),
|
||||||
|
);
|
||||||
|
map.insert(Valid(json::mesh::Semantic::Normals), json::Index::new(2));
|
||||||
|
map
|
||||||
|
},
|
||||||
|
extensions: Default::default(),
|
||||||
|
extras: Default::default(),
|
||||||
|
indices: None,
|
||||||
|
material: None,
|
||||||
|
mode: Valid(json::mesh::Mode::Triangles),
|
||||||
|
targets: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mesh = json::Mesh {
|
||||||
|
extensions: Default::default(),
|
||||||
|
extras: Default::default(),
|
||||||
|
name: None,
|
||||||
|
primitives: vec![primitive],
|
||||||
|
weights: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let node = json::Node {
|
||||||
|
camera: None,
|
||||||
|
children: None,
|
||||||
|
extensions: Default::default(),
|
||||||
|
extras: Default::default(),
|
||||||
|
matrix: None,
|
||||||
|
mesh: Some(json::Index::new(0)),
|
||||||
|
name: None,
|
||||||
|
rotation: None,
|
||||||
|
scale: None,
|
||||||
|
translation: None,
|
||||||
|
skin: None,
|
||||||
|
weights: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let root = json::Root {
|
||||||
|
accessors: vec![positions, uvs, normals],
|
||||||
|
buffers: vec![buffer],
|
||||||
|
buffer_views: vec![buffer_view],
|
||||||
|
meshes: vec![mesh],
|
||||||
|
nodes: vec![node],
|
||||||
|
scenes: vec![json::Scene {
|
||||||
|
extensions: Default::default(),
|
||||||
|
extras: Default::default(),
|
||||||
|
name: None,
|
||||||
|
nodes: vec![json::Index::new(0)],
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
match output {
|
||||||
|
Output::Standard => {
|
||||||
|
let _ = fs::create_dir("triangle");
|
||||||
|
|
||||||
|
let writer = fs::File::create("triangle/triangle.gltf").expect("I/O error");
|
||||||
|
json::serialize::to_writer_pretty(writer, &root).expect("Serialization error");
|
||||||
|
|
||||||
|
let bin = to_padded_byte_vector(vertices);
|
||||||
|
let mut writer = fs::File::create("triangle/buffer0.bin").expect("I/O error");
|
||||||
|
writer.write_all(&bin).expect("I/O error");
|
||||||
|
}
|
||||||
|
Output::Binary => {
|
||||||
|
let json_string = json::serialize::to_string(&root).expect("Serialization error");
|
||||||
|
let mut json_offset = json_string.len() as u32;
|
||||||
|
align_to_multiple_of_four(&mut json_offset);
|
||||||
|
let glb = gltf::binary::Glb {
|
||||||
|
header: gltf::binary::Header {
|
||||||
|
magic: *b"glTF",
|
||||||
|
version: 2,
|
||||||
|
length: json_offset + buffer_length,
|
||||||
|
},
|
||||||
|
bin: Some(Cow::Owned(to_padded_byte_vector(vertices))),
|
||||||
|
json: Cow::Owned(json_string.into_bytes()),
|
||||||
|
};
|
||||||
|
let writer = std::fs::File::create("output.glb").expect("I/O error");
|
||||||
|
glb.to_writer(writer).expect("glTF binary output error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(path: &Path) -> Result<Model, vmdl::ModelError> {
|
||||||
|
let data = fs::read(path)?;
|
||||||
|
let mdl = Mdl::read(&data)?;
|
||||||
|
let data = fs::read(path.with_extension("dx90.vtx"))?;
|
||||||
|
let vtx = Vtx::read(&data)?;
|
||||||
|
let data = fs::read(path.with_extension("vvd"))?;
|
||||||
|
let vvd = Vvd::read(&data)?;
|
||||||
|
|
||||||
|
Ok(Model::from_parts(mdl, vtx, vvd))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Error> {
|
||||||
|
let path = PathBuf::from(args_os().nth(1).expect("No model file provided"));
|
||||||
|
let source_model = load(&path)?;
|
||||||
|
|
||||||
|
export(source_model, Output::Binary);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -119,7 +119,7 @@ pub struct Mesh<'a> {
|
||||||
|
|
||||||
impl<'a> Mesh<'a> {
|
impl<'a> Mesh<'a> {
|
||||||
/// Vertex indices into the model's vertex list
|
/// Vertex indices into the model's vertex list
|
||||||
pub fn vertex_strip_indices(&self) -> impl Iterator<Item = impl Iterator<Item = usize> + '_> {
|
pub fn vertex_strip_indices(&self) -> impl Iterator<Item = impl Iterator<Item = usize> + 'a> {
|
||||||
let mdl_offset = self.mdl.vertex_offset as usize + self.model_vertex_offset;
|
let mdl_offset = self.mdl.vertex_offset as usize + self.model_vertex_offset;
|
||||||
self.vtx.strip_groups.iter().flat_map(move |strip_group| {
|
self.vtx.strip_groups.iter().flat_map(move |strip_group| {
|
||||||
let group_indices = &strip_group.indices;
|
let group_indices = &strip_group.indices;
|
||||||
|
|
@ -137,7 +137,7 @@ impl<'a> Mesh<'a> {
|
||||||
self.mdl.material
|
self.mdl.material
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vertices(&self) -> impl Iterator<Item = &'a Vertex> + '_ {
|
pub fn vertices(&self) -> impl Iterator<Item = &'a Vertex> + 'a {
|
||||||
self.vertex_strip_indices()
|
self.vertex_strip_indices()
|
||||||
.flat_map(|strip| strip.map(|index| &self.vertices[index]))
|
.flat_map(|strip| strip.map(|index| &self.vertices[index]))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue