mirror of
https://codeberg.org/icewind/vmdl.git
synced 2026-06-03 16:44:11 +02:00
viewer wip
This commit is contained in:
parent
fc98416aa3
commit
88bcdb35e7
8 changed files with 329 additions and 5 deletions
|
|
@ -9,4 +9,8 @@ arrayvec = "0.7.2"
|
|||
binrw = "0.8.0"
|
||||
thiserror = "1.0.30"
|
||||
static_assertions = "1.1.0"
|
||||
bitflags = "1.0.4"
|
||||
bitflags = "1.0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
three-d = "0.10.2"
|
||||
miette = { version = "4.2.1", features = ["fancy"] }
|
||||
|
|
@ -1,14 +1,31 @@
|
|||
use std::env::args;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use vmdl::mdl::Mdl;
|
||||
use vmdl::vtx::Vtx;
|
||||
use vmdl::vvd::Vvd;
|
||||
use vmdl::Model;
|
||||
|
||||
fn main() -> Result<(), vmdl::ModelError> {
|
||||
let mut args = args();
|
||||
let _ = args.next();
|
||||
let data = fs::read(args.next().expect("No demo file provided"))?;
|
||||
let mdl = Mdl::read(&data)?;
|
||||
let path = PathBuf::from(args.next().expect("No demo file provided"));
|
||||
|
||||
dbg!(mdl.header);
|
||||
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)?;
|
||||
dbg!(vvd.vertices.last());
|
||||
|
||||
let model = Model::from_parts(mdl, vtx, vvd);
|
||||
for strip in model.vertex_strips() {
|
||||
for vertex in strip {
|
||||
println!("{:?}", vertex);
|
||||
}
|
||||
println!("")
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
252
examples/view.rs
Normal file
252
examples/view.rs
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
use std::env::args_os;
|
||||
use std::fs;
|
||||
use std::fs::read;
|
||||
use std::path::{Path, PathBuf};
|
||||
use thiserror::Error;
|
||||
use three_d::*;
|
||||
use vmdl::mdl::Mdl;
|
||||
use vmdl::vtx::Vtx;
|
||||
use vmdl::vvd::Vvd;
|
||||
use vmdl::Model;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
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("{0}")]
|
||||
Other(&'static str),
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
miette::set_panic_hook();
|
||||
|
||||
let mut args = args_os();
|
||||
let _ = args.next();
|
||||
let path = PathBuf::from(args.next().expect("No demo file provided"));
|
||||
let model = load(&path).unwrap();
|
||||
|
||||
let window = Window::new(WindowSettings {
|
||||
title: path.display().to_string(),
|
||||
min_size: (512, 512),
|
||||
max_size: Some((1280, 720)),
|
||||
..Default::default()
|
||||
})
|
||||
.unwrap();
|
||||
let context = window.gl().unwrap();
|
||||
|
||||
let forward_pipeline = ForwardPipeline::new(&context).unwrap();
|
||||
let mut camera = Camera::new_perspective(
|
||||
&context,
|
||||
window.viewport().unwrap(),
|
||||
vec3(2.0, 2.0, 5.0),
|
||||
vec3(0.0, 0.0, 0.0),
|
||||
vec3(0.0, 1.0, 0.0),
|
||||
degrees(90.0),
|
||||
0.01,
|
||||
300.0,
|
||||
)
|
||||
.unwrap();
|
||||
let mut control = OrbitControl::new(*camera.target(), 1.0, 100.0);
|
||||
let mut gui = three_d::GUI::new(&context).unwrap();
|
||||
|
||||
let mut cpu_mesh = model_to_mesh(&model);
|
||||
let material = PhysicalMaterial {
|
||||
albedo: Color {
|
||||
r: 128,
|
||||
g: 128,
|
||||
b: 128,
|
||||
a: 255,
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut model = three_d::Model::new_with_material(&context, &cpu_mesh, material)?;
|
||||
model.set_transformation(Mat4::from_angle_x(degrees(90.0)));
|
||||
|
||||
let mut lights = Lights {
|
||||
ambient: Some(AmbientLight {
|
||||
color: Color::WHITE,
|
||||
intensity: 0.2,
|
||||
..Default::default()
|
||||
}),
|
||||
directional: vec![
|
||||
DirectionalLight::new(&context, 1.0, Color::WHITE, &vec3(0.0, -1.0, 0.0))?,
|
||||
DirectionalLight::new(&context, 1.0, Color::WHITE, &vec3(0.0, 1.0, 0.0))?,
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// main loop
|
||||
let mut shadows_enabled = true;
|
||||
let mut directional_intensity = lights.directional[0].intensity();
|
||||
let mut depth_max = 30.0;
|
||||
let mut fov = 60.0;
|
||||
let mut debug_type = DebugType::NONE;
|
||||
|
||||
window.render_loop(move |mut frame_input| {
|
||||
let mut change = frame_input.first_frame;
|
||||
let mut panel_width = frame_input.viewport.width;
|
||||
change |= gui
|
||||
.update(&mut frame_input, |gui_context| {
|
||||
use three_d::egui::*;
|
||||
SidePanel::left("side_panel").show(gui_context, |ui| {
|
||||
ui.heading("Debug Panel");
|
||||
|
||||
ui.label("Light options");
|
||||
ui.add(
|
||||
Slider::new(&mut lights.ambient.as_mut().unwrap().intensity, 0.0..=1.0)
|
||||
.text("Ambient intensity"),
|
||||
);
|
||||
ui.add(
|
||||
Slider::new(&mut directional_intensity, 0.0..=1.0)
|
||||
.text("Directional intensity"),
|
||||
);
|
||||
lights.directional[0].set_intensity(directional_intensity);
|
||||
lights.directional[1].set_intensity(directional_intensity);
|
||||
if ui.checkbox(&mut shadows_enabled, "Shadows").clicked() {
|
||||
if !shadows_enabled {
|
||||
lights.directional[0].clear_shadow_map();
|
||||
lights.directional[1].clear_shadow_map();
|
||||
}
|
||||
}
|
||||
|
||||
ui.label("Debug options");
|
||||
ui.radio_value(&mut debug_type, DebugType::NONE, "None");
|
||||
ui.radio_value(&mut debug_type, DebugType::POSITION, "Position");
|
||||
ui.radio_value(&mut debug_type, DebugType::NORMAL, "Normal");
|
||||
ui.radio_value(&mut debug_type, DebugType::COLOR, "Color");
|
||||
ui.radio_value(&mut debug_type, DebugType::DEPTH, "Depth");
|
||||
ui.radio_value(&mut debug_type, DebugType::ORM, "ORM");
|
||||
|
||||
ui.label("View options");
|
||||
ui.add(Slider::new(&mut depth_max, 1.0..=30.0).text("Depth max"));
|
||||
ui.add(Slider::new(&mut fov, 45.0..=90.0).text("FOV"));
|
||||
|
||||
ui.label("Position");
|
||||
ui.add(Label::new(format!("\tx: {}", camera.position().x)));
|
||||
ui.add(Label::new(format!("\ty: {}", camera.position().y)));
|
||||
ui.add(Label::new(format!("\tz: {}", camera.position().z)));
|
||||
});
|
||||
panel_width = gui_context.used_size().x as u32;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let viewport = Viewport {
|
||||
x: panel_width as i32,
|
||||
y: 0,
|
||||
width: frame_input.viewport.width - panel_width,
|
||||
height: frame_input.viewport.height,
|
||||
};
|
||||
change |= camera.set_viewport(viewport).unwrap();
|
||||
change |= control
|
||||
.handle_events(&mut camera, &mut frame_input.events)
|
||||
.unwrap();
|
||||
|
||||
// Draw
|
||||
{
|
||||
camera
|
||||
.set_perspective_projection(degrees(fov), camera.z_near(), camera.z_far())
|
||||
.unwrap();
|
||||
if shadows_enabled {
|
||||
lights.directional[0]
|
||||
.generate_shadow_map(4.0, 1024, 1024, &[&model])
|
||||
.unwrap();
|
||||
lights.directional[1]
|
||||
.generate_shadow_map(4.0, 1024, 1024, &[&model])
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Light pass
|
||||
Screen::write(&context, ClearState::default(), || {
|
||||
match debug_type {
|
||||
DebugType::NORMAL => {
|
||||
model.render_with_material(
|
||||
&NormalMaterial::from_physical_material(&model.material),
|
||||
&camera,
|
||||
&lights,
|
||||
)?;
|
||||
}
|
||||
DebugType::DEPTH => {
|
||||
let mut depth_material = DepthMaterial::default();
|
||||
depth_material.max_distance = Some(depth_max);
|
||||
model.render_with_material(&depth_material, &camera, &lights)?;
|
||||
}
|
||||
DebugType::ORM => {
|
||||
model.render_with_material(
|
||||
&ORMMaterial::from_physical_material(&model.material),
|
||||
&camera,
|
||||
&lights,
|
||||
)?;
|
||||
}
|
||||
DebugType::POSITION => {
|
||||
let position_material = PositionMaterial::default();
|
||||
model.render_with_material(&position_material, &camera, &lights)?;
|
||||
}
|
||||
DebugType::UV => {
|
||||
let uv_material = UVMaterial::default();
|
||||
model.render_with_material(&uv_material, &camera, &lights)?;
|
||||
}
|
||||
DebugType::COLOR => {
|
||||
model.render_with_material(
|
||||
&ColorMaterial::from_physical_material(&model.material),
|
||||
&camera,
|
||||
&lights,
|
||||
)?;
|
||||
}
|
||||
DebugType::NONE => forward_pipeline.render_pass(&camera, &[&model], &lights)?,
|
||||
};
|
||||
gui.render()?;
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let _ = change;
|
||||
|
||||
FrameOutput::default()
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
// 1 hammer unit is ~1.905cm
|
||||
const UNIT_SCALE: f32 = 1.0 / (1.905 * 100.0);
|
||||
|
||||
fn model_to_mesh(model: &Model) -> CPUMesh {
|
||||
let positions: Vec<f32> = model
|
||||
.vertex_strips()
|
||||
.flat_map(|mut strip| {
|
||||
let mut a = strip.next().unwrap();
|
||||
let mut b = strip.next().unwrap();
|
||||
strip
|
||||
.flat_map(move |c| {
|
||||
let tri = [a, b, c];
|
||||
a = b;
|
||||
b = c;
|
||||
tri
|
||||
})
|
||||
.flat_map(|vec| vec.iter())
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut mesh = CPUMesh {
|
||||
positions,
|
||||
..Default::default()
|
||||
};
|
||||
mesh.compute_normals();
|
||||
mesh
|
||||
}
|
||||
34
src/lib.rs
34
src/lib.rs
|
|
@ -5,12 +5,44 @@ mod shared;
|
|||
pub mod vtx;
|
||||
pub mod vvd;
|
||||
|
||||
use crate::mdl::Mdl;
|
||||
use crate::vtx::Vtx;
|
||||
use crate::vvd::Vvd;
|
||||
use binrw::{BinRead, BinReaderExt};
|
||||
pub use error::*;
|
||||
pub use handle::Handle;
|
||||
pub use shared::*;
|
||||
use std::any::type_name;
|
||||
use std::io::Cursor;
|
||||
|
||||
pub struct Model {
|
||||
mdl: Mdl,
|
||||
vtx: Vtx,
|
||||
vvd: Vvd,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn from_parts(mdl: Mdl, vtx: Vtx, vvd: Vvd) -> Self {
|
||||
Model { mdl, vtx, vvd }
|
||||
}
|
||||
|
||||
pub fn vertex_strips(&self) -> impl Iterator<Item = impl Iterator<Item = Vector> + '_> {
|
||||
self.vtx
|
||||
.body_parts
|
||||
.iter()
|
||||
.flat_map(|part| part.models.iter())
|
||||
.flat_map(|model| model.lods.iter().next())
|
||||
.flat_map(|lod| lod.meshes.iter())
|
||||
.flat_map(|mesh| mesh.strip_groups.iter())
|
||||
.map(|strip_group| {
|
||||
strip_group
|
||||
.indices
|
||||
.iter()
|
||||
.map(|index| self.vvd.vertices[(*index) as usize].position)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn read_indexes<'a, I: Iterator<Item = usize> + 'static, T: BinRead>(
|
||||
indexes: I,
|
||||
data: &'a [u8],
|
||||
|
|
@ -21,7 +53,7 @@ where
|
|||
indexes
|
||||
.map(|index| {
|
||||
data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
|
||||
data: "Bone",
|
||||
data: type_name::<T>(),
|
||||
offset: index,
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,6 +11,12 @@ pub struct Vector {
|
|||
pub z: f32,
|
||||
}
|
||||
|
||||
impl Vector {
|
||||
pub fn iter(&self) -> impl Iterator<Item = f32> {
|
||||
[self.x, self.y, self.z].into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vector> for [f32; 3] {
|
||||
fn from(vector: Vector) -> Self {
|
||||
[vector.x, vector.y, vector.z]
|
||||
|
|
|
|||
|
|
@ -142,6 +142,7 @@ impl Mesh {
|
|||
pub struct StripGroup {
|
||||
// todo vertex indexes
|
||||
// todo topologies
|
||||
pub indices: Vec<u16>,
|
||||
pub vertices: Vec<Vertex>,
|
||||
pub strips: Vec<Strip>,
|
||||
pub flags: StripGroupFlags,
|
||||
|
|
@ -173,6 +174,17 @@ impl StripGroup {
|
|||
Strip::read(data, header)
|
||||
})
|
||||
.collect::<Result<_>>()?,
|
||||
indices: header
|
||||
.index_indexes()
|
||||
.map(|index| {
|
||||
let data = data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
|
||||
data: "VertexIndex",
|
||||
offset: index,
|
||||
})?;
|
||||
let mut reader = Cursor::new(data);
|
||||
Ok(reader.read_le()?)
|
||||
})
|
||||
.collect::<Result<_>>()?,
|
||||
flags: header.flags,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ impl StripGroupHeader {
|
|||
}
|
||||
|
||||
pub fn index_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
index_range(self.index_offset, self.index_count, size_of::<Vertex>())
|
||||
index_range(self.index_offset, self.index_count, size_of::<u16>())
|
||||
}
|
||||
|
||||
pub fn strip_indexes(&self) -> impl Iterator<Item = usize> {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use std::fs::read;
|
|||
use vmdl::mdl::Mdl;
|
||||
use vmdl::vtx::Vtx;
|
||||
use vmdl::vvd::Vvd;
|
||||
use vmdl::Model;
|
||||
|
||||
#[test]
|
||||
fn parse_mdl() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue