Skip to content

File syn_quaternion.c

File List > src > syntropic > util > syn_quaternion.c

Go to the documentation of this file

#include "syn_quaternion.h"

#include "syn_assert.h"

void syn_quat_identity(SYN_Quaternion *q)
{
    SYN_ASSERT(q != NULL);
    q->w = Q16_ONE;
    q->x = 0;
    q->y = 0;
    q->z = 0;
}

void syn_quat_init(SYN_Quaternion *q, q16_t w, q16_t x, q16_t y, q16_t z)
{
    SYN_ASSERT(q != NULL);
    q->w = w;
    q->x = x;
    q->y = y;
    q->z = z;
}

void syn_quat_mul(const SYN_Quaternion *q1, const SYN_Quaternion *q2, SYN_Quaternion *out)
{
    SYN_ASSERT(q1 != NULL && q2 != NULL && out != NULL);
    if (q1 == NULL || q2 == NULL || out == NULL)
        return;

    int64_t w1 = q1->w, x1 = q1->x, y1 = q1->y, z1 = q1->z;

    int64_t w2 = q2->w, x2 = q2->x, y2 = q2->y, z2 = q2->z;

    int64_t w = (w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2) >> Q16_SHIFT;
    int64_t x = (w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2) >> Q16_SHIFT;
    int64_t y = (w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2) >> Q16_SHIFT;
    int64_t z = (w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2) >> Q16_SHIFT;

    out->w = (q16_t)w;
    out->x = (q16_t)x;
    out->y = (q16_t)y;
    out->z = (q16_t)z;
}

q16_t syn_quat_norm(const SYN_Quaternion *q)
{
    SYN_ASSERT(q != NULL);
    int64_t sum =
        (int64_t)q->w * q->w + (int64_t)q->x * q->x + (int64_t)q->y * q->y + (int64_t)q->z * q->z;
    q16_t sum_q16 = (q16_t)(sum >> Q16_SHIFT);
    return q16_sqrt(sum_q16);
}

SYN_Status syn_quat_normalize(SYN_Quaternion *q)
{
    SYN_ASSERT(q != NULL);
    int64_t sum =
        (int64_t)q->w * q->w + (int64_t)q->x * q->x + (int64_t)q->y * q->y + (int64_t)q->z * q->z;
    q16_t norm_sq = (q16_t)(sum >> Q16_SHIFT);
    if (norm_sq == 0)
        return SYN_ERROR;

    q16_t inv_norm = q16_rsqrt(norm_sq);
    q->w = q16_mul(q->w, inv_norm);
    q->x = q16_mul(q->x, inv_norm);
    q->y = q16_mul(q->y, inv_norm);
    q->z = q16_mul(q->z, inv_norm);
    return SYN_OK;
}

void syn_quat_conjugate(const SYN_Quaternion *q, SYN_Quaternion *out)
{
    SYN_ASSERT(q != NULL && out != NULL);
    if (q == NULL || out == NULL)
        return;
    out->w = q->w;

    out->x = -q->x;
    out->y = -q->y;
    out->z = -q->z;
}

SYN_Status syn_quat_inverse(const SYN_Quaternion *q, SYN_Quaternion *out)
{
    SYN_ASSERT(q != NULL && out != NULL);
    int64_t norm_sq =
        (int64_t)q->w * q->w + (int64_t)q->x * q->x + (int64_t)q->y * q->y + (int64_t)q->z * q->z;
    q16_t n2 = (q16_t)(norm_sq >> Q16_SHIFT);
    if (n2 == 0)
        return SYN_ERROR;

    out->w = q16_div(q->w, n2);
    out->x = q16_div(-q->x, n2);
    out->y = q16_div(-q->y, n2);
    out->z = q16_div(-q->z, n2);
    return SYN_OK;
}

void syn_quat_rotate_vec3(const SYN_Quaternion *q, const q16_t *v, q16_t *out)
{
    SYN_ASSERT(q != NULL && v != NULL && out != NULL);

    SYN_Quaternion v_quat;
    syn_quat_init(&v_quat, 0, v[0], v[1], v[2]);

    SYN_Quaternion q_conj, tmp;
    syn_quat_conjugate(q, &q_conj);

    syn_quat_mul(q, &v_quat, &tmp);
    syn_quat_mul(&tmp, &q_conj, &v_quat);

    out[0] = v_quat.x;
    out[1] = v_quat.y;
    out[2] = v_quat.z;
}

void syn_quat_to_mat3x3(const SYN_Quaternion *q, SYN_Matrix *out)
{
    SYN_ASSERT(q != NULL && out != NULL);
    SYN_ASSERT(out->rows == 3 && out->cols == 3);

    q16_t w = q->w, x = q->x, y = q->y, z = q->z;

    q16_t xx = (q16_t)(((int64_t)x * x) >> Q16_SHIFT);
    q16_t yy = (q16_t)(((int64_t)y * y) >> Q16_SHIFT);
    q16_t zz = (q16_t)(((int64_t)z * z) >> Q16_SHIFT);
    q16_t xy = (q16_t)(((int64_t)x * y) >> Q16_SHIFT);
    q16_t xz = (q16_t)(((int64_t)x * z) >> Q16_SHIFT);
    q16_t yz = (q16_t)(((int64_t)y * z) >> Q16_SHIFT);
    q16_t wx = (q16_t)(((int64_t)w * x) >> Q16_SHIFT);
    q16_t wy = (q16_t)(((int64_t)w * y) >> Q16_SHIFT);
    q16_t wz = (q16_t)(((int64_t)w * z) >> Q16_SHIFT);

    SYN_MAT_AT(out, 0, 0) = Q16_ONE - (yy + zz) * 2;
    SYN_MAT_AT(out, 0, 1) = (xy - wz) * 2;
    SYN_MAT_AT(out, 0, 2) = (xz + wy) * 2;

    SYN_MAT_AT(out, 1, 0) = (xy + wz) * 2;
    SYN_MAT_AT(out, 1, 1) = Q16_ONE - (xx + zz) * 2;
    SYN_MAT_AT(out, 1, 2) = (yz - wx) * 2;

    SYN_MAT_AT(out, 2, 0) = (xz - wy) * 2;
    SYN_MAT_AT(out, 2, 1) = (yz + wx) * 2;
    SYN_MAT_AT(out, 2, 2) = Q16_ONE - (xx + yy) * 2;
}

void syn_quat_from_euler(SYN_Quaternion *q, q16_t roll, q16_t pitch, q16_t yaw)
{
    SYN_ASSERT(q != NULL);

    q16_t cr = q16_cos(roll / 2);
    q16_t sr = q16_sin(roll / 2);
    q16_t cp = q16_cos(pitch / 2);
    q16_t sp = q16_sin(pitch / 2);
    q16_t cy = q16_cos(yaw / 2);
    q16_t sy = q16_sin(yaw / 2);

    int64_t w = ((int64_t)cr * cp >> Q16_SHIFT) * cy >> Q16_SHIFT;
    w += ((int64_t)sr * sp >> Q16_SHIFT) * sy >> Q16_SHIFT;

    int64_t x = ((int64_t)sr * cp >> Q16_SHIFT) * cy >> Q16_SHIFT;
    x -= ((int64_t)cr * sp >> Q16_SHIFT) * sy >> Q16_SHIFT;

    int64_t y = ((int64_t)cr * sp >> Q16_SHIFT) * cy >> Q16_SHIFT;
    y += ((int64_t)sr * cp >> Q16_SHIFT) * sy >> Q16_SHIFT;

    int64_t z = ((int64_t)cr * cp >> Q16_SHIFT) * sy >> Q16_SHIFT;
    z -= ((int64_t)sr * sp >> Q16_SHIFT) * cy >> Q16_SHIFT;

    q->w = (q16_t)w;
    q->x = (q16_t)x;
    q->y = (q16_t)y;
    q->z = (q16_t)z;
}

void syn_quat_to_euler(const SYN_Quaternion *q, q16_t *roll, q16_t *pitch, q16_t *yaw)
{
    SYN_ASSERT(q != NULL && roll != NULL && pitch != NULL && yaw != NULL);

    q16_t w = q->w, x = q->x, y = q->y, z = q->z;

    /* Roll (x-axis rotation) */
    int64_t sinr_cosp = 2 * (((int64_t)w * x + (int64_t)y * z) >> Q16_SHIFT);
    int64_t cosr_cosp = Q16_ONE - 2 * (((int64_t)x * x + (int64_t)y * y) >> Q16_SHIFT);
    *roll = q16_atan2((q16_t)sinr_cosp, (q16_t)cosr_cosp);

    /* Pitch (y-axis rotation) */
    int64_t sinp = 2 * (((int64_t)w * y - (int64_t)z * x) >> Q16_SHIFT);
    if (q16_abs((q16_t)sinp) >= Q16_ONE)
        *pitch = ((q16_t)sinp > 0) ? Q16_PI_2 : -Q16_PI_2;
    else
        *pitch = q16_asin((q16_t)sinp);

    /* Yaw (z-axis rotation) */
    int64_t siny_cosp = 2 * (((int64_t)w * z + (int64_t)x * y) >> Q16_SHIFT);
    int64_t cosy_cosp = Q16_ONE - 2 * (((int64_t)y * y + (int64_t)z * z) >> Q16_SHIFT);
    *yaw = q16_atan2((q16_t)siny_cosp, (q16_t)cosy_cosp);
}

void syn_quat_slerp(const SYN_Quaternion *q1, const SYN_Quaternion *q2, q16_t t,
                    SYN_Quaternion *out)
{
    SYN_ASSERT(q1 != NULL && q2 != NULL && out != NULL);

    int64_t dot = ((int64_t)q1->w * q2->w + (int64_t)q1->x * q2->x + (int64_t)q1->y * q2->y +
                   (int64_t)q1->z * q2->z) >>
                  Q16_SHIFT;

    SYN_Quaternion q2_norm = *q2;
    if (dot < 0) {
        dot = -dot;
        q2_norm.w = -q2_norm.w;
        q2_norm.x = -q2_norm.x;
        q2_norm.y = -q2_norm.y;
        q2_norm.z = -q2_norm.z;
    }

    if (dot > Q16_ONE - Q16_FROM_FRAC(1, 1000)) {
        /* Linear interpolation for small angles */
        out->w = q1->w + (q16_t)(((int64_t)(q2_norm.w - q1->w) * t) >> Q16_SHIFT);
        out->x = q1->x + (q16_t)(((int64_t)(q2_norm.x - q1->x) * t) >> Q16_SHIFT);
        out->y = q1->y + (q16_t)(((int64_t)(q2_norm.y - q1->y) * t) >> Q16_SHIFT);
        out->z = q1->z + (q16_t)(((int64_t)(q2_norm.z - q1->z) * t) >> Q16_SHIFT);
        (void)syn_quat_normalize(out);
        return;
    }

    q16_t theta = q16_acos((q16_t)dot);
    q16_t sin_theta = q16_sin(theta);

    q16_t ratio_a = q16_div(q16_sin(q16_mul(Q16_ONE - t, theta)), sin_theta);
    q16_t ratio_b = q16_div(q16_sin(q16_mul(t, theta)), sin_theta);

    out->w = (q16_t)(((int64_t)q1->w * ratio_a + (int64_t)q2_norm.w * ratio_b) >> Q16_SHIFT);
    out->x = (q16_t)(((int64_t)q1->x * ratio_a + (int64_t)q2_norm.x * ratio_b) >> Q16_SHIFT);
    out->y = (q16_t)(((int64_t)q1->y * ratio_a + (int64_t)q2_norm.y * ratio_b) >> Q16_SHIFT);
    out->z = (q16_t)(((int64_t)q1->z * ratio_a + (int64_t)q2_norm.z * ratio_b) >> Q16_SHIFT);
}