forked from Qortal/Brooklyn
647 lines
14 KiB
C
647 lines
14 KiB
C
/*
|
|
Copyright (c) 2012, Broadcom Europe Ltd
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
* Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
* Neither the name of the copyright holder nor the
|
|
names of its contributors may be used to endorse or promote products
|
|
derived from this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "interface/khronos/common/khrn_int_common.h"
|
|
#include "interface/khronos/vg/vg_int_mat3x3.h"
|
|
#include "interface/khronos/common/khrn_int_math.h"
|
|
#include "interface/khronos/common/khrn_int_util.h"
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
-
|
|
|
|
Postconditions:
|
|
|
|
a is the identity matrix.
|
|
*/
|
|
|
|
void vg_mat3x3_set_identity(VG_MAT3X3_T *a)
|
|
{
|
|
a->m[0][0] = 1.0f; a->m[0][1] = 0.0f; a->m[0][2] = 0.0f;
|
|
a->m[1][0] = 0.0f; a->m[1][1] = 1.0f; a->m[1][2] = 0.0f;
|
|
a->m[2][0] = 0.0f; a->m[2][1] = 0.0f; a->m[2][2] = 1.0f;
|
|
}
|
|
|
|
/*
|
|
copies matrix (column-major) into a, "cleaning" the elements (see
|
|
clean_float). forces the bottom row of a to (0, 0, 1) if force_affine is
|
|
true.
|
|
|
|
Preconditions:
|
|
|
|
-
|
|
|
|
Postconditions:
|
|
|
|
no elements of a are nan or infinity.
|
|
if force_affine is true, a is affine.
|
|
*/
|
|
|
|
void vg_mat3x3_set_clean(VG_MAT3X3_T *a, const float *matrix, bool force_affine)
|
|
{
|
|
a->m[0][0] = clean_float(matrix[0]);
|
|
a->m[0][1] = clean_float(matrix[3]);
|
|
a->m[0][2] = clean_float(matrix[6]);
|
|
|
|
a->m[1][0] = clean_float(matrix[1]);
|
|
a->m[1][1] = clean_float(matrix[4]);
|
|
a->m[1][2] = clean_float(matrix[7]);
|
|
|
|
if (force_affine) {
|
|
a->m[2][0] = 0.0f;
|
|
a->m[2][1] = 0.0f;
|
|
a->m[2][2] = 1.0f;
|
|
} else {
|
|
a->m[2][0] = clean_float(matrix[2]);
|
|
a->m[2][1] = clean_float(matrix[5]);
|
|
a->m[2][2] = clean_float(matrix[8]);
|
|
}
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
-
|
|
|
|
Postconditions:
|
|
|
|
matrix (column-major) is set to a.
|
|
*/
|
|
|
|
void vg_mat3x3_get(const VG_MAT3X3_T *a, float *matrix)
|
|
{
|
|
matrix[0] = a->m[0][0];
|
|
matrix[3] = a->m[0][1];
|
|
matrix[6] = a->m[0][2];
|
|
|
|
matrix[1] = a->m[1][0];
|
|
matrix[4] = a->m[1][1];
|
|
matrix[7] = a->m[1][2];
|
|
|
|
matrix[2] = a->m[2][0];
|
|
matrix[5] = a->m[2][1];
|
|
matrix[8] = a->m[2][2];
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
-
|
|
|
|
Postconditions:
|
|
|
|
returns true iff a and b are bitwise identical.
|
|
*/
|
|
|
|
bool vg_mat3x3_identical(const VG_MAT3X3_T *a, const VG_MAT3X3_T *b)
|
|
{
|
|
return floats_identical(a->m[0][0], b->m[0][0]) && floats_identical(a->m[0][1], b->m[0][1]) && floats_identical(a->m[0][2], b->m[0][2]) &&
|
|
floats_identical(a->m[1][0], b->m[1][0]) && floats_identical(a->m[1][1], b->m[1][1]) && floats_identical(a->m[1][2], b->m[1][2]) &&
|
|
floats_identical(a->m[2][0], b->m[2][0]) && floats_identical(a->m[2][1], b->m[2][1]) && floats_identical(a->m[2][2], b->m[2][2]);
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
a does not point to the same matrix as b or c.
|
|
|
|
Postconditions:
|
|
|
|
a is set to b * c.
|
|
*/
|
|
|
|
void vg_mat3x3_mul(VG_MAT3X3_T *a, const VG_MAT3X3_T *b, const VG_MAT3X3_T *c)
|
|
{
|
|
uint32_t j, i;
|
|
for (j = 0; j != 3; ++j) {
|
|
for (i = 0; i != 3; ++i) {
|
|
a->m[j][i] =
|
|
(b->m[j][0] * c->m[0][i]) +
|
|
(b->m[j][1] * c->m[1][i]) +
|
|
(b->m[j][2] * c->m[2][i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
-
|
|
|
|
Postconditions:
|
|
|
|
a is set to a * translation_matrix(x, y).
|
|
*/
|
|
|
|
void vg_mat3x3_postmul_translate(VG_MAT3X3_T *a, float x, float y)
|
|
{
|
|
a->m[0][2] += (a->m[0][0] * x) + (a->m[0][1] * y);
|
|
a->m[1][2] += (a->m[1][0] * x) + (a->m[1][1] * y);
|
|
a->m[2][2] += (a->m[2][0] * x) + (a->m[2][1] * y);
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
-
|
|
|
|
Postconditions:
|
|
|
|
a is set to a * scale_matrix(x, y).
|
|
*/
|
|
|
|
void vg_mat3x3_postmul_scale(VG_MAT3X3_T *a, float x, float y)
|
|
{
|
|
a->m[0][0] *= x;
|
|
a->m[0][1] *= y;
|
|
|
|
a->m[1][0] *= x;
|
|
a->m[1][1] *= y;
|
|
|
|
a->m[2][0] *= x;
|
|
a->m[2][1] *= y;
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
-
|
|
|
|
Postconditions:
|
|
|
|
a is set to a * shear_matrix(x, y).
|
|
*/
|
|
|
|
void vg_mat3x3_postmul_shear(VG_MAT3X3_T *a, float x, float y)
|
|
{
|
|
float m00 = a->m[0][0], m10 = a->m[1][0], m20 = a->m[2][0];
|
|
|
|
a->m[0][0] += a->m[0][1] * y;
|
|
a->m[0][1] += m00 * x;
|
|
|
|
a->m[1][0] += a->m[1][1] * y;
|
|
a->m[1][1] += m10 * x;
|
|
|
|
a->m[2][0] += a->m[2][1] * y;
|
|
a->m[2][1] += m20 * x;
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
angle is in radians.
|
|
|
|
Postconditions:
|
|
|
|
a is set to a * rotation_matrix(angle).
|
|
*/
|
|
|
|
void vg_mat3x3_postmul_rotate(VG_MAT3X3_T *a, float angle)
|
|
{
|
|
float s, c;
|
|
sin_cos_(&s, &c, angle);
|
|
vg_mat3x3_postmul_rotate_sc(a, s, c);
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
there is some angle such that:
|
|
s = sin(angle)
|
|
c = cos(angle)
|
|
|
|
Postconditions:
|
|
|
|
a is set to a * rotation_matrix(angle).
|
|
*/
|
|
|
|
void vg_mat3x3_postmul_rotate_sc(VG_MAT3X3_T *a, float s, float c)
|
|
{
|
|
float m00 = a->m[0][0], m10 = a->m[1][0], m20 = a->m[2][0];
|
|
|
|
a->m[0][0] = (m00 * c) + (a->m[0][1] * s);
|
|
a->m[0][1] = (a->m[0][1] * c) - (m00 * s);
|
|
|
|
a->m[1][0] = (m10 * c) + (a->m[1][1] * s);
|
|
a->m[1][1] = (a->m[1][1] * c) - (m10 * s);
|
|
|
|
a->m[2][0] = (m20 * c) + (a->m[2][1] * s);
|
|
a->m[2][1] = (a->m[2][1] * c) - (m20 * s);
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
-
|
|
|
|
Postconditions:
|
|
|
|
a is set to translation_matrix(x, y) * a.
|
|
*/
|
|
|
|
void vg_mat3x3_premul_translate(VG_MAT3X3_T *a, float x, float y)
|
|
{
|
|
a->m[0][0] += a->m[2][0] * x;
|
|
a->m[0][1] += a->m[2][1] * x;
|
|
a->m[0][2] += a->m[2][2] * x;
|
|
|
|
a->m[1][0] += a->m[2][0] * y;
|
|
a->m[1][1] += a->m[2][1] * y;
|
|
a->m[1][2] += a->m[2][2] * y;
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
-
|
|
|
|
Postconditions:
|
|
|
|
a is set to scale_matrix(x, y) * a.
|
|
*/
|
|
|
|
void vg_mat3x3_premul_scale(VG_MAT3X3_T *a, float x, float y)
|
|
{
|
|
a->m[0][0] *= x;
|
|
a->m[0][1] *= x;
|
|
a->m[0][2] *= x;
|
|
|
|
a->m[1][0] *= y;
|
|
a->m[1][1] *= y;
|
|
a->m[1][2] *= y;
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
-
|
|
|
|
Postconditions:
|
|
|
|
a is set to shear_matrix(x, y) * a.
|
|
*/
|
|
|
|
void vg_mat3x3_premul_shear(VG_MAT3X3_T *a, float x, float y)
|
|
{
|
|
float m00 = a->m[0][0], m01 = a->m[0][1], m02 = a->m[0][2];
|
|
|
|
a->m[0][0] += a->m[1][0] * x;
|
|
a->m[0][1] += a->m[1][1] * x;
|
|
a->m[0][2] += a->m[1][2] * x;
|
|
|
|
a->m[1][0] += m00 * y;
|
|
a->m[1][1] += m01 * y;
|
|
a->m[1][2] += m02 * y;
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
angle is in radians.
|
|
|
|
Postconditions:
|
|
|
|
a is set to rotation_matrix(angle) * a.
|
|
*/
|
|
|
|
void vg_mat3x3_premul_rotate(VG_MAT3X3_T *a, float angle)
|
|
{
|
|
float s, c;
|
|
sin_cos_(&s, &c, angle);
|
|
vg_mat3x3_premul_rotate_sc(a, s, c);
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
there is some angle such that:
|
|
s = sin(angle)
|
|
c = cos(angle)
|
|
|
|
Postconditions:
|
|
|
|
a is set to rotation_matrix(angle) * a.
|
|
*/
|
|
|
|
void vg_mat3x3_premul_rotate_sc(VG_MAT3X3_T *a, float s, float c)
|
|
{
|
|
float m00 = a->m[0][0], m01 = a->m[0][1], m02 = a->m[0][2];
|
|
|
|
a->m[0][0] = (m00 * c) - (a->m[1][0] * s);
|
|
a->m[0][1] = (m01 * c) - (a->m[1][1] * s);
|
|
a->m[0][2] = (m02 * c) - (a->m[1][2] * s);
|
|
|
|
a->m[1][0] = (m00 * s) + (a->m[1][0] * c);
|
|
a->m[1][1] = (m01 * s) + (a->m[1][1] * c);
|
|
a->m[1][2] = (m02 * s) + (a->m[1][2] * c);
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
-
|
|
|
|
Postconditions:
|
|
|
|
returns true iff a is affine.
|
|
*/
|
|
|
|
bool vg_mat3x3_is_affine(const VG_MAT3X3_T *a)
|
|
{
|
|
return (a->m[2][0] == 0.0f) && (a->m[2][1] == 0.0f) && (a->m[2][2] == 1.0f);
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
-
|
|
|
|
Postconditions:
|
|
|
|
returns true iff a is affine or has nans in the bad elements.
|
|
*/
|
|
|
|
bool vg_mat3x3_is_affine_or_nans(const VG_MAT3X3_T *a)
|
|
{
|
|
return !nan_ne_(a->m[2][0], 0.0f) && !nan_ne_(a->m[2][1], 0.0f) && !nan_ne_(a->m[2][2], 1.0f);
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
-
|
|
|
|
Postconditions:
|
|
|
|
returns the determinant of a.
|
|
*/
|
|
|
|
float vg_mat3x3_det(const VG_MAT3X3_T *a)
|
|
{
|
|
return (a->m[0][0] * ((a->m[2][2] * a->m[1][1]) - (a->m[2][1] * a->m[1][2]))) +
|
|
(a->m[1][0] * ((a->m[0][2] * a->m[2][1]) - (a->m[0][1] * a->m[2][2]))) +
|
|
(a->m[2][0] * ((a->m[1][2] * a->m[0][1]) - (a->m[1][1] * a->m[0][2])));
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
a must be affine (or have nans in the bad elements).
|
|
|
|
Postconditions:
|
|
|
|
returns the determinant of a.
|
|
*/
|
|
|
|
float vg_mat3x3_affine_det(const VG_MAT3X3_T *a)
|
|
{
|
|
vcos_assert(vg_mat3x3_is_affine_or_nans(a));
|
|
return (a->m[0][0] * a->m[1][1]) - (a->m[1][0] * a->m[0][1]);
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
-
|
|
|
|
Postconditions:
|
|
|
|
returns false iff a is not invertible (or very close to not being
|
|
invertible).
|
|
*/
|
|
|
|
bool vg_mat3x3_is_invertible(const VG_MAT3X3_T *a)
|
|
{
|
|
return absf_(vg_mat3x3_det(a)) >= EPS;
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
a must be affine (or have nans in the bad elements).
|
|
|
|
Postconditions:
|
|
|
|
returns false iff a is not invertible (or very close to not being
|
|
invertible).
|
|
*/
|
|
|
|
bool vg_mat3x3_affine_is_invertible(const VG_MAT3X3_T *a)
|
|
{
|
|
return absf_(vg_mat3x3_affine_det(a)) >= EPS;
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
a must be invertible, according to vg_mat3x3_is_invertible.
|
|
|
|
Postconditions:
|
|
|
|
a is inverted.
|
|
*/
|
|
|
|
void vg_mat3x3_invert(VG_MAT3X3_T *a)
|
|
{
|
|
float oo_det;
|
|
VG_MAT3X3_T b;
|
|
|
|
vcos_assert(vg_mat3x3_is_invertible(a));
|
|
|
|
oo_det = recip_(vg_mat3x3_det(a));
|
|
|
|
b.m[0][0] = ((a->m[2][2] * a->m[1][1]) - (a->m[2][1] * a->m[1][2])) * oo_det;
|
|
b.m[0][1] = ((a->m[0][2] * a->m[2][1]) - (a->m[0][1] * a->m[2][2])) * oo_det;
|
|
b.m[0][2] = ((a->m[1][2] * a->m[0][1]) - (a->m[1][1] * a->m[0][2])) * oo_det;
|
|
|
|
b.m[1][0] = ((a->m[2][0] * a->m[1][2]) - (a->m[2][2] * a->m[1][0])) * oo_det;
|
|
b.m[1][1] = ((a->m[0][0] * a->m[2][2]) - (a->m[0][2] * a->m[2][0])) * oo_det;
|
|
b.m[1][2] = ((a->m[1][0] * a->m[0][2]) - (a->m[1][2] * a->m[0][0])) * oo_det;
|
|
|
|
b.m[2][0] = ((a->m[2][1] * a->m[1][0]) - (a->m[2][0] * a->m[1][1])) * oo_det;
|
|
b.m[2][1] = ((a->m[0][1] * a->m[2][0]) - (a->m[0][0] * a->m[2][1])) * oo_det;
|
|
b.m[2][2] = ((a->m[1][1] * a->m[0][0]) - (a->m[1][0] * a->m[0][1])) * oo_det;
|
|
|
|
*a = b;
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
a must be affine (or have nans in the bad elements).
|
|
a must be invertible, according to vg_mat3x3_affine_is_invertible.
|
|
|
|
Postconditions:
|
|
|
|
a is inverted.
|
|
*/
|
|
|
|
void vg_mat3x3_affine_invert(VG_MAT3X3_T *a)
|
|
{
|
|
float oo_det;
|
|
float m00, m01, m02, m10;
|
|
|
|
vcos_assert(vg_mat3x3_affine_is_invertible(a));
|
|
|
|
oo_det = recip_(vg_mat3x3_affine_det(a));
|
|
m00 = a->m[0][0];
|
|
m01 = a->m[0][1];
|
|
m02 = a->m[0][2];
|
|
m10 = a->m[1][0];
|
|
|
|
a->m[0][0] = a->m[1][1] * oo_det;
|
|
a->m[0][1] = -m01 * oo_det;
|
|
a->m[0][2] = ((a->m[1][2] * m01) - (a->m[1][1] * m02)) * oo_det;
|
|
|
|
a->m[1][0] = -m10 * oo_det;
|
|
a->m[1][1] = m00 * oo_det;
|
|
a->m[1][2] = ((m10 * m02) - (a->m[1][2] * m00)) * oo_det;
|
|
|
|
a->m[2][0] = 0.0f;
|
|
a->m[2][1] = 0.0f;
|
|
a->m[2][2] = 1.0f;
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
-
|
|
|
|
Postconditions:
|
|
|
|
(x, y, -) is set to a * (x, y, 1).
|
|
*/
|
|
|
|
void vg_mat3x3_affine_transform(const VG_MAT3X3_T *a, float *x_io, float *y_io)
|
|
{
|
|
float x = (a->m[0][0] * *x_io) + (a->m[0][1] * *y_io) + a->m[0][2];
|
|
float y = (a->m[1][0] * *x_io) + (a->m[1][1] * *y_io) + a->m[1][2];
|
|
*x_io = x;
|
|
*y_io = y;
|
|
}
|
|
|
|
/*
|
|
Preconditions:
|
|
|
|
-
|
|
|
|
Postconditions:
|
|
|
|
(x, y, -) is set to a * (x, y, 0).
|
|
*/
|
|
|
|
void vg_mat3x3_affine_transform_t(const VG_MAT3X3_T *a, float *x_io, float *y_io)
|
|
{
|
|
float x = (a->m[0][0] * *x_io) + (a->m[0][1] * *y_io);
|
|
float y = (a->m[1][0] * *x_io) + (a->m[1][1] * *y_io);
|
|
*x_io = x;
|
|
*y_io = y;
|
|
}
|
|
|
|
/*
|
|
the top-left 2x2 submatrix of a is decomposed into (R * S * Q), where R is a
|
|
rotation matrix, S is a scale matrix, and Q is a rotation/flip matrix. note
|
|
that this decomposition is not unique.
|
|
|
|
Preconditions:
|
|
|
|
r may be NULL.
|
|
s0 may be NULL.
|
|
s1 may be NULL.
|
|
|
|
Postconditions:
|
|
|
|
if r is non-NULL, it is set to the angle of R (so R = rotation_matrix(r)) in
|
|
radians.
|
|
if s0 is non-NULL, it is set to S[0][0].
|
|
if s1 is non-NULL, it is set to S[1][1].
|
|
so if we set both, S = scale_matrix(s0, s1).
|
|
*/
|
|
|
|
void vg_mat3x3_rsq(const VG_MAT3X3_T *a,
|
|
float *r, float *s0, float *s1)
|
|
{
|
|
/*
|
|
a = R * S * Q (svd, R is rotation, S is scale, Q is rotation/flip)
|
|
a^T = Q^T * S * R^T
|
|
a * a^T = R * S * Q * Q^T * S * R^T = R * S^2 * R^T
|
|
|
|
eigenvalues of a * a^T will give S^2
|
|
eigenvectors of a * a^T will give R
|
|
*/
|
|
|
|
/*
|
|
( b c ) = a * a^T
|
|
( d e )
|
|
*/
|
|
|
|
float b = (a->m[0][0] * a->m[0][0]) + (a->m[0][1] * a->m[0][1]);
|
|
float c = (a->m[0][0] * a->m[1][0]) + (a->m[0][1] * a->m[1][1]);
|
|
/* d = c */
|
|
float e = (a->m[1][0] * a->m[1][0]) + (a->m[1][1] * a->m[1][1]);
|
|
|
|
float bpe = b + e;
|
|
float bme = b - e;
|
|
|
|
/*
|
|
solve:
|
|
|
|
bx + cy = sx
|
|
dx + ey = sy
|
|
|
|
cy * dx = (s - b)x * (s - e)y
|
|
c^2 = (s - b) * (s - e)
|
|
s^2 - (b + e)s + (be - c^2) = 0
|
|
s = (b + e +/- sqrt((b + e)^2 - 4(be - c^2))) / 2
|
|
s = (b + e +/- sqrt(b^2 + e^2 - 2be + 4c^2)) / 2
|
|
s = (b + e +/- sqrt((b - e)^2 + 4c^2)) / 2
|
|
*/
|
|
|
|
float t = sqrt_((bme * bme) + (4.0f * c * c));
|
|
float v = (bpe + t) * 0.5f; /* first eigenvalue */
|
|
if (s0) {
|
|
*s0 = sqrt_(v);
|
|
}
|
|
if (s1) {
|
|
*s1 = sqrt_(
|
|
/* second eigenvalue */
|
|
_maxf(bpe - t, 0.0f) * 0.5f);
|
|
}
|
|
|
|
/*
|
|
angle of eigenvector corresponds to r
|
|
*/
|
|
|
|
if (r) {
|
|
/* first eigenvector is (c, v - b) / (v - e, c) */
|
|
float x = (v - e) + c;
|
|
float y = (v - b) + c;
|
|
*r = ((absf_(x) < EPS) && (absf_(y) < EPS)) ? 0.0f : atan2_(y, x);
|
|
}
|
|
}
|