Skip to content

graphene_quaternion_init_from_matrix not handling case when trace is -1 #287

Description

@alexlarsson

I've been playing around a bit with gthree and claude code, and it seems my look_up() implementation is not working in some corner cases due to this.

Here is a reproducer that fails to roundtrip a matrix via quaternions:

#include <graphene.h>
#include <stdio.h>
#include <math.h>

int
main (void)
{
  graphene_matrix_t m, reconstructed;
  graphene_quaternion_t q;
  float orig[16], recon[16];
  int i;

  /* A valid rotation matrix (det = +1) with trace = -1. */
  graphene_matrix_init_from_float (&m,
    (float[16]){
      -1,  0,  0, 0,
       0,  0, -1, 0,
       0, -1,  0, 0,
       0,  0,  0, 1
    });

  graphene_quaternion_init_from_matrix (&q, &m);
  graphene_quaternion_to_matrix (&q, &reconstructed);

  graphene_matrix_to_float (&m, orig);
  graphene_matrix_to_float (&reconstructed, recon);

  printf ("Original matrix:\n");
  for (i = 0; i < 3; i++)
    printf ("  [%6.2f %6.2f %6.2f]\n",
            orig[i], orig[4+i], orig[8+i]);

  printf ("Reconstructed from quaternion:\n");
  for (i = 0; i < 3; i++)
    printf ("  [%6.2f %6.2f %6.2f]\n",
            recon[i], recon[4+i], recon[8+i]);

  int match = 1;
  for (i = 0; i < 16; i++)
    if (fabsf (orig[i] - recon[i]) > 0.001f)
      match = 0;

  printf ("\nRoundtrip %s\n", match ? "PASS" : "FAIL");
  return !match;
}

Claude says it is because this matrix has trace -1 which makes w = 0 in graphene_matrix_init_from_float, which means graphene can't determine quaternion component signs, and it proposes using the "sheppherd method" instead, and generated this code:

static void
quaternion_init_from_rotation_matrix (graphene_quaternion_t    *q,
                                      const graphene_matrix_t  *m)
{
  /* graphene_matrix_get_value(a,b) returns mathematical element
     at (row=b, col=a) due to column-major storage */
  float m11 = graphene_matrix_get_value (m, 0, 0);
  float m12 = graphene_matrix_get_value (m, 1, 0);
  float m13 = graphene_matrix_get_value (m, 2, 0);
  float m21 = graphene_matrix_get_value (m, 0, 1);
  float m22 = graphene_matrix_get_value (m, 1, 1);
  float m23 = graphene_matrix_get_value (m, 2, 1);
  float m31 = graphene_matrix_get_value (m, 0, 2);
  float m32 = graphene_matrix_get_value (m, 1, 2);
  float m33 = graphene_matrix_get_value (m, 2, 2);
  float trace = m11 + m22 + m33;

  if (trace > 0)
    {
      float s = 0.5f / sqrtf (trace + 1.0f);
      graphene_quaternion_init (q,
                                (m32 - m23) * s,
                                (m13 - m31) * s,
                                (m21 - m12) * s,
                                0.25f / s);
    }
  else if (m11 > m22 && m11 > m33)
    {
      float s = 2.0f * sqrtf (1.0f + m11 - m22 - m33);
      graphene_quaternion_init (q,
                                0.25f * s,
                                (m12 + m21) / s,
                                (m13 + m31) / s,
                                (m32 - m23) / s);
    }
  else if (m22 > m33)
    {
      float s = 2.0f * sqrtf (1.0f + m22 - m11 - m33);
      graphene_quaternion_init (q,
                                (m12 + m21) / s,
                                0.25f * s,
                                (m23 + m32) / s,
                                (m13 - m31) / s);
    }
  else
    {
      float s = 2.0f * sqrtf (1.0f + m33 - m11 - m22);
      graphene_quaternion_init (q,
                                (m13 + m31) / s,
                                (m23 + m32) / s,
                                0.25f * s,
                                (m21 - m12) / s);
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions