we interutiosn ptow neffinrmenbs
This commit is contained in:
parent
11a864682a
commit
2c312df51a
198
lib/bsp_loader/src/brush.rs
Normal file
198
lib/bsp_loader/src/brush.rs
Normal file
@ -0,0 +1,198 @@
|
||||
use strafesnet_common::{model,integer};
|
||||
use strafesnet_common::integer::{vec3::Vector3,Fixed,Ratio};
|
||||
|
||||
use crate::{valve_transform,valve_transform_dist};
|
||||
|
||||
#[derive(Hash,Eq,PartialEq)]
|
||||
struct Face{
|
||||
normal:integer::Planar64Vec3,
|
||||
dot:integer::Planar64,
|
||||
}
|
||||
|
||||
struct Faces{
|
||||
faces:Vec<Vec<integer::Planar64Vec3>>,
|
||||
}
|
||||
|
||||
fn solve3(c0:&Face,c1:&Face,c2:&Face)->Option<Ratio<Vector3<Fixed<3,96>>,Fixed<3,96>>>{
|
||||
const EPSILON:Fixed<3,96>=Fixed::from_bits(Fixed::<3,96>::ONE.to_bits().shr(10));
|
||||
let n0_n1=c0.normal.cross(c1.normal);
|
||||
let det=c2.normal.dot(n0_n1);
|
||||
if det.abs()<EPSILON{
|
||||
return None;
|
||||
}
|
||||
Some((
|
||||
c1.normal.cross(c2.normal)*c0.dot
|
||||
+c2.normal.cross(c0.normal)*c1.dot
|
||||
+c0.normal.cross(c1.normal)*c2.dot
|
||||
)/det)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BrushToMeshError{
|
||||
SliceBrushSides,
|
||||
MissingPlane,
|
||||
InvalidFaceCount{
|
||||
count:usize,
|
||||
},
|
||||
}
|
||||
impl std::fmt::Display for BrushToMeshError{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl core::error::Error for BrushToMeshError{}
|
||||
|
||||
fn planes_to_faces(face_list:Vec<Face>)->Option<Faces>{
|
||||
// for each face, determine one edge at a time until you complete the face
|
||||
let mut dedup=std::collections::HashSet::new();
|
||||
'face: for face0 in &face_list{
|
||||
// don't generate duplicate faces
|
||||
if !dedup.insert(face0){
|
||||
continue 'face;
|
||||
}
|
||||
|
||||
// 1. find first edge
|
||||
// 2. follow edges around face
|
||||
|
||||
// === finding first edge ===
|
||||
// 1. pick the most perpendicular set of 3 faces
|
||||
// 2. check if any faces occlude the intersection
|
||||
// 3. use this test to replace left and right alternating until they are not occluded
|
||||
|
||||
// find the most perpendicular face to face0
|
||||
let mut face1=face_list.iter().min_by_key(|&p|{
|
||||
face0.normal.dot(p.normal).abs()
|
||||
})?;
|
||||
|
||||
// direction of edge formed by face0 x face1
|
||||
let edge_dir=face0.normal.cross(face1.normal);
|
||||
|
||||
// find the most perpendicular face to both face0 and face1
|
||||
let mut face2=face_list.iter().max_by_key(|&p|{
|
||||
// find the best *oriented* face (no .abs())
|
||||
edge_dir.dot(p.normal)
|
||||
})?;
|
||||
|
||||
let mut intersection=solve3(face0,face1,face2)?;
|
||||
|
||||
// repeatedly update face0, face1 until all faces form part of the convex solid
|
||||
'find: loop{
|
||||
// test if any *other* faces occlude the intersection
|
||||
for new_face in &face_list{
|
||||
if core::ptr::eq(face0,new_face){
|
||||
continue;
|
||||
}
|
||||
if core::ptr::eq(face1,new_face){
|
||||
continue;
|
||||
}
|
||||
if core::ptr::eq(face2,new_face){
|
||||
continue;
|
||||
}
|
||||
// new face occludes intersection point
|
||||
if new_face.dot*intersection.den<new_face.normal.dot(intersection.num){
|
||||
// replace one of the faces with the new face
|
||||
// dont' try to replace face0 because we are exploring that face in particular
|
||||
if let Some(new_intersection)=solve3(face0,new_face,face2){
|
||||
// face1 does not occlude (or intersect) the new intersection
|
||||
if face1.dot*new_intersection.den>face1.normal.dot(new_intersection.num){
|
||||
face1=new_face;
|
||||
intersection=new_intersection;
|
||||
continue 'find;
|
||||
}
|
||||
}
|
||||
if let Some(new_intersection)=solve3(face0,face1,new_face){
|
||||
// face2 does not occlude (or intersect) the new intersection
|
||||
if face2.dot*new_intersection.den>face2.normal.dot(new_intersection.num){
|
||||
face2=new_face;
|
||||
intersection=new_intersection;
|
||||
continue 'find;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we have found a set of faces for which the intersection is on the convex solid
|
||||
break 'find;
|
||||
}
|
||||
|
||||
// check if face0 must go, meaning it is a degenerate face and does not contribute anything to the convex solid
|
||||
for new_face in &face_list{
|
||||
if core::ptr::eq(face0,new_face){
|
||||
continue;
|
||||
}
|
||||
if core::ptr::eq(face1,new_face){
|
||||
continue;
|
||||
}
|
||||
if core::ptr::eq(face2,new_face){
|
||||
continue;
|
||||
}
|
||||
if let Some(new_intersection)=solve3(new_face,face1,face2){
|
||||
// face0 does not occlude (or intersect) the new intersection
|
||||
if face0.dot*new_intersection.den>face0.normal.dot(new_intersection.num){
|
||||
// abort! reject face0 entirely
|
||||
continue 'face;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === follow edges around face ===
|
||||
// Note that we chose face2 so that the faces create a particular winding order.
|
||||
// If we choose a consistent face to follow (face1, face2) it will always wind with a consistent chirality
|
||||
|
||||
loop{
|
||||
// the measure
|
||||
let edge_dir=face0.normal.cross(face1.normal);
|
||||
|
||||
// the dot product to beat
|
||||
let d_intersection=edge_dir.dot(intersection.num)/intersection.den;
|
||||
|
||||
for new_face in &face_list{
|
||||
if core::ptr::eq(face0,new_face){
|
||||
continue;
|
||||
}
|
||||
if core::ptr::eq(face1,new_face){
|
||||
continue;
|
||||
}
|
||||
if core::ptr::eq(face2,new_face){
|
||||
continue;
|
||||
}
|
||||
if let Some(new_intersection)=solve3(new_face,face1,face2){
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn brush_to_mesh(bsp:&vbsp::Bsp,brush:&vbsp::Brush)->Result<model::Mesh,BrushToMeshError>{
|
||||
let brush_start_idx=brush.brush_side as usize;
|
||||
let sides_range=brush_start_idx..brush_start_idx+brush.num_brush_sides as usize;
|
||||
let sides=bsp.brush_sides.get(sides_range).ok_or(BrushToMeshError::SliceBrushSides)?;
|
||||
let face_list=sides.iter().map(|side|{
|
||||
let plane=bsp.plane(side.plane as usize)?;
|
||||
Some(Face{
|
||||
normal:valve_transform(plane.normal.into()),
|
||||
dot:valve_transform_dist(plane.dist.into()),
|
||||
})
|
||||
}).collect::<Option<Vec<_>>>().ok_or(BrushToMeshError::MissingPlane)?;
|
||||
|
||||
if face_list.len()<4{
|
||||
return Err(BrushToMeshError::InvalidFaceCount{count:face_list.len()});
|
||||
}
|
||||
|
||||
let faces=planes_to_faces(face_list)?;
|
||||
|
||||
// generate the mesh
|
||||
let mut polygon_list=Vec::new();
|
||||
let mut mb=model::MeshBuilder::new();
|
||||
let color=mb.acquire_color_id(glam::Vec4::ONE);
|
||||
let tex=mb.acquire_tex_id(glam::Vec2::ZERO);
|
||||
|
||||
let polygon_groups=model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list));
|
||||
let physics_groups=vec![model::IndexedPhysicsGroup{
|
||||
groups:vec![model::PolygonGroupId::new(0)],
|
||||
}];
|
||||
|
||||
Ok(mb.build(vec![polygon_groups],vec![],physics_groups))
|
||||
}
|
@ -5,8 +5,6 @@ use strafesnet_deferred_loader::deferred_loader::{MeshDeferredLoader,RenderConfi
|
||||
use strafesnet_deferred_loader::mesh::Meshes;
|
||||
use strafesnet_deferred_loader::texture::{RenderConfigs,Texture};
|
||||
|
||||
use vbsp::Plane;
|
||||
|
||||
use crate::valve_transform;
|
||||
|
||||
fn ingest_vertex(
|
||||
@ -34,22 +32,6 @@ fn ingest_vertex(
|
||||
})
|
||||
}
|
||||
|
||||
fn solve3(c0:&Plane,c1:&Plane,c2:&Plane)->Option<glam::Vec3>{
|
||||
const EPSILON:f32=1.0/1024.0;
|
||||
let n0=glam::Vec3::from_array(c0.normal.into());
|
||||
let n1=glam::Vec3::from_array(c1.normal.into());
|
||||
let n2=glam::Vec3::from_array(c2.normal.into());
|
||||
let n0_n1=n0.cross(n1);
|
||||
let det=n2.dot(n0_n1);
|
||||
if det.abs()<EPSILON{
|
||||
return None;
|
||||
}
|
||||
let d0=c0.dist;
|
||||
let d1=c1.dist;
|
||||
let d2=c2.dist;
|
||||
Some((n1.cross(n2)*d0+n2.cross(n0)*d1+n0.cross(n1)*d2)/det)
|
||||
}
|
||||
|
||||
pub fn convert<'a>(
|
||||
bsp:&'a crate::Bsp,
|
||||
render_config_deferred_loader:&mut RenderConfigDeferredLoader<Cow<'a,str>>,
|
||||
@ -140,42 +122,10 @@ pub fn convert<'a>(
|
||||
|
||||
let brush_mesh_start_idx=world_meshes.len();
|
||||
for brush in &bsp.brushes{
|
||||
let brush_start_idx=brush.brush_side as usize;
|
||||
if let Some(sides)=bsp.brush_sides.get(brush_start_idx..brush_start_idx+brush.num_brush_sides as usize){
|
||||
let maybe_plane_list:Option<Vec<_>>=sides.iter().map(|side|bsp.plane(side.plane as usize)).collect();
|
||||
if let Some(plane_list)=maybe_plane_list{
|
||||
if plane_list.len()<4{
|
||||
println!("sussy planes!");
|
||||
continue;
|
||||
}
|
||||
let mut polygon_list=Vec::new();
|
||||
let mut mb=model::MeshBuilder::new();
|
||||
let color=mb.acquire_color_id(glam::Vec4::ONE);
|
||||
let tex=mb.acquire_tex_id(glam::Vec2::ZERO);
|
||||
|
||||
// for each face, determine one edge at a time until you complete the face
|
||||
for (plane_id,plane0) in plane_list.iter().enumerate(){
|
||||
// 1. find first edge
|
||||
// 2. follow edges around face
|
||||
|
||||
// === finding first edge ===
|
||||
// 1. pick any two additional planes to make a set of three
|
||||
// 2. check if any planes occlude the intersection
|
||||
// 3. use this test to replace left and right alternating until they are not occluded
|
||||
let mut plane1=&plane_list[(plane_id+1).rem_euclid(plane_list.len())];
|
||||
let mut plane2=&plane_list[(plane_id+2).rem_euclid(plane_list.len())];
|
||||
loop{
|
||||
// test if any other faces occlude the intersection
|
||||
solve3(plane0,plane1,plane2);
|
||||
}
|
||||
}
|
||||
|
||||
let polygon_groups=model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list));
|
||||
let physics_groups=vec![model::IndexedPhysicsGroup{
|
||||
groups:vec![model::PolygonGroupId::new(0)],
|
||||
}];
|
||||
world_meshes.push(mb.build(vec![polygon_groups],vec![],physics_groups));
|
||||
}
|
||||
let mesh_result=crate::brush::brush_to_mesh(bsp,brush);
|
||||
match mesh_result{
|
||||
Ok(mesh)=>world_meshes.push(mesh),
|
||||
Err(e)=>println!("Brush mesh error: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,13 @@ use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLo
|
||||
|
||||
mod bsp;
|
||||
mod mesh;
|
||||
mod brush;
|
||||
pub mod loader;
|
||||
|
||||
const VALVE_SCALE:f32=1.0/16.0;
|
||||
pub(crate) fn valve_transform_dist(d:f32)->strafesnet_common::integer::Planar64{
|
||||
(d*VALVE_SCALE).try_into().unwrap()
|
||||
}
|
||||
pub(crate) fn valve_transform([x,y,z]:[f32;3])->strafesnet_common::integer::Planar64Vec3{
|
||||
strafesnet_common::integer::vec3::try_from_f32_array([x*VALVE_SCALE,z*VALVE_SCALE,-y*VALVE_SCALE]).unwrap()
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::vector::Vector;
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
|
||||
pub struct Matrix<const X:usize,const Y:usize,T>{
|
||||
pub(crate) array:[[T;Y];X],
|
||||
|
@ -3,6 +3,7 @@
|
||||
/// v.x += v.z;
|
||||
/// println!("v.x={}",v.x);
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)]
|
||||
pub struct Vector<const N:usize,T>{
|
||||
pub(crate) array:[T;N],
|
||||
|
Loading…
x
Reference in New Issue
Block a user