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

ekarpp / lumo / 4788921599

pending completion
4788921599

push

github

GitHub
0.2.6 (#24)

618 of 618 new or added lines in 32 files covered. (100.0%)

1627 of 3087 relevant lines covered (52.7%)

9641000.87 hits per line

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

81.51
/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
            h.p = r.at(h.t);
20,000✔
86
            h.object = self;
20,000✔
87
            h
20,000✔
88
        })
20,000✔
89
    }
20,000✔
90

91
    fn material(&self) -> &Material {
×
92
        self.object.material()
×
93
    }
×
94
}
95

96
impl<T: Sampleable> Sampleable for Instance<T> {
97
    fn area(&self) -> f64 {
×
98
        self.object.area();
×
99
        todo!()
×
100
    }
101

102
    fn sample_on(&self, rand_sq: DVec2) -> (DVec3, DVec3) {
×
103
        let (sample_local, ng_local) = self.object.sample_on(rand_sq);
×
104

×
105
        let sampled = self.transform.transform_point3(sample_local);
×
106
        let normal = self.normal_transform * ng_local;
×
107

×
108
        (sampled, normal)
×
109
    }
×
110

111
    fn sample_towards(&self, xo: DVec3, rand_sq: DVec2) -> DVec3 {
20,000✔
112
        let xo_local = self.inv_transform.transform_point3(xo);
20,000✔
113
        let dir_local = self.object.sample_towards(xo_local, rand_sq);
20,000✔
114

20,000✔
115
        self.transform.transform_vector3(dir_local)
20,000✔
116
    }
20,000✔
117

118
    fn sample_towards_pdf(&self, ri: &Ray) -> (f64, Option<Hit>) {
20,000✔
119
        let ri_local = ri.transform(self.inv_transform);
20,000✔
120
        let (pdf_local, hi_local) = self.object.sample_towards_pdf(&ri_local);
20,000✔
121
        if let Some(mut hi) = hi_local {
20,000✔
122
            let ng_local = hi.ng;
20,000✔
123

20,000✔
124
            let xi = ri.at(hi.t);
20,000✔
125
            let ng = (self.normal_transform * ng_local).normalize();
20,000✔
126

20,000✔
127
            // object pdf just needs these in world coordinates
20,000✔
128
            hi.p = xi;
20,000✔
129
            hi.ng = ng;
20,000✔
130

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

20,000✔
141
            // p_y(y) = p_y(T(x)) = p_x(x) / |J_T(x)|
20,000✔
142
            (pdf_local / jacobian, Some(hi))
20,000✔
143
        } else {
144
            (0.0, None)
×
145
        }
146
    }
20,000✔
147
}
148

149
/// Object that can be instanced
150
pub trait Instanceable<T> {
151
    /// Translate object by `xyz`
152
    fn translate(self, x: f64, y: f64, z: f64) -> Box<Instance<T>>;
153

154
    /// Apply scale `xyz`
155
    fn scale(self, x: f64, y: f64, z: f64) -> Box<Instance<T>>;
156

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

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

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

166
    /// Rotate around `axis` by `r` radisn
167
    fn rotate_axis(self, axis: DVec3, r: f64) -> Box<Instance<T>>;
168
}
169

170
/// To make applying transformations to objects easy
171
impl<T: Object> Instanceable<T> for T {
172
    fn translate(self, x: f64, y: f64, z: f64) -> Box<Instance<T>> {
1✔
173
        let t = DVec3::new(x, y, z);
1✔
174
        Instance::new(self, DAffine3::from_translation(t))
1✔
175
    }
1✔
176

177
    fn scale(self, x: f64, y: f64, z: f64) -> Box<Instance<T>> {
2✔
178
        assert!(x * y * z != 0.0);
2✔
179
        let s = DVec3::new(x, y, z);
2✔
180
        Instance::new(self, DAffine3::from_scale(s))
2✔
181
    }
2✔
182

183
    fn rotate_x(self, r: f64) -> Box<Instance<T>> {
1✔
184
        Instance::new(self, DAffine3::from_rotation_x(r))
1✔
185
    }
1✔
186

187
    fn rotate_y(self, r: f64) -> Box<Instance<T>> {
×
188
        Instance::new(self, DAffine3::from_rotation_y(r))
×
189
    }
×
190

191
    fn rotate_z(self, r: f64) -> Box<Instance<T>> {
×
192
        Instance::new(self, DAffine3::from_rotation_z(r))
×
193
    }
×
194

195
    fn rotate_axis(self, axis: DVec3, r: f64) -> Box<Instance<T>> {
×
196
        Instance::new(self, DAffine3::from_axis_angle(axis, r))
×
197
    }
×
198
}
199

200
/// Prevent nested Instance structs
201
impl<T: Object> Instance<T> {
202
    /// Apply translation AFTER curret transformations
203
    pub fn translate(self, x: f64, y: f64, z: f64) -> Box<Self> {
3✔
204
        let t = DVec3::new(x, y, z);
3✔
205
        Self::new(self.object, DAffine3::from_translation(t) * self.transform)
3✔
206
    }
3✔
207

208
    /// Apply scale AFTER current transformations
209
    pub fn scale(self, x: f64, y: f64, z: f64) -> Box<Self> {
4✔
210
        assert!(x * y * z != 0.0);
4✔
211
        let s = DVec3::new(x, y, z);
4✔
212
        Self::new(self.object, DAffine3::from_scale(s) * self.transform)
4✔
213
    }
4✔
214

215
    /// Apply x-rotation AFTER current transformations.
216
    /// Looking at positive x, rotation in clockwise direction.
217
    pub fn rotate_x(self, r: f64) -> Box<Self> {
1✔
218
        Self::new(self.object, DAffine3::from_rotation_x(r) * self.transform)
1✔
219
    }
1✔
220

221
    /// Apply y-rotation AFTER current transformations
222
    pub fn rotate_y(self, r: f64) -> Box<Self> {
1✔
223
        Self::new(self.object, DAffine3::from_rotation_y(r) * self.transform)
1✔
224
    }
1✔
225

226
    /// Apply z-rotation AFTER current transformations
227
    pub fn rotate_z(self, r: f64) -> Box<Self> {
1✔
228
        Self::new(self.object, DAffine3::from_rotation_z(r) * self.transform)
1✔
229
    }
1✔
230

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