#include <stdbool.h>
#ifndef M_PI_F
-#define M_PI_F (3.14159265358979323846f)
+#define M_PI_F (3.14159265358979323846f)
+#define M_1_PI_F (0.31830988618379067154f)
+#define M_PI_2_F (1.57079632679f)
#endif
q.w = cosf(angle/2);
return q;
}
+
+// fwd declare, needed in some ctors
+static inline struct quat qnormalize(struct quat q);
+
// construct a quaternion such that q * a = b,
// and the rotation axis is orthogonal to the plane defined by a and b,
// and the rotation is less than 180 degrees.
}
float const halfcos = 0.5f * cosangle;
// since angle is < 180deg, the positive sqrt is always correct
- float const sinhalfangle = sqrtf(0.5f - halfcos);
- float const coshalfangle = sqrtf(0.5f + halfcos);
+ float const sinhalfangle = sqrtf(fmax(0.5f - halfcos, 0.0f));
+ float const coshalfangle = sqrtf(fmax(0.5f + halfcos, 0.0f));
struct vec const qimag = vscl(sinhalfangle / sinangle, cross);
float const qreal = coshalfangle;
return quatvw(qimag, qreal);
return quatvw(vscl(w/2, rpy), w);
}
}
+// construct quaternion from orthonormal matrix.
+static inline struct quat mat2quat(struct mat33 m) {
+ float w = sqrtf(fmax(0.0f, 1.0f + m.m[0][0] + m.m[1][1] + m.m[2][2])) / 2.0f;
+ float x = sqrtf(fmax(0.0f, 1.0f + m.m[0][0] - m.m[1][1] - m.m[2][2])) / 2.0f;
+ float y = sqrtf(fmax(0.0f, 1.0f - m.m[0][0] + m.m[1][1] - m.m[2][2])) / 2.0f;
+ float z = sqrtf(fmax(0.0f, 1.0f - m.m[0][0] - m.m[1][1] + m.m[2][2])) / 2.0f;
+ x = copysign(x, m.m[2][1] - m.m[1][2]);
+ y = copysign(y, m.m[0][2] - m.m[2][0]);
+ z = copysign(z, m.m[1][0] - m.m[0][1]);
+ return mkquat(x, y, z, w);
+}
//
// conversions to other parameterizations of 3D rotations
return vscl(s, mkvec(q.x, q.y, q.z));
}
// compute the angle of a quaternion's axis-angle decomposition.
+// result lies in domain (-pi, pi].
static inline float quat2angle(struct quat q) {
- return 2 * acosf(q.w);
+ float angle = 2 * acosf(q.w);
+ if (angle > M_PI_F) {
+ angle -= 2.0f * M_PI_F;
+ }
+ return angle;
}
// vector containing the imaginary part of the quaternion, i.e. (x, y, z)
static inline struct vec quatimagpart(struct quat q) {
static inline struct quat qneg(struct quat q) {
return mkquat(-q.x, -q.y, -q.z, -q.w);
}
+static inline struct quat qposreal(struct quat q) {
+ if (q.w < 0) return qneg(q);
+ return q;
+}
// quaternion dot product. is cosine of angle between them.
static inline float qdot(struct quat a, struct quat b) {
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
}
static inline float qanglebetween(struct quat a, struct quat b) {
- return acosf(qdot(a, b));
+ float const dot = qdot(qposreal(a), qposreal(b));
+ if (dot > 1.0f - 1e9f) return 0.0f;
+ if (dot < -1.0f + 1e9f) return M_PI_F;
+ return acosf(dot);
}
static inline bool qeq(struct quat a, struct quat b) {
return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w;
return mkvec(randu(-1.0f, 1.0f), randu(-1.0f, 1.0f), randu(-1.0f, 1.0f));
}
+// returns a random quaternion not necessarily uniformly sampled
+struct quat randquat() {
+ struct quat q = mkquat(
+ randu(-1.0f, 1.0f), randu(-1.0f, 1.0f), randu(-1.0f, 1.0f), randu(-1.0f, 1.0f));
+ return qnormalize(q);
+}
+
void printvec(struct vec v) {
printf("%f, %f, %f", (double)v.x, (double)v.y, (double)v.z);
}
printf("%s passed\n", __func__);
}
+void test_quat_mat_conversions()
+{
+ srand(0); // deterministic
+ int const N = 10000;
+ for (int i = 0; i < N; ++i) {
+ struct quat const q = randquat();
+ struct mat33 const m = quat2rotmat(q);
+ struct quat const qq = mat2quat(m);
+ float const angle = qanglebetween(q, qq);
+ assert(fabs(angle) < radians(1e-1));
+ }
+ printf("%s passed\n", __func__);
+}
+
void test_qvectovec()
{
srand(0); // deterministic
struct quat const q = qvectovec(a, b);
struct vec const qa = qvrot(q, a);
ASSERT_VEQ_EPSILON(qa, b, 0.00001f);
+
+ struct vec cross = vcross(a, b);
+ struct vec const qcross = qvrot(q, cross);
+ ASSERT_VEQ_EPSILON(qcross, cross, 0.00001f);
}
printf("%s passed\n", __func__);
}
typedef void (*voidvoid_fn)(void);
voidvoid_fn test_fns[] = {
test_quat_rpy_conversions,
+ test_quat_mat_conversions,
test_qvectovec,
};