1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
use crate::{ Point, Float, Direction, Normal, efloat, Vec2, Vec3 };
use crate::tracer::{ material::Material, object::Sampleable, ray::Ray };
/// Stores information about a hit between a ray and an object
pub struct Hit<'a> {
/// The `t` value of ray at which the hit occurred
pub t: Float,
/// Material of the object which got hit
pub material: &'a Material,
/// 3D point where object was hit
pub p: Point,
/// Optional reference to light if we hit one
pub light: Option<&'a dyn Sampleable>,
/// Floating point error bounds of the impact point
pub fp_error: Vec3,
/// Normal of the surface used for shading calculations
pub ns: Normal,
/// Geometric normal of the surface
pub ng: Normal,
/// Texture coordinates in `\[0,1\]^2`
pub uv: Vec2,
/// Are we on the backface?
pub backface: bool,
}
impl<'a> Hit<'a> {
/// # Arguments
///
/// * `t` - Value of ray at which hit occurred
/// * `material` - Material of the object which got hit
/// * `wo` - Direction towards to point of intersection
/// * `xi` - Point in world space at which object got hit
/// * `fp_error` - Floating point error bounds for `xi`
/// * `ns` - Shading normal of the object at the point of impact
/// * `ng` - Geometric normal of the object at the point of impact
/// * `uv` - Texture coordinates in `\[0,1\]^2`
#[allow(clippy::too_many_arguments)]
pub fn new(
t: Float,
material: &'a Material,
wo: Direction,
xi: Point,
fp_error: Vec3,
ns: Normal,
ng: Normal,
uv: Vec2,
) -> Option<Self> {
let backface = wo.dot(ng) > 0.0;
Some(Self {
t,
material,
backface,
p: xi,
light: None,
fp_error,
ns,
ng,
uv,
})
}
/// Generates a ray at point of impact. Would be better to use accurate
/// error bounds instead of `EPSILON`.
pub fn generate_ray(&self, wi: Direction) -> Ray {
let scaled_err = self.fp_error.dot(self.ns.abs());
let offset = if wi.dot(self.ng) >= 0.0 {
self.ns * scaled_err
} else {
-self.ns * scaled_err
};
let xi = self.p + offset;
let move_double = |v: Float, n: Float| {
if n > 0.0 {
efloat::next_double(v)
} else if n < 0.0 {
efloat::previous_double(v)
} else {
v
}
};
let xi = Point::new(
move_double(xi.x, offset.x),
move_double(xi.y, offset.y),
move_double(xi.z, offset.z),
);
Ray::new(
xi,
wi
)
}
/// Did we hit a medium?
pub fn is_medium(&self) -> bool {
matches!(self.material, Material::Volumetric(..))
}
/// Did we hit a light?
pub fn is_light(&self) -> bool {
matches!(self.material, Material::Light(..))
}
}