• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ekarpp / lumo / 6042042780

31 Aug 2023 08:17PM UTC coverage: 48.466% (-1.0%) from 49.493%
6042042780

push

github

web-flow
0.3.1 (#30)

* Create own struct for colors
* Type alias floats in `lib.rs`
* Implement pixel filters
* Fixes mediums in BDPT
* Microfacet bug fixes

Closes #29 
Closes #31

908 of 908 new or added lines in 58 files covered. (100.0%)

2180 of 4498 relevant lines covered (48.47%)

7018890.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.94
/src/tracer/object/instance.rs
1
use super::*;
2

3
#[cfg(test)]
4
mod instance_tests;
5

6
/// Instance of an object i.e. an object to which affine transformations have
7
/// been applied
8
pub struct Instance<T> {
9
    /// Object to be instanced
10
    object: T,
11
    /// Transformation from local to world
12
    transform: Transform,
13
    /// Transformation from world to local
14
    inv_transform: Transform,
15
    /// Transformation for normals from local to world.
16
    /// Transpose of `inv_transform` without translation.
17
    normal_transform: Mat3,
18
}
19

20
impl<T> Instance<T> {
21
    /// Constructs an instance of `object` that is transformed with
22
    /// `transform`.
23
    pub fn new(object: T, transform: Transform) -> Box<Self> {
14✔
24
        let inv_transform = transform.inverse();
14✔
25
        let normal_transform = inv_transform.matrix3.transpose().into();
14✔
26

14✔
27
        Box::new(Self {
14✔
28
            object,
14✔
29
            transform,
14✔
30
            inv_transform,
14✔
31
            normal_transform,
14✔
32
        })
14✔
33
    }
14✔
34
}
35

36
impl<T: Bounded> Instance<T> {
37
    /// Translate `self`, such that bounding box center is at the origin
38
    pub fn to_origin(self) -> Box<Self> {
2✔
39
        let AaBoundingBox { ax_min, ax_max } = self.bounding_box();
2✔
40
        let ax_mid = -(ax_min + ax_max) / 2.0;
2✔
41

2✔
42
        self.translate(ax_mid.x, ax_mid.y, ax_mid.z)
2✔
43
    }
2✔
44
}
45

46
impl<T: Bounded> Bounded for Instance<T> {
47
    fn bounding_box(&self) -> AaBoundingBox {
2✔
48
        /* Graphics Gems I, TRANSFORMING AXIS-ALIGNED BOUNDING BOXES */
2✔
49
        let mut ax_min = Point::ZERO;
2✔
50
        let mut ax_max = Point::ZERO;
2✔
51
        let aabb = self.object.bounding_box();
2✔
52

2✔
53
        let a0 = self.transform.matrix3.row(0) * aabb.min(Axis::X);
2✔
54
        let b0 = self.transform.matrix3.row(0) * aabb.max(Axis::X);
2✔
55
        let a0b0 = a0.min(b0);
2✔
56
        ax_min.x += a0b0.x + a0b0.y + a0b0.z;
2✔
57
        let a0b0 = a0.max(b0);
2✔
58
        ax_max.x += a0b0.x + a0b0.y + a0b0.z;
2✔
59

2✔
60
        let a1 = self.transform.matrix3.row(1) * aabb.min(Axis::Y);
2✔
61
        let b1 = self.transform.matrix3.row(1) * aabb.max(Axis::Y);
2✔
62
        let a1b1 = a1.min(b1);
2✔
63
        ax_min.y += a1b1.x + a1b1.y + a1b1.z;
2✔
64
        let a1b1 = a1.max(b1);
2✔
65
        ax_max.y += a1b1.x + a1b1.y + a1b1.z;
2✔
66

2✔
67
        let a2 = self.transform.matrix3.row(2) * aabb.min(Axis::Z);
2✔
68
        let b2 = self.transform.matrix3.row(2) * aabb.max(Axis::Z);
2✔
69
        let a2b2 = a2.min(b2);
2✔
70
        ax_min.z += a2b2.x + a2b2.y + a2b2.z;
2✔
71
        let a2b2 = a2.max(b2);
2✔
72
        ax_max.z += a2b2.x + a2b2.y + a2b2.z;
2✔
73

2✔
74
        // translate
2✔
75
        ax_min.x += self.transform.translation.x;
2✔
76
        ax_min.y += self.transform.translation.y;
2✔
77
        ax_min.z += self.transform.translation.z;
2✔
78

2✔
79
        ax_max.x += self.transform.translation.x;
2✔
80
        ax_max.y += self.transform.translation.y;
2✔
81
        ax_max.z += self.transform.translation.z;
2✔
82

2✔
83
        AaBoundingBox::new(ax_min, ax_max)
2✔
84
    }
2✔
85
}
86

87
impl<T: Object> Object for Instance<T> {
88
    fn hit(&self, r: &Ray, t_min: Float, t_max: Float) -> Option<Hit> {
20,000✔
89
        // inner object is in world coordinates. hence apply inverse
20,000✔
90
        // transformation to ray instead of transformation to object.
20,000✔
91
        let ray_local = r.transform(self.inv_transform);
20,000✔
92

20,000✔
93
        self.object.hit(&ray_local, t_min, t_max).map(|mut h| {
20,000✔
94
            h.ns = (self.normal_transform * h.ns).normalize();
20,000✔
95
            h.ng = (self.normal_transform * h.ng).normalize();
20,000✔
96

20,000✔
97
            let err = efloat::gamma(3) * Vec3::new(
20,000✔
98
                (Vec3::from(self.transform.matrix3.row(0)) * h.p)
20,000✔
99
                    .abs().dot(Vec3::ONE) + self.transform.translation.x.abs(),
20,000✔
100
                (Vec3::from(self.transform.matrix3.row(1)) * h.p)
20,000✔
101
                    .abs().dot(Vec3::ONE) + self.transform.translation.y.abs(),
20,000✔
102
                (Vec3::from(self.transform.matrix3.row(2)) * h.p)
20,000✔
103
                    .abs().dot(Vec3::ONE) + self.transform.translation.z.abs(),
20,000✔
104
            );
20,000✔
105

20,000✔
106
            h.p = self.transform.transform_point3(h.p);
20,000✔
107
            // TODO: just add them for now...
20,000✔
108
            h.fp_error += err;
20,000✔
109
            h
20,000✔
110
        })
20,000✔
111
    }
20,000✔
112
}
113

114
impl<T: Sampleable> Sampleable for Instance<T> {
115
    fn area(&self) -> Float {
×
116
        self.object.area();
×
117
        todo!()
×
118
    }
119

120
    fn sample_on(&self, rand_sq: Vec2) -> Hit {
×
121
        let mut ho = self.object.sample_on(rand_sq);
×
122

×
123
        ho.ng = self.normal_transform * ho.ng;
×
124
        ho.ns = self.normal_transform * ho.ns;
×
125
        ho.p = self.transform.transform_point3(ho.p);
×
126

×
127
        ho
×
128
    }
×
129

130
    fn sample_towards(&self, xo: Point, rand_sq: Vec2) -> Direction {
20,000✔
131
        let xo_local = self.inv_transform.transform_point3(xo);
20,000✔
132
        let dir_local = self.object.sample_towards(xo_local, rand_sq);
20,000✔
133

20,000✔
134
        self.transform.transform_vector3(dir_local)
20,000✔
135
    }
20,000✔
136

137
    fn sample_towards_pdf(&self, ri: &Ray) -> (Float, Option<Hit>) {
20,000✔
138
        let ri_local = ri.transform(self.inv_transform);
20,000✔
139
        let (pdf_local, hi_local) = self.object.sample_towards_pdf(&ri_local);
20,000✔
140
        if let Some(mut hi) = hi_local {
20,000✔
141
            let ng_local = hi.ng;
20,000✔
142

20,000✔
143
            let xi = ri.at(hi.t);
20,000✔
144
            let ng = (self.normal_transform * ng_local).normalize();
20,000✔
145

20,000✔
146
            // object pdf just needs these in world coordinates
20,000✔
147
            hi.p = xi;
20,000✔
148
            hi.ng = ng;
20,000✔
149

20,000✔
150
            // imagine a unit cube at the sampled point with the surface normal
20,000✔
151
            // at that point as one of the cube edges. apply the linear
20,000✔
152
            // transformation to the cube and we get a parallellepiped.
20,000✔
153
            // the base of the parallellepiped gives us the area scale at the
20,000✔
154
            // point of impact. think this is not exact with ansiotropic
20,000✔
155
            // scaling of solids. how to do for solid angle?
20,000✔
156
            let height = ng.dot(self.transform.matrix3 * ng_local).abs();
20,000✔
157
            let volume = self.transform.matrix3.determinant().abs();
20,000✔
158
            let jacobian = volume / height;
20,000✔
159

20,000✔
160
            // p_y(y) = p_y(T(x)) = p_x(x) / |J_T(x)|
20,000✔
161
            (pdf_local / jacobian, Some(hi))
20,000✔
162
        } else {
163
            (0.0, None)
×
164
        }
165
    }
20,000✔
166
}
167

168
/// Object that can be instanced
169
pub trait Instanceable<T> {
170
    /// Translate object by `xyz`
171
    fn translate(self, x: Float, y: Float, z: Float) -> Box<Instance<T>>;
172

173
    /// Apply scale `xyz`
174
    fn scale(self, x: Float, y: Float, z: Float) -> Box<Instance<T>>;
175

176
    /// Rotate around x-axis by `r` radians
177
    fn rotate_x(self, r: Float) -> Box<Instance<T>>;
178

179
    /// Rotate around y-axis by `r` radians
180
    fn rotate_y(self, r: Float) -> Box<Instance<T>>;
181

182
    /// Rotate around z-axis by `r` radians
183
    fn rotate_z(self, r: Float) -> Box<Instance<T>>;
184

185
    /// Rotate around `axis` by `r` radisn
186
    fn rotate_axis(self, axis: Direction, r: Float) -> Box<Instance<T>>;
187
}
188

189
/// To make applying transformations to objects easy
190
impl<T: Object> Instanceable<T> for T {
191
    fn translate(self, x: Float, y: Float, z: Float) -> Box<Instance<T>> {
1✔
192
        let t = Vec3::new(x, y, z);
1✔
193
        Instance::new(self, Transform::from_translation(t))
1✔
194
    }
1✔
195

196
    fn scale(self, x: Float, y: Float, z: Float) -> Box<Instance<T>> {
2✔
197
        assert!(x * y * z != 0.0);
2✔
198
        let s = Vec3::new(x, y, z);
2✔
199
        Instance::new(self, Transform::from_scale(s))
2✔
200
    }
2✔
201

202
    fn rotate_x(self, r: Float) -> Box<Instance<T>> {
1✔
203
        Instance::new(self, Transform::from_rotation_x(r))
1✔
204
    }
1✔
205

206
    fn rotate_y(self, r: Float) -> Box<Instance<T>> {
×
207
        Instance::new(self, Transform::from_rotation_y(r))
×
208
    }
×
209

210
    fn rotate_z(self, r: Float) -> Box<Instance<T>> {
×
211
        Instance::new(self, Transform::from_rotation_z(r))
×
212
    }
×
213

214
    fn rotate_axis(self, axis: Direction, r: Float) -> Box<Instance<T>> {
×
215
        Instance::new(self, Transform::from_axis_angle(axis, r))
×
216
    }
×
217
}
218

219
/// Prevent nested Instance structs
220
impl<T: Object> Instance<T> {
221
    /// Apply translation AFTER curret transformations
222
    pub fn translate(self, x: Float, y: Float, z: Float) -> Box<Self> {
3✔
223
        let t = Vec3::new(x, y, z);
3✔
224
        Self::new(self.object, Transform::from_translation(t) * self.transform)
3✔
225
    }
3✔
226

227
    /// Apply scale AFTER current transformations
228
    pub fn scale(self, x: Float, y: Float, z: Float) -> Box<Self> {
4✔
229
        assert!(x * y * z != 0.0);
4✔
230
        let s = Vec3::new(x, y, z);
4✔
231
        Self::new(self.object, Transform::from_scale(s) * self.transform)
4✔
232
    }
4✔
233

234
    /// Apply x-rotation AFTER current transformations.
235
    /// Looking at positive x, rotation in clockwise direction.
236
    pub fn rotate_x(self, r: Float) -> Box<Self> {
1✔
237
        Self::new(self.object, Transform::from_rotation_x(r) * self.transform)
1✔
238
    }
1✔
239

240
    /// Apply y-rotation AFTER current transformations
241
    pub fn rotate_y(self, r: Float) -> Box<Self> {
1✔
242
        Self::new(self.object, Transform::from_rotation_y(r) * self.transform)
1✔
243
    }
1✔
244

245
    /// Apply z-rotation AFTER current transformations
246
    pub fn rotate_z(self, r: Float) -> Box<Self> {
1✔
247
        Self::new(self.object, Transform::from_rotation_z(r) * self.transform)
1✔
248
    }
1✔
249

250
    /// Apply axis rotation AFTER current transformations
251
    pub fn rotate_axis(self, axis: Direction, r: Float) -> Box<Instance<T>> {
×
252
        Self::new(self.object, Transform::from_axis_angle(axis, r) * self.transform)
×
253
    }
×
254
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc