quaternion: move out of coord infrastructure

This commit is contained in:
Danny Robson 2016-10-17 22:14:57 +11:00
parent b5b7ae3a9a
commit e96ef7af32
7 changed files with 251 additions and 110 deletions

View File

@ -206,6 +206,7 @@ UTIL_FILES = \
preprocessor.hpp \
quaternion.cpp \
quaternion.hpp \
quaternion.ipp \
raii.hpp \
rand/lcg.cpp \
rand/lcg.hpp \

View File

@ -21,7 +21,6 @@ namespace util {
template <size_t,typename> struct colour;
template <size_t,typename> struct extent;
template <size_t,typename> struct point;
template <size_t,typename> struct quaternion;
template <size_t,typename> struct vector;
}

View File

@ -58,7 +58,6 @@ namespace util {
struct has_norm : public std::false_type { };
template <> struct has_norm<vector> : public std::true_type { };
template <> struct has_norm<quaternion> : public std::true_type { };
template <template <size_t,typename> class K>
constexpr auto has_norm_v = has_norm<K>::value;
@ -71,7 +70,6 @@ namespace util {
template <> struct has_scalar_op<colour> : public std::true_type { };
template <> struct has_scalar_op<extent> : public std::true_type { };
template <> struct has_scalar_op<point> : public std::true_type { };
template <> struct has_scalar_op<quaternion> : public std::true_type { };
template <> struct has_scalar_op<vector> : public std::true_type { };
template <template <size_t,typename> class K>
@ -84,7 +82,6 @@ namespace util {
template <size_t S, typename T> struct is_coord<extent<S,T>> : std::true_type { };
template <size_t S, typename T> struct is_coord<vector<S,T>> : std::true_type { };
template <size_t S, typename T> struct is_coord<colour<S,T>> : std::true_type { };
template <size_t S, typename T> struct is_coord<quaternion<S,T>> : std::true_type { };
template <class K>
constexpr bool

View File

@ -11,7 +11,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2011 Danny Robson <danny@nerdcruft.net>
* Copyright 2011-2016 Danny Robson <danny@nerdcruft.net>
*/
@ -22,19 +22,15 @@
#include <cmath>
//-----------------------------------------------------------------------------
///////////////////////////////////////////////////////////////////////////////
using util::quaternion;
//-----------------------------------------------------------------------------
template<> const quaternion<4, float> quaternion<4, float>::IDENTITY = { 1, 0, 0, 0 };
template<> const quaternion<4, double> quaternion<4, double>::IDENTITY = { 1, 0, 0, 0 };
///////////////////////////////////////////////////////////////////////////////
template <size_t S, typename T>
quaternion<S,T>
quaternion<S,T>::angle_axis (const T radians, const vector<3,T> axis)
template <typename T>
quaternion<T>
quaternion<T>::angle_axis (const T radians, const vector<3,T> axis)
{
CHECK (is_normalised (axis));
@ -48,9 +44,9 @@ quaternion<S,T>::angle_axis (const T radians, const vector<3,T> axis)
//-----------------------------------------------------------------------------
template <size_t S, typename T>
quaternion<S,T>
quaternion<S,T>::from_euler (vector<3,T> angles)
template <typename T>
quaternion<T>
quaternion<T>::from_euler (vector<3,T> angles)
{
auto half = angles / 2;
@ -69,9 +65,9 @@ quaternion<S,T>::from_euler (vector<3,T> angles)
///////////////////////////////////////////////////////////////////////////////
// vector-to-vector rotation algorithm from:
// http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final
template <size_t S, typename T>
quaternion<S,T>
quaternion<S,T>::from_to (const vector<3,T> u, const vector<3,T> v)
template <typename T>
quaternion<T>
quaternion<T>::from_to (const vector<3,T> u, const vector<3,T> v)
{
CHECK (is_normalised (u));
CHECK (is_normalised (v));
@ -103,28 +99,24 @@ quaternion<S,T>::from_to (const vector<3,T> u, const vector<3,T> v)
w = cross(u, v);
}
return normalised (util::quaternion<4,T> (real_part, w.x, w.y, w.z));
return normalised (util::quaternion<T> {real_part, w.x, w.y, w.z});
#endif
}
///////////////////////////////////////////////////////////////////////////////
template <typename T>
quaternion<4,T>
util::conjugate (quaternion<4,T> q)
quaternion<T>
util::conjugate (quaternion<T> q)
{
return { q.w, -q.x, -q.y, -q.z };
}
//-----------------------------------------------------------------------------
template quaternion<4,float> util::conjugate (quaternion<4,float>);
///////////////////////////////////////////////////////////////////////////////
template <size_t S, typename T>
quaternion<S,T>
util::operator* (const quaternion<S,T> a, const quaternion<S,T> b)
template <typename T>
quaternion<T>
util::operator* (const quaternion<T> a, const quaternion<T> b)
{
return {
a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z,
@ -134,18 +126,16 @@ util::operator* (const quaternion<S,T> a, const quaternion<S,T> b)
};
}
template quaternion<4,float> util::operator* (quaternion<4,float>, quaternion<4,float>);
//-----------------------------------------------------------------------------
template <size_t S, typename T>
quaternion<S,T>
util::operator/ (const quaternion<S,T> a, const quaternion<S,T> b)
template <typename T>
quaternion<T>
util::operator/ (const quaternion<T> a, const quaternion<T> b)
{
CHECK (is_normalised (a));
CHECK (is_normalised (b));
return quaternion<S,T> {
return quaternion<T> {
a.w * b.w + a.x * b.x + a.y * b.y + a.z * b.z,
- a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
- a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
@ -153,13 +143,11 @@ util::operator/ (const quaternion<S,T> a, const quaternion<S,T> b)
};
}
template quaternion<4,float> util::operator/ (quaternion<4,float>, quaternion<4,float>);
///////////////////////////////////////////////////////////////////////////////
template <size_t S, typename T>
template <typename T>
util::matrix4<T>
quaternion<S, T>::as_matrix (void) const
quaternion<T>::as_matrix (void) const
{
CHECK (is_normalised (*this));
@ -180,13 +168,13 @@ quaternion<S, T>::as_matrix (void) const
// https://gamedev.stackexchange.com/questions/28395/rotating-vector3-by-a-quaternion
template <typename T>
util::vector3<T>
util::rotate (vector3<T> v, quaternion<4,T> q)
util::rotate (vector3<T> v, quaternion<T> q)
{
CHECK (is_normalised (v));
#if 0
// Naive:
quaternion<4,T> p { 0, v.x, v.y, v.z };
quaternion<T> p { 0, v.x, v.y, v.z };
auto p_ = q * p * conjugate (q);
return { p_.x, p_.y, p_.z };
#else
@ -196,17 +184,12 @@ util::rotate (vector3<T> v, quaternion<4,T> q)
}
//-----------------------------------------------------------------------------
template util::vector3f util::rotate (util::vector3f, util::quaternionf);
template util::vector3d util::rotate (util::vector3d, util::quaterniond);
///////////////////////////////////////////////////////////////////////////////
// based on the implementation at:
// http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-17-quaternions/
template <size_t S, typename T>
quaternion<S,T>
quaternion<S,T>::look (vector<3,T> fwd, vector<3,T> up)
template <typename T>
quaternion<T>
quaternion<T>::look (vector<3,T> fwd, vector<3,T> up)
{
CHECK (is_normalised (fwd));
CHECK (is_normalised (up));
@ -232,36 +215,50 @@ quaternion<S,T>::look (vector<3,T> fwd, vector<3,T> up)
///////////////////////////////////////////////////////////////////////////////
template <size_t S, typename T>
template <typename T>
bool
util::almost_equal (quaternion<T> a, quaternion<T> b)
{
return almost_equal (a.w, b.w) &&
almost_equal (a.x, b.x) &&
almost_equal (a.y, b.y) &&
almost_equal (a.z, b.z);
}
///////////////////////////////////////////////////////////////////////////////
template <typename T>
std::ostream&
util::operator<< (std::ostream &os, const quaternion<S,T> q)
util::operator<< (std::ostream &os, const quaternion<T> q)
{
return os << "[" << q.w << ", " << q.x << ", " << q.y << ", " << q.z << "]";
}
//-----------------------------------------------------------------------------
template std::ostream& util::operator<< (std::ostream&, quaternion<4,float>);
template std::ostream& util::operator<< (std::ostream&, quaternion<4,double>);
///////////////////////////////////////////////////////////////////////////////
namespace util { namespace debug {
template <size_t S, typename T>
struct validator<quaternion<S,T>> {
static bool is_valid (const quaternion<S,T> &q)
namespace util::debug {
template <typename T>
struct validator<quaternion<T>> {
static constexpr
bool
is_valid (const quaternion<T> &q)
{
return is_normalised (q);
}
};
} }
//-----------------------------------------------------------------------------
template bool util::debug::is_valid(const quaternion<4,float>&);
template bool util::debug::is_valid(const quaternion<4,double>&);
}
///////////////////////////////////////////////////////////////////////////////
template struct util::quaternion<4,float>;
template struct util::quaternion<4,double>;
#define INSTANTIATE(T) \
template util::vector3<T> util::rotate (util::vector3<T>, util::quaternion<T>); \
template quaternion<T> util::conjugate (quaternion<T>); \
template quaternion<T> util::operator* (quaternion<T>, quaternion<T>); \
template quaternion<T> util::operator/ (quaternion<T>, quaternion<T>); \
template bool util::almost_equal (util::quaternion<T>, util::quaternion<T>); \
template std::ostream& util::operator<< (std::ostream&, quaternion<T>); \
template bool util::debug::is_valid(const quaternion<T>&); \
template struct util::quaternion<T>;
INSTANTIATE(float)
INSTANTIATE(double)

View File

@ -11,7 +11,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2011 Danny Robson <danny@nerdcruft.net>
* Copyright 2011-2016 Danny Robson <danny@nerdcruft.net>
*/
#ifndef __UTIL_QUATERNION_HPP
@ -26,20 +26,17 @@
namespace util {
// quaternions must be 4 elements, but we include a size parameter so it
// fits with the generic coord infrastructure more easily.
// quaternion's are _just_ different enough to other coord types that we
// special case as a distinct POD type and provide many of the same
// functions as distinct declarations.
//
// specifically:
// large regions of base code require a template template parameter with
// size and type arguments, which is annoying to work around for this one
// case.
//
// we protect against invalid instantiations through static_assert
template <size_t S, typename T>
struct quaternion : public coord::base<4,T,quaternion,coord::wxyz,coord::abcd> {
static_assert (S == 4, "quaternions must be 4 elements");
using coord::base<S,T,::util::quaternion,::util::coord::wxyz,::util::coord::abcd>::base;
// issues include:
// * strictly 4 dimensions
// * scalar operations sometimes don't make sense on the w component
// * objects must be normalised to make sense
template <typename T>
struct quaternion {
T w, x, y, z;
static quaternion angle_axis (T radians, vector<3,T> axis);
static quaternion from_euler (vector<3,T>);
@ -49,32 +46,81 @@ namespace util {
matrix4<T> as_matrix (void) const;
static const quaternion IDENTITY;
static constexpr quaternion<T> identity (void);
};
template <typename T>
quaternion<4,T>
conjugate (quaternion<4,T>);
template <size_t S, typename T>
quaternion<S,T>
operator* (const quaternion<S,T>, const quaternion<S,T>);
template <size_t S, typename T>
quaternion<S,T>
operator/ (const quaternion<S,T>, const quaternion<S,T>);
typedef quaternion<4,float> quaternionf;
typedef quaternion<4,double> quaterniond;
///////////////////////////////////////////////////////////////////////////
template <typename T>
vector3<T>
rotate (vector3<T>, quaternion<4,T>);
rotate (vector3<T>, quaternion<T>);
template <size_t S, typename T>
///////////////////////////////////////////////////////////////////////////
template <typename T>
constexpr
T
norm2 (quaternion<T>);
template <typename T>
constexpr
T
norm (quaternion<T>);
template <typename T>
constexpr
bool
is_normalised (quaternion<T>);
template <typename T>
constexpr
quaternion<T>
normalised (quaternion<T>);
///////////////////////////////////////////////////////////////////////////
template <typename T>
quaternion<T>
conjugate (quaternion<T>);
///////////////////////////////////////////////////////////////////////////
template <typename T>
quaternion<T>
operator* (quaternion<T>, quaternion<T>);
//-------------------------------------------------------------------------
template <typename T>
quaternion<T>
operator/ (quaternion<T>, quaternion<T>);
//-------------------------------------------------------------------------
template <typename T>
constexpr
quaternion<T>
operator/ (quaternion<T>, T);
///////////////////////////////////////////////////////////////////////////
template <typename T>
constexpr
bool operator== (quaternion<T>, quaternion<T>);
//-------------------------------------------------------------------------
template <typename T>
bool almost_equal (quaternion<T>, quaternion<T>);
///////////////////////////////////////////////////////////////////////////
typedef quaternion<float> quaternionf;
typedef quaternion<double> quaterniond;
///////////////////////////////////////////////////////////////////////////
template <typename T>
std::ostream&
operator<< (std::ostream&, quaternion<S,T>);
operator<< (std::ostream&, quaternion<T>);
}
#include "./quaternion.ipp"
#endif

99
quaternion.ipp Normal file
View File

@ -0,0 +1,99 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2011-2016 Danny Robson <danny@nerdcruft.net>
*/
#if defined(CRUFT_UTIL_QUATERNION_IPP)
#error
#endif
#define CRUFT_UTIL_QUATERNION_IPP
#include <cmath>
///////////////////////////////////////////////////////////////////////////////
template <typename T>
constexpr
util::quaternion<T>
util::quaternion<T>::identity (void)
{
return { 1, 0, 0, 0 };
}
///////////////////////////////////////////////////////////////////////////////
template <typename T>
constexpr
T
util::norm2 (quaternion<T> q)
{
return q.w * q.w +
q.x * q.x +
q.y * q.y +
q.z * q.z;
}
//-----------------------------------------------------------------------------
template <typename T>
constexpr
T
util::norm (quaternion<T> q)
{
return std::sqrt (norm2 (q));
}
//-----------------------------------------------------------------------------
template <typename T>
constexpr
bool
util::is_normalised (quaternion<T> q)
{
return almost_equal (T{1}, norm2 (q));
}
//-----------------------------------------------------------------------------
template <typename T>
constexpr
util::quaternion<T>
util::normalised (quaternion<T> q)
{
return q / norm (q);
}
///////////////////////////////////////////////////////////////////////////////
template <typename T>
constexpr
util::quaternion<T>
util::operator/ (quaternion<T> q, T t)
{
return { q.w / t, q.x / t, q.y / t, q.z / t };
}
///////////////////////////////////////////////////////////////////////////////
template <typename T>
constexpr
bool
util::operator== (quaternion<T> a, quaternion<T> b)
{
return exactly_equal (a.w, b.w) &&
exactly_equal (a.x, b.x) &&
exactly_equal (a.y, b.y) &&
exactly_equal (a.z, b.z);
}

View File

@ -17,22 +17,22 @@ main (void)
// identity relations
tap.expect_eq (
norm (quaternionf::IDENTITY), 1.f,
norm (quaternionf::identity ()), 1.f,
"identity magnitude is unit"
);
tap.expect_eq (
quaternionf::IDENTITY * quaternionf::IDENTITY,
quaternionf::IDENTITY,
quaternionf::identity () * quaternionf::identity (),
quaternionf::identity (),
"identity multiplication with identity"
);
// normalisation
{
auto val = normalised (quaternionf (2, 3, 4, 7));
auto val = normalised (quaternionf {2, 3, 4, 7});
tap.expect_eq (
val * quaternionf::IDENTITY,
val * quaternionf::identity (),
val,
"identity multiplication with quaternion constant"
);
@ -47,9 +47,11 @@ main (void)
// towards rotations than general maths).
util::vector4f a_v { 2, -11, 5, -17};
util::vector4f b_v { 3, 13, -7, -19};
a_v = normalised (a_v);
b_v = normalised (b_v);
auto a = normalised (a_v).as<quaternion> ();
auto b = normalised (b_v).as<quaternion> ();
auto a = quaternionf { a_v[0], a_v[1], a_v[2], a_v[3] };
auto b = quaternionf { b_v[0], b_v[1], b_v[2], b_v[3] };
auto c = quaternionf {
-0.27358657116960006f,
-0.43498209092420004f,
@ -62,7 +64,7 @@ main (void)
}
tap.expect_eq (
quaternionf::IDENTITY.as_matrix (),
quaternionf::identity ().as_matrix (),
util::matrix4f::IDENTITY,
"identity quaternion to matrix"
);
@ -90,7 +92,7 @@ main (void)
tap.expect_lt (util::sum (diff), 1e-6f, "single basis rotation %zu", i);
}
auto q = quaternionf::IDENTITY;
auto q = quaternionf::identity ();
auto m = util::matrix4f::IDENTITY;
for (auto r: ROTATIONS) {