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

ekarpp / lumo / 5843175784

pending completion
5843175784

push

github

web-flow
0.3.0 (#26)

* add BDPT draft

* refactor hit to contain material instead of object. how to assert that correct materials in scene add methods?

* make BDPT "work"

* add comment to BDPT

* add thin lens approximation for depth of field to cameras

* refactor BDPT

* connect all paths in BDPT

* add light source sampling to BDPT

* add handling of full camera paths to BDPT

* fix rays from camera straight to light in BDPT

* add MIS skeleton to BDPT

* use shading normals in geometry term of BDPT

* fix vertex handling in walk of BDPT

* fix borrow checker issues in BDPT

* fixes light sampling in BDPT

* tune cornell box

* change box image dimensions

* comment current status of BDPT

* add untested .mtl parser

* add parsing multiple meshes from one .obj file

* add test for planar kdtrees. does not pass

* first draft of watertight triangle intersection

* fixes triangle intersection

* add permutations to avoid division by zero in triangle intersection

* compute error bounds for triangles and spheres

* comment triangle intersection

* flatten meshes in kdtree tests and teapot

* make cones aligned with y axis

* fix cone texture mapping

* add cone tests

* make cylinder base at y=0

* make cone texture mapping like in cylinder

* add cylinder tests

* add assertions to cone constructor

* add reprojection to cylinder hit

* fix error bounds in AABB

* add quadratic solving to efloat

* add error bounds to cylinder

* add epsilon to t check in triangle hit

* fix offset update in hit generate ray

* add 'proper' error bounds to hits

* make diffuse work properly on backfaces

* tuning examples

* fixes to tests and cylinder in nefe

* fix self intersection test in triangle

* add triangle behind hit test. cleanup cylinder tests

* use t_min instead of t_start in recursive kdtree subtree hits

* add floating point error bound checks to triangle hit

*... (continued)

2049 of 2049 new or added lines in 45 files covered. (100.0%)

2147 of 4336 relevant lines covered (49.52%)

7281164.49 hits per line

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

83.87
/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: DAffine3,
13
    /// Transformation from world to local
14
    inv_transform: DAffine3,
15
    /// Transformation for normals from local to world.
16
    /// Transpose of `inv_transform` without translation.
17
    normal_transform: DMat3,
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: DAffine3) -> Box<Self> {
14✔
24
        let inv_transform = transform.inverse();
14✔
25
        let normal_transform = inv_transform.matrix3.transpose();
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 = DVec3::ZERO;
2✔
50
        let mut ax_max = DVec3::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
        ax_min.x += a0.min(b0).dot(DVec3::ONE);
2✔
56
        ax_max.x += a0.max(b0).dot(DVec3::ONE);
2✔
57

2✔
58
        let a1 = self.transform.matrix3.row(1) * aabb.min(Axis::Y);
2✔
59
        let b1 = self.transform.matrix3.row(1) * aabb.max(Axis::Y);
2✔
60
        ax_min.y += a1.min(b1).dot(DVec3::ONE);
2✔
61
        ax_max.y += a1.max(b1).dot(DVec3::ONE);
2✔
62

2✔
63
        let a2 = self.transform.matrix3.row(2) * aabb.min(Axis::Z);
2✔
64
        let b2 = self.transform.matrix3.row(2) * aabb.max(Axis::Z);
2✔
65
        ax_min.z += a2.min(b2).dot(DVec3::ONE);
2✔
66
        ax_max.z += a2.max(b2).dot(DVec3::ONE);
2✔
67

2✔
68
        // translate
2✔
69
        ax_min += self.transform.translation;
2✔
70
        ax_max += self.transform.translation;
2✔
71

2✔
72
        AaBoundingBox::new(ax_min, ax_max)
2✔
73
    }
2✔
74
}
75

76
impl<T: Object> Object for Instance<T> {
77
    fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> Option<Hit> {
20,000✔
78
        // inner object is in world coordinates. hence apply inverse
20,000✔
79
        // transformation to ray instead of transformation to object.
20,000✔
80
        let ray_local = r.transform(self.inv_transform);
20,000✔
81

20,000✔
82
        self.object.hit(&ray_local, t_min, t_max).map(|mut h| {
20,000✔
83
            h.ns = (self.normal_transform * h.ns).normalize();
20,000✔
84
            h.ng = (self.normal_transform * h.ng).normalize();
20,000✔
85

20,000✔
86
            let err = efloat::gamma(3) * DVec3::new(
20,000✔
87
                (self.transform.matrix3.row(0) * h.p)
20,000✔
88
                    .abs().dot(DVec3::ONE) + self.transform.translation.x.abs(),
20,000✔
89
                (self.transform.matrix3.row(1) * h.p)
20,000✔
90
                    .abs().dot(DVec3::ONE) + self.transform.translation.y.abs(),
20,000✔
91
                (self.transform.matrix3.row(2) * h.p)
20,000✔
92
                    .abs().dot(DVec3::ONE) + self.transform.translation.z.abs(),
20,000✔
93
            );
20,000✔
94

20,000✔
95
            h.p = self.transform.transform_point3(h.p);
20,000✔
96
            // TODO: just add them for now...
20,000✔
97
            h.fp_error += err;
20,000✔
98
            h
20,000✔
99
        })
20,000✔
100
    }
20,000✔
101
}
102

103
impl<T: Sampleable> Sampleable for Instance<T> {
104
    fn area(&self) -> f64 {
×
105
        self.object.area();
×
106
        todo!()
×
107
    }
108

109
    fn sample_on(&self, rand_sq: DVec2) -> Hit {
×
110
        let mut ho = self.object.sample_on(rand_sq);
×
111

×
112
        ho.ng = self.normal_transform * ho.ng;
×
113
        ho.ns = self.normal_transform * ho.ns;
×
114
        ho.p = self.transform.transform_point3(ho.p);
×
115

×
116
        ho
×
117
    }
×
118

119
    fn sample_towards(&self, xo: DVec3, rand_sq: DVec2) -> DVec3 {
20,000✔
120
        let xo_local = self.inv_transform.transform_point3(xo);
20,000✔
121
        let dir_local = self.object.sample_towards(xo_local, rand_sq);
20,000✔
122

20,000✔
123
        self.transform.transform_vector3(dir_local)
20,000✔
124
    }
20,000✔
125

126
    fn sample_towards_pdf(&self, ri: &Ray) -> (f64, Option<Hit>) {
20,000✔
127
        let ri_local = ri.transform(self.inv_transform);
20,000✔
128
        let (pdf_local, hi_local) = self.object.sample_towards_pdf(&ri_local);
20,000✔
129
        if let Some(mut hi) = hi_local {
20,000✔
130
            let ng_local = hi.ng;
20,000✔
131

20,000✔
132
            let xi = ri.at(hi.t);
20,000✔
133
            let ng = (self.normal_transform * ng_local).normalize();
20,000✔
134

20,000✔
135
            // object pdf just needs these in world coordinates
20,000✔
136
            hi.p = xi;
20,000✔
137
            hi.ng = ng;
20,000✔
138

20,000✔
139
            // imagine a unit cube at the sampled point with the surface normal
20,000✔
140
            // at that point as one of the cube edges. apply the linear
20,000✔
141
            // transformation to the cube and we get a parallellepiped.
20,000✔
142
            // the base of the parallellepiped gives us the area scale at the
20,000✔
143
            // point of impact. think this is not exact with ansiotropic
20,000✔
144
            // scaling of solids. how to do for solid angle?
20,000✔
145
            let height = ng.dot(self.transform.matrix3 * ng_local).abs();
20,000✔
146
            let volume = self.transform.matrix3.determinant().abs();
20,000✔
147
            let jacobian = volume / height;
20,000✔
148

20,000✔
149
            // p_y(y) = p_y(T(x)) = p_x(x) / |J_T(x)|
20,000✔
150
            (pdf_local / jacobian, Some(hi))
20,000✔
151
        } else {
152
            (0.0, None)
×
153
        }
154
    }
20,000✔
155
}
156

157
/// Object that can be instanced
158
pub trait Instanceable<T> {
159
    /// Translate object by `xyz`
160
    fn translate(self, x: f64, y: f64, z: f64) -> Box<Instance<T>>;
161

162
    /// Apply scale `xyz`
163
    fn scale(self, x: f64, y: f64, z: f64) -> Box<Instance<T>>;
164

165
    /// Rotate around x-axis by `r` radians
166
    fn rotate_x(self, r: f64) -> Box<Instance<T>>;
167

168
    /// Rotate around y-axis by `r` radians
169
    fn rotate_y(self, r: f64) -> Box<Instance<T>>;
170

171
    /// Rotate around z-axis by `r` radians
172
    fn rotate_z(self, r: f64) -> Box<Instance<T>>;
173

174
    /// Rotate around `axis` by `r` radisn
175
    fn rotate_axis(self, axis: DVec3, r: f64) -> Box<Instance<T>>;
176
}
177

178
/// To make applying transformations to objects easy
179
impl<T: Object> Instanceable<T> for T {
180
    fn translate(self, x: f64, y: f64, z: f64) -> Box<Instance<T>> {
1✔
181
        let t = DVec3::new(x, y, z);
1✔
182
        Instance::new(self, DAffine3::from_translation(t))
1✔
183
    }
1✔
184

185
    fn scale(self, x: f64, y: f64, z: f64) -> Box<Instance<T>> {
2✔
186
        assert!(x * y * z != 0.0);
2✔
187
        let s = DVec3::new(x, y, z);
2✔
188
        Instance::new(self, DAffine3::from_scale(s))
2✔
189
    }
2✔
190

191
    fn rotate_x(self, r: f64) -> Box<Instance<T>> {
1✔
192
        Instance::new(self, DAffine3::from_rotation_x(r))
1✔
193
    }
1✔
194

195
    fn rotate_y(self, r: f64) -> Box<Instance<T>> {
×
196
        Instance::new(self, DAffine3::from_rotation_y(r))
×
197
    }
×
198

199
    fn rotate_z(self, r: f64) -> Box<Instance<T>> {
×
200
        Instance::new(self, DAffine3::from_rotation_z(r))
×
201
    }
×
202

203
    fn rotate_axis(self, axis: DVec3, r: f64) -> Box<Instance<T>> {
×
204
        Instance::new(self, DAffine3::from_axis_angle(axis, r))
×
205
    }
×
206
}
207

208
/// Prevent nested Instance structs
209
impl<T: Object> Instance<T> {
210
    /// Apply translation AFTER curret transformations
211
    pub fn translate(self, x: f64, y: f64, z: f64) -> Box<Self> {
3✔
212
        let t = DVec3::new(x, y, z);
3✔
213
        Self::new(self.object, DAffine3::from_translation(t) * self.transform)
3✔
214
    }
3✔
215

216
    /// Apply scale AFTER current transformations
217
    pub fn scale(self, x: f64, y: f64, z: f64) -> Box<Self> {
4✔
218
        assert!(x * y * z != 0.0);
4✔
219
        let s = DVec3::new(x, y, z);
4✔
220
        Self::new(self.object, DAffine3::from_scale(s) * self.transform)
4✔
221
    }
4✔
222

223
    /// Apply x-rotation AFTER current transformations.
224
    /// Looking at positive x, rotation in clockwise direction.
225
    pub fn rotate_x(self, r: f64) -> Box<Self> {
1✔
226
        Self::new(self.object, DAffine3::from_rotation_x(r) * self.transform)
1✔
227
    }
1✔
228

229
    /// Apply y-rotation AFTER current transformations
230
    pub fn rotate_y(self, r: f64) -> Box<Self> {
1✔
231
        Self::new(self.object, DAffine3::from_rotation_y(r) * self.transform)
1✔
232
    }
1✔
233

234
    /// Apply z-rotation AFTER current transformations
235
    pub fn rotate_z(self, r: f64) -> Box<Self> {
1✔
236
        Self::new(self.object, DAffine3::from_rotation_z(r) * self.transform)
1✔
237
    }
1✔
238

239
    /// Apply axis rotation AFTER current transformations
240
    pub fn rotate_axis(self, axis: DVec3, r: f64) -> Box<Instance<T>> {
×
241
        Self::new(self.object, DAffine3::from_axis_angle(axis, r) * self.transform)
×
242
    }
×
243
}
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