1
2 //
3 // This source file is part of appleseed.
4 // Visit https://appleseedhq.net/ for additional information and resources.
5 //
6 // This software is released under the MIT license.
7 //
8 // Copyright (c) 2010-2013 Francois Beaune, Jupiter Jazz Limited
9 // Copyright (c) 2014-2018 Francois Beaune, The appleseedhq Organization
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining a copy
12 // of this software and associated documentation files (the "Software"), to deal
13 // in the Software without restriction, including without limitation the rights
14 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 // copies of the Software, and to permit persons to whom the Software is
16 // furnished to do so, subject to the following conditions:
17 //
18 // The above copyright notice and this permission notice shall be included in
19 // all copies or substantial portions of the Software.
20 //
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 // THE SOFTWARE.
28 //
29
30 #pragma once
31
32 // appleseed.foundation headers.
33 #include "foundation/math/scalar.h"
34 #include "foundation/math/vector.h"
35 #include "foundation/utility/poison.h"
36
37 // Standard headers.
38 #include <cassert>
39 #include <cmath>
40
41 namespace foundation
42 {
43
44 //
45 // The Basis3 class represents a 3-dimensional orthonormal basis, and allows
46 // transformation of vectors from parent space to local space and from local
47 // space to parent space.
48 //
49
50 template <typename T>
51 class Basis3
52 {
53 public:
54 // Vector and basis types.
55 typedef Vector<T, 3> VectorType;
56 typedef Basis3<T> BasisType;
57
58 // Constructors.
59 #if APPLESEED_COMPILER_CXX_DEFAULTED_FUNCTIONS
60 Basis3() = default; // leave all components uninitialized
61 #else
Basis3()62 Basis3() {} // leave all components uninitialized
63 #endif
64 explicit Basis3(const VectorType& normal); // normal must be unit-length
65 Basis3(
66 const VectorType& normal, // must be unit-length
67 const VectorType& u); // does not need to be unit-length
68 Basis3(
69 const VectorType& normal, // must be unit-length
70 const VectorType& u, // must be unit-length
71 const VectorType& v); // must be unit-length
72
73 // Construct a basis from another basis of a different type.
74 template <typename U>
75 explicit Basis3(const Basis3<U>& rhs);
76
77 // Rebuild the basis for a given unit-length normal vector.
78 void build(const VectorType& normal); // normal must be unit-length
79
80 // Rebuild the basis for a given unit-length normal and U tangent vector.
81 void build(
82 const VectorType& normal, // must be unit-length
83 const VectorType& u); // does not need to be unit-length
84
85 // Rebuild the basis for a given unit-length normal and tangent vectors.
86 void build(
87 const VectorType& normal, // must be unit-length
88 const VectorType& u, // must be unit-length
89 const VectorType& v); // must be unit-length
90
91 // Transform a 3D vector. The vector is not required to be unit-length,
92 // and is not normalized after transformation. However the length of the
93 // vector is preserved by the transformation.
94 VectorType transform_to_local(const VectorType& v) const;
95 VectorType transform_to_parent(const VectorType& v) const;
96
97 // Transform a 3D vector of a different type.
98 template <typename U>
99 Vector<U, 3> transform_to_local(const Vector<U, 3>& v) const;
100 template <typename U>
101 Vector<U, 3> transform_to_parent(const Vector<U, 3>& v) const;
102
103 // Retrieve the individual basis vectors.
104 const VectorType& get_normal() const;
105 const VectorType& get_tangent_u() const;
106 const VectorType& get_tangent_v() const;
107
108 private:
109 template <typename> friend class Basis3;
110 template <typename> friend class PoisonImpl;
111
112 VectorType m_n, m_u, m_v;
113
114 #ifndef NDEBUG
115 // Run a bunch of assertions on the basis vectors.
116 void checks();
117 #endif
118 };
119
120 // Poisoning.
121 template <typename T>
122 class PoisonImpl<Basis3<T>>
123 {
124 public:
125 static void do_poison(Basis3<T>& basis);
126 };
127
128
129 //
130 // Specializations for single and double precision orthonormal basis.
131 //
132
133 typedef Basis3<float> Basis3f;
134 typedef Basis3<double> Basis3d;
135
136
137 //
138 // Basis3 class implementation.
139 //
140
141 template <typename T>
Basis3(const VectorType & normal)142 inline Basis3<T>::Basis3(const VectorType& normal)
143 {
144 build(normal);
145 }
146
147 template <typename T>
Basis3(const VectorType & normal,const VectorType & u)148 inline Basis3<T>::Basis3(
149 const VectorType& normal,
150 const VectorType& u)
151 {
152 build(normal, u);
153 }
154
155 template <typename T>
Basis3(const VectorType & normal,const VectorType & u,const VectorType & v)156 inline Basis3<T>::Basis3(
157 const VectorType& normal,
158 const VectorType& u,
159 const VectorType& v)
160 {
161 build(normal, u, v);
162 }
163
164 template <typename T>
165 template <typename U>
Basis3(const Basis3<U> & rhs)166 inline Basis3<T>::Basis3(const Basis3<U>& rhs)
167 : m_n(rhs.m_n)
168 , m_u(rhs.m_u)
169 , m_v(rhs.m_v)
170 {
171 }
172
173 template <typename T>
build(const VectorType & normal)174 inline void Basis3<T>::build(const VectorType& normal)
175 {
176 //
177 // Reference:
178 //
179 // Tom Duff, James Burgess, Per Christensen, Christophe Hery, Andrew Kensler,
180 // Max Liani, and Ryusuke Villemin, Building an Orthonormal Basis, Revisited,
181 // Journal of Computer Graphics Techniques (JCGT), vol. 6, no. 1, 1-8, 2017
182 // http://jcgt.org/published/0006/01/01/paper-lowres.pdf
183 //
184
185 assert(is_normalized(normal));
186
187 m_n = normal;
188
189 #if APPLESEED_COMPILER_CXX_DEFAULTED_FUNCTIONS
190 const T sign = std::copysign(T(1.0), m_n[2]);
191 #else
192 const T sign = m_n[2] < T(0.0) ? T(-1.0) : T(1.0);
193 #endif
194
195 const T a = T(-1.0) / (sign + m_n[2]);
196 const T b = m_n[0] * m_n[1] * a;
197
198 m_u = VectorType(b, sign + m_n[1] * m_n[1] * a, -m_n[1]);
199 m_v = VectorType(T(1.0) + sign * m_n[0] * m_n[0] * a, sign * b, -sign * m_n[0]);
200
201 #ifndef NDEBUG
202 checks();
203 #endif
204 }
205
206 template <typename T>
build(const VectorType & normal,const VectorType & u)207 inline void Basis3<T>::build(
208 const VectorType& normal,
209 const VectorType& u)
210 {
211 assert(is_normalized(normal));
212
213 m_n = normal;
214 m_v = normalize(cross(u, m_n));
215 m_u = cross(m_n, m_v);
216
217 #ifndef NDEBUG
218 checks();
219 #endif
220 }
221
222 template <typename T>
build(const VectorType & normal,const VectorType & u,const VectorType & v)223 inline void Basis3<T>::build(
224 const VectorType& normal,
225 const VectorType& u,
226 const VectorType& v)
227 {
228 assert(is_normalized(normal));
229 assert(is_normalized(u));
230 assert(is_normalized(v));
231
232 m_n = normal;
233 m_u = u;
234 m_v = v;
235
236 #ifndef NDEBUG
237 checks();
238 #endif
239 }
240
241 template <typename T>
transform_to_local(const VectorType & v)242 inline Vector<T, 3> Basis3<T>::transform_to_local(const VectorType& v) const
243 {
244 return
245 Vector<T, 3>(
246 dot(v, m_u),
247 dot(v, m_n),
248 dot(v, m_v));
249 }
250
251 template <typename T>
transform_to_parent(const VectorType & v)252 inline Vector<T, 3> Basis3<T>::transform_to_parent(const VectorType& v) const
253 {
254 return
255 v[0] * m_u +
256 v[1] * m_n +
257 v[2] * m_v;
258 }
259
260 template <typename T>
261 template <typename U>
transform_to_local(const Vector<U,3> & v)262 inline Vector<U, 3> Basis3<T>::transform_to_local(const Vector<U, 3>& v) const
263 {
264 return
265 Vector<U, 3>(
266 dot(v, Vector<U, 3>(m_u)),
267 dot(v, Vector<U, 3>(m_n)),
268 dot(v, Vector<U, 3>(m_v)));
269 }
270
271 template <typename T>
272 template <typename U>
transform_to_parent(const Vector<U,3> & v)273 inline Vector<U, 3> Basis3<T>::transform_to_parent(const Vector<U, 3>& v) const
274 {
275 return
276 v[0] * Vector<U, 3>(m_u) +
277 v[1] * Vector<U, 3>(m_n) +
278 v[2] * Vector<U, 3>(m_v);
279 }
280
281 template <typename T>
get_normal()282 inline const Vector<T, 3>& Basis3<T>::get_normal() const
283 {
284 return m_n;
285 }
286
287 template <typename T>
get_tangent_u()288 inline const Vector<T, 3>& Basis3<T>::get_tangent_u() const
289 {
290 return m_u;
291 }
292
293 template <typename T>
get_tangent_v()294 inline const Vector<T, 3>& Basis3<T>::get_tangent_v() const
295 {
296 return m_v;
297 }
298
299 template <typename T>
do_poison(Basis3<T> & basis)300 void PoisonImpl<Basis3<T>>::do_poison(Basis3<T>& basis)
301 {
302 always_poison(basis.m_n);
303 always_poison(basis.m_u);
304 always_poison(basis.m_v);
305 }
306
307 #ifndef NDEBUG
308
309 template <typename T>
checks()310 void Basis3<T>::checks()
311 {
312 // Make sure (m_u, m_n, m_v) forms an orthonormal basis.
313 assert(is_normalized(m_u));
314 assert(is_normalized(m_n));
315 assert(is_normalized(m_v));
316 assert(fz(dot(m_u, m_n), make_eps<T>(1.0e-4f, 1.0e-6)));
317 assert(fz(dot(m_u, m_v), make_eps<T>(1.0e-4f, 1.0e-6)));
318 assert(fz(dot(m_n, m_v), make_eps<T>(1.0e-4f, 1.0e-6)));
319
320 // Make sure (m_u, m_n, m_v) is right-handed.
321 assert(feq(dot(cross(m_u, m_n), m_v), T(1.0), make_eps<T>(1.0e-4f, 1.0e-5)));
322 }
323
324 #endif
325
326 } // namespace foundation
327