mirror of
https://codeberg.org/icewind/vbspview.git
synced 2026-06-03 18:24:09 +02:00
update threed
This commit is contained in:
parent
c625fa85eb
commit
7dec8aa2c6
9 changed files with 4997 additions and 550 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -56,5 +56,5 @@ jobs:
|
|||
- run: nix build .#${{ matrix.target }}
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: vbspview-${{ matrix.asset_suffix }}
|
||||
name: vbspview-${{ matrix.target }}
|
||||
path: result/bin/vbspview${{ matrix.artifact_suffix }}
|
||||
|
|
|
|||
695
Cargo.lock
generated
695
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -6,9 +6,10 @@ authors = ["Robin Appelman <robin@icewind.nl>"]
|
|||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
three-d = { version = "0.14.0", features = ["egui-gui"] }
|
||||
vbsp = { version = "0.2.0", git = "https://github.com/icewind1991/vbsp" }
|
||||
#vbsp = { version = "0.2.0", path = "../bsp" }
|
||||
three-d = { version = "0.16.3", features = ["egui-gui"] }
|
||||
three-d-asset = { version = "0.6" }
|
||||
#vbsp = { version = "0.2.0", git = "https://github.com/icewind1991/vbsp" }
|
||||
vbsp = { version = "0.2.0", path = "../bsp" }
|
||||
miette = { version = "5.5.0", features = ["fancy"] }
|
||||
thiserror = "1.0.37"
|
||||
delaunator = "1.0.1"
|
||||
|
|
|
|||
35
flake.nix
35
flake.nix
|
|
@ -35,22 +35,24 @@
|
|||
hostTarget = pkgs.hostPlatform.config;
|
||||
targets = ["x86_64-unknown-linux-musl" "x86_64-pc-windows-gnu" hostTarget];
|
||||
|
||||
artifactSuffixForTarget = cross-naersk'.execSufficForTarget;
|
||||
assetSuffixForTarget = target: "${builtins.replaceStrings ["-unknown" "-gnu" "-musl" "eabihf" "-pc"] ["" "" "" "" ""] target}${cross-naersk'.execSufficForTarget target}";
|
||||
|
||||
hostNaersk = naerskForTarget hostTarget;
|
||||
cross-naersk' = pkgs.callPackage cross-naersk {inherit naersk;};
|
||||
src = lib.sources.sourceByRegex (lib.cleanSource ./.) ["Cargo.*" "(src)(/.*)?"];
|
||||
nearskOpt = {
|
||||
pname = "vbspview";
|
||||
root = src;
|
||||
nativeBuildInputs = buildDependencies;
|
||||
nativeBuildInputs = (buildDependencies pkgs) ++ (runtimeDependencies pkgs);
|
||||
};
|
||||
buildDependencies = with pkgs; [
|
||||
freetype
|
||||
pkg-config
|
||||
cmake
|
||||
fontconfig
|
||||
crossOpts = {
|
||||
crossArgs = {
|
||||
"x86_64-unknown-linux-musl" = {
|
||||
# targetNativeBuildInputs = buildDependencies;
|
||||
# buildInputs = runtimeDependencies pkgs.pkgsCross.musl64;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
runtimeDependencies = pkgsForPlatform: with pkgsForPlatform; [
|
||||
xorg.libX11
|
||||
xorg.libXcursor
|
||||
xorg.libXrandr
|
||||
|
|
@ -59,12 +61,17 @@
|
|||
egl-wayland
|
||||
libGL
|
||||
];
|
||||
buildDependencies = pkgsForPlatform: with pkgsForPlatform; [
|
||||
fontconfig
|
||||
freetype
|
||||
pkg-config
|
||||
cmake
|
||||
];
|
||||
|
||||
buildMatrix = targets: {
|
||||
include = builtins.map (target: {
|
||||
inherit target;
|
||||
artifact_suffix = artifactSuffixForTarget target;
|
||||
asset_suffix = assetSuffixForTarget target;
|
||||
artifact_suffix = cross-naersk'.execSufficForTarget target;
|
||||
}) targets;
|
||||
};
|
||||
in rec {
|
||||
|
|
@ -72,9 +79,11 @@
|
|||
vbspview = packages.${hostTarget};
|
||||
check = hostNaersk.buildPackage (nearskOpt // {
|
||||
mode = "check";
|
||||
buildInputs = buildDependencies pkgs;
|
||||
});
|
||||
clippy = hostNaersk.buildPackage (nearskOpt // {
|
||||
mode = "clippy";
|
||||
buildInputs = buildDependencies pkgs;
|
||||
});
|
||||
default = vbspview;
|
||||
};
|
||||
|
|
@ -84,7 +93,7 @@
|
|||
inherit targets;
|
||||
|
||||
devShells.default = pkgs.mkShell {
|
||||
nativeBuildInputs = with pkgs; [
|
||||
nativeBuildInputs = (with pkgs; [
|
||||
pkgs.rust-bin.stable.latest.default
|
||||
bacon
|
||||
cargo-edit
|
||||
|
|
@ -92,7 +101,7 @@
|
|||
clippy
|
||||
cargo-audit
|
||||
cargo-msrv
|
||||
] ++ buildDependencies;
|
||||
]) ++ (buildDependencies pkgs) ++ (runtimeDependencies pkgs);
|
||||
|
||||
LD_LIBRARY_PATH = with pkgs; "/run/opengl-driver/lib/:${lib.makeLibraryPath ([libGL libGLU])}";
|
||||
};
|
||||
|
|
|
|||
101
src/bsp.rs
101
src/bsp.rs
|
|
@ -1,10 +1,12 @@
|
|||
use crate::material::load_material_fallback;
|
||||
use crate::prop::load_props;
|
||||
use crate::{Error, Loader};
|
||||
use cgmath::vec4;
|
||||
use std::collections::HashMap;
|
||||
use three_d::{CpuMesh, CpuModel, Mat4, Positions, Vec2, Vec3};
|
||||
use vbsp::{Bsp, Face, Handle};
|
||||
use cgmath::Matrix4;
|
||||
use itertools::Itertools;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use three_d::{CpuModel, Positions, Vec3};
|
||||
use three_d_asset::{Geometry, Primitive, TriMesh};
|
||||
use vbsp::{Bsp, Handle};
|
||||
|
||||
pub fn load_map(data: &[u8], loader: &mut Loader) -> Result<Vec<CpuModel>, Error> {
|
||||
let (world, bsp) = load_world(data, loader)?;
|
||||
|
|
@ -12,11 +14,6 @@ pub fn load_map(data: &[u8], loader: &mut Loader) -> Result<Vec<CpuModel>, Error
|
|||
Ok(vec![world, props])
|
||||
}
|
||||
|
||||
pub fn apply_transform<C: Into<Vec3>>(coord: C, transform: Mat4) -> Vec3 {
|
||||
let coord = coord.into();
|
||||
(transform * vec4(coord.x, coord.y, coord.z, 1.0)).truncate()
|
||||
}
|
||||
|
||||
pub fn map_coords<C: Into<Vec3>>(vec: C) -> Vec3 {
|
||||
let vec = vec.into();
|
||||
Vec3 {
|
||||
|
|
@ -29,66 +26,66 @@ pub fn map_coords<C: Into<Vec3>>(vec: C) -> Vec3 {
|
|||
// 1 hammer unit is ~1.905cm
|
||||
pub const UNIT_SCALE: f32 = 1.0 / (1.905 * 100.0);
|
||||
|
||||
fn face_to_mesh(face: &Handle<Face>) -> CpuMesh {
|
||||
let texture = face.texture();
|
||||
let positions = face.vertex_positions().map(map_coords).collect();
|
||||
let uvs = face
|
||||
.vertex_positions()
|
||||
.map(|pos| Vec2 {
|
||||
x: texture.u(pos),
|
||||
y: texture.v(pos),
|
||||
})
|
||||
fn model_to_model(model: Handle<vbsp::data::Model>, loader: &Loader) -> CpuModel {
|
||||
let textures: HashSet<&str> = model.textures().map(|texture| texture.name()).collect();
|
||||
let textures: Vec<&str> = textures.into_iter().collect();
|
||||
|
||||
let faces_by_texture: HashMap<&str, _> = model
|
||||
.faces()
|
||||
.filter(|face| face.is_visible())
|
||||
.map(|face| (face.texture().name(), face))
|
||||
.into_group_map();
|
||||
|
||||
let geometries: Vec<_> = faces_by_texture
|
||||
.into_values()
|
||||
.map(|faces| {
|
||||
let positions: Vec<_> = faces
|
||||
.iter()
|
||||
.flat_map(|face| face.vertex_positions())
|
||||
.map(map_coords)
|
||||
.collect();
|
||||
|
||||
let mut mesh = CpuMesh {
|
||||
let uvs: Vec<_> = faces
|
||||
.iter()
|
||||
.flat_map(|face| {
|
||||
let texture = face.texture();
|
||||
face.vertex_positions()
|
||||
.map(move |position| texture.uv(position))
|
||||
})
|
||||
.map(|uv| uv.into())
|
||||
.collect();
|
||||
|
||||
let mut mesh = TriMesh {
|
||||
positions: Positions::F32(positions),
|
||||
uvs: Some(uvs),
|
||||
material_name: Some(texture.name().into()),
|
||||
..Default::default()
|
||||
};
|
||||
mesh.compute_normals();
|
||||
mesh.compute_tangents();
|
||||
mesh
|
||||
}
|
||||
|
||||
fn model_to_model(model: Handle<vbsp::data::Model>, loader: &Loader) -> CpuModel {
|
||||
let mut faces_by_texture: HashMap<&str, Vec<_>> = HashMap::with_capacity(64);
|
||||
for face in model.faces().filter(|face| face.is_visible()) {
|
||||
faces_by_texture
|
||||
.entry(face.texture().name())
|
||||
.or_default()
|
||||
.push(face)
|
||||
}
|
||||
let texture = faces.first().unwrap().texture().name();
|
||||
let material_index = textures
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(i, tex)| (*tex == texture).then_some(i));
|
||||
|
||||
let geometries = faces_by_texture
|
||||
.values()
|
||||
.map(|faces| {
|
||||
let mut faces = faces.iter();
|
||||
let first = faces.next().unwrap();
|
||||
let mut mesh = face_to_mesh(first);
|
||||
for face in faces {
|
||||
let face_mesh = face_to_mesh(face);
|
||||
if let Positions::F32(positions) = &mut mesh.positions {
|
||||
positions.extend_from_slice(&face_mesh.positions.into_f32());
|
||||
Primitive {
|
||||
name: "".to_string(),
|
||||
transformation: Matrix4::from_scale(1.0),
|
||||
animations: vec![],
|
||||
geometry: Geometry::Triangles(mesh),
|
||||
material_index,
|
||||
}
|
||||
if let Some(uvs) = &mut mesh.uvs {
|
||||
uvs.extend_from_slice(&face_mesh.uvs.unwrap());
|
||||
}
|
||||
}
|
||||
mesh.compute_normals();
|
||||
mesh
|
||||
})
|
||||
.collect();
|
||||
|
||||
let materials = faces_by_texture
|
||||
.values()
|
||||
.map(|face| {
|
||||
let texture = face.first().unwrap().texture();
|
||||
load_material_fallback(texture.name(), &["".into()], loader)
|
||||
})
|
||||
let materials: Vec<_> = textures
|
||||
.iter()
|
||||
.map(|texture| load_material_fallback(texture, &["".into()], loader))
|
||||
.collect();
|
||||
|
||||
CpuModel {
|
||||
name: "".to_string(),
|
||||
geometries,
|
||||
materials,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ use crate::loader::Loader;
|
|||
use crate::Error;
|
||||
use std::str::FromStr;
|
||||
use steamy_vdf::{Entry, Table};
|
||||
use three_d::{Color, CpuMaterial, CpuTexture, TextureData};
|
||||
use three_d::{CpuMaterial, CpuTexture, TextureData};
|
||||
use three_d_asset::Srgba;
|
||||
use tracing::error;
|
||||
use vtf::vtf::VTF;
|
||||
|
||||
|
|
@ -16,7 +17,7 @@ pub fn load_material_fallback(name: &str, search_dirs: &[String], loader: &Loade
|
|||
"failed to load material, falling back"
|
||||
);
|
||||
CpuMaterial {
|
||||
albedo: Color {
|
||||
albedo: Srgba {
|
||||
r: 255,
|
||||
g: 0,
|
||||
b: 255,
|
||||
|
|
@ -61,7 +62,7 @@ pub fn load_material(
|
|||
|
||||
if material_type == "water" {
|
||||
return Ok(CpuMaterial {
|
||||
albedo: Color {
|
||||
albedo: Srgba {
|
||||
r: 82,
|
||||
g: 180,
|
||||
b: 217,
|
||||
|
|
@ -109,7 +110,7 @@ pub fn load_material(
|
|||
|
||||
Ok(CpuMaterial {
|
||||
name: name.into(),
|
||||
albedo: Color::WHITE,
|
||||
albedo: Srgba::WHITE,
|
||||
albedo_texture: Some(texture),
|
||||
alpha_cutout: alpha_test.then_some(alpha_cutout),
|
||||
normal_texture: bump_map,
|
||||
|
|
@ -159,16 +160,15 @@ fn resolve_vmt_patch(vmt: Table, loader: &Loader) -> Result<Table, Error> {
|
|||
if let Some(Entry::Table(patch)) = vmt.get("patch") {
|
||||
let include = patch
|
||||
.get("include")
|
||||
.expect("no include in patch")
|
||||
.as_value()
|
||||
.expect("include is not a value")
|
||||
.to_string();
|
||||
.ok_or(Error::Other("no include in patch"))?
|
||||
.as_str()
|
||||
.ok_or(Error::Other("include is not a string"))?;
|
||||
let _replace = patch
|
||||
.get("replace")
|
||||
.expect("no replace in patch")
|
||||
.ok_or(Error::Other("no replace in patch"))?
|
||||
.as_table()
|
||||
.expect("replace is not a table");
|
||||
let included_raw = loader.load(&include)?.to_ascii_lowercase();
|
||||
.ok_or(Error::Other("replace is not a table"))?;
|
||||
let included_raw = loader.load(include)?.to_ascii_lowercase();
|
||||
|
||||
// todo actually patch
|
||||
parse_vdf(&included_raw)
|
||||
|
|
|
|||
51
src/prop.rs
51
src/prop.rs
|
|
@ -1,9 +1,9 @@
|
|||
use crate::bsp::{apply_transform, map_coords};
|
||||
use crate::bsp::map_coords;
|
||||
use crate::material::load_material_fallback;
|
||||
use crate::{Error, Loader};
|
||||
use cgmath::{Matrix, SquareMatrix};
|
||||
use std::collections::HashMap;
|
||||
use three_d::{CpuMaterial, CpuMesh, CpuModel, Mat4, Positions, Vec2, Vec3, Vec4};
|
||||
use three_d::{CpuMaterial, CpuModel, Mat4, Positions, Vec2, Vec3, Vec4};
|
||||
use three_d_asset::{Geometry, Primitive, TriMesh};
|
||||
use tracing::warn;
|
||||
use vbsp::{Handle, StaticPropLump};
|
||||
use vmdl::mdl::{Mdl, TextureInfo};
|
||||
|
|
@ -35,18 +35,25 @@ pub fn load_props<'a, I: Iterator<Item = Handle<'a, StaticPropLump>>>(
|
|||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
let geometries = props.iter().flat_map(prop_to_meshes).collect();
|
||||
|
||||
let textures: HashMap<_, _> = props
|
||||
let materials: HashMap<_, _> = props
|
||||
.iter()
|
||||
.flat_map(|prop| prop.model.textures())
|
||||
.map(|tex| (tex.name.as_str(), tex))
|
||||
.collect();
|
||||
let materials: Vec<_> = textures
|
||||
.into_values()
|
||||
let materials: Vec<_> = materials.into_values().collect();
|
||||
|
||||
let geometries = props
|
||||
.iter()
|
||||
.flat_map(|prop| prop_to_meshes(prop, materials.as_slice()))
|
||||
.collect();
|
||||
|
||||
let materials: Vec<_> = materials
|
||||
.into_iter()
|
||||
.map(|tex| prop_texture_to_material(tex, loader))
|
||||
.collect();
|
||||
|
||||
Ok(CpuModel {
|
||||
name: "props".into(),
|
||||
geometries,
|
||||
materials,
|
||||
})
|
||||
|
|
@ -58,9 +65,11 @@ struct PropData {
|
|||
skin: i32,
|
||||
}
|
||||
|
||||
fn prop_to_meshes(prop: &PropData) -> impl Iterator<Item = CpuMesh> + '_ {
|
||||
fn prop_to_meshes<'a>(
|
||||
prop: &'a PropData,
|
||||
textures: &'a [&TextureInfo],
|
||||
) -> impl Iterator<Item = Primitive> + 'a {
|
||||
let transform = prop.transform;
|
||||
let normal_transform = transform.invert().unwrap().transpose() * -1.0;
|
||||
let model = &prop.model;
|
||||
|
||||
let skin = match model.skin_tables().nth(prop.skin as usize) {
|
||||
|
|
@ -75,16 +84,20 @@ fn prop_to_meshes(prop: &PropData) -> impl Iterator<Item = CpuMesh> + '_ {
|
|||
let texture = skin
|
||||
.texture(mesh.material_index())
|
||||
.expect("texture out of bounds");
|
||||
let material_index = textures
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(i, texture_info)| (texture_info.name == texture).then_some(i));
|
||||
|
||||
let positions: Vec<Vec3> = mesh
|
||||
.vertices()
|
||||
.map(|vertex| map_coords(vertex.position))
|
||||
.map(|v| apply_transform(v, transform))
|
||||
// .map(|v| apply_transform(v, transform))
|
||||
.collect();
|
||||
let normals: Vec<Vec3> = mesh
|
||||
.vertices()
|
||||
.map(|vertex| map_coords(vertex.normal))
|
||||
.map(|v| apply_transform(v, normal_transform))
|
||||
// .map(|v| apply_transform(v, normal_transform))
|
||||
.collect();
|
||||
let uvs: Vec<Vec2> = mesh
|
||||
.vertices()
|
||||
|
|
@ -93,13 +106,21 @@ fn prop_to_meshes(prop: &PropData) -> impl Iterator<Item = CpuMesh> + '_ {
|
|||
|
||||
let tangents: Vec<Vec4> = mesh.tangents().map(|tangent| tangent.into()).collect();
|
||||
|
||||
CpuMesh {
|
||||
let geometry = Geometry::Triangles(TriMesh {
|
||||
positions: Positions::F32(positions),
|
||||
indices: Default::default(),
|
||||
normals: Some(normals),
|
||||
uvs: Some(uvs),
|
||||
material_name: Some(texture.into()),
|
||||
tangents: Some(tangents),
|
||||
..Default::default()
|
||||
colors: None,
|
||||
});
|
||||
|
||||
Primitive {
|
||||
name: "".to_string(),
|
||||
transformation: transform,
|
||||
animations: vec![],
|
||||
geometry,
|
||||
material_index,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,17 +24,17 @@ impl<C: Control> Renderer<C> {
|
|||
vec3(0.0, 1.0, 0.0),
|
||||
degrees(60.0),
|
||||
0.1,
|
||||
30.0,
|
||||
45.0,
|
||||
);
|
||||
|
||||
let ambient_lights = vec![AmbientLight {
|
||||
color: Color::WHITE,
|
||||
color: Srgba::WHITE,
|
||||
intensity: 0.2,
|
||||
..Default::default()
|
||||
}];
|
||||
let directional_lights = 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)),
|
||||
DirectionalLight::new(&context, 1.0, Srgba::WHITE, &vec3(0.0, -1.0, 0.0)),
|
||||
DirectionalLight::new(&context, 1.0, Srgba::WHITE, &vec3(0.0, 1.0, 0.0)),
|
||||
];
|
||||
// let control = FirstPerson::new(0.1);
|
||||
|
||||
|
|
@ -68,11 +68,8 @@ impl<C: Control> Renderer<C> {
|
|||
self.directional_lights[0].intensity = self.gui.directional_intensity;
|
||||
self.directional_lights[1].intensity = self.gui.directional_intensity;
|
||||
self.ambient_lights[0].intensity = self.gui.ambient_intensity;
|
||||
self.camera.set_perspective_projection(
|
||||
degrees(self.gui.fov),
|
||||
self.camera.z_near(),
|
||||
self.camera.z_far(),
|
||||
);
|
||||
self.camera
|
||||
.set_perspective_projection(degrees(self.gui.fov), 0.1, 45.0);
|
||||
}
|
||||
|
||||
let viewport = Viewport {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue