use cgmath::{EuclideanSpace as _, Point3, Vector3};
use crate::block::{recursive_ray, Evoxel};
use crate::camera::LightingOption;
use crate::math::{Face7, FaceMap, FreeCoordinate, GridArray, GridPoint, Rgb, Rgba};
use crate::raycast::{Ray, Raycaster};
use crate::raytracer::{RtBlockData, SpaceRaytracer, TracingBlock, TracingCubeData};
#[derive(Copy, Clone, Debug, PartialEq)]
pub(crate) struct Surface<'a, D> {
pub block_data: &'a D,
pub diffuse_color: Rgba,
cube: GridPoint,
pub t_distance: FreeCoordinate,
intersection_point: Point3<FreeCoordinate>,
pub normal: Face7,
}
impl<D: RtBlockData> Surface<'_, D> {
#[inline]
pub(crate) fn to_lit_color(&self, rt: &SpaceRaytracer<D>) -> Option<Rgba> {
let diffuse_color = rt
.graphics_options
.transparency
.limit_alpha(self.diffuse_color);
if diffuse_color.fully_transparent() {
return None;
}
let adjusted_rgb = diffuse_color.to_rgb() * self.compute_illumination(rt);
Some(adjusted_rgb.with_alpha(diffuse_color.alpha()))
}
fn compute_illumination(&self, rt: &SpaceRaytracer<D>) -> Rgb {
match rt.graphics_options.lighting_display {
LightingOption::None => Rgb::ONE,
LightingOption::Flat => {
rt.get_lighting(self.cube + self.normal.normal_vector())
* fixed_directional_lighting(self.normal)
}
LightingOption::Smooth => {
rt.get_interpolated_light(self.intersection_point, self.normal)
* fixed_directional_lighting(self.normal)
}
}
}
}
fn fixed_directional_lighting(face: Face7) -> f32 {
const LIGHT_1_DIRECTION: Vector3<f32> = Vector3::new(0.4, -0.1, 0.0);
const LIGHT_2_DIRECTION: Vector3<f32> = Vector3::new(-0.4, 0.35, 0.25);
(1.0 - 1.0 / 16.0)
+ 0.25 * (face.dot(LIGHT_1_DIRECTION).max(0.0) + face.dot(LIGHT_2_DIRECTION).max(0.0))
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub(crate) struct Span<'a, D> {
pub surface: Surface<'a, D>,
pub exit_t_distance: FreeCoordinate,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub(crate) enum TraceStep<'a, D> {
EnterSurface(Surface<'a, D>),
Invisible { t_distance: FreeCoordinate },
EnterBlock { t_distance: FreeCoordinate },
}
#[derive(Clone, Debug)]
pub(crate) struct SurfaceIter<'a, D> {
ray: Ray,
block_raycaster: Raycaster,
state: SurfaceIterState,
current_block: Option<VoxelSurfaceIter<'a, D>>,
blocks: &'a [TracingBlock<D>],
array: &'a GridArray<TracingCubeData>,
}
#[derive(Clone, Copy, Debug)]
enum SurfaceIterState {
Initial,
EnteredSpace,
}
impl<'a, D: RtBlockData> SurfaceIter<'a, D> {
#[inline]
pub(crate) fn new(rt: &'a SpaceRaytracer<D>, ray: Ray) -> Self {
Self {
ray,
block_raycaster: ray
.cast()
.within(rt.cubes.bounds()),
state: SurfaceIterState::Initial,
current_block: None,
blocks: &rt.blocks,
array: &rt.cubes,
}
}
}
impl<'a, D> Iterator for SurfaceIter<'a, D> {
type Item = TraceStep<'a, D>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(b) = &mut self.current_block {
if let Some(surface) = b.next() {
return Some(surface);
}
}
self.current_block = None;
let rc_step = self.block_raycaster.next()?;
let cube_data: &TracingCubeData = match self.array.get(rc_step.cube_ahead()) {
Some(cube_data) => {
match &self.state {
SurfaceIterState::Initial => {
self.block_raycaster.remove_bound();
self.block_raycaster
.set_bounds(self.array.bounds().expand(FaceMap::repeat(1)));
self.state = SurfaceIterState::EnteredSpace;
}
SurfaceIterState::EnteredSpace => {}
}
cube_data
}
None => {
return Some(TraceStep::Invisible {
t_distance: rc_step.t_distance(),
});
}
};
if cube_data.always_invisible {
return Some(TraceStep::Invisible {
t_distance: rc_step.t_distance(),
});
}
Some(match &self.blocks[cube_data.block_index as usize] {
TracingBlock::Atom(block_data, color) => {
if color.fully_transparent() {
TraceStep::Invisible {
t_distance: rc_step.t_distance(),
}
} else {
TraceStep::EnterSurface(Surface {
block_data,
diffuse_color: *color,
cube: rc_step.cube_ahead(),
t_distance: rc_step.t_distance(),
intersection_point: rc_step.intersection_point(self.ray),
normal: rc_step.face(),
})
}
}
TracingBlock::Recur(block_data, resolution, array) => {
let block_cube = rc_step.cube_ahead();
let resolution = *resolution;
let sub_ray = recursive_ray(self.ray, block_cube, resolution);
let antiscale = FreeCoordinate::from(resolution).recip();
self.current_block = Some(VoxelSurfaceIter {
voxel_ray: sub_ray,
voxel_raycaster: sub_ray.cast().within(array.bounds()),
block_data,
antiscale,
array,
block_cube,
});
TraceStep::EnterBlock {
t_distance: rc_step.t_distance(),
}
}
})
}
}
#[derive(Clone, Debug)]
struct VoxelSurfaceIter<'a, D> {
voxel_ray: Ray,
voxel_raycaster: Raycaster,
block_data: &'a D,
antiscale: FreeCoordinate,
array: &'a GridArray<Evoxel>,
block_cube: GridPoint,
}
impl<'a, D> VoxelSurfaceIter<'a, D> {
fn next(&mut self) -> Option<TraceStep<'a, D>> {
let rc_step = self.voxel_raycaster.next()?;
let voxel = self.array.get(rc_step.cube_ahead())?;
if voxel.color.fully_transparent() {
return Some(TraceStep::Invisible {
t_distance: rc_step.t_distance() * self.antiscale,
});
}
Some(TraceStep::EnterSurface(Surface {
block_data: self.block_data,
diffuse_color: voxel.color,
cube: self.block_cube,
t_distance: rc_step.t_distance() * self.antiscale,
intersection_point: rc_step.intersection_point(self.voxel_ray) * self.antiscale
+ self.block_cube.map(FreeCoordinate::from).to_vec(),
normal: rc_step.face(),
}))
}
}
pub(crate) struct DepthIter<'a, D> {
surface_iter: SurfaceIter<'a, D>,
last_surface: Option<Surface<'a, D>>,
}
impl<'a, D> DepthIter<'a, D> {
#[inline]
pub(crate) fn new(surface_iter: SurfaceIter<'a, D>) -> Self {
Self {
surface_iter,
last_surface: None,
}
}
}
impl<'a, D> Iterator for DepthIter<'a, D> {
type Item = DepthStep<'a, D>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
Some(match self.surface_iter.next()? {
TraceStep::EnterSurface(this_surface) => {
let exit_t_distance = this_surface.t_distance;
match std::mem::replace(&mut self.last_surface, Some(this_surface)) {
Some(last_surface) => DepthStep::Span(Span {
surface: last_surface,
exit_t_distance,
}),
None => DepthStep::Invisible,
}
}
TraceStep::Invisible { t_distance } | TraceStep::EnterBlock { t_distance } => {
match self.last_surface.take() {
Some(last_surface) => DepthStep::Span(Span {
surface: last_surface,
exit_t_distance: t_distance,
}),
None => DepthStep::Invisible,
}
}
})
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub(crate) enum DepthStep<'a, D> {
Invisible,
Span(Span<'a, D>),
}
#[cfg(test)]
mod tests {
use super::*;
use crate::block::{Block, Resolution::*};
use crate::camera::GraphicsOptions;
use crate::content::{make_slab, palette};
use crate::math::GridAab;
use crate::space::Space;
use crate::universe::Universe;
use pretty_assertions::assert_eq;
use TraceStep::{EnterBlock, EnterSurface, Invisible};
#[test]
fn surface_iter_smoke_test() {
let universe = &mut Universe::new();
let mut space = Space::empty(GridAab::from_lower_size([0, 0, 0], [1, 3, 1]));
let solid_test_color = rgba_const!(1., 0., 0., 1.);
space.set([0, 1, 0], Block::from(solid_test_color)).unwrap();
space.set([0, 2, 0], make_slab(universe, 2, R4)).unwrap();
let rt = SpaceRaytracer::<()>::new(&space, GraphicsOptions::default(), ());
assert_eq!(
SurfaceIter::new(&rt, Ray::new([0.5, -0.5, 0.5], [0., 1., 0.]))
.collect::<Vec<TraceStep<'_, ()>>>(),
vec![
Invisible { t_distance: 0.5 }, EnterSurface(Surface {
block_data: &(),
diffuse_color: solid_test_color,
cube: GridPoint::new(0, 1, 0),
t_distance: 1.5, intersection_point: Point3::new(0.5, 1.0, 0.5),
normal: Face7::NY
}),
EnterBlock { t_distance: 2.5 },
EnterSurface(Surface {
block_data: &(),
diffuse_color: palette::PLANK.with_alpha_one(),
cube: GridPoint::new(0, 2, 0),
t_distance: 2.5,
intersection_point: Point3::new(0.5, 2.0, 0.5),
normal: Face7::NY
}),
EnterSurface(Surface {
block_data: &(),
diffuse_color: (palette::PLANK * 1.06).with_alpha_one(),
cube: GridPoint::new(0, 2, 0),
t_distance: 2.75, intersection_point: Point3::new(0.5, 2.25, 0.5),
normal: Face7::NY
}),
Invisible { t_distance: 3.0 },
Invisible { t_distance: 3.25 },
Invisible { t_distance: 3.5 },
]
);
}
#[test]
fn surface_iter_exit_block_at_end_of_space() {
let solid_test_color = rgba_const!(1., 0., 0., 1.);
let space = Space::builder(GridAab::from_lower_size([0, 0, 0], [1, 1, 1]))
.filled_with(Block::from(solid_test_color))
.build();
let rt = SpaceRaytracer::<()>::new(&space, GraphicsOptions::default(), ());
assert_eq!(
SurfaceIter::new(&rt, Ray::new([-0.5, 0.5, 0.5], [1., 0., 0.]))
.collect::<Vec<TraceStep<'_, ()>>>(),
vec![
EnterSurface(Surface {
block_data: &(),
diffuse_color: solid_test_color,
cube: GridPoint::new(0, 0, 0),
t_distance: 0.5, intersection_point: Point3::new(0.0, 0.5, 0.5),
normal: Face7::NX
}),
Invisible { t_distance: 1.5 },
]
);
}
}