/*! @header * This header defines functions for constructing and using quaternions. * @copyright 2015-2016 Apple, Inc. All rights reserved. * @unsorted */ #ifndef SIMD_QUATERNIONS #define SIMD_QUATERNIONS #include #if SIMD_COMPILER_HAS_REQUIRED_FEATURES #include #include #ifdef __cplusplus extern "C" { #endif /* MARK: - C and Objective-C float interfaces */ /*! @abstract Constructs a quaternion from four scalar values. * * @param ix The first component of the imaginary (vector) part. * @param iy The second component of the imaginary (vector) part. * @param iz The third component of the imaginary (vector) part. * * @param r The real (scalar) part. */ static inline SIMD_CFUNC simd_quatf simd_quaternion(float ix, float iy, float iz, float r) { return (simd_quatf){ { ix, iy, iz, r } }; } /*! @abstract Constructs a quaternion from an array of four scalars. * * @discussion Note that the imaginary part of the quaternion comes from * array elements 0, 1, and 2, and the real part comes from element 3. */ static inline SIMD_NONCONST simd_quatf simd_quaternion(const float xyzr[4]) { return (simd_quatf){ *(const simd_packed_float4 *)xyzr }; } /*! @abstract Constructs a quaternion from a four-element vector. * * @discussion Note that the imaginary (vector) part of the quaternion comes * from lanes 0, 1, and 2 of the vector, and the real (scalar) part comes from * lane 3. */ static inline SIMD_CFUNC simd_quatf simd_quaternion(simd_float4 xyzr) { return (simd_quatf){ xyzr }; } /*! @abstract Constructs a quaternion that rotates by `angle` radians about * `axis`. */ static inline SIMD_CFUNC simd_quatf simd_quaternion(float angle, simd_float3 axis); /*! @abstract Construct a quaternion that rotates from one vector to another. * * @param from A normalized three-element vector. * @param to A normalized three-element vector. * * @discussion The rotation axis is `simd_cross(from, to)`. If `from` and * `to` point in opposite directions (to within machine precision), an * arbitrary rotation axis is chosen, and the angle is pi radians. */ static SIMD_NOINLINE simd_quatf simd_quaternion(simd_float3 from, simd_float3 to); /*! @abstract Construct a quaternion from a 3x3 rotation `matrix`. * * @discussion If `matrix` is not orthogonal with determinant 1, the result * is undefined. */ static SIMD_NOINLINE simd_quatf simd_quaternion(simd_float3x3 matrix); /*! @abstract Construct a quaternion from a 4x4 rotation `matrix`. * * @discussion The last row and column of the matrix are ignored. This * function is equivalent to calling simd_quaternion with the upper-left 3x3 * submatrix . */ static SIMD_NOINLINE simd_quatf simd_quaternion(simd_float4x4 matrix); /*! @abstract The real (scalar) part of the quaternion `q`. */ static inline SIMD_CFUNC float simd_real(simd_quatf q) { return q.vector.w; } /*! @abstract The imaginary (vector) part of the quaternion `q`. */ static inline SIMD_CFUNC simd_float3 simd_imag(simd_quatf q) { return q.vector.xyz; } /*! @abstract The angle (in radians) of rotation represented by `q`. */ static inline SIMD_CFUNC float simd_angle(simd_quatf q); /*! @abstract The normalized axis (a 3-element vector) around which the * action of the quaternion `q` rotates. */ static inline SIMD_CFUNC simd_float3 simd_axis(simd_quatf q); /*! @abstract The sum of the quaternions `p` and `q`. */ static inline SIMD_CFUNC simd_quatf simd_add(simd_quatf p, simd_quatf q); /*! @abstract The difference of the quaternions `p` and `q`. */ static inline SIMD_CFUNC simd_quatf simd_sub(simd_quatf p, simd_quatf q); /*! @abstract The product of the quaternions `p` and `q`. */ static inline SIMD_CFUNC simd_quatf simd_mul(simd_quatf p, simd_quatf q); /*! @abstract The quaternion `q` scaled by the real value `a`. */ static inline SIMD_CFUNC simd_quatf simd_mul(simd_quatf q, float a); /*! @abstract The quaternion `q` scaled by the real value `a`. */ static inline SIMD_CFUNC simd_quatf simd_mul(float a, simd_quatf q); /*! @abstract The conjugate of the quaternion `q`. */ static inline SIMD_CFUNC simd_quatf simd_conjugate(simd_quatf q); /*! @abstract The (multiplicative) inverse of the quaternion `q`. */ static inline SIMD_CFUNC simd_quatf simd_inverse(simd_quatf q); /*! @abstract The negation (additive inverse) of the quaternion `q`. */ static inline SIMD_CFUNC simd_quatf simd_negate(simd_quatf q); /*! @abstract The dot product of the quaternions `p` and `q` interpreted as * four-dimensional vectors. */ static inline SIMD_CFUNC float simd_dot(simd_quatf p, simd_quatf q); /*! @abstract The length of the quaternion `q`. */ static inline SIMD_CFUNC float simd_length(simd_quatf q); /*! @abstract The unit quaternion obtained by normalizing `q`. */ static inline SIMD_CFUNC simd_quatf simd_normalize(simd_quatf q); /*! @abstract Rotates the vector `v` by the quaternion `q`. */ static inline SIMD_CFUNC simd_float3 simd_act(simd_quatf q, simd_float3 v); /*! @abstract Logarithm of the quaternion `q`. * @discussion Do not call this function directly; use `log(q)` instead. * * We can write a quaternion `q` in the form: `r(cos(t) + sin(t)v)` where * `r` is the length of `q`, `t` is an angle, and `v` is a unit 3-vector. * The logarithm of `q` is `log(r) + tv`, just like the logarithm of the * complex number `r*(cos(t) + i sin(t))` is `log(r) + it`. * * Note that this function is not robust against poorly-scaled non-unit * quaternions, because it is primarily used for spline interpolation of * unit quaternions. If you need to compute a robust logarithm of general * quaternions, you can use the following approach: * * scale = simd_reduce_max(simd_abs(q.vector)); * logq = log(simd_recip(scale)*q); * logq.real += log(scale); * return logq; */ static SIMD_NOINLINE simd_quatf __tg_log(simd_quatf q); /*! @abstract Inverse of `log( )`; the exponential map on quaternions. * @discussion Do not call this function directly; use `exp(q)` instead. */ static SIMD_NOINLINE simd_quatf __tg_exp(simd_quatf q); /*! @abstract Spherical linear interpolation along the shortest arc between * quaternions `q0` and `q1`. */ static SIMD_NOINLINE simd_quatf simd_slerp(simd_quatf q0, simd_quatf q1, float t); /*! @abstract Spherical linear interpolation along the longest arc between * quaternions `q0` and `q1`. */ static SIMD_NOINLINE simd_quatf simd_slerp_longest(simd_quatf q0, simd_quatf q1, float t); /*! @abstract Interpolate between quaternions along a spherical cubic spline. * * @discussion The function interpolates between q1 and q2. q0 is the left * endpoint of the previous interval, and q3 is the right endpoint of the next * interval. Use this function to smoothly interpolate between a sequence of * rotations. */ static SIMD_NOINLINE simd_quatf simd_spline(simd_quatf q0, simd_quatf q1, simd_quatf q2, simd_quatf q3, float t); /*! @abstract Spherical cubic Bezier interpolation between quaternions. * * @discussion The function treats q0 ... q3 as control points and uses slerp * in place of lerp in the De Castlejeau algorithm. The endpoints of * interpolation are thus q0 and q3, and the curve will not generally pass * through q1 or q2. Note that the convex hull property of "standard" Bezier * curve does not hold on the sphere. */ static SIMD_NOINLINE simd_quatf simd_bezier(simd_quatf q0, simd_quatf q1, simd_quatf q2, simd_quatf q3, float t); #ifdef __cplusplus } /* extern "C" */ /* MARK: - C++ float interfaces */ namespace simd { struct quatf : ::simd_quatf { /*! @abstract The identity quaternion. */ quatf( ) : ::simd_quatf(::simd_quaternion((float4){0,0,0,1})) { } /*! @abstract Constructs a C++ quaternion from a C quaternion. */ quatf(::simd_quatf q) : ::simd_quatf(q) { } /*! @abstract Constructs a quaternion from components. */ quatf(float ix, float iy, float iz, float r) : ::simd_quatf(::simd_quaternion(ix, iy, iz, r)) { } /*! @abstract Constructs a quaternion from an array of scalars. */ quatf(const float xyzr[4]) : ::simd_quatf(::simd_quaternion(xyzr)) { } /*! @abstract Constructs a quaternion from a vector. */ quatf(float4 xyzr) : ::simd_quatf(::simd_quaternion(xyzr)) { } /*! @abstract Quaternion representing rotation about `axis` by `angle` * radians. */ quatf(float angle, float3 axis) : ::simd_quatf(::simd_quaternion(angle, axis)) { } /*! @abstract Quaternion that rotates `from` into `to`. */ quatf(float3 from, float3 to) : ::simd_quatf(::simd_quaternion(from, to)) { } /*! @abstract Constructs a quaternion from a rotation matrix. */ quatf(::simd_float3x3 matrix) : ::simd_quatf(::simd_quaternion(matrix)) { } /*! @abstract Constructs a quaternion from a rotation matrix. */ quatf(::simd_float4x4 matrix) : ::simd_quatf(::simd_quaternion(matrix)) { } /*! @abstract The real (scalar) part of the quaternion. */ float real(void) const { return ::simd_real(*this); } /*! @abstract The imaginary (vector) part of the quaternion. */ float3 imag(void) const { return ::simd_imag(*this); } /*! @abstract The angle the quaternion rotates by. */ float angle(void) const { return ::simd_angle(*this); } /*! @abstract The axis the quaternion rotates about. */ float3 axis(void) const { return ::simd_axis(*this); } /*! @abstract The length of the quaternion. */ float length(void) const { return ::simd_length(*this); } /*! @abstract Act on the vector `v` by rotation. */ float3 operator()(const ::simd_float3 v) const { return ::simd_act(*this, v); } }; static SIMD_CPPFUNC quatf operator+(const ::simd_quatf p, const ::simd_quatf q) { return ::simd_add(p, q); } static SIMD_CPPFUNC quatf operator-(const ::simd_quatf p, const ::simd_quatf q) { return ::simd_sub(p, q); } static SIMD_CPPFUNC quatf operator-(const ::simd_quatf p) { return ::simd_negate(p); } static SIMD_CPPFUNC quatf operator*(const float r, const ::simd_quatf p) { return ::simd_mul(r, p); } static SIMD_CPPFUNC quatf operator*(const ::simd_quatf p, const float r) { return ::simd_mul(p, r); } static SIMD_CPPFUNC quatf operator*(const ::simd_quatf p, const ::simd_quatf q) { return ::simd_mul(p, q); } static SIMD_CPPFUNC quatf operator/(const ::simd_quatf p, const ::simd_quatf q) { return ::simd_mul(p, ::simd_inverse(q)); } static SIMD_INLINE SIMD_NODEBUG quatf operator+=(quatf &p, const ::simd_quatf q) { return p = p+q; } static SIMD_INLINE SIMD_NODEBUG quatf operator-=(quatf &p, const ::simd_quatf q) { return p = p-q; } static SIMD_INLINE SIMD_NODEBUG quatf operator*=(quatf &p, const float r) { return p = p*r; } static SIMD_INLINE SIMD_NODEBUG quatf operator*=(quatf &p, const ::simd_quatf q) { return p = p*q; } static SIMD_INLINE SIMD_NODEBUG quatf operator/=(quatf &p, const ::simd_quatf q) { return p = p/q; } /*! @abstract The conjugate of the quaternion `q`. */ static SIMD_CPPFUNC quatf conjugate(const ::simd_quatf p) { return ::simd_conjugate(p); } /*! @abstract The (multiplicative) inverse of the quaternion `q`. */ static SIMD_CPPFUNC quatf inverse(const ::simd_quatf p) { return ::simd_inverse(p); } /*! @abstract The dot product of the quaternions `p` and `q` interpreted as * four-dimensional vectors. */ static SIMD_CPPFUNC float dot(const ::simd_quatf p, const ::simd_quatf q) { return ::simd_dot(p, q); } /*! @abstract The unit quaternion obtained by normalizing `q`. */ static SIMD_CPPFUNC quatf normalize(const ::simd_quatf p) { return ::simd_normalize(p); } /*! @abstract logarithm of the quaternion `q`. */ static SIMD_CPPFUNC quatf log(const ::simd_quatf q) { return ::__tg_log(q); } /*! @abstract exponential map of quaterion `q`. */ static SIMD_CPPFUNC quatf exp(const ::simd_quatf q) { return ::__tg_exp(q); } /*! @abstract Spherical linear interpolation along the shortest arc between * quaternions `q0` and `q1`. */ static SIMD_CPPFUNC quatf slerp(const ::simd_quatf p0, const ::simd_quatf p1, float t) { return ::simd_slerp(p0, p1, t); } /*! @abstract Spherical linear interpolation along the longest arc between * quaternions `q0` and `q1`. */ static SIMD_CPPFUNC quatf slerp_longest(const ::simd_quatf p0, const ::simd_quatf p1, float t) { return ::simd_slerp_longest(p0, p1, t); } /*! @abstract Interpolate between quaternions along a spherical cubic spline. * * @discussion The function interpolates between q1 and q2. q0 is the left * endpoint of the previous interval, and q3 is the right endpoint of the next * interval. Use this function to smoothly interpolate between a sequence of * rotations. */ static SIMD_CPPFUNC quatf spline(const ::simd_quatf p0, const ::simd_quatf p1, const ::simd_quatf p2, const ::simd_quatf p3, float t) { return ::simd_spline(p0, p1, p2, p3, t); } /*! @abstract Spherical cubic Bezier interpolation between quaternions. * * @discussion The function treats q0 ... q3 as control points and uses slerp * in place of lerp in the De Castlejeau algorithm. The endpoints of * interpolation are thus q0 and q3, and the curve will not generally pass * through q1 or q2. Note that the convex hull property of "standard" Bezier * curve does not hold on the sphere. */ static SIMD_CPPFUNC quatf bezier(const ::simd_quatf p0, const ::simd_quatf p1, const ::simd_quatf p2, const ::simd_quatf p3, float t) { return ::simd_bezier(p0, p1, p2, p3, t); } } extern "C" { #endif /* __cplusplus */ /* MARK: - float implementations */ #include #include /* tg_promote is implementation gobbledygook that enables the compile-time * dispatching in tgmath.h to work its magic. */ static simd_quatf __attribute__((__overloadable__)) __tg_promote(simd_quatf); /*! @abstract Constructs a quaternion from imaginary and real parts. * @discussion This function is hidden behind an underscore to avoid confusion * with the angle-axis constructor. */ static inline SIMD_CFUNC simd_quatf _simd_quaternion(simd_float3 imag, float real) { return simd_quaternion(simd_make_float4(imag, real)); } static inline SIMD_CFUNC simd_quatf simd_quaternion(float angle, simd_float3 axis) { return _simd_quaternion(sin(angle/2) * axis, cos(angle/2)); } static inline SIMD_CFUNC float simd_angle(simd_quatf q) { return 2*atan2(simd_length(q.vector.xyz), q.vector.w); } static inline SIMD_CFUNC simd_float3 simd_axis(simd_quatf q) { return simd_normalize(q.vector.xyz); } static inline SIMD_CFUNC simd_quatf simd_add(simd_quatf p, simd_quatf q) { return simd_quaternion(p.vector + q.vector); } static inline SIMD_CFUNC simd_quatf simd_sub(simd_quatf p, simd_quatf q) { return simd_quaternion(p.vector - q.vector); } static inline SIMD_CFUNC simd_quatf simd_mul(simd_quatf p, simd_quatf q) { #pragma STDC FP_CONTRACT ON return simd_quaternion((p.vector.x * __builtin_shufflevector(q.vector, -q.vector, 3,6,1,4) + p.vector.y * __builtin_shufflevector(q.vector, -q.vector, 2,3,4,5)) + (p.vector.z * __builtin_shufflevector(q.vector, -q.vector, 5,0,3,6) + p.vector.w * q.vector)); } static inline SIMD_CFUNC simd_quatf simd_mul(simd_quatf q, float a) { return simd_quaternion(a * q.vector); } static inline SIMD_CFUNC simd_quatf simd_mul(float a, simd_quatf q) { return simd_mul(q,a); } static inline SIMD_CFUNC simd_quatf simd_conjugate(simd_quatf q) { return simd_quaternion(q.vector * (simd_float4){-1,-1,-1, 1}); } static inline SIMD_CFUNC simd_quatf simd_inverse(simd_quatf q) { return simd_quaternion(simd_conjugate(q).vector * simd_recip(simd_length_squared(q.vector))); } static inline SIMD_CFUNC simd_quatf simd_negate(simd_quatf q) { return simd_quaternion(-q.vector); } static inline SIMD_CFUNC float simd_dot(simd_quatf p, simd_quatf q) { return simd_dot(p.vector, q.vector); } static inline SIMD_CFUNC float simd_length(simd_quatf q) { return simd_length(q.vector); } static inline SIMD_CFUNC simd_quatf simd_normalize(simd_quatf q) { float length_squared = simd_length_squared(q.vector); if (length_squared == 0) { return simd_quaternion((simd_float4){0,0,0,1}); } return simd_quaternion(q.vector * simd_rsqrt(length_squared)); } #if defined __arm__ || defined __arm64__ /*! @abstract Multiplies the vector `v` by the quaternion `q`. * * @discussion This IS NOT the action of `q` on `v` (i.e. this is not rotation * by `q`. That operation is provided by `simd_act(q, v)`. This function is an * implementation detail and you should not call it directly. It may be * removed or modified in future versions of the simd module. */ static inline SIMD_CFUNC simd_quatf _simd_mul_vq(simd_float3 v, simd_quatf q) { #pragma STDC FP_CONTRACT ON return simd_quaternion(v.x * __builtin_shufflevector(q.vector, -q.vector, 3,6,1,4) + v.y * __builtin_shufflevector(q.vector, -q.vector, 2,3,4,5) + v.z * __builtin_shufflevector(q.vector, -q.vector, 5,0,3,6)); } #endif static inline SIMD_CFUNC simd_float3 simd_act(simd_quatf q, simd_float3 v) { #if defined __arm__ || defined __arm64__ return simd_mul(q, _simd_mul_vq(v, simd_conjugate(q))).vector.xyz; #else #pragma STDC FP_CONTRACT ON simd_float3 t = 2*simd_cross(simd_imag(q),v); return v + simd_real(q)*t + simd_cross(simd_imag(q), t); #endif } static SIMD_NOINLINE simd_quatf __tg_log(simd_quatf q) { float real = __tg_log(simd_length_squared(q.vector))/2; if (simd_equal(simd_imag(q), 0)) return _simd_quaternion(0, real); simd_float3 imag = __tg_acos(simd_real(q)/simd_length(q)) * simd_normalize(simd_imag(q)); return _simd_quaternion(imag, real); } static SIMD_NOINLINE simd_quatf __tg_exp(simd_quatf q) { // angle is actually *twice* the angle of the rotation corresponding to // the resulting quaternion, which is why we don't simply use the (angle, // axis) constructor to generate `unit`. float angle = simd_length(simd_imag(q)); if (angle == 0) return _simd_quaternion(0, exp(simd_real(q))); simd_float3 axis = simd_normalize(simd_imag(q)); simd_quatf unit = _simd_quaternion(sin(angle)*axis, cosf(angle)); return simd_mul(exp(simd_real(q)), unit); } /*! @abstract Implementation detail of the `simd_quaternion(from, to)` * initializer. * * @discussion Computes the quaternion rotation `from` to `to` if they are * separated by less than 90 degrees. Not numerically stable for larger * angles. This function is an implementation detail and you should not * call it directly. It may be removed or modified in future versions of the * simd module. */ static inline SIMD_CFUNC simd_quatf _simd_quaternion_reduced(simd_float3 from, simd_float3 to) { simd_float3 half = simd_normalize(from + to); return _simd_quaternion(simd_cross(from, half), simd_dot(from, half)); } static SIMD_NOINLINE simd_quatf simd_quaternion(simd_float3 from, simd_float3 to) { // If the angle between from and to is not too big, we can compute the // rotation accurately using a simple implementation. if (simd_dot(from, to) >= 0) { return _simd_quaternion_reduced(from, to); } // Because from and to are more than 90 degrees apart, we compute the // rotation in two stages (from -> half), (half -> to) to preserve numerical // accuracy. simd_float3 half = simd_normalize(from) + simd_normalize(to); if (simd_length_squared(half) <= 0x1p-46f) { // half is nearly zero, so from and to point in nearly opposite directions // and the rotation is numerically underspecified. Pick an axis orthogonal // to the vectors, and use an angle of pi radians. simd_float3 abs_from = simd_abs(from); if (abs_from.x <= abs_from.y && abs_from.x <= abs_from.z) return _simd_quaternion(simd_normalize(simd_cross(from, (simd_float3){1,0,0})), 0.f); else if (abs_from.y <= abs_from.z) return _simd_quaternion(simd_normalize(simd_cross(from, (simd_float3){0,1,0})), 0.f); else return _simd_quaternion(simd_normalize(simd_cross(from, (simd_float3){0,0,1})), 0.f); } // Compute the two-step rotation. */ half = simd_normalize(half); return simd_mul(_simd_quaternion_reduced(from, half), _simd_quaternion_reduced(half, to)); } static SIMD_NOINLINE simd_quatf simd_quaternion(simd_float3x3 matrix) { const simd_float3 *mat = matrix.columns; float trace = mat[0][0] + mat[1][1] + mat[2][2]; if (trace >= 0.0) { float r = 2*sqrt(1 + trace); float rinv = simd_recip(r); return simd_quaternion(rinv*(mat[1][2] - mat[2][1]), rinv*(mat[2][0] - mat[0][2]), rinv*(mat[0][1] - mat[1][0]), r/4); } else if (mat[0][0] >= mat[1][1] && mat[0][0] >= mat[2][2]) { float r = 2*sqrt(1 - mat[1][1] - mat[2][2] + mat[0][0]); float rinv = simd_recip(r); return simd_quaternion(r/4, rinv*(mat[0][1] + mat[1][0]), rinv*(mat[0][2] + mat[2][0]), rinv*(mat[1][2] - mat[2][1])); } else if (mat[1][1] >= mat[2][2]) { float r = 2*sqrt(1 - mat[0][0] - mat[2][2] + mat[1][1]); float rinv = simd_recip(r); return simd_quaternion(rinv*(mat[0][1] + mat[1][0]), r/4, rinv*(mat[1][2] + mat[2][1]), rinv*(mat[2][0] - mat[0][2])); } else { float r = 2*sqrt(1 - mat[0][0] - mat[1][1] + mat[2][2]); float rinv = simd_recip(r); return simd_quaternion(rinv*(mat[0][2] + mat[2][0]), rinv*(mat[1][2] + mat[2][1]), r/4, rinv*(mat[0][1] - mat[1][0])); } } static SIMD_NOINLINE simd_quatf simd_quaternion(simd_float4x4 matrix) { const simd_float4 *mat = matrix.columns; float trace = mat[0][0] + mat[1][1] + mat[2][2]; if (trace >= 0.0) { float r = 2*sqrt(1 + trace); float rinv = simd_recip(r); return simd_quaternion(rinv*(mat[1][2] - mat[2][1]), rinv*(mat[2][0] - mat[0][2]), rinv*(mat[0][1] - mat[1][0]), r/4); } else if (mat[0][0] >= mat[1][1] && mat[0][0] >= mat[2][2]) { float r = 2*sqrt(1 - mat[1][1] - mat[2][2] + mat[0][0]); float rinv = simd_recip(r); return simd_quaternion(r/4, rinv*(mat[0][1] + mat[1][0]), rinv*(mat[0][2] + mat[2][0]), rinv*(mat[1][2] - mat[2][1])); } else if (mat[1][1] >= mat[2][2]) { float r = 2*sqrt(1 - mat[0][0] - mat[2][2] + mat[1][1]); float rinv = simd_recip(r); return simd_quaternion(rinv*(mat[0][1] + mat[1][0]), r/4, rinv*(mat[1][2] + mat[2][1]), rinv*(mat[2][0] - mat[0][2])); } else { float r = 2*sqrt(1 - mat[0][0] - mat[1][1] + mat[2][2]); float rinv = simd_recip(r); return simd_quaternion(rinv*(mat[0][2] + mat[2][0]), rinv*(mat[1][2] + mat[2][1]), r/4, rinv*(mat[0][1] - mat[1][0])); } } /*! @abstract The angle between p and q interpreted as 4-dimensional vectors. * * @discussion This function is an implementation detail and you should not * call it directly. It may be removed or modified in future versions of the * simd module. */ static SIMD_NOINLINE float _simd_angle(simd_quatf p, simd_quatf q) { return 2*atan2(simd_length(p.vector - q.vector), simd_length(p.vector + q.vector)); } /*! @abstract sin(x)/x. * * @discussion This function is an implementation detail and you should not * call it directly. It may be removed or modified in future versions of the * simd module. */ static SIMD_CFUNC float _simd_sinc(float x) { if (x == 0) return 1; return sin(x)/x; } /*! @abstract Spherical lerp between q0 and q1. * * @discussion This function may interpolate along either the longer or * shorter path between q0 and q1; it is used as an implementation detail * in `simd_slerp` and `simd_slerp_longest`; you should use those functions * instead of calling this directly. */ static SIMD_NOINLINE simd_quatf _simd_slerp_internal(simd_quatf q0, simd_quatf q1, float t) { float s = 1 - t; float a = _simd_angle(q0, q1); float r = simd_recip(_simd_sinc(a)); return simd_normalize(simd_quaternion(_simd_sinc(s*a)*r*s*q0.vector + _simd_sinc(t*a)*r*t*q1.vector)); } static SIMD_NOINLINE simd_quatf simd_slerp(simd_quatf q0, simd_quatf q1, float t) { if (simd_dot(q0, q1) >= 0) return _simd_slerp_internal(q0, q1, t); return _simd_slerp_internal(q0, simd_negate(q1), t); } static SIMD_NOINLINE simd_quatf simd_slerp_longest(simd_quatf q0, simd_quatf q1, float t) { if (simd_dot(q0, q1) >= 0) return _simd_slerp_internal(q0, simd_negate(q1), t); return _simd_slerp_internal(q0, q1, t); } /*! @discussion This function is an implementation detail and you should not * call it directly. It may be removed or modified in future versions of the * simd module. */ static SIMD_NOINLINE simd_quatf _simd_intermediate(simd_quatf q0, simd_quatf q1, simd_quatf q2) { simd_quatf p0 = __tg_log(simd_mul(q0, simd_inverse(q1))); simd_quatf p2 = __tg_log(simd_mul(q2, simd_inverse(q1))); return simd_normalize(simd_mul(q1, __tg_exp(simd_mul(-0.25, simd_add(p0,p2))))); } /*! @discussion This function is an implementation detail and you should not * call it directly. It may be removed or modified in future versions of the * simd module. */ static SIMD_NOINLINE simd_quatf _simd_squad(simd_quatf q0, simd_quatf qa, simd_quatf qb, simd_quatf q1, float t) { simd_quatf r0 = _simd_slerp_internal(q0, q1, t); simd_quatf r1 = _simd_slerp_internal(qa, qb, t); return _simd_slerp_internal(r0, r1, 2*t*(1 - t)); } static SIMD_NOINLINE simd_quatf simd_spline(simd_quatf q0, simd_quatf q1, simd_quatf q2, simd_quatf q3, float t) { simd_quatf qa = _simd_intermediate(q0, q1, q2); simd_quatf qb = _simd_intermediate(q1, q2, q3); return _simd_squad(q1, qa, qb, q2, t); } static SIMD_NOINLINE simd_quatf simd_bezier(simd_quatf q0, simd_quatf q1, simd_quatf q2, simd_quatf q3, float t) { simd_quatf q01 = _simd_slerp_internal(q0, q1, t); simd_quatf q12 = _simd_slerp_internal(q1, q2, t); simd_quatf q23 = _simd_slerp_internal(q2, q3, t); simd_quatf q012 = _simd_slerp_internal(q01, q12, t); simd_quatf q123 = _simd_slerp_internal(q12, q23, t); return _simd_slerp_internal(q012, q123, t); } /* MARK: - C and Objective-C double interfaces */ /*! @abstract Constructs a quaternion from four scalar values. * * @param ix The first component of the imaginary (vector) part. * @param iy The second component of the imaginary (vector) part. * @param iz The third component of the imaginary (vector) part. * * @param r The real (scalar) part. */ static inline SIMD_CFUNC simd_quatd simd_quaternion(double ix, double iy, double iz, double r) { return (simd_quatd){ { ix, iy, iz, r } }; } /*! @abstract Constructs a quaternion from an array of four scalars. * * @discussion Note that the imaginary part of the quaternion comes from * array elements 0, 1, and 2, and the real part comes from element 3. */ static inline SIMD_NONCONST simd_quatd simd_quaternion(const double xyzr[4]) { return (simd_quatd){ *(const simd_packed_double4 *)xyzr }; } /*! @abstract Constructs a quaternion from a four-element vector. * * @discussion Note that the imaginary (vector) part of the quaternion comes * from lanes 0, 1, and 2 of the vector, and the real (scalar) part comes from * lane 3. */ static inline SIMD_CFUNC simd_quatd simd_quaternion(simd_double4 xyzr) { return (simd_quatd){ xyzr }; } /*! @abstract Constructs a quaternion that rotates by `angle` radians about * `axis`. */ static inline SIMD_CFUNC simd_quatd simd_quaternion(double angle, simd_double3 axis); /*! @abstract Construct a quaternion that rotates from one vector to another. * * @param from A normalized three-element vector. * @param to A normalized three-element vector. * * @discussion The rotation axis is `simd_cross(from, to)`. If `from` and * `to` point in opposite directions (to within machine precision), an * arbitrary rotation axis is chosen, and the angle is pi radians. */ static SIMD_NOINLINE simd_quatd simd_quaternion(simd_double3 from, simd_double3 to); /*! @abstract Construct a quaternion from a 3x3 rotation `matrix`. * * @discussion If `matrix` is not orthogonal with determinant 1, the result * is undefined. */ static SIMD_NOINLINE simd_quatd simd_quaternion(simd_double3x3 matrix); /*! @abstract Construct a quaternion from a 4x4 rotation `matrix`. * * @discussion The last row and column of the matrix are ignored. This * function is equivalent to calling simd_quaternion with the upper-left 3x3 * submatrix . */ static SIMD_NOINLINE simd_quatd simd_quaternion(simd_double4x4 matrix); /*! @abstract The real (scalar) part of the quaternion `q`. */ static inline SIMD_CFUNC double simd_real(simd_quatd q) { return q.vector.w; } /*! @abstract The imaginary (vector) part of the quaternion `q`. */ static inline SIMD_CFUNC simd_double3 simd_imag(simd_quatd q) { return q.vector.xyz; } /*! @abstract The angle (in radians) of rotation represented by `q`. */ static inline SIMD_CFUNC double simd_angle(simd_quatd q); /*! @abstract The normalized axis (a 3-element vector) around which the * action of the quaternion `q` rotates. */ static inline SIMD_CFUNC simd_double3 simd_axis(simd_quatd q); /*! @abstract The sum of the quaternions `p` and `q`. */ static inline SIMD_CFUNC simd_quatd simd_add(simd_quatd p, simd_quatd q); /*! @abstract The difference of the quaternions `p` and `q`. */ static inline SIMD_CFUNC simd_quatd simd_sub(simd_quatd p, simd_quatd q); /*! @abstract The product of the quaternions `p` and `q`. */ static inline SIMD_CFUNC simd_quatd simd_mul(simd_quatd p, simd_quatd q); /*! @abstract The quaternion `q` scaled by the real value `a`. */ static inline SIMD_CFUNC simd_quatd simd_mul(simd_quatd q, double a); /*! @abstract The quaternion `q` scaled by the real value `a`. */ static inline SIMD_CFUNC simd_quatd simd_mul(double a, simd_quatd q); /*! @abstract The conjugate of the quaternion `q`. */ static inline SIMD_CFUNC simd_quatd simd_conjugate(simd_quatd q); /*! @abstract The (multiplicative) inverse of the quaternion `q`. */ static inline SIMD_CFUNC simd_quatd simd_inverse(simd_quatd q); /*! @abstract The negation (additive inverse) of the quaternion `q`. */ static inline SIMD_CFUNC simd_quatd simd_negate(simd_quatd q); /*! @abstract The dot product of the quaternions `p` and `q` interpreted as * four-dimensional vectors. */ static inline SIMD_CFUNC double simd_dot(simd_quatd p, simd_quatd q); /*! @abstract The length of the quaternion `q`. */ static inline SIMD_CFUNC double simd_length(simd_quatd q); /*! @abstract The unit quaternion obtained by normalizing `q`. */ static inline SIMD_CFUNC simd_quatd simd_normalize(simd_quatd q); /*! @abstract Rotates the vector `v` by the quaternion `q`. */ static inline SIMD_CFUNC simd_double3 simd_act(simd_quatd q, simd_double3 v); /*! @abstract Logarithm of the quaternion `q`. * @discussion Do not call this function directly; use `log(q)` instead. * * We can write a quaternion `q` in the form: `r(cos(t) + sin(t)v)` where * `r` is the length of `q`, `t` is an angle, and `v` is a unit 3-vector. * The logarithm of `q` is `log(r) + tv`, just like the logarithm of the * complex number `r*(cos(t) + i sin(t))` is `log(r) + it`. * * Note that this function is not robust against poorly-scaled non-unit * quaternions, because it is primarily used for spline interpolation of * unit quaternions. If you need to compute a robust logarithm of general * quaternions, you can use the following approach: * * scale = simd_reduce_max(simd_abs(q.vector)); * logq = log(simd_recip(scale)*q); * logq.real += log(scale); * return logq; */ static SIMD_NOINLINE simd_quatd __tg_log(simd_quatd q); /*! @abstract Inverse of `log( )`; the exponential map on quaternions. * @discussion Do not call this function directly; use `exp(q)` instead. */ static SIMD_NOINLINE simd_quatd __tg_exp(simd_quatd q); /*! @abstract Spherical linear interpolation along the shortest arc between * quaternions `q0` and `q1`. */ static SIMD_NOINLINE simd_quatd simd_slerp(simd_quatd q0, simd_quatd q1, double t); /*! @abstract Spherical linear interpolation along the longest arc between * quaternions `q0` and `q1`. */ static SIMD_NOINLINE simd_quatd simd_slerp_longest(simd_quatd q0, simd_quatd q1, double t); /*! @abstract Interpolate between quaternions along a spherical cubic spline. * * @discussion The function interpolates between q1 and q2. q0 is the left * endpoint of the previous interval, and q3 is the right endpoint of the next * interval. Use this function to smoothly interpolate between a sequence of * rotations. */ static SIMD_NOINLINE simd_quatd simd_spline(simd_quatd q0, simd_quatd q1, simd_quatd q2, simd_quatd q3, double t); /*! @abstract Spherical cubic Bezier interpolation between quaternions. * * @discussion The function treats q0 ... q3 as control points and uses slerp * in place of lerp in the De Castlejeau algorithm. The endpoints of * interpolation are thus q0 and q3, and the curve will not generally pass * through q1 or q2. Note that the convex hull property of "standard" Bezier * curve does not hold on the sphere. */ static SIMD_NOINLINE simd_quatd simd_bezier(simd_quatd q0, simd_quatd q1, simd_quatd q2, simd_quatd q3, double t); #ifdef __cplusplus } /* extern "C" */ /* MARK: - C++ double interfaces */ namespace simd { struct quatd : ::simd_quatd { /*! @abstract The identity quaternion. */ quatd( ) : ::simd_quatd(::simd_quaternion((double4){0,0,0,1})) { } /*! @abstract Constructs a C++ quaternion from a C quaternion. */ quatd(::simd_quatd q) : ::simd_quatd(q) { } /*! @abstract Constructs a quaternion from components. */ quatd(double ix, double iy, double iz, double r) : ::simd_quatd(::simd_quaternion(ix, iy, iz, r)) { } /*! @abstract Constructs a quaternion from an array of scalars. */ quatd(const double xyzr[4]) : ::simd_quatd(::simd_quaternion(xyzr)) { } /*! @abstract Constructs a quaternion from a vector. */ quatd(double4 xyzr) : ::simd_quatd(::simd_quaternion(xyzr)) { } /*! @abstract Quaternion representing rotation about `axis` by `angle` * radians. */ quatd(double angle, double3 axis) : ::simd_quatd(::simd_quaternion(angle, axis)) { } /*! @abstract Quaternion that rotates `from` into `to`. */ quatd(double3 from, double3 to) : ::simd_quatd(::simd_quaternion(from, to)) { } /*! @abstract Constructs a quaternion from a rotation matrix. */ quatd(::simd_double3x3 matrix) : ::simd_quatd(::simd_quaternion(matrix)) { } /*! @abstract Constructs a quaternion from a rotation matrix. */ quatd(::simd_double4x4 matrix) : ::simd_quatd(::simd_quaternion(matrix)) { } /*! @abstract The real (scalar) part of the quaternion. */ double real(void) const { return ::simd_real(*this); } /*! @abstract The imaginary (vector) part of the quaternion. */ double3 imag(void) const { return ::simd_imag(*this); } /*! @abstract The angle the quaternion rotates by. */ double angle(void) const { return ::simd_angle(*this); } /*! @abstract The axis the quaternion rotates about. */ double3 axis(void) const { return ::simd_axis(*this); } /*! @abstract The length of the quaternion. */ double length(void) const { return ::simd_length(*this); } /*! @abstract Act on the vector `v` by rotation. */ double3 operator()(const ::simd_double3 v) const { return ::simd_act(*this, v); } }; static SIMD_CPPFUNC quatd operator+(const ::simd_quatd p, const ::simd_quatd q) { return ::simd_add(p, q); } static SIMD_CPPFUNC quatd operator-(const ::simd_quatd p, const ::simd_quatd q) { return ::simd_sub(p, q); } static SIMD_CPPFUNC quatd operator-(const ::simd_quatd p) { return ::simd_negate(p); } static SIMD_CPPFUNC quatd operator*(const double r, const ::simd_quatd p) { return ::simd_mul(r, p); } static SIMD_CPPFUNC quatd operator*(const ::simd_quatd p, const double r) { return ::simd_mul(p, r); } static SIMD_CPPFUNC quatd operator*(const ::simd_quatd p, const ::simd_quatd q) { return ::simd_mul(p, q); } static SIMD_CPPFUNC quatd operator/(const ::simd_quatd p, const ::simd_quatd q) { return ::simd_mul(p, ::simd_inverse(q)); } static SIMD_INLINE SIMD_NODEBUG quatd operator+=(quatd &p, const ::simd_quatd q) { return p = p+q; } static SIMD_INLINE SIMD_NODEBUG quatd operator-=(quatd &p, const ::simd_quatd q) { return p = p-q; } static SIMD_INLINE SIMD_NODEBUG quatd operator*=(quatd &p, const double r) { return p = p*r; } static SIMD_INLINE SIMD_NODEBUG quatd operator*=(quatd &p, const ::simd_quatd q) { return p = p*q; } static SIMD_INLINE SIMD_NODEBUG quatd operator/=(quatd &p, const ::simd_quatd q) { return p = p/q; } /*! @abstract The conjugate of the quaternion `q`. */ static SIMD_CPPFUNC quatd conjugate(const ::simd_quatd p) { return ::simd_conjugate(p); } /*! @abstract The (multiplicative) inverse of the quaternion `q`. */ static SIMD_CPPFUNC quatd inverse(const ::simd_quatd p) { return ::simd_inverse(p); } /*! @abstract The dot product of the quaternions `p` and `q` interpreted as * four-dimensional vectors. */ static SIMD_CPPFUNC double dot(const ::simd_quatd p, const ::simd_quatd q) { return ::simd_dot(p, q); } /*! @abstract The unit quaternion obtained by normalizing `q`. */ static SIMD_CPPFUNC quatd normalize(const ::simd_quatd p) { return ::simd_normalize(p); } /*! @abstract logarithm of the quaternion `q`. */ static SIMD_CPPFUNC quatd log(const ::simd_quatd q) { return ::__tg_log(q); } /*! @abstract exponential map of quaterion `q`. */ static SIMD_CPPFUNC quatd exp(const ::simd_quatd q) { return ::__tg_exp(q); } /*! @abstract Spherical linear interpolation along the shortest arc between * quaternions `q0` and `q1`. */ static SIMD_CPPFUNC quatd slerp(const ::simd_quatd p0, const ::simd_quatd p1, double t) { return ::simd_slerp(p0, p1, t); } /*! @abstract Spherical linear interpolation along the longest arc between * quaternions `q0` and `q1`. */ static SIMD_CPPFUNC quatd slerp_longest(const ::simd_quatd p0, const ::simd_quatd p1, double t) { return ::simd_slerp_longest(p0, p1, t); } /*! @abstract Interpolate between quaternions along a spherical cubic spline. * * @discussion The function interpolates between q1 and q2. q0 is the left * endpoint of the previous interval, and q3 is the right endpoint of the next * interval. Use this function to smoothly interpolate between a sequence of * rotations. */ static SIMD_CPPFUNC quatd spline(const ::simd_quatd p0, const ::simd_quatd p1, const ::simd_quatd p2, const ::simd_quatd p3, double t) { return ::simd_spline(p0, p1, p2, p3, t); } /*! @abstract Spherical cubic Bezier interpolation between quaternions. * * @discussion The function treats q0 ... q3 as control points and uses slerp * in place of lerp in the De Castlejeau algorithm. The endpoints of * interpolation are thus q0 and q3, and the curve will not generally pass * through q1 or q2. Note that the convex hull property of "standard" Bezier * curve does not hold on the sphere. */ static SIMD_CPPFUNC quatd bezier(const ::simd_quatd p0, const ::simd_quatd p1, const ::simd_quatd p2, const ::simd_quatd p3, double t) { return ::simd_bezier(p0, p1, p2, p3, t); } } extern "C" { #endif /* __cplusplus */ /* MARK: - double implementations */ #include #include /* tg_promote is implementation gobbledygook that enables the compile-time * dispatching in tgmath.h to work its magic. */ static simd_quatd __attribute__((__overloadable__)) __tg_promote(simd_quatd); /*! @abstract Constructs a quaternion from imaginary and real parts. * @discussion This function is hidden behind an underscore to avoid confusion * with the angle-axis constructor. */ static inline SIMD_CFUNC simd_quatd _simd_quaternion(simd_double3 imag, double real) { return simd_quaternion(simd_make_double4(imag, real)); } static inline SIMD_CFUNC simd_quatd simd_quaternion(double angle, simd_double3 axis) { return _simd_quaternion(sin(angle/2) * axis, cos(angle/2)); } static inline SIMD_CFUNC double simd_angle(simd_quatd q) { return 2*atan2(simd_length(q.vector.xyz), q.vector.w); } static inline SIMD_CFUNC simd_double3 simd_axis(simd_quatd q) { return simd_normalize(q.vector.xyz); } static inline SIMD_CFUNC simd_quatd simd_add(simd_quatd p, simd_quatd q) { return simd_quaternion(p.vector + q.vector); } static inline SIMD_CFUNC simd_quatd simd_sub(simd_quatd p, simd_quatd q) { return simd_quaternion(p.vector - q.vector); } static inline SIMD_CFUNC simd_quatd simd_mul(simd_quatd p, simd_quatd q) { #pragma STDC FP_CONTRACT ON return simd_quaternion((p.vector.x * __builtin_shufflevector(q.vector, -q.vector, 3,6,1,4) + p.vector.y * __builtin_shufflevector(q.vector, -q.vector, 2,3,4,5)) + (p.vector.z * __builtin_shufflevector(q.vector, -q.vector, 5,0,3,6) + p.vector.w * q.vector)); } static inline SIMD_CFUNC simd_quatd simd_mul(simd_quatd q, double a) { return simd_quaternion(a * q.vector); } static inline SIMD_CFUNC simd_quatd simd_mul(double a, simd_quatd q) { return simd_mul(q,a); } static inline SIMD_CFUNC simd_quatd simd_conjugate(simd_quatd q) { return simd_quaternion(q.vector * (simd_double4){-1,-1,-1, 1}); } static inline SIMD_CFUNC simd_quatd simd_inverse(simd_quatd q) { return simd_quaternion(simd_conjugate(q).vector * simd_recip(simd_length_squared(q.vector))); } static inline SIMD_CFUNC simd_quatd simd_negate(simd_quatd q) { return simd_quaternion(-q.vector); } static inline SIMD_CFUNC double simd_dot(simd_quatd p, simd_quatd q) { return simd_dot(p.vector, q.vector); } static inline SIMD_CFUNC double simd_length(simd_quatd q) { return simd_length(q.vector); } static inline SIMD_CFUNC simd_quatd simd_normalize(simd_quatd q) { double length_squared = simd_length_squared(q.vector); if (length_squared == 0) { return simd_quaternion((simd_double4){0,0,0,1}); } return simd_quaternion(q.vector * simd_rsqrt(length_squared)); } #if defined __arm__ || defined __arm64__ /*! @abstract Multiplies the vector `v` by the quaternion `q`. * * @discussion This IS NOT the action of `q` on `v` (i.e. this is not rotation * by `q`. That operation is provided by `simd_act(q, v)`. This function is an * implementation detail and you should not call it directly. It may be * removed or modified in future versions of the simd module. */ static inline SIMD_CFUNC simd_quatd _simd_mul_vq(simd_double3 v, simd_quatd q) { #pragma STDC FP_CONTRACT ON return simd_quaternion(v.x * __builtin_shufflevector(q.vector, -q.vector, 3,6,1,4) + v.y * __builtin_shufflevector(q.vector, -q.vector, 2,3,4,5) + v.z * __builtin_shufflevector(q.vector, -q.vector, 5,0,3,6)); } #endif static inline SIMD_CFUNC simd_double3 simd_act(simd_quatd q, simd_double3 v) { #if defined __arm__ || defined __arm64__ return simd_mul(q, _simd_mul_vq(v, simd_conjugate(q))).vector.xyz; #else #pragma STDC FP_CONTRACT ON simd_double3 t = 2*simd_cross(simd_imag(q),v); return v + simd_real(q)*t + simd_cross(simd_imag(q), t); #endif } static SIMD_NOINLINE simd_quatd __tg_log(simd_quatd q) { double real = __tg_log(simd_length_squared(q.vector))/2; if (simd_equal(simd_imag(q), 0)) return _simd_quaternion(0, real); simd_double3 imag = __tg_acos(simd_real(q)/simd_length(q)) * simd_normalize(simd_imag(q)); return _simd_quaternion(imag, real); } static SIMD_NOINLINE simd_quatd __tg_exp(simd_quatd q) { // angle is actually *twice* the angle of the rotation corresponding to // the resulting quaternion, which is why we don't simply use the (angle, // axis) constructor to generate `unit`. double angle = simd_length(simd_imag(q)); if (angle == 0) return _simd_quaternion(0, exp(simd_real(q))); simd_double3 axis = simd_normalize(simd_imag(q)); simd_quatd unit = _simd_quaternion(sin(angle)*axis, cosf(angle)); return simd_mul(exp(simd_real(q)), unit); } /*! @abstract Implementation detail of the `simd_quaternion(from, to)` * initializer. * * @discussion Computes the quaternion rotation `from` to `to` if they are * separated by less than 90 degrees. Not numerically stable for larger * angles. This function is an implementation detail and you should not * call it directly. It may be removed or modified in future versions of the * simd module. */ static inline SIMD_CFUNC simd_quatd _simd_quaternion_reduced(simd_double3 from, simd_double3 to) { simd_double3 half = simd_normalize(from + to); return _simd_quaternion(simd_cross(from, half), simd_dot(from, half)); } static SIMD_NOINLINE simd_quatd simd_quaternion(simd_double3 from, simd_double3 to) { // If the angle between from and to is not too big, we can compute the // rotation accurately using a simple implementation. if (simd_dot(from, to) >= 0) { return _simd_quaternion_reduced(from, to); } // Because from and to are more than 90 degrees apart, we compute the // rotation in two stages (from -> half), (half -> to) to preserve numerical // accuracy. simd_double3 half = simd_normalize(from) + simd_normalize(to); if (simd_length_squared(half) <= 0x1p-104) { // half is nearly zero, so from and to point in nearly opposite directions // and the rotation is numerically underspecified. Pick an axis orthogonal // to the vectors, and use an angle of pi radians. simd_double3 abs_from = simd_abs(from); if (abs_from.x <= abs_from.y && abs_from.x <= abs_from.z) return _simd_quaternion(simd_normalize(simd_cross(from, (simd_double3){1,0,0})), 0.f); else if (abs_from.y <= abs_from.z) return _simd_quaternion(simd_normalize(simd_cross(from, (simd_double3){0,1,0})), 0.f); else return _simd_quaternion(simd_normalize(simd_cross(from, (simd_double3){0,0,1})), 0.f); } // Compute the two-step rotation. */ half = simd_normalize(half); return simd_mul(_simd_quaternion_reduced(from, half), _simd_quaternion_reduced(half, to)); } static SIMD_NOINLINE simd_quatd simd_quaternion(simd_double3x3 matrix) { const simd_double3 *mat = matrix.columns; double trace = mat[0][0] + mat[1][1] + mat[2][2]; if (trace >= 0.0) { double r = 2*sqrt(1 + trace); double rinv = simd_recip(r); return simd_quaternion(rinv*(mat[1][2] - mat[2][1]), rinv*(mat[2][0] - mat[0][2]), rinv*(mat[0][1] - mat[1][0]), r/4); } else if (mat[0][0] >= mat[1][1] && mat[0][0] >= mat[2][2]) { double r = 2*sqrt(1 - mat[1][1] - mat[2][2] + mat[0][0]); double rinv = simd_recip(r); return simd_quaternion(r/4, rinv*(mat[0][1] + mat[1][0]), rinv*(mat[0][2] + mat[2][0]), rinv*(mat[1][2] - mat[2][1])); } else if (mat[1][1] >= mat[2][2]) { double r = 2*sqrt(1 - mat[0][0] - mat[2][2] + mat[1][1]); double rinv = simd_recip(r); return simd_quaternion(rinv*(mat[0][1] + mat[1][0]), r/4, rinv*(mat[1][2] + mat[2][1]), rinv*(mat[2][0] - mat[0][2])); } else { double r = 2*sqrt(1 - mat[0][0] - mat[1][1] + mat[2][2]); double rinv = simd_recip(r); return simd_quaternion(rinv*(mat[0][2] + mat[2][0]), rinv*(mat[1][2] + mat[2][1]), r/4, rinv*(mat[0][1] - mat[1][0])); } } static SIMD_NOINLINE simd_quatd simd_quaternion(simd_double4x4 matrix) { const simd_double4 *mat = matrix.columns; double trace = mat[0][0] + mat[1][1] + mat[2][2]; if (trace >= 0.0) { double r = 2*sqrt(1 + trace); double rinv = simd_recip(r); return simd_quaternion(rinv*(mat[1][2] - mat[2][1]), rinv*(mat[2][0] - mat[0][2]), rinv*(mat[0][1] - mat[1][0]), r/4); } else if (mat[0][0] >= mat[1][1] && mat[0][0] >= mat[2][2]) { double r = 2*sqrt(1 - mat[1][1] - mat[2][2] + mat[0][0]); double rinv = simd_recip(r); return simd_quaternion(r/4, rinv*(mat[0][1] + mat[1][0]), rinv*(mat[0][2] + mat[2][0]), rinv*(mat[1][2] - mat[2][1])); } else if (mat[1][1] >= mat[2][2]) { double r = 2*sqrt(1 - mat[0][0] - mat[2][2] + mat[1][1]); double rinv = simd_recip(r); return simd_quaternion(rinv*(mat[0][1] + mat[1][0]), r/4, rinv*(mat[1][2] + mat[2][1]), rinv*(mat[2][0] - mat[0][2])); } else { double r = 2*sqrt(1 - mat[0][0] - mat[1][1] + mat[2][2]); double rinv = simd_recip(r); return simd_quaternion(rinv*(mat[0][2] + mat[2][0]), rinv*(mat[1][2] + mat[2][1]), r/4, rinv*(mat[0][1] - mat[1][0])); } } /*! @abstract The angle between p and q interpreted as 4-dimensional vectors. * * @discussion This function is an implementation detail and you should not * call it directly. It may be removed or modified in future versions of the * simd module. */ static SIMD_NOINLINE double _simd_angle(simd_quatd p, simd_quatd q) { return 2*atan2(simd_length(p.vector - q.vector), simd_length(p.vector + q.vector)); } /*! @abstract sin(x)/x. * * @discussion This function is an implementation detail and you should not * call it directly. It may be removed or modified in future versions of the * simd module. */ static SIMD_CFUNC double _simd_sinc(double x) { if (x == 0) return 1; return sin(x)/x; } /*! @abstract Spherical lerp between q0 and q1. * * @discussion This function may interpolate along either the longer or * shorter path between q0 and q1; it is used as an implementation detail * in `simd_slerp` and `simd_slerp_longest`; you should use those functions * instead of calling this directly. */ static SIMD_NOINLINE simd_quatd _simd_slerp_internal(simd_quatd q0, simd_quatd q1, double t) { double s = 1 - t; double a = _simd_angle(q0, q1); double r = simd_recip(_simd_sinc(a)); return simd_normalize(simd_quaternion(_simd_sinc(s*a)*r*s*q0.vector + _simd_sinc(t*a)*r*t*q1.vector)); } static SIMD_NOINLINE simd_quatd simd_slerp(simd_quatd q0, simd_quatd q1, double t) { if (simd_dot(q0, q1) >= 0) return _simd_slerp_internal(q0, q1, t); return _simd_slerp_internal(q0, simd_negate(q1), t); } static SIMD_NOINLINE simd_quatd simd_slerp_longest(simd_quatd q0, simd_quatd q1, double t) { if (simd_dot(q0, q1) >= 0) return _simd_slerp_internal(q0, simd_negate(q1), t); return _simd_slerp_internal(q0, q1, t); } /*! @discussion This function is an implementation detail and you should not * call it directly. It may be removed or modified in future versions of the * simd module. */ static SIMD_NOINLINE simd_quatd _simd_intermediate(simd_quatd q0, simd_quatd q1, simd_quatd q2) { simd_quatd p0 = __tg_log(simd_mul(q0, simd_inverse(q1))); simd_quatd p2 = __tg_log(simd_mul(q2, simd_inverse(q1))); return simd_normalize(simd_mul(q1, __tg_exp(simd_mul(-0.25, simd_add(p0,p2))))); } /*! @discussion This function is an implementation detail and you should not * call it directly. It may be removed or modified in future versions of the * simd module. */ static SIMD_NOINLINE simd_quatd _simd_squad(simd_quatd q0, simd_quatd qa, simd_quatd qb, simd_quatd q1, double t) { simd_quatd r0 = _simd_slerp_internal(q0, q1, t); simd_quatd r1 = _simd_slerp_internal(qa, qb, t); return _simd_slerp_internal(r0, r1, 2*t*(1 - t)); } static SIMD_NOINLINE simd_quatd simd_spline(simd_quatd q0, simd_quatd q1, simd_quatd q2, simd_quatd q3, double t) { simd_quatd qa = _simd_intermediate(q0, q1, q2); simd_quatd qb = _simd_intermediate(q1, q2, q3); return _simd_squad(q1, qa, qb, q2, t); } static SIMD_NOINLINE simd_quatd simd_bezier(simd_quatd q0, simd_quatd q1, simd_quatd q2, simd_quatd q3, double t) { simd_quatd q01 = _simd_slerp_internal(q0, q1, t); simd_quatd q12 = _simd_slerp_internal(q1, q2, t); simd_quatd q23 = _simd_slerp_internal(q2, q3, t); simd_quatd q012 = _simd_slerp_internal(q01, q12, t); simd_quatd q123 = _simd_slerp_internal(q12, q23, t); return _simd_slerp_internal(q012, q123, t); } #ifdef __cplusplus } /* extern "C" */ #endif /* __cplusplus */ #endif /* SIMD_COMPILER_HAS_REQUIRED_FEATURES */ #endif /* SIMD_QUATERNIONS */