diff --git a/src/bsp.rs b/src/bsp.rs index cdf2495..4a1f362 100644 --- a/src/bsp.rs +++ b/src/bsp.rs @@ -1,16 +1,21 @@ +use bitflags::Flags; use cgmath::InnerSpace; use crate::coords::{FCoords, FModelCoords}; -use crate::math::{points_are_same, points_are_near, FVector, THRESH_VECTORS_ARE_NEAR}; +use crate::math::{points_are_near, points_are_same, FVector, FPlane, THRESH_NORMALS_ARE_SAME, THRESH_POINTS_ARE_NEAR, THRESH_POINTS_ARE_SAME, THRESH_VECTORS_ARE_NEAR}; use crate::fpoly::{EPolyFlags, ESplitType, FPoly, RemoveColinearsResult, FPOLY_MAX_VERTICES, FPOLY_VERTEX_THRESHOLD}; -use crate::model::{EBspNodeFlags, UModel, BSP_NODE_MAX_NODE_VERTICES}; +use crate::model::{EBspNodeFlags, FBspNode, FBspSurf, FVert, UModel, BSP_NODE_MAX_NODE_VERTICES}; use crate::brush::ABrush; use crate::sphere::FSphere; use std::borrow::BorrowMut; +use arrayvec::ArrayVec; +use std::iter; use std::rc::Rc; use std::cell::RefCell; + /// Possible positions of a child Bsp node relative to its parent (for `BspAddToNode`). +#[derive(Clone, Copy, Debug, PartialEq)] pub enum ENodePlace { /// Node is in back of parent -> `Bsp[iParent].iBack`. Back, @@ -23,6 +28,7 @@ pub enum ENodePlace { } /// Status of filtered polygons: +#[derive(Clone, Copy, Debug, PartialEq)] pub enum EPolyNodeFilter { /// Leaf is an exterior leaf (visible to viewers). Outside, @@ -206,7 +212,7 @@ pub fn merge_near_points(model: &mut UModel, dist: f32) -> (usize, usize) { k += 1; } } - node.vertex_count = if k >= 3 { k as u8 } else { 0 }; + node.vertex_count = if k >= 3 { k } else { 0 }; if k < 3 { collapsed += 1; @@ -217,8 +223,187 @@ pub fn merge_near_points(model: &mut UModel, dist: f32) -> (usize, usize) { } -fn bsp_add_node(model: &mut UModel, node_index: Option, node_place: ENodePlace, node_flags: EBspNodeFlags, ed_poly: &mut FPoly) { - !todo!() +/// Add an editor polygon to the Bsp, and also stick a reference to it +/// in the editor polygon's BspNodes list. If the editor polygon has more sides +/// than the Bsp will allow, split it up into several sub-polygons. +/// +/// Returns: Index to newly-created node of Bsp. If several nodes were created because +/// of split polys, returns the parent (highest one up in the Bsp). +pub fn bsp_add_node(model: &mut UModel, mut parent_node_index: usize, node_place: ENodePlace, mut node_flags: EBspNodeFlags, ed_poly: &FPoly) -> usize { + if node_place == ENodePlace::Plane { + // Make sure coplanars are added at the end of the coplanar list so that + // we don't insert NF_IsNew nodes with non NF_IsNew coplanar children. + while let Some(plane_index) = model.nodes[parent_node_index].plane_index { + parent_node_index = plane_index + } + } + + println!("{:?}", ed_poly.link); + + let (surface, surface_index) = match ed_poly.link { + None => { + let mut surf = FBspSurf::default(); + + // This node has a new polygon being added by bspBrushCSG; must set its properties here. + surf.base_point_index = model.bsp_add_point(ed_poly.base, true); + surf.normal_index = model.bsp_add_vector(ed_poly.normal, true); + surf.texture_u_index = model.bsp_add_vector(ed_poly.texture_u, false); + surf.texture_v_index = model.bsp_add_vector(ed_poly.texture_v, false); + // Surf->Material = EdPoly->Material; + surf.poly_flags = ed_poly.poly_flags & !EPolyFlags::NoAddToBSP; + surf.light_map_scale = ed_poly.light_map_scale; + // Surf->Actor = EdPoly->Actor; + surf.brush_polygon_index = ed_poly.brush_poly_index; + surf.plane = FPlane::new_from_origin_and_normal(ed_poly.vertices.first().unwrap(), &ed_poly.normal); + + let surface_index = model.surfaces.len(); + model.surfaces.push(surf); + (&model.surfaces[surface_index], surface_index) + } + Some(link) => { + assert!(link < model.surfaces.len()); + (&model.surfaces[link], link) + } + }; + + // Set NodeFlags. + if surface.poly_flags.contains(EPolyFlags::NotSolid) { + node_flags |= EBspNodeFlags::NotCsg; + } + if surface.poly_flags.contains(EPolyFlags::Invisible | EPolyFlags::Portal) { + node_flags |= EBspNodeFlags::NotVisBlocking; + } + if surface.poly_flags.contains(EPolyFlags::Masked) { + node_flags |= EBspNodeFlags::ShootThrough; + } + + if ed_poly.vertices.len() > BSP_NODE_MAX_NODE_VERTICES { + // Split up into two coplanar sub-polygons (one with MAX_NODE_VERTICES vertices and + // one with all the remaining vertices) and recursively add them. + + // Copy first bunch of verts. + // TODO: test and extract this to a function. + let mut ed_poly1 = ed_poly.clone(); + ed_poly1.vertices.truncate(BSP_NODE_MAX_NODE_VERTICES); + let mut ed_poly2 = ed_poly.clone(); + ed_poly2.vertices.drain(1..BSP_NODE_MAX_NODE_VERTICES); + + let node_index = bsp_add_node(model, parent_node_index, node_place, node_flags, &mut ed_poly1); + bsp_add_node(model, node_index, ENodePlace::Plane, node_flags, &mut ed_poly2); + + node_index + } else { + // Add node. + let node_index = model.nodes.len(); + model.nodes.push(FBspNode::new()); + + { + let node = &mut model.nodes[node_index]; + // Set node properties. + node.surface_index = surface_index; + node.node_flags = node_flags; + node.render_bound = None; + node.collision_bound = None; + node.plane = FPlane::new_from_origin_and_normal(ed_poly.vertices.first().unwrap(), &ed_poly.normal); + node.vertex_pool_index = model.vertices.len(); + model.vertices.extend(iter::repeat_with(|| FVert::new()).take(ed_poly.vertices.len())); + node.front_node_index = None; + node.back_node_index = None; + node.plane_index = None; + } + + // TODO: get_many_mut will fail here in the Root case since node_index and parent_node_index are the same. + // Tell transaction tracking system that parent is about to be modified. + { + match node_place { + ENodePlace::Root => { + assert_eq!(0, node_index); + let node = &mut model.nodes[node_index]; + node.leaf_indices[0] = None; + node.leaf_indices[1] = None; + node.zone[0] = 0; + node.zone[1] = 0; + node.zone_mask = !0u64; + }, + _ => { + let [node, parent_node] = model.nodes.get_many_mut([node_index, parent_node_index]).unwrap(); + + node.zone_mask = parent_node.zone_mask; + + match node_place { + ENodePlace::Front | ENodePlace::Back => { + let zone_front_index = if node_place == ENodePlace::Front { 1usize } else { 0usize }; + node.leaf_indices[0] = parent_node.leaf_indices[zone_front_index]; + node.leaf_indices[1] = parent_node.leaf_indices[zone_front_index]; + node.zone[0] = parent_node.zone[zone_front_index]; + node.zone[1] = parent_node.zone[zone_front_index]; + } + _ => { + let is_flipped_index = if node.plane.plane_dot(parent_node.plane.normal()) < 0.0 { 1usize } else { 0usize }; + node.leaf_indices[0] = parent_node.leaf_indices[is_flipped_index]; + node.leaf_indices[1] = parent_node.leaf_indices[1 - is_flipped_index]; + node.zone[0] = parent_node.zone[is_flipped_index]; + node.zone[1] = parent_node.zone[1 - is_flipped_index]; + } + } + + // Link parent to this node. + match node_place { + ENodePlace::Front => { parent_node.front_node_index = Some(node_index); } + ENodePlace::Back => { parent_node.back_node_index = Some(node_index); } + ENodePlace::Plane => { parent_node.plane_index = Some(node_index); } + _ => {} + } + } + } + } + + // Add all points to point table, merging nearly-overlapping polygon points + // with other points in the poly to prevent criscrossing vertices from + // being generated. + + // Must maintain Node->NumVertices on the fly so that bspAddPoint is always + // called with the Bsp in a clean state. + let mut points: ArrayVec = ArrayVec::new(); + let n = ed_poly.vertices.len(); + let vertex_pool_range = { + let node = &mut model.nodes[node_index]; + node.vertex_count = 0; + node.vertex_pool_index..node.vertex_pool_index + n + }; + for ed_poly_vertex in ed_poly.vertices.iter() { + let vertex_index = model.bsp_add_point(*ed_poly_vertex, false); + let vert_pool = &mut model.vertices[vertex_pool_range.clone()]; + let node = &mut model.nodes[node_index]; + if node.vertex_count == 0 || vert_pool[node.vertex_count - 1].vertex_index != vertex_index { + points.push(*ed_poly_vertex); + vert_pool[node.vertex_count].side_index = None; + vert_pool[node.vertex_count].vertex_index = vertex_index; + node.vertex_count += 1; + } + } + + let node = &mut model.nodes[node_index]; + let vert_pool = &mut model.vertices[vertex_pool_range.clone()]; + + if node.vertex_count >= 2 && vert_pool[0].vertex_index == vert_pool[node.vertex_count - 1].vertex_index { + node.vertex_count -= 1; + } + + if node.vertex_count < 3 { + // GErrors++; + println!("bspAddNode: Infinitesimal polygon {} ({})", node.vertex_count, n); + node.vertex_count = 0; + } + + node.section_index = None; + node.light_map_index = None; + + // Calculate a bounding sphere for this node. + node.exclusive_sphere_bound = FSphere::new_from_points(points.as_slice()); + + node_index + } } @@ -237,7 +422,7 @@ struct RebuildContext { g_errors: usize, } -fn add_brush_to_world(model: &mut UModel, node_index: Option, ed_poly: &mut FPoly, filter: EPolyNodeFilter, node_place: ENodePlace) { +fn add_brush_to_world(model: &mut UModel, node_index: usize, ed_poly: &mut FPoly, filter: EPolyNodeFilter, node_place: ENodePlace) { match filter { EPolyNodeFilter::Outside | EPolyNodeFilter::CoplanarOutside => { bsp_add_node(model, node_index, node_place, EBspNodeFlags::IsNew, ed_poly); @@ -251,14 +436,15 @@ fn add_brush_to_world(model: &mut UModel, node_index: Option, ed_poly: &m } } -fn add_world_to_brush(context: &mut RebuildContext, model: &mut UModel, node_index: Option, ed_poly: &mut FPoly, filter: EPolyNodeFilter, _: ENodePlace) { +fn add_world_to_brush(context: &mut RebuildContext, model: &mut UModel, node_index: usize, ed_poly: &mut FPoly, filter: EPolyNodeFilter, _: ENodePlace) { // Get a mutable refernce from the Rc and add the node. let g_model = Rc::get_mut(&mut context.g_model).unwrap(); match filter { EPolyNodeFilter::Outside | EPolyNodeFilter::CoplanarOutside => { // Only affect the world poly if it has been cut. if ed_poly.poly_flags.contains(EPolyFlags::EdCut) { - bsp_add_node(g_model, context.g_last_coplanar, ENodePlace::Plane, EBspNodeFlags::IsNew, ed_poly); + // TODO: how do we know that g_last_coplanar is not None? + bsp_add_node(g_model, context.g_last_coplanar.unwrap(), ENodePlace::Plane, EBspNodeFlags::IsNew, ed_poly); } } _ => { @@ -527,11 +713,11 @@ fn filter_ed_poly(filter_func: BspFilterFunc, model: &mut UModel, mut node_index fn add_brush_to_world_func(model: &mut UModel, node_index: usize, ed_poly: &mut FPoly, filter: EPolyNodeFilter, node_place: ENodePlace) { match filter { EPolyNodeFilter::Outside | EPolyNodeFilter::CoplanarOutside => { - bsp_add_node(model, Some(node_index), node_place, EBspNodeFlags::IsNew, ed_poly); + bsp_add_node(model, node_index, node_place, EBspNodeFlags::IsNew, ed_poly); }, EPolyNodeFilter::CospatialFacingOut => { if !ed_poly.poly_flags.contains(EPolyFlags::Semisolid) { - bsp_add_node(model, Some(node_index), node_place, EBspNodeFlags::IsNew, ed_poly); + bsp_add_node(model, node_index, node_place, EBspNodeFlags::IsNew, ed_poly); } }, _ => {} @@ -542,7 +728,7 @@ fn subtract_brush_from_world_func(model: &mut UModel, node_index: usize, ed_poly match filter { EPolyNodeFilter::CoplanarInside | EPolyNodeFilter::Inside => { ed_poly.reverse(); - bsp_add_node(model, Some(node_index), node_place, EBspNodeFlags::IsNew, ed_poly); // Add to Bsp back + bsp_add_node(model, node_index, node_place, EBspNodeFlags::IsNew, ed_poly); // Add to Bsp back ed_poly.reverse(); }, _ => {} diff --git a/src/fpoly.rs b/src/fpoly.rs index b9e6fdf..28752c6 100644 --- a/src/fpoly.rs +++ b/src/fpoly.rs @@ -86,6 +86,9 @@ bitflags! { const EdCut = 0x80000000; /// Occludes even if PF_NoOcclude. const Occlude = 0x80000000; + + // PF_NoAddToBSP = PF_EdCut | PF_EdProcessed | PF_Selected | PF_Memorized, + const NoAddToBSP = 0x80000000 | 0x40000000 | 0x02000000 | 0x01000000; } } diff --git a/src/math.rs b/src/math.rs index 1ec4c35..86c830c 100644 --- a/src/math.rs +++ b/src/math.rs @@ -1,5 +1,5 @@ use cgmath::{Vector3, InnerSpace}; -use crate::coords::FCoords; +use crate::{coords::FCoords, model::FBspNode}; pub type FVector = Vector3; @@ -13,6 +13,25 @@ pub struct FPlane { } impl FPlane { + pub fn new() -> FPlane { + FPlane { + x: 0.0, + y: 0.0, + z: 0.0, + w: 0.0, + } + } + + pub fn new_from_origin_and_normal(origin: &FVector, normal: &FVector) -> FPlane { + let w = origin.dot(*normal); + FPlane { + x: normal.x, + y: normal.y, + z: normal.z, + w, + } + } + pub fn normal(&self) -> FVector { FVector::new(self.x, self.y, self.z) } diff --git a/src/model.rs b/src/model.rs index a3e26b3..a679eb7 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,8 +1,12 @@ +use std::result; + use bitflags::bitflags; +use cgmath::MetricSpace; use crate::fpoly::{EPolyFlags, FPoly}; use crate::math::{FPlane, FVector}; use crate::sphere::FSphere; use crate::box_::FBox; +use crate::math::{THRESH_POINTS_ARE_SAME, THRESH_POINTS_ARE_NEAR, THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR}; pub struct FBspVertex { pub position: FVector, @@ -14,7 +18,7 @@ pub struct FBspVertex { } /// Flags associated with a Bsp node. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct EBspNodeFlags(u16); bitflags! { @@ -56,11 +60,27 @@ pub struct FBspSurf { pub texture_v_index: usize, pub brush_polygon_index: Option, //pub actor: Rc, - pub node_indices: Vec, + pub node_indices: Vec, // TODO: what's this one?? pub plane: FPlane, pub light_map_scale: f32 } +impl Default for FBspSurf { + fn default() -> Self { + FBspSurf { + poly_flags: EPolyFlags::empty(), + base_point_index: 0, + normal_index: 0, + texture_u_index: 0, + texture_v_index: 0, + brush_polygon_index: None, + node_indices: Vec::new(), + plane: FPlane::new(), + light_map_scale: 0.0 + } + } +} + /// Max vertices in a Bsp node, pre clipping. pub const BSP_NODE_MAX_NODE_VERTICES: usize = 16; /// Max vertices in a Bsp node, post clipping. @@ -101,26 +121,48 @@ pub struct FBspNode { pub exclusive_sphere_bound: FSphere, /// Collision bound. - pub collision_bound: usize, + pub collision_bound: Option, /// Rendering bound. - pub render_bound: usize, + pub render_bound: Option, /// Visibility zone in 1=front, 0=back. pub zone: [u8;2], /// Number of vertices in node. - pub vertex_count: u8, + pub vertex_count: usize, /// Node flags. pub node_flags: EBspNodeFlags, /// Leaf in back and front, INDEX_NONE = NOT A LEAF. pub leaf_indices: [Option;2], - pub section_index: usize, + pub section_index: Option, pub first_vertex_index: usize, - pub light_map_index: usize, + pub light_map_index: Option, } impl FBspNode { + pub fn new() -> FBspNode { + FBspNode { + plane: FPlane::new(), + zone_mask: 0, + vertex_pool_index: 0, + surface_index: 0, + back_node_index: None, + front_node_index: None, + plane_index: None, + exclusive_sphere_bound: FSphere::default(), + collision_bound: None, + render_bound: None, + zone: [0, 0], + vertex_count: 0, + node_flags: EBspNodeFlags::empty(), + leaf_indices: [None, None], + section_index: None, + first_vertex_index: 0, + light_map_index: None, + } + } + pub fn is_csg(&self, extra_flags: EBspNodeFlags) -> bool { self.vertex_count > 0 && self.node_flags.contains(EBspNodeFlags::IsNew | EBspNodeFlags::NotCsg | extra_flags) } @@ -143,6 +185,15 @@ pub struct FVert { pub side_index: Option, } +impl FVert { + pub fn new() -> FVert { + FVert { + vertex_index: 0, + side_index: None, + } + } +} + #[derive(Clone, Debug, PartialEq)] /// Information about a convex volume. pub struct FLeaf { @@ -261,4 +312,125 @@ impl UModel { } self.bounding_sphere = FSphere::from(&self.bounding_box); } + + // Find Bsp node vertex nearest to a point (within a certain radius) and + // set the location. Returns distance, or -1.f if no point was found. + pub fn find_nearest_vertex(&self, source_point: FVector, dest_point: &mut FVector, min_radius: f32, vertex_index: &mut usize) -> f32 { + if self.nodes.is_empty() { + return -1.0; + } + self.find_nearest_vertex_recursive(source_point, dest_point, min_radius, Some(0), vertex_index) + } + + fn find_nearest_vertex_recursive(&self, source_point: FVector, dest_point: &mut FVector, mut min_radius: f32, node_index: Option, vertex_index: &mut usize) -> f32 { + let mut result_radius = -1.0f32; + + let mut next_node_index = node_index; + while let Some(node_index) = next_node_index { + let node = &self.nodes[node_index]; + let back_index = node.back_node_index; + let plane_distance = node.plane.plane_dot(source_point); + + if plane_distance >= -min_radius { + if let Some(front_node_index) = node.front_node_index { + // Check front. + let temp_radius = self.find_nearest_vertex_recursive(source_point, dest_point, min_radius, Some(front_node_index), vertex_index); + if temp_radius >= 0.0 { + result_radius = temp_radius; + min_radius = temp_radius; + } + } + } + + if plane_distance > -min_radius && plane_distance <= min_radius { + // Check this node's poly's vertices. + next_node_index = back_index; + while let Some(node_index) = next_node_index { + // Loop through all coplanars. + let node = &self.nodes[node_index]; + let surf = &self.surfaces[node.surface_index]; + let base = &self.points[surf.base_point_index]; + let temp_radius_squared = source_point.distance2(*base); + + if temp_radius_squared < (min_radius * min_radius) { + *vertex_index = surf.base_point_index; + min_radius = temp_radius_squared.sqrt(); + result_radius = min_radius; + *dest_point = *base; + } + + let vert_pool = &self.vertices[node.vertex_pool_index..(node.vertex_pool_index + node.vertex_count as usize)]; + for vert in vert_pool { + let vertex = &self.points[vert.vertex_index]; + let temp_radius_squared = source_point.distance2(*vertex); + if temp_radius_squared < min_radius * min_radius { + *vertex_index = vert.vertex_index; + min_radius = temp_radius_squared.sqrt(); + result_radius = min_radius; + *dest_point = *vertex; + } + } + + next_node_index = node.plane_index; + } + } + + if plane_distance > min_radius { + break; + } + + next_node_index = back_index; + } + + result_radius + } + + /// Add a new point to the model, merging near-duplicates, and return its index. + pub fn bsp_add_point(&mut self, v: FVector, exact: bool) -> usize { + let thresh = if exact { THRESH_POINTS_ARE_SAME } else { THRESH_POINTS_ARE_NEAR }; + + // Try to find a match quickly from the Bsp. This finds all potential matches + // except for any dissociated from nodes/surfaces during a rebuild. + let mut temp = FVector::new(0.0, 0.0, 0.0); + let mut vertex_index = 0usize; + let nearest_distance = self.find_nearest_vertex(v, &mut temp, thresh, &mut vertex_index); + + if nearest_distance >= 0.0 && nearest_distance <= thresh { + // Found an existing point. + vertex_index + } else { + let fast_rebuild = false; + add_thing(&mut self.points, v, thresh, fast_rebuild) + } + } + + /// Add a new vector to the model, merging near-duplicates, and return its index. + pub fn bsp_add_vector(&mut self, v: FVector, is_normal: bool) -> usize { + add_thing(&mut self.vectors, v, + if is_normal { THRESH_NORMALS_ARE_SAME } else { THRESH_VECTORS_ARE_NEAR }, + false) + } +} + + +/// Add a new point to the model (preventing duplicates) and return its +/// index. +fn add_thing(vectors: &mut Vec, v: FVector, threshold: f32, check: bool) -> usize { + if check { + for (i, table_vector) in vectors.iter().enumerate() { + let temp = v.x - table_vector.x; + if temp > -threshold && temp < threshold { + let temp = v.y - table_vector.y; + if temp > -threshold && temp < threshold { + let temp = v.z - table_vector.z; + if temp > -threshold && temp < threshold { + // Found nearly-matching vector. + return i + } + } + } + } + } + vectors.push(v); + vectors.len() - 1 } \ No newline at end of file diff --git a/tests/bsp_tests.rs b/tests/bsp_tests.rs index af5e05c..52f7144 100644 --- a/tests/bsp_tests.rs +++ b/tests/bsp_tests.rs @@ -1,8 +1,8 @@ use bdk_rs::coords::FModelCoords; use bdk_rs::math::FVector; -use bdk_rs::bsp::{merge_coplanars, try_to_merge}; +use bdk_rs::bsp::{merge_coplanars, try_to_merge, bsp_add_node}; use bdk_rs::fpoly::{self, FPoly}; -use bdk_rs::model::UModel; +use bdk_rs::model::{self, EBspNodeFlags, UModel}; #[test] fn try_to_merge_disjoint_test() { @@ -172,3 +172,23 @@ fn merge_coplanars_quad_grid_with_skipped_index_test() { assert_eq!(merged_polys[1].vertices.to_vec(), polys[2].vertices.to_vec()); assert_eq!(merged_polys[2].vertices.to_vec(), polys[3].vertices.to_vec()); } + + +#[test] +fn bsp_add_node_root_node() { + let mut model = UModel::new(); + let poly = FPoly::from_vertices(&vec![ + FVector::new(0.0, 0.0, 0.0), + FVector::new(1.0, 0.0, 0.0), + FVector::new(1.0, 1.0, 0.0), + ]); + bsp_add_node(&mut model, + 0, + bdk_rs::bsp::ENodePlace::Root, + EBspNodeFlags::empty(), + &poly + ); + + println!("{:?}", model.nodes); + +} \ No newline at end of file