use glam::Vec3;
use crate::{
BspData,
data::{
nodes::{BspLeafContentFlags, BspNodeRef},
visdata::VisDataRef,
},
util::{self, VisdataIter},
};
#[derive(Debug, Clone, Copy, Default)]
pub struct RaycastResult {
pub impact: Option<RaycastImpact>,
pub leaf_idx: usize,
}
#[derive(Debug, Clone, Copy)]
pub struct RaycastImpact {
pub fraction: f32,
pub position: Vec3,
pub normal: Vec3,
pub node_idx: u32,
}
pub enum VisResult<'a> {
Clusters(VisdataIter<'a>),
LeafIndices(VisdataIter<'a>),
}
impl<'a> VisResult<'a> {
pub fn into_clusters(self) -> Result<VisdataIter<'a>, Self> {
if let Self::Clusters(clusters) = self { Ok(clusters) } else { Err(self) }
}
pub fn into_leaf_indices(self) -> Result<VisdataIter<'a>, Self> {
if let Self::LeafIndices(leaf_indices) = self { Ok(leaf_indices) } else { Err(self) }
}
}
impl BspData {
pub fn leaf_at_point(&self, model_idx: usize, point: Vec3) -> usize {
self.leaf_at_point_in_node(self.models[model_idx].hulls.root, point)
}
pub fn leaf_at_point_in_node(&self, mut node_ref: BspNodeRef, point: Vec3) -> usize {
loop {
match node_ref {
BspNodeRef::Leaf(leaf_idx) => return leaf_idx as usize,
BspNodeRef::Node(node_idx) => {
let node = &self.nodes[node_idx as usize];
let plane = &self.planes[node.plane_idx as usize];
if plane.point_side(point) >= 0. {
node_ref = *node.front;
} else {
node_ref = *node.back;
}
}
}
}
}
pub fn potentially_visible_set(&self, leaf_idx: usize) -> Option<VisResult<'_>> {
let vis_offset = self.leaves.get(leaf_idx)?.visdata;
let map_to_ref: fn(VisdataIter) -> VisResult = match vis_offset {
VisDataRef::Cluster(_) => |iter| VisResult::Clusters(iter),
VisDataRef::Offset(_) => |iter| VisResult::LeafIndices(iter),
};
Some(map_to_ref(util::calculate_visdata_indices(
self.visibility.pvs(vis_offset)?,
self.leaves.len(),
)))
}
pub fn potentially_visible_set_at(&self, model_idx: usize, point: Vec3) -> Option<VisResult<'_>> {
self.potentially_visible_set(self.leaf_at_point(model_idx, point))
}
pub fn potentially_audible_set(&self, leaf_idx: usize) -> Option<VisResult<'_>> {
let vis_offset = self.leaves.get(leaf_idx)?.visdata;
let map_to_ref: fn(VisdataIter) -> VisResult = match vis_offset {
VisDataRef::Cluster(_) => |iter| VisResult::Clusters(iter),
VisDataRef::Offset(_) => |iter| VisResult::LeafIndices(iter),
};
Some(map_to_ref(util::calculate_visdata_indices(
self.visibility.phs(vis_offset)?,
self.leaves.len(),
)))
}
pub fn potentially_audible_set_at(&self, model_idx: usize, point: Vec3) -> Option<VisResult<'_>> {
self.potentially_audible_set(self.leaf_at_point(model_idx, point))
}
pub fn raycast(&self, model_idx: usize, from: Vec3, to: Vec3) -> RaycastResult {
if from == to {
return RaycastResult {
impact: None,
leaf_idx: self.leaf_at_point(model_idx, from),
};
}
const DIST_EPSILON: f32 = 0.03125;
struct Ctx<'a> {
start: Vec3,
end: Vec3,
data: &'a BspData,
}
fn internal(ctx: &Ctx, mut node_ref: BspNodeRef, from: Vec3, to: Vec3) -> RaycastResult {
'reenter: loop {
let node_idx = match node_ref {
BspNodeRef::Leaf(leaf_idx) => {
return RaycastResult {
impact: None,
leaf_idx: leaf_idx as usize,
};
}
BspNodeRef::Node(node_idx) => node_idx,
};
let node = &ctx.data.nodes[node_idx as usize];
let plane = &ctx.data.planes[node.plane_idx as usize];
let from_dist = plane.point_side(from);
let to_dist = plane.point_side(to);
if from_dist >= 0. && to_dist >= 0. {
node_ref = *node.front;
continue 'reenter;
} else if from_dist < 0. && to_dist < 0. {
node_ref = *node.back;
continue 'reenter;
}
let front_side = from_dist >= 0.;
let frac = if front_side { from_dist + DIST_EPSILON } else { from_dist - DIST_EPSILON } / (from_dist - to_dist);
let mid = from.lerp(to, frac);
let side_result = internal(ctx, if front_side { *node.front } else { *node.back }, from, mid);
if ctx.data.leaves[side_result.leaf_idx].contents.contains(BspLeafContentFlags::SOLID) {
return side_result;
}
let mid_leaf_idx = ctx.data.leaf_at_point_in_node(if front_side { *node.back } else { *node.front }, mid);
if !ctx.data.leaves[mid_leaf_idx].contents.contains(BspLeafContentFlags::SOLID) {
return internal(ctx, if front_side { *node.back } else { *node.front }, mid, to);
}
let real_mid = from.lerp(to, from_dist / (from_dist - to_dist));
let impact = RaycastImpact {
fraction: ((real_mid - ctx.start) / (ctx.end - ctx.start)).element_sum() / 3.,
position: real_mid,
normal: if front_side { -plane.normal } else { plane.normal },
node_idx,
};
return RaycastResult {
impact: Some(impact),
leaf_idx: mid_leaf_idx,
};
}
}
let ctx = Ctx {
start: from,
end: to,
data: self,
};
internal(&ctx, self.models[model_idx].hulls.root, from, to)
}
}