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

ekarpp / lumo / 4709520944

pending completion
4709520944

push

github

GitHub
0.2.4 (#16)

177 of 177 new or added lines in 9 files covered. (100.0%)

927 of 2465 relevant lines covered (37.61%)

12122570.51 hits per line

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

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

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

17
impl<T> Instance<T> {
18
    /// Constructs an instance of `object` that is transformed with
19
    /// `transform`.
20
    pub fn new(object: T, transform: DAffine3) -> Box<Self> {
6✔
21
        let inv_transform = transform.inverse();
6✔
22
        let normal_transform = inv_transform.matrix3.transpose();
6✔
23

6✔
24
        Box::new(Self {
6✔
25
            object,
6✔
26
            transform,
6✔
27
            inv_transform,
6✔
28
            normal_transform,
6✔
29
        })
6✔
30
    }
6✔
31
}
32

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

2✔
39
        self.translate(ax_mid.x, ax_mid.y, ax_mid.z)
2✔
40
    }
2✔
41
}
42

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

2✔
50
        let a0 = self.transform.matrix3.row(0) * aabb.min(Axis::X);
2✔
51
        let b0 = self.transform.matrix3.row(0) * aabb.max(Axis::X);
2✔
52
        ax_min.x += a0.min(b0).dot(DVec3::ONE);
2✔
53
        ax_max.x += a0.max(b0).dot(DVec3::ONE);
2✔
54

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

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

2✔
65
        // translate
2✔
66
        ax_min += self.transform.translation;
2✔
67
        ax_max += self.transform.translation;
2✔
68

2✔
69
        AaBoundingBox::new(ax_min, ax_max)
2✔
70
    }
2✔
71
}
72

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

20,000✔
79
        self.object.hit(&ray_local, t_min, t_max).map(|mut h| {
20,000✔
80
            h.ns = (self.normal_transform * h.ns).normalize();
20,000✔
81
            h.ng = (self.normal_transform * h.ng).normalize();
20,000✔
82
            h.p = r.at(h.t);
20,000✔
83
            h.object = self;
20,000✔
84
            h
20,000✔
85
        })
20,000✔
86
    }
20,000✔
87

88
    fn material(&self) -> &Material {
×
89
        self.object.material()
×
90
    }
×
91
}
92

93
impl<T: Sampleable> Sampleable for Instance<T> {
94
    fn sample_on(&self, rand_sq: DVec2) -> DVec3 {
×
95
        let sample_local = self.object.sample_on(rand_sq);
×
96

×
97
        self.transform.transform_point3(sample_local)
×
98
    }
×
99

100
    fn sample_towards(&self, xo: DVec3, rand_sq: DVec2) -> Ray {
×
101
        let xo_local = self.inv_transform.transform_point3(xo);
×
102
        let sample_local = self.object.sample_towards(xo_local, rand_sq);
×
103

×
104
        Ray::new(
×
105
            xo,
×
106
            self.transform.transform_vector3(sample_local.dir)
×
107
        )
×
108
    }
×
109

110
    fn sample_towards_pdf(&self, ri: &Ray) -> (f64, Option<Hit>) {
×
111
        let ri_local = ri.transform(self.inv_transform);
×
112
        let (pdf_local, hi_local) = self.object.sample_towards_pdf(&ri_local);
×
113
        if let Some(mut hi) = hi_local {
×
114
            let ng_local = hi.ng;
×
115

×
116
            let xi = ri.at(hi.t);
×
117
            let ng = (self.normal_transform * ng_local).normalize();
×
118

×
119
            // object pdf just needs these in world coordinates
×
120
            hi.p = xi;
×
121
            hi.ng = ng;
×
122

×
123
            // imagine a unit cube at the sampled point with the surface normal
×
124
            // at that point as one of the cube edges. apply the linear
×
125
            // transformation to the cube and we get a parallellepiped.
×
126
            // the base of the parallellepiped gives us the area scale at the
×
127
            // point of impact. think this is not exact with ansiotropic
×
128
            // scaling of solids. how to do for solid angle?
×
129
            let height = ng.dot(self.transform.matrix3 * ng_local);
×
130
            let volume = self.transform.matrix3.determinant();
×
131
            let jacobian = volume / height;
×
132

×
133
            // p_y(y) = p_y(T(x)) = p_x(x) / |J_T(x)|
×
134
            (pdf_local / jacobian, Some(hi))
×
135
        } else {
136
            (0.0, None)
×
137
        }
138
    }
×
139
}
140

141
/// Object that can be instanced
142
pub trait Instanceable<T> {
143
    /// Translate object by `xyz`
144
    fn translate(self, x: f64, y: f64, z: f64) -> Box<Instance<T>>;
145

146
    /// Apply scale `xyz`
147
    fn scale(self, x: f64, y: f64, z: f64) -> Box<Instance<T>>;
148

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

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

155
    /// Rotate around z-axis by `r` radians
156
    fn rotate_z(self, r: f64) -> Box<Instance<T>>;
157
}
158

159
/// To make applying transformations to objects easy
160
impl<T: Object> Instanceable<T> for T {
161
    fn translate(self, x: f64, y: f64, z: f64) -> Box<Instance<T>> {
×
162
        let t = DVec3::new(x, y, z);
×
163
        Instance::new(self, DAffine3::from_translation(t))
×
164
    }
×
165

166
    fn scale(self, x: f64, y: f64, z: f64) -> Box<Instance<T>> {
2✔
167
        assert!(x * y * z != 0.0);
2✔
168
        let s = DVec3::new(x, y, z);
2✔
169
        Instance::new(self, DAffine3::from_scale(s))
2✔
170
    }
2✔
171

172
    fn rotate_x(self, r: f64) -> Box<Instance<T>> {
×
173
        Instance::new(self, DAffine3::from_rotation_x(r))
×
174
    }
×
175

176
    fn rotate_y(self, r: f64) -> Box<Instance<T>> {
×
177
        Instance::new(self, DAffine3::from_rotation_y(r))
×
178
    }
×
179

180
    fn rotate_z(self, r: f64) -> Box<Instance<T>> {
×
181
        Instance::new(self, DAffine3::from_rotation_z(r))
×
182
    }
×
183
}
184

185
/// Prevent nested Instance structs
186
impl<T: Object> Instance<T> {
187
    /// Apply translation AFTER curret transformations
188
    pub fn translate(self, x: f64, y: f64, z: f64) -> Box<Self> {
2✔
189
        let t = DVec3::new(x, y, z);
2✔
190
        Self::new(self.object, DAffine3::from_translation(t) * self.transform)
2✔
191
    }
2✔
192

193
    /// Apply scale AFTER current transformations
194
    pub fn scale(self, x: f64, y: f64, z: f64) -> Box<Self> {
2✔
195
        assert!(x * y * z != 0.0);
2✔
196
        let s = DVec3::new(x, y, z);
2✔
197
        Self::new(self.object, DAffine3::from_scale(s) * self.transform)
2✔
198
    }
2✔
199

200
    /// Apply x-rotation AFTER current transformations.
201
    /// Looking at positive x, rotation in clockwise direction.
202
    pub fn rotate_x(self, r: f64) -> Box<Self> {
×
203
        Self::new(self.object, DAffine3::from_rotation_x(r) * self.transform)
×
204
    }
×
205

206
    /// Apply y-rotation AFTER current transformations
207
    pub fn rotate_y(self, r: f64) -> Box<Self> {
×
208
        Self::new(self.object, DAffine3::from_rotation_y(r) * self.transform)
×
209
    }
×
210

211
    /// Apply z-rotation AFTER current transformations
212
    pub fn rotate_z(self, r: f64) -> Box<Self> {
×
213
        Self::new(self.object, DAffine3::from_rotation_z(r) * self.transform)
×
214
    }
×
215
}
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