Skip to content

Commit

Permalink
Implement CPU raycasting
Browse files Browse the repository at this point in the history
  • Loading branch information
HactarCE committed Jan 13, 2021
1 parent 091fc12 commit 5855229
Show file tree
Hide file tree
Showing 5 changed files with 272 additions and 21 deletions.
3 changes: 1 addition & 2 deletions ui/src/gridview/render/shaders/octree.frag
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,7 @@ void main() {
t1 = mix(tm_stack[layer], t1_stack[layer], next_child);
tm = (t0 + t1) / 2.0;

// Compute the next sibling of `next_child` to visit after this
// one.
// Compute the sibling of `next_child` to visit after this one.
uint exit_axis = exitAxis(t1);
// `== true` is required to work around a bug in AMD drivers.
// Yes this is cursed.
Expand Down
86 changes: 76 additions & 10 deletions ui/src/gridview/view3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ use ndcell_core::prelude::*;

use super::generic::{GenericGridView, GridViewDimension};
use super::render::{CellDrawParams, GridViewRender3D, RenderParams, RenderResult};
use super::viewpoint::{Viewpoint, Viewpoint3D};
use super::viewpoint::{CellTransform3D, Viewpoint, Viewpoint3D};
use super::{DragHandler, DragType};
use crate::commands::*;
use crate::math::raycast;

pub type GridView3D = GenericGridView<GridViewDim3D>;

Expand Down Expand Up @@ -71,15 +72,80 @@ impl GridViewDimension for GridViewDim3D {
}
}
impl GridView3D {
/// Returns the cell position underneath the cursor. Floor this to get an
/// integer cell position.
pub fn hovered_cell_pos(&self, mouse_pos: Option<FVec2D>) -> Option<FixedVec3D> {
// TODO: Raycast or something
let z = Viewpoint3D::DISTANCE_TO_PIVOT;
mouse_pos.and_then(|pos| {
self.viewpoint()
.cell_transform()
.pixel_to_global_pos(pos, z)
pub fn screen_pos(&self, pixel: FVec2D) -> ScreenPos3D {
let vp = self.viewpoint();
let xform = vp.cell_transform();

let mut raycast_hit;
{
// Get the `NdTreeSlice` containing all of the visible cells.
let global_visible_rect = vp.global_visible_rect();
let visible_octree = self.automaton.ndtree.slice_containing(&global_visible_rect);
let local_octree_base_pos =
xform.global_to_local_int(&visible_octree.base_pos).unwrap();

// Compute the ray, relative to the root node of the octree slice.
let (mut start, delta) = xform.pixel_to_local_ray(pixel);
printlnd!("octree off {}", local_octree_base_pos);
start -= local_octree_base_pos.to_fvec();

let layer = xform.render_cell_layer;
let node = visible_octree.root.as_ref();
raycast_hit = crate::math::raycast::octree_raycast(start, delta, layer, node);

// Convert coordinates back from octree node space into local space.
if let Some(hit) = &mut raycast_hit {
hit.start += local_octree_base_pos.to_fvec();
hit.pos_int += local_octree_base_pos;
hit.pos_float += local_octree_base_pos.to_fvec();
}
};

ScreenPos3D {
pixel,
xform,
raycast_hit,
}
}
}

#[derive(Debug, Clone)]
pub struct ScreenPos3D {
pixel: FVec2D,
xform: CellTransform3D,
raycast_hit: Option<raycast::Hit>,
}
impl ScreenPos3D {
/// Returns the position of the mouse in pixel space.
pub fn pixel(&self) -> FVec2D {
self.pixel
}
/// Returns the global cell position at the mouse cursor in the plane parallel
/// to the screen at the distance of the viewpoint pivot.
pub fn global_pos_at_pivot_depth(&self) -> FixedVec3D {
self.xform
.pixel_to_global_pos(self.pixel, Viewpoint3D::DISTANCE_TO_PIVOT)
}

/// Returns the global position of the cell visible at the mouse cursor.
pub fn raycast(&self) -> Option<RaycastHit> {
self.raycast_hit.map(|hit| RaycastHit {
pos: self.xform.local_to_global_float(hit.pos_float),
cell: self.xform.local_to_global_int(hit.pos_int),
face: (hit.face_axis, hit.face_sign),
})
}
/// Returns the global position of the cell visible at the mouse cursor.
pub fn raycast_face(&self) -> Option<(Axis, Sign)> {
self.raycast_hit.map(|hit| (hit.face_axis, hit.face_sign))
}
}

pub struct RaycastHit {
/// Point of intersection between ray and cell.
pub pos: FixedVec3D,
/// Position of intersected cell.
pub cell: BigVec3D,
/// Face of cell intersected.
pub face: (Axis, Sign),
}
1 change: 1 addition & 0 deletions ui/src/math/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod bresenham;
pub mod raycast;
182 changes: 182 additions & 0 deletions ui/src/math/raycast.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
//! Octree traversal algorithm based on An Efficient Parametric Algorithm for
//! Octree Traversal by J. Revelles, C. Ureña, M. Lastra.
//!
//! This is also implemented in GLSL in the octree fragment shader.
use ndcell_core::prelude::*;

#[derive(Debug, Copy, Clone)]
pub struct Hit {
pub start: FVec3D,
pub delta: FVec3D,

pub t0: R64,
pub t1: R64,

pub pos_int: IVec3D,
pub pos_float: FVec3D,

pub face_axis: Axis,
pub face_sign: Sign,
}

/// Computes a 3D octree raycast. `start` and `delta` are both in units of
/// `min_layer` nodes, and `start` is relative to the lower corner of `node`.
pub fn octree_raycast(
mut start: FVec3D,
mut delta: FVec3D,
min_layer: Layer,
node: NodeRef<'_, Dim3D>,
) -> Option<Hit> {
let node_len = r64((node.layer() - min_layer).big_len().to_f64().unwrap());

// Check each component of the delta vector to see if it's negative. If it
// is, then mirror the ray along that axis so that the delta vector is
// positive and also mirror the quadtree along that axis using
// `invert_mask`, which will flip bits of child indices.
let mut invert_mask = 0;
for &ax in Dim3D::axes() {
if delta[ax].is_negative() {
invert_mask |= ax.bit();
start[ax] = -start[ax] + node_len;
delta[ax] = -delta[ax];
}
}

// At what `t` does the ray enter the root node (considering only one axis
// at a time)?
let t0: FVec3D = -start / delta;
// At what `t` does the ray exit the root node (considering only one axis at
// a time)?
let t1: FVec3D = (-start + node_len) / delta;

// At what `t` has the ray entered the root node along all axes?
let max_t0: R64 = *t0.max_component();
// At what `t` has the ray entered the root node along all axes?
let min_t1: R64 = *t1.min_component();
// If we enter AFTER we exit, or exit at a negative `t` ...
if max_t0 >= min_t1 || r64(0.0) > min_t1 {
// ... then the ray does not intersect with the root node.
return None;
} else {
// Otherwise, the ray does intersect with the root node.
return raycast_node_child(start, delta, t0, t1, min_layer, node, invert_mask);
}
}

fn raycast_node_child(
start: FVec3D,
delta: FVec3D,
t0: FVec3D,
t1: FVec3D,
min_layer: Layer,
node: NodeRef<'_, Dim3D>,
invert_mask: usize,
) -> Option<Hit> {
if *t1.min_component() < r64(0.0) {
// This node is completely behind the ray; skip it.
return None;
} else if *t0.max_component() < r64(0.0) && node.layer() <= min_layer {
// This is a leaf node and the ray starts inside it; skip it.
return None;
}

if node.is_empty() {
// This is an empty node; skip it.
return None;
}

// At what `t` does the ray cross the middle of the current node
// (considering only one axis at a time)?
let tm: FVec3D = (t0 + t1) / 2.0;

if node.layer() <= min_layer {
// This is a nonzero leaf node, so return a hit.
let pos_int = (start + delta * tm).floor().to_ivec();
let pos_float = start + delta * t0;
let face_axis = entry_axis(t0);
let face_sign = if invert_mask & face_axis.bit() == 0 {
Sign::Minus // ray is positive; hit negative face of cell
} else {
Sign::Plus // ray is negative; hit positive face of cell
};

return Some(Hit {
start,
delta,

t0: *t0.max_component(),
t1: *t1.min_component(),

pos_int,
pos_float,

face_axis,
face_sign,
});
}

let children = node.subdivide().unwrap();
let mut child_index = entry_child(t0, tm);
loop {
// Compute the parameter `t` values for th `child_index`.
let mut child_t0 = t0;
let mut child_t1 = tm;
for &ax in Dim3D::axes() {
if child_index & ax.bit() != 0 {
child_t0[ax] = tm[ax];
child_t1[ax] = t1[ax];
}
}

// Recurse!
match raycast_node_child(
start,
delta,
child_t0,
child_t1,
min_layer,
children[child_index ^ invert_mask],
invert_mask,
) {
Some(hit) => return Some(hit),
None => (),
}

// Compute the sibling of `child_index` to visit after this one.
let exit_axis = exit_axis(child_t1);
if child_index & exit_axis.bit() != 0 {
// The ray has exited the current node.
return None;
} else {
// Advance along `exit_axis` to get the next child to visit.
child_index |= exit_axis.bit();
}
}
}

// Given the parameters `t0` at which the ray enters a node along each axis and
// `tm` at which the ray crosses the middle of a node along each axis, returns
// the child index of the first child of that node intersected by the ray.
fn entry_child(t0: FVec3D, tm: FVec3D) -> usize {
let max_t0 = t0.max_component(); // when the ray actually enters the node
let mut child_index = 0;
for &ax in Dim3D::axes() {
if *max_t0 > tm[ax] {
child_index |= ax.bit();
}
}
return child_index;
}

// Given the parameters `t0` at which the ray enters a node along each axis,
// returns the axis along which the ray enters the node.
fn entry_axis(t0: FVec3D) -> Axis {
t0.max_axis()
}

// Given the parameters `t1` at which the ray exits a node along each axis,
// returns the axis along which the ray exits the node.
fn exit_axis(t1: FVec3D) -> Axis {
t1.min_axis()
}
21 changes: 12 additions & 9 deletions ui/src/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use imgui::*;
use std::time::Duration;

use ndcell_core::prelude::*;
use Axis::{X, Y, Z};

#[cfg(debug_assertions)]
mod debug;
Expand Down Expand Up @@ -105,7 +104,7 @@ impl MainWindow {
}
if let Some(pixel) = mouse.pos {
let cell = view2d.screen_pos(pixel).cell();
ui.text(format!("Cursor: X = {}, Y = {}", cell[X], cell[Y]));
ui.text(format!("Cursor: {}", cell));
} else {
ui.text("");
}
Expand All @@ -124,13 +123,17 @@ impl MainWindow {
}
ui.text(format!("Pitch = {:.2?}°", vp.pitch().0));
ui.text(format!("Yaw = {:.2?}°", vp.yaw().0));
if let Some(hover_pos) = view3d.hovered_cell_pos(mouse.pos) {
ui.text(format!(
"Cursor: X = {}, Y = {}, Z = {}",
hover_pos[X].floor(),
hover_pos[Y].floor(),
hover_pos[Z].floor(),
));
if let Some(hit) = mouse
.pos
.and_then(|pixel| view3d.screen_pos(pixel).raycast())
{
let (axis, sign) = hit.face;
let sign = match sign {
Sign::Minus => "-",
Sign::NoSign => "",
Sign::Plus => "+",
};
ui.text(format!("Cursor: {} {}{:?}", hit.cell, sign, axis));
} else {
ui.text("");
}
Expand Down

0 comments on commit 5855229

Please sign in to comment.