update threed

This commit is contained in:
Robin Appelman 2023-12-13 16:02:43 +01:00
commit 7dec8aa2c6
9 changed files with 4997 additions and 550 deletions

View file

@ -56,5 +56,5 @@ jobs:
- run: nix build .#${{ matrix.target }} - run: nix build .#${{ matrix.target }}
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
with: with:
name: vbspview-${{ matrix.asset_suffix }} name: vbspview-${{ matrix.target }}
path: result/bin/vbspview${{ matrix.artifact_suffix }} path: result/bin/vbspview${{ matrix.artifact_suffix }}

695
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -6,9 +6,10 @@ authors = ["Robin Appelman <robin@icewind.nl>"]
license = "MIT" license = "MIT"
[dependencies] [dependencies]
three-d = { version = "0.14.0", features = ["egui-gui"] } three-d = { version = "0.16.3", features = ["egui-gui"] }
vbsp = { version = "0.2.0", git = "https://github.com/icewind1991/vbsp" } three-d-asset = { version = "0.6" }
#vbsp = { version = "0.2.0", path = "../bsp" } #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"] } miette = { version = "5.5.0", features = ["fancy"] }
thiserror = "1.0.37" thiserror = "1.0.37"
delaunator = "1.0.1" delaunator = "1.0.1"

View file

@ -35,22 +35,24 @@
hostTarget = pkgs.hostPlatform.config; hostTarget = pkgs.hostPlatform.config;
targets = ["x86_64-unknown-linux-musl" "x86_64-pc-windows-gnu" hostTarget]; 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; hostNaersk = naerskForTarget hostTarget;
cross-naersk' = pkgs.callPackage cross-naersk {inherit naersk;}; cross-naersk' = pkgs.callPackage cross-naersk {inherit naersk;};
src = lib.sources.sourceByRegex (lib.cleanSource ./.) ["Cargo.*" "(src)(/.*)?"]; src = lib.sources.sourceByRegex (lib.cleanSource ./.) ["Cargo.*" "(src)(/.*)?"];
nearskOpt = { nearskOpt = {
pname = "vbspview"; pname = "vbspview";
root = src; root = src;
nativeBuildInputs = buildDependencies; nativeBuildInputs = (buildDependencies pkgs) ++ (runtimeDependencies pkgs);
}; };
buildDependencies = with pkgs; [ crossOpts = {
freetype crossArgs = {
pkg-config "x86_64-unknown-linux-musl" = {
cmake # targetNativeBuildInputs = buildDependencies;
fontconfig # buildInputs = runtimeDependencies pkgs.pkgsCross.musl64;
};
};
};
runtimeDependencies = pkgsForPlatform: with pkgsForPlatform; [
xorg.libX11 xorg.libX11
xorg.libXcursor xorg.libXcursor
xorg.libXrandr xorg.libXrandr
@ -59,12 +61,17 @@
egl-wayland egl-wayland
libGL libGL
]; ];
buildDependencies = pkgsForPlatform: with pkgsForPlatform; [
fontconfig
freetype
pkg-config
cmake
];
buildMatrix = targets: { buildMatrix = targets: {
include = builtins.map (target: { include = builtins.map (target: {
inherit target; inherit target;
artifact_suffix = artifactSuffixForTarget target; artifact_suffix = cross-naersk'.execSufficForTarget target;
asset_suffix = assetSuffixForTarget target;
}) targets; }) targets;
}; };
in rec { in rec {
@ -72,9 +79,11 @@
vbspview = packages.${hostTarget}; vbspview = packages.${hostTarget};
check = hostNaersk.buildPackage (nearskOpt // { check = hostNaersk.buildPackage (nearskOpt // {
mode = "check"; mode = "check";
buildInputs = buildDependencies pkgs;
}); });
clippy = hostNaersk.buildPackage (nearskOpt // { clippy = hostNaersk.buildPackage (nearskOpt // {
mode = "clippy"; mode = "clippy";
buildInputs = buildDependencies pkgs;
}); });
default = vbspview; default = vbspview;
}; };
@ -84,7 +93,7 @@
inherit targets; inherit targets;
devShells.default = pkgs.mkShell { devShells.default = pkgs.mkShell {
nativeBuildInputs = with pkgs; [ nativeBuildInputs = (with pkgs; [
pkgs.rust-bin.stable.latest.default pkgs.rust-bin.stable.latest.default
bacon bacon
cargo-edit cargo-edit
@ -92,7 +101,7 @@
clippy clippy
cargo-audit cargo-audit
cargo-msrv cargo-msrv
] ++ buildDependencies; ]) ++ (buildDependencies pkgs) ++ (runtimeDependencies pkgs);
LD_LIBRARY_PATH = with pkgs; "/run/opengl-driver/lib/:${lib.makeLibraryPath ([libGL libGLU])}"; LD_LIBRARY_PATH = with pkgs; "/run/opengl-driver/lib/:${lib.makeLibraryPath ([libGL libGLU])}";
}; };

4605
mats.txt Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,12 @@
use crate::material::load_material_fallback; use crate::material::load_material_fallback;
use crate::prop::load_props; use crate::prop::load_props;
use crate::{Error, Loader}; use crate::{Error, Loader};
use cgmath::vec4; use cgmath::Matrix4;
use std::collections::HashMap; use itertools::Itertools;
use three_d::{CpuMesh, CpuModel, Mat4, Positions, Vec2, Vec3}; use std::collections::{HashMap, HashSet};
use vbsp::{Bsp, Face, Handle}; 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> { pub fn load_map(data: &[u8], loader: &mut Loader) -> Result<Vec<CpuModel>, Error> {
let (world, bsp) = load_world(data, loader)?; 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]) 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 { pub fn map_coords<C: Into<Vec3>>(vec: C) -> Vec3 {
let vec = vec.into(); let vec = vec.into();
Vec3 { Vec3 {
@ -29,66 +26,66 @@ pub fn map_coords<C: Into<Vec3>>(vec: C) -> Vec3 {
// 1 hammer unit is ~1.905cm // 1 hammer unit is ~1.905cm
pub const UNIT_SCALE: f32 = 1.0 / (1.905 * 100.0); 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),
})
.collect();
let mut mesh = CpuMesh {
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 { fn model_to_model(model: Handle<vbsp::data::Model>, loader: &Loader) -> CpuModel {
let mut faces_by_texture: HashMap<&str, Vec<_>> = HashMap::with_capacity(64); let textures: HashSet<&str> = model.textures().map(|texture| texture.name()).collect();
for face in model.faces().filter(|face| face.is_visible()) { let textures: Vec<&str> = textures.into_iter().collect();
faces_by_texture
.entry(face.texture().name())
.or_default()
.push(face)
}
let geometries = faces_by_texture let faces_by_texture: HashMap<&str, _> = model
.values() .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| { .map(|faces| {
let mut faces = faces.iter(); let positions: Vec<_> = faces
let first = faces.next().unwrap(); .iter()
let mut mesh = face_to_mesh(first); .flat_map(|face| face.vertex_positions())
for face in faces { .map(map_coords)
let face_mesh = face_to_mesh(face); .collect();
if let Positions::F32(positions) = &mut mesh.positions {
positions.extend_from_slice(&face_mesh.positions.into_f32()); let uvs: Vec<_> = faces
} .iter()
if let Some(uvs) = &mut mesh.uvs { .flat_map(|face| {
uvs.extend_from_slice(&face_mesh.uvs.unwrap()); 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),
..Default::default()
};
mesh.compute_normals(); mesh.compute_normals();
mesh mesh.compute_tangents();
let texture = faces.first().unwrap().texture().name();
let material_index = textures
.iter()
.enumerate()
.find_map(|(i, tex)| (*tex == texture).then_some(i));
Primitive {
name: "".to_string(),
transformation: Matrix4::from_scale(1.0),
animations: vec![],
geometry: Geometry::Triangles(mesh),
material_index,
}
}) })
.collect(); .collect();
let materials = faces_by_texture let materials: Vec<_> = textures
.values() .iter()
.map(|face| { .map(|texture| load_material_fallback(texture, &["".into()], loader))
let texture = face.first().unwrap().texture();
load_material_fallback(texture.name(), &["".into()], loader)
})
.collect(); .collect();
CpuModel { CpuModel {
name: "".to_string(),
geometries, geometries,
materials, materials,
} }

View file

@ -2,7 +2,8 @@ use crate::loader::Loader;
use crate::Error; use crate::Error;
use std::str::FromStr; use std::str::FromStr;
use steamy_vdf::{Entry, Table}; 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 tracing::error;
use vtf::vtf::VTF; 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" "failed to load material, falling back"
); );
CpuMaterial { CpuMaterial {
albedo: Color { albedo: Srgba {
r: 255, r: 255,
g: 0, g: 0,
b: 255, b: 255,
@ -61,7 +62,7 @@ pub fn load_material(
if material_type == "water" { if material_type == "water" {
return Ok(CpuMaterial { return Ok(CpuMaterial {
albedo: Color { albedo: Srgba {
r: 82, r: 82,
g: 180, g: 180,
b: 217, b: 217,
@ -109,7 +110,7 @@ pub fn load_material(
Ok(CpuMaterial { Ok(CpuMaterial {
name: name.into(), name: name.into(),
albedo: Color::WHITE, albedo: Srgba::WHITE,
albedo_texture: Some(texture), albedo_texture: Some(texture),
alpha_cutout: alpha_test.then_some(alpha_cutout), alpha_cutout: alpha_test.then_some(alpha_cutout),
normal_texture: bump_map, 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") { if let Some(Entry::Table(patch)) = vmt.get("patch") {
let include = patch let include = patch
.get("include") .get("include")
.expect("no include in patch") .ok_or(Error::Other("no include in patch"))?
.as_value() .as_str()
.expect("include is not a value") .ok_or(Error::Other("include is not a string"))?;
.to_string();
let _replace = patch let _replace = patch
.get("replace") .get("replace")
.expect("no replace in patch") .ok_or(Error::Other("no replace in patch"))?
.as_table() .as_table()
.expect("replace is not a table"); .ok_or(Error::Other("replace is not a table"))?;
let included_raw = loader.load(&include)?.to_ascii_lowercase(); let included_raw = loader.load(include)?.to_ascii_lowercase();
// todo actually patch // todo actually patch
parse_vdf(&included_raw) parse_vdf(&included_raw)

View file

@ -1,9 +1,9 @@
use crate::bsp::{apply_transform, map_coords}; use crate::bsp::map_coords;
use crate::material::load_material_fallback; use crate::material::load_material_fallback;
use crate::{Error, Loader}; use crate::{Error, Loader};
use cgmath::{Matrix, SquareMatrix};
use std::collections::HashMap; 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 tracing::warn;
use vbsp::{Handle, StaticPropLump}; use vbsp::{Handle, StaticPropLump};
use vmdl::mdl::{Mdl, TextureInfo}; use vmdl::mdl::{Mdl, TextureInfo};
@ -35,18 +35,25 @@ pub fn load_props<'a, I: Iterator<Item = Handle<'a, StaticPropLump>>>(
}) })
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
let geometries = props.iter().flat_map(prop_to_meshes).collect(); let materials: HashMap<_, _> = props
let textures: HashMap<_, _> = props
.iter() .iter()
.flat_map(|prop| prop.model.textures()) .flat_map(|prop| prop.model.textures())
.map(|tex| (tex.name.as_str(), tex)) .map(|tex| (tex.name.as_str(), tex))
.collect(); .collect();
let materials: Vec<_> = textures let materials: Vec<_> = materials.into_values().collect();
.into_values()
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)) .map(|tex| prop_texture_to_material(tex, loader))
.collect(); .collect();
Ok(CpuModel { Ok(CpuModel {
name: "props".into(),
geometries, geometries,
materials, materials,
}) })
@ -58,9 +65,11 @@ struct PropData {
skin: i32, 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 transform = prop.transform;
let normal_transform = transform.invert().unwrap().transpose() * -1.0;
let model = &prop.model; let model = &prop.model;
let skin = match model.skin_tables().nth(prop.skin as usize) { 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 let texture = skin
.texture(mesh.material_index()) .texture(mesh.material_index())
.expect("texture out of bounds"); .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 let positions: Vec<Vec3> = mesh
.vertices() .vertices()
.map(|vertex| map_coords(vertex.position)) .map(|vertex| map_coords(vertex.position))
.map(|v| apply_transform(v, transform)) // .map(|v| apply_transform(v, transform))
.collect(); .collect();
let normals: Vec<Vec3> = mesh let normals: Vec<Vec3> = mesh
.vertices() .vertices()
.map(|vertex| map_coords(vertex.normal)) .map(|vertex| map_coords(vertex.normal))
.map(|v| apply_transform(v, normal_transform)) // .map(|v| apply_transform(v, normal_transform))
.collect(); .collect();
let uvs: Vec<Vec2> = mesh let uvs: Vec<Vec2> = mesh
.vertices() .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(); let tangents: Vec<Vec4> = mesh.tangents().map(|tangent| tangent.into()).collect();
CpuMesh { let geometry = Geometry::Triangles(TriMesh {
positions: Positions::F32(positions), positions: Positions::F32(positions),
indices: Default::default(),
normals: Some(normals), normals: Some(normals),
uvs: Some(uvs), uvs: Some(uvs),
material_name: Some(texture.into()),
tangents: Some(tangents), tangents: Some(tangents),
..Default::default() colors: None,
});
Primitive {
name: "".to_string(),
transformation: transform,
animations: vec![],
geometry,
material_index,
} }
}) })
} }

View file

@ -24,17 +24,17 @@ impl<C: Control> Renderer<C> {
vec3(0.0, 1.0, 0.0), vec3(0.0, 1.0, 0.0),
degrees(60.0), degrees(60.0),
0.1, 0.1,
30.0, 45.0,
); );
let ambient_lights = vec![AmbientLight { let ambient_lights = vec![AmbientLight {
color: Color::WHITE, color: Srgba::WHITE,
intensity: 0.2, intensity: 0.2,
..Default::default() ..Default::default()
}]; }];
let directional_lights = vec![ let directional_lights = vec![
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, Color::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); // 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[0].intensity = self.gui.directional_intensity;
self.directional_lights[1].intensity = self.gui.directional_intensity; self.directional_lights[1].intensity = self.gui.directional_intensity;
self.ambient_lights[0].intensity = self.gui.ambient_intensity; self.ambient_lights[0].intensity = self.gui.ambient_intensity;
self.camera.set_perspective_projection( self.camera
degrees(self.gui.fov), .set_perspective_projection(degrees(self.gui.fov), 0.1, 45.0);
self.camera.z_near(),
self.camera.z_far(),
);
} }
let viewport = Viewport { let viewport = Viewport {