quakeforge/libs/util/mathlib.c
Bill Currie e00a871824 [util] Clarify the comment for AngleVectors
I found it rather confusing that the matrices were all backwards, and
the existing comments about being "horizontal" didn't really help all
that much. After spending some time with maxima, I was able to verify
that the comments were indeed correct, just transposed (horizontal),
with the final composition reversed to reflect that transposition.
2023-08-13 17:33:46 +09:00

1495 lines
33 KiB
C

/*
mathlib.c
math primitives
Copyright (C) 1996-1997 Id Software, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include "qfalloca.h"
#include <math.h>
#define IMPLEMENT_R_Cull
#define IMPLEMENT_VectorNormalize
#include "QF/mathlib.h"
#include "QF/qtypes.h"
#include "QF/set.h"
#include "QF/sys.h"
static vec3_t _vec3_origin = { 0, 0, 0 };
VISIBLE const vec_t * const vec3_origin = _vec3_origin;
static quat_t _quat_origin = { 0, 0, 0, 0 };
VISIBLE const vec_t * const quat_origin = _quat_origin;
#define DEG2RAD(a) (a * (M_PI / 180.0))
#define FMANTBITS 23
#define FMANTMASK ((1 << FMANTBITS) - 1)
#define FEXPBITS 8
#define FEXPMASK ((1 << FEXPBITS) - 1)
#define FBIAS (1 << (FEXPBITS - 1))
#define FEXPMAX ((1 << FEXPBITS) - 1)
#define HMANTBITS 10
#define HMANTMASK ((1 << HMANTBITS) - 1)
#define HEXPBITS 5
#define HEXPMASK ((1 << HEXPBITS) - 1)
#define HBIAS (1 << (HEXPBITS - 1))
#define HEXPMAX ((1 << HEXPBITS) - 1)
int16_t
FloatToHalf (float x)
{
union {
float f;
uint32_t u;
} uf;
unsigned sign;
int exp;
unsigned mant;
int16_t half;
uf.f = x;
sign = (uf.u >> (FEXPBITS + FMANTBITS)) & 1;
exp = ((uf.u >> FMANTBITS) & FEXPMASK) - FBIAS + HBIAS;
mant = (uf.u & FMANTMASK) >> (FMANTBITS - HMANTBITS);
if (exp <= 0) {
mant |= 1 << HMANTBITS;
mant >>= min (1 - exp, HMANTBITS + 1);
exp = 0;
} else if (exp >= HEXPMAX) {
mant = 0;
exp = HEXPMAX;
}
half = (sign << (HEXPBITS + HMANTBITS)) | (exp << HMANTBITS) | mant;
return half;
}
float
HalfToFloat (int16_t x)
{
union {
float f;
uint32_t u;
} uf;
unsigned sign;
int exp;
unsigned mant;
sign = (x >> (HEXPBITS + HMANTBITS)) & 1;
exp = ((x >> HMANTBITS) & HEXPMASK);
mant = (x & HMANTMASK) << (FMANTBITS - HMANTBITS);
if (exp == 0) {
if (mant) {
while (mant < (1 << FMANTBITS)) {
mant <<= 1;
exp--;
}
mant &= (1 << FMANTBITS) - 1;
exp += FBIAS - HBIAS + 1;
}
} else if (exp == HEXPMAX) {
exp = FEXPMAX;
} else {
exp += FBIAS - HBIAS;
}
uf.u = (sign << (FEXPBITS + FMANTBITS)) | (exp << FMANTBITS) | mant;
return uf.f;
}
static void
ProjectPointOnPlane (vec3_t dst, const vec3_t p, const vec3_t normal)
{
float inv_denom, d;
vec3_t n;
inv_denom = 1.0F / DotProduct (normal, normal);
d = DotProduct (normal, p) * inv_denom;
VectorScale (normal, inv_denom * d, n);
VectorSubtract (p, n, dst);
}
// assumes "src" is normalized
static void
PerpendicularVector (vec3_t dst, const vec3_t src)
{
int pos, i;
float minelem = 1.0F;
vec3_t tempvec;
/* find the smallest magnitude axially aligned vector */
for (pos = 0, i = 0; i < 3; i++) {
if (fabs (src[i]) < minelem) {
pos = i;
minelem = fabs (src[i]);
}
}
VectorZero (tempvec);
tempvec[pos] = 1.0F;
/* project the point onto the plane defined by src */
ProjectPointOnPlane (dst, tempvec, src);
/* normalize the result */
VectorNormalize (dst);
}
#if defined(_WIN32) && !defined(__GNUC__)
# pragma optimize( "", off )
#endif
VISIBLE void
VectorVectors(const vec3_t forward, vec3_t right, vec3_t up)
{
float d;
right[0] = forward[2];
right[1] = -forward[0];
right[2] = forward[1];
d = DotProduct(forward, right);
VectorMultSub (right, d, forward, right);
VectorNormalize (right);
CrossProduct(right, forward, up);
}
VISIBLE void
RotatePointAroundVector (vec3_t dst, const vec3_t axis, const vec3_t point,
float degrees)
{
float m[3][3];
float im[3][3];
float zrot[3][3];
float tmpmat[3][3];
float rot[3][3];
int i;
vec3_t vr, vup, vf;
VectorCopy (axis, vf);
PerpendicularVector (vr, axis);
CrossProduct (vr, vf, vup);
m[0][0] = vr[0];
m[1][0] = vr[1];
m[2][0] = vr[2];
m[0][1] = vup[0];
m[1][1] = vup[1];
m[2][1] = vup[2];
m[0][2] = vf[0];
m[1][2] = vf[1];
m[2][2] = vf[2];
memcpy (im, m, sizeof (im));
im[0][1] = m[1][0];
im[0][2] = m[2][0];
im[1][0] = m[0][1];
im[1][2] = m[2][1];
im[2][0] = m[0][2];
im[2][1] = m[1][2];
memset (zrot, 0, sizeof (zrot));
zrot[0][0] = zrot[1][1] = zrot[2][2] = 1.0F;
zrot[0][0] = cos (DEG2RAD (degrees));
zrot[0][1] = sin (DEG2RAD (degrees));
zrot[1][0] = -sin (DEG2RAD (degrees));
zrot[1][1] = cos (DEG2RAD (degrees));
R_ConcatRotations (m, zrot, tmpmat);
R_ConcatRotations (tmpmat, im, rot);
for (i = 0; i < 3; i++) {
dst[i] = DotProduct (rot[i], point);
}
}
VISIBLE void
QuatMult (const quat_t q1, const quat_t q2, quat_t out)
{
vec_t s;
vec3_t v;
s = q1[3] * q2[3] - DotProduct (q1, q2);
CrossProduct (q1, q2, v);
VectorMultAdd (v, q1[3], q2, v);
VectorMultAdd (v, q2[3], q1, out);
out[3] = s;
}
VISIBLE void
QuatMultVec (const quat_t q, const vec3_t v, vec3_t out)
{
vec3_t tv;
vec_t dqv, dqq;
vec_t s;
s = q[3];
CrossProduct (q, v, tv);
dqv = DotProduct (q, v);
dqq = DotProduct (q, q);
VectorScale (tv, s, tv);
VectorMultAdd (tv, dqv, q, tv);
VectorScale (tv, 2, tv);
VectorMultAdd (tv, s * s - dqq, v, out);
}
VISIBLE void
QuatRotation(const vec3_t a, const vec3_t b, quat_t out)
{
vec_t ma, mb;
vec_t den, mba_mab;
vec3_t t;
ma = VectorLength(a);
mb = VectorLength(b);
den = 2 * ma * mb;
VectorScale (a, mb, t);
VectorMultAdd(t, ma, b, t);
mba_mab = VectorLength(t);
CrossProduct (a, b, t);
VectorScale(t, 1 / mba_mab, out);
out[3] = mba_mab / den;
}
VISIBLE void
QuatInverse (const quat_t in, quat_t out)
{
quat_t q;
vec_t m;
m = QDotProduct (in, in); // in * in*
QuatConj (in, q);
QuatScale (q, 1 / m, out);
}
VISIBLE void
QuatExp (const quat_t a, quat_t b)
{
vec3_t n;
vec_t th;
vec_t r;
vec_t c, s;
VectorCopy (a, n);
th = VectorNormalize (n);
r = expf (a[3]);
c = cosf (th);
s = sinf (th);
VectorScale (n, r * s, b);
b[3] = r * c;
}
VISIBLE void
QuatToMatrix (const quat_t q, vec_t *m, int homogenous, int vertical)
{
vec_t xx, xy, xz, xw, yy, yz, yw, zz, zw;
vec_t *_m[4] = {
m + (homogenous ? 0 : 0),
m + (homogenous ? 4 : 3),
m + (homogenous ? 8 : 6),
m + (homogenous ? 12 : 9),
};
xx = 2 * q[0] * q[0];
xy = 2 * q[0] * q[1];
xz = 2 * q[0] * q[2];
xw = 2 * q[0] * q[3];
yy = 2 * q[1] * q[1];
yz = 2 * q[1] * q[2];
yw = 2 * q[1] * q[3];
zz = 2 * q[2] * q[2];
zw = 2 * q[2] * q[3];
if (vertical) {
VectorSet (1.0f - yy - zz, xy + zw, xz - yw, _m[0]);
VectorSet (xy - zw, 1.0f - xx - zz, yz + xw, _m[1]);
VectorSet (xz + yw, yz - xw, 1.0f - xx - yy, _m[2]);
} else {
VectorSet (1.0f - yy - zz, xy - zw, xz + yw, _m[0]);
VectorSet (xy + zw, 1.0f - xx - zz, yz - xw, _m[1]);
VectorSet (xz - yw, yz + xw, 1.0f - xx - yy, _m[2]);
}
if (homogenous) {
_m[0][3] = 0;
_m[1][3] = 0;
_m[2][3] = 0;
VectorZero (_m[3]);
_m[3][3] = 1;
}
}
#if defined(_WIN32) && !defined(__GNUC__)
# pragma optimize( "", on )
#endif
VISIBLE float
anglemod (float a)
{
a = (360.0 / 65536) * ((int) (a * (65536 / 360.0)) & 65535);
return a;
}
/*
BOPS_Error
Split out like this for ASM to call.
*/
void __attribute__ ((noreturn)) BOPS_Error (void);
VISIBLE void __attribute__ ((noreturn))
BOPS_Error (void)
{
Sys_Error ("BoxOnPlaneSide: Bad signbits");
}
#ifndef USE_INTEL_ASM
/*
BoxOnPlaneSide
Returns 1, 2, or 1 + 2
*/
VISIBLE int
BoxOnPlaneSide (const vec3_t emins, const vec3_t emaxs, const plane_t *p)
{
float dist1, dist2;
int sides;
#if 0
// this is done by the BOX_ON_PLANE_SIDE macro before
// calling this function
// fast axial cases
if (p->type < 3) {
if (p->dist <= emins[p->type])
return 1;
if (p->dist >= emaxs[p->type])
return 2;
return 3;
}
#endif
// general case
switch (p->signbits) {
case 0:
dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] +
p->normal[2] * emaxs[2];
dist2 = p->normal[0] * emins[0] + p->normal[1] * emins[1] +
p->normal[2] * emins[2];
break;
case 1:
dist1 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] +
p->normal[2] * emaxs[2];
dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] +
p->normal[2] * emins[2];
break;
case 2:
dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] +
p->normal[2] * emaxs[2];
dist2 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] +
p->normal[2] * emins[2];
break;
case 3:
dist1 = p->normal[0] * emins[0] + p->normal[1] * emins[1] +
p->normal[2] * emaxs[2];
dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] +
p->normal[2] * emins[2];
break;
case 4:
dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] +
p->normal[2] * emins[2];
dist2 = p->normal[0] * emins[0] + p->normal[1] * emins[1] +
p->normal[2] * emaxs[2];
break;
case 5:
dist1 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] +
p->normal[2] * emins[2];
dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] +
p->normal[2] * emaxs[2];
break;
case 6:
dist1 = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] +
p->normal[2] * emins[2];
dist2 = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] +
p->normal[2] * emaxs[2];
break;
case 7:
dist1 = p->normal[0] * emins[0] + p->normal[1] * emins[1] +
p->normal[2] * emins[2];
dist2 = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] +
p->normal[2] * emaxs[2];
break;
default:
BOPS_Error ();
}
#if 0
int i;
vec3_t corners[2];
for (i = 0; i < 3; i++) {
if (plane->normal[i] < 0) {
corners[0][i] = emins[i];
corners[1][i] = emaxs[i];
} else {
corners[1][i] = emins[i];
corners[0][i] = emaxs[i];
}
}
dist = DotProduct (plane->normal, corners[0]) - plane->dist;
dist2 = DotProduct (plane->normal, corners[1]) - plane->dist;
sides = 0;
if (dist1 >= 0)
sides = 1;
if (dist2 < 0)
sides |= 2;
#endif
sides = 0;
if (dist1 >= -p->dist)
sides = 1;
if (dist2 < -p->dist)
sides |= 2;
#ifdef PARANOID
if (sides == 0)
Sys_Error ("BoxOnPlaneSide: sides==0");
#endif
return sides;
}
#endif
/*
angles is a left handed system: 'pitch yaw roll' with x (pitch) axis to
the right, y (yaw) axis up and z (roll) axis forward. However, the
rotations themselves are right-handed in that they follow the right-hand
rule for the world axes: pitch around +y, yaw around +z, and roll around
+x.
This results in the entity frame having forward pointed along the world +x
axis, right along the world -y axis, and up along the world +z axis.
Whether this means the entity frame is left-handed depends on whether
forward is local X and right is local Y (left handed), or forward is local
Y and right is local X (right handed).
NOTE: the matrices in this coment are transposed so the vector names
can be written naturally.
pitch =
cp 0 -sp
0 1 0
sp 0 cp
yaw =
cy sy 0
-sy cy 0
0 0 1
roll =
1 0 0
0 cr sr
0 -sr cr
// NOTE: as these matrices are transposed, this is the reverse of the
// actual operation
final = roll * (pitch * yaw)
final =
[forward]
[-right] -ve due to left handed to right handed conversion
[up]
*/
VISIBLE void
AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up)
{
float angle, sr, sp, sy, cr, cp, cy;
angle = angles[YAW] * (M_PI * 2 / 360);
sy = sin (angle);
cy = cos (angle);
angle = angles[PITCH] * (M_PI * 2 / 360);
sp = sin (angle);
cp = cos (angle);
angle = angles[ROLL] * (M_PI * 2 / 360);
sr = sin (angle);
cr = cos (angle);
forward[0] = cp * cy;
forward[1] = cp * sy;
forward[2] = -sp;
// need to flip right because the trig produces +Y but right is -Y
right[0] = -(sr * sp * cy + cr * -sy);
right[1] = -(sr * sp * sy + cr * cy);
right[2] = -(sr * cp);
up[0] = (cr * sp * cy + -sr * -sy);
up[1] = (cr * sp * sy + -sr * cy);
up[2] = cr * cp;
}
VISIBLE void
AngleQuat (const vec3_t angles, quat_t q)
{
float alpha, sr, sp, sy, cr, cp, cy;
// alpha is half the angle
alpha = angles[YAW] * (M_PI / 360);
sy = sin (alpha);
cy = cos (alpha);
alpha = angles[PITCH] * (M_PI / 360);
sp = sin (alpha);
cp = cos (alpha);
alpha = angles[ROLL] * (M_PI / 360);
sr = sin (alpha);
cr = cos (alpha);
QuatSet (cy * cp * sr - sy * sp * cr, // x
cy * sp * cr + sy * cp * sr, // y
sy * cp * cr - cy * sp * sr, // z
cy * cp * cr + sy * sp * sr, // w
q);
}
VISIBLE int
_VectorCompare (const vec3_t v1, const vec3_t v2)
{
int i;
for (i = 0; i < 3; i++)
if (fabs (v1[i] - v2[i]) > EQUAL_EPSILON)
return 0;
return 1;
}
VISIBLE void
_VectorMA (const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc)
{
vecc[0] = veca[0] + scale * vecb[0];
vecc[1] = veca[1] + scale * vecb[1];
vecc[2] = veca[2] + scale * vecb[2];
}
VISIBLE vec_t
_DotProduct (const vec3_t v1, const vec3_t v2)
{
return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
}
VISIBLE void
_VectorSubtract (const vec3_t veca, const vec3_t vecb, vec3_t out)
{
out[0] = veca[0] - vecb[0];
out[1] = veca[1] - vecb[1];
out[2] = veca[2] - vecb[2];
}
VISIBLE void
_VectorAdd (const vec3_t veca, const vec3_t vecb, vec3_t out)
{
out[0] = veca[0] + vecb[0];
out[1] = veca[1] + vecb[1];
out[2] = veca[2] + vecb[2];
}
VISIBLE void
_VectorCopy (const vec3_t in, vec3_t out)
{
out[0] = in[0];
out[1] = in[1];
out[2] = in[2];
}
VISIBLE void
CrossProduct (const vec3_t v1, const vec3_t v2, vec3_t cross)
{
float v10 = v1[0];
float v11 = v1[1];
float v12 = v1[2];
float v20 = v2[0];
float v21 = v2[1];
float v22 = v2[2];
cross[0] = v11 * v22 - v12 * v21;
cross[1] = v12 * v20 - v10 * v22;
cross[2] = v10 * v21 - v11 * v20;
}
VISIBLE vec_t
_VectorLength (const vec3_t v)
{
float length;
length = sqrt (DotProduct (v, v));
return length;
}
VISIBLE vec_t
_VectorNormalize (vec3_t v)
{
int i;
double length;
length = 0;
for (i = 0; i < 3; i++)
length += v[i] * v[i];
length = sqrt (length);
if (length == 0)
return 0;
for (i = 0; i < 3; i++)
v[i] /= length;
return length;
}
VISIBLE void
_VectorScale (const vec3_t in, vec_t scale, vec3_t out)
{
out[0] = in[0] * scale;
out[1] = in[1] * scale;
out[2] = in[2] * scale;
}
VISIBLE int
Q_log2 (int val)
{
int answer = 0;
while ((val >>= 1) != 0)
answer++;
return answer;
}
VISIBLE void
R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3])
{
out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] +
in1[0][2] * in2[2][0];
out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] +
in1[0][2] * in2[2][1];
out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] +
in1[0][2] * in2[2][2];
out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] +
in1[1][2] * in2[2][0];
out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] +
in1[1][2] * in2[2][1];
out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] +
in1[1][2] * in2[2][2];
out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] +
in1[2][2] * in2[2][0];
out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] +
in1[2][2] * in2[2][1];
out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] +
in1[2][2] * in2[2][2];
}
VISIBLE void
R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4])
{
out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] +
in1[0][2] * in2[2][0];
out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] +
in1[0][2] * in2[2][1];
out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] +
in1[0][2] * in2[2][2];
out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] +
in1[0][2] * in2[2][3] + in1[0][3];
out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] +
in1[1][2] * in2[2][0];
out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] +
in1[1][2] * in2[2][1];
out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] +
in1[1][2] * in2[2][2];
out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] +
in1[1][2] * in2[2][3] + in1[1][3];
out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] +
in1[2][2] * in2[2][0];
out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] +
in1[2][2] * in2[2][1];
out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] +
in1[2][2] * in2[2][2];
out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] +
in1[2][2] * in2[2][3] + in1[2][3];
}
/*
FloorDivMod
Returns mathematically correct (floor-based) quotient and remainder for
numer and denom, both of which should contain no fractional part. The
quotient must fit in 32 bits.
*/
VISIBLE void
FloorDivMod (double numer, double denom, int *quotient, int *rem)
{
double x;
int q, r;
#ifndef PARANOID
if (denom <= 0.0)
Sys_Error ("FloorDivMod: bad denominator %f", denom);
// if ((floor(numer) != numer) || (floor(denom) != denom))
// Sys_Error ("FloorDivMod: non-integer numer or denom %f %f",
// numer, denom);
#endif
if (numer >= 0.0) {
x = floor (numer / denom);
q = (int) x;
r = (int) floor (numer - (x * denom));
} else {
// perform operations with positive values, and fix mod to make
// floor-based
x = floor (-numer / denom);
q = -(int) x;
r = (int) floor (-numer - (x * denom));
if (r != 0) {
q--;
r = (int) denom - r;
}
}
*quotient = q;
*rem = r;
}
VISIBLE int
GreatestCommonDivisor (int i1, int i2)
{
if (i1 > i2) {
if (i2 == 0)
return (i1);
return GreatestCommonDivisor (i2, i1 % i2);
} else {
if (i1 == 0)
return (i2);
return GreatestCommonDivisor (i1, i2 % i1);
}
}
#ifndef USE_INTEL_ASM
/*
Invert24To16
Inverts an 8.24 value to a 16.16 value
*/
VISIBLE fixed16_t
Invert24To16 (fixed16_t val)
{
if (val < 256)
return (0xFFFFFFFF);
return (fixed16_t)
(((double) 0x10000 * (double) 0x1000000 / (double) val) + 0.5);
}
#endif
void
Mat3Init (const quat_t rot, const vec3_t scale, mat3_t mat)
{
QuatToMatrix (rot, mat, 0, 1);
VectorScale (mat + 0, scale[0], mat + 0);
VectorScale (mat + 3, scale[1], mat + 3);
VectorScale (mat + 6, scale[2], mat + 6);
}
void
Mat3Transpose (const mat3_t a, mat3_t b)
{
vec_t t;
int i, j;
for (i = 0; i < 2; i++) {
b[i * 3 + i] = a[i * 3 + i]; // in case b != a
for (j = i + 1; j < 3; j++) {
t = a[i * 3 + j]; // in case b == a
b[i * 3 + j] = a[j * 3 + i];
b[j * 3 + i] = t;
}
}
b[i * 3 + i] = a[i * 3 + i]; // in case b != a
}
vec_t
Mat3Determinant (const mat3_t m)
{
vec3_t t;
CrossProduct (m + 3, m + 6, t);
return DotProduct (m + 0, t);
}
typedef vec_t mat2_t[2 * 2];
static void
Mat3Sub2 (const mat3_t m3, mat2_t m2, int i, int j)
{
int si, sj, di, dj;
for (di = 0; di < 2; di++) {
for (dj = 0; dj < 2; dj++) {
si = di + ((di >= i) ? 1 : 0);
sj = dj + ((dj >= j) ? 1 : 0);
m2[di * 2 + dj] = m3[si * 3 + sj];
}
}
}
static vec_t
Mat2Det (const mat2_t m)
{
return m[0] * m[3] - m[1] * m[2];
}
int
Mat3Inverse (const mat3_t a, mat3_t b)
{
mat3_t tb;
mat2_t m2;
vec_t *m = b;
int i, j;
vec_t det;
vec_t sign[2] = { 1, -1};
det = Mat3Determinant (a);
if (det * det < 1e-6) {
Mat3Identity (b);
return 0;
}
if (b == a)
m = tb;
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
Mat3Sub2 (a, m2, i, j);
m[j * 3 + i] = sign[(i + j) & 1] * Mat2Det (m2) / det;
}
}
if (m != b)
Mat3Copy (m, b);
return 1;
}
void Mat3Mult (const mat3_t a, const mat3_t b, mat3_t c)
{
mat3_t ta, tb; // in case c == b or c == a
int i, j, k;
Mat3Transpose (a, ta); // transpose so we can use dot
Mat3Copy (b, tb);
k = 0;
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
c[k++] = DotProduct (ta + 3 * j, tb + 3 * i);
}
}
}
void Mat3MultVec (const mat3_t a, const vec3_t b, vec3_t c)
{
int i;
vec3_t tb;
VectorCopy (b, tb);
for (i = 0; i < 3; i++)
c[i] = a[i + 0] * tb[0] + a[i + 3] * b[1] + a[i + 6] * b[2];
}
#define sqr(x) ((x) * (x))
void Mat3SymEigen (const mat3_t m, vec3_t e)
{
vec_t p, q, r;
vec_t phi;
mat3_t B;
p = sqr (m[1]) + sqr (m[2]) + sqr (m[5]);
if (p < 1e-6) {
e[0] = m[0];
e[1] = m[4];
e[2] = m[8];
return;
}
q = Mat3Trace (m) / 3;
p = sqr (m[0] - q) + sqr (m[4] - q) + sqr (m[8] - q) + 2 * p;
p = sqrt (p);
Mat3Zero (B);
B[0] = B[4] = B[8] = q;
Mat3Subtract (m, B, B);
Mat3Scale (B, 1.0 / p, B);
r = Mat3Determinant (B) / 2;
if (r >= 1)
phi = 0;
else if (r <= -1)
phi = M_PI / 3;
else
phi = acos (r) / 3;
e[0] = q + 2 * p * cos (phi);
e[2] = q + 2 * p * cos (phi + M_PI * 2 / 3);
e[1] = 3 * q - e[0] - e[2];
}
void
Mat4Init (const quat_t rot, const vec3_t scale, const vec3_t trans, mat4_t mat)
{
QuatToMatrix (rot, mat, 1, 1);
VectorScale (mat + 0, scale[0], mat + 0);
VectorScale (mat + 4, scale[1], mat + 4);
VectorScale (mat + 8, scale[2], mat + 8);
VectorCopy (trans, mat + 12);
}
void
Mat4Transpose (const mat4_t a, mat4_t b)
{
vec_t t;
int i, j;
for (i = 0; i < 3; i++) {
b[i * 4 + i] = a[i * 4 + i]; // in case b != a
for (j = i + 1; j < 4; j++) {
t = a[i * 4 + j]; // in case b == a
b[i * 4 + j] = a[j * 4 + i];
b[j * 4 + i] = t;
}
}
b[i * 4 + i] = a[i * 4 + i]; // in case b != a
}
static void
Mat4Sub3 (const mat4_t m4, mat3_t m3, int i, int j)
{
int si, sj, di, dj;
for (di = 0; di < 3; di++) {
for (dj = 0; dj < 3; dj++) {
si = di + ((di >= i) ? 1 : 0);
sj = dj + ((dj >= j) ? 1 : 0);
m3[di * 3 + dj] = m4[si * 4 + sj];
}
}
}
static vec_t
Mat4Det (const mat4_t m)
{
mat3_t t;
int i;
vec_t res = 0, det, s = 1;
for (i = 0; i < 4; i++, s = -s) {
Mat4Sub3 (m, t, 0, i);
det = Mat3Determinant (t);
res += m[i] * det * s;
}
return res;
}
int
Mat4Inverse (const mat4_t a, mat4_t b)
{
mat4_t tb;
mat3_t m3;
vec_t *m = b;
int i, j;
vec_t det;
vec_t sign[2] = { 1, -1};
det = Mat4Det (a);
if (det * det < 1e-6) {
Mat4Identity (b);
return 0;
}
if (b == a)
m = tb;
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) {
Mat4Sub3 (a, m3, i, j);
m[j * 4 + i] = sign[(i + j) & 1] * Mat3Determinant (m3) / det;
}
}
if (m != b)
Mat4Copy (m, b);
return 1;
}
void
Mat4Mult (const mat4_t a, const mat4_t b, mat4_t c)
{
mat4_t ta, tb; // in case c == b or c == a
int i, j, k;
Mat4Transpose (a, ta); // transpose so we can use dot
Mat4Copy (b, tb);
k = 0;
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) {
c[k++] = QDotProduct (ta + 4 * j, tb + 4 * i);
}
}
}
void
Mat4MultVec (const mat4_t a, const vec3_t b, vec3_t c)
{
int i;
vec3_t tb;
VectorCopy (b, tb);
for (i = 0; i < 3; i++)
c[i] = a[i + 0] * tb[0] + a[i + 4] * b[1] + a[i + 8] * b[2] + a[i +12];
}
void
Mat4as3MultVec (const mat4_t a, const vec3_t b, vec3_t c)
{
int i;
vec3_t tb;
VectorCopy (b, tb);
for (i = 0; i < 3; i++)
c[i] = a[i + 0] * tb[0] + a[i + 4] * b[1] + a[i + 8] * b[2];
}
int
Mat3Decompose (const mat3_t mat, quat_t rot, vec3_t shear, vec3_t scale)
{
vec3_t row[3], shr, scl;
vec_t l, t;
int i, j;
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++)
row[j][i] = mat[i * 3 + j];
l = DotProduct (row[0], row[0]);
if (l < 1e-5)
return 0;
scl[0] = sqrt (l);
VectorScale (row[0], 1/scl[0], row[0]);
shr[0] = DotProduct (row[0], row[1]);
VectorMultSub (row[1], shr[0], row[0], row[1]);
l = DotProduct (row[1], row[1]);
if (l < 1e-5)
return 0;
scl[1] = sqrt (l);
shr[0] /= scl[1];
VectorScale (row[1], 1/scl[1], row[1]);
shr[1] = DotProduct (row[0], row[2]);
VectorMultSub (row[2], shr[1], row[0], row[2]);
shr[2] = DotProduct (row[1], row[2]);
VectorMultSub (row[2], shr[2], row[1], row[2]);
l = DotProduct (row[2], row[2]);
if (l < 1e-5)
return 0;
scl[2] = sqrt (l);
shr[1] /= scl[2];
shr[2] /= scl[2];
VectorScale (row[2], 1/scl[2], row[2]);
if (scale)
VectorCopy (scl, scale);
if (shear)
VectorCopy (shr, shear);
if (!rot)
return 1;
t = 1 + row[0][0] + row[1][1] + row[2][2];
if (t >= 1e-5) {
vec_t s = sqrt (t) * 2;
rot[0] = (row[2][1] - row[1][2]) / s;
rot[1] = (row[0][2] - row[2][0]) / s;
rot[2] = (row[1][0] - row[0][1]) / s;
rot[3] = s / 4;
} else {
if (row[0][0] > row[1][1] && row[0][0] > row[2][2]) {
vec_t s = sqrt (1 + row[0][0] - row[1][1] - row[2][2]) * 2;
rot[0] = s / 4;
rot[1] = (row[1][0] + row[0][1]) / s;
rot[2] = (row[0][2] + row[2][0]) / s;
rot[3] = (row[2][1] - row[1][2]) / s;
} else if (row[1][1] > row[2][2]) {
vec_t s = sqrt (1 + row[1][1] - row[0][0] - row[2][2]) * 2;
rot[0] = (row[1][0] + row[0][1]) / s;
rot[1] = s / 4;
rot[2] = (row[2][1] + row[1][2]) / s;
rot[3] = (row[0][2] - row[2][0]) / s;
} else {
vec_t s = sqrt (1 + row[2][2] - row[0][0] - row[1][1]) * 2;
rot[0] = (row[0][2] + row[2][0]) / s;
rot[1] = (row[2][1] + row[1][2]) / s;
rot[2] = s / 4;
rot[3] = (row[1][0] - row[0][1]) / s;
}
}
return 1;
}
int
Mat4Decompose (const mat4_t mat, quat_t rot, vec3_t shear, vec3_t scale,
vec3_t trans)
{
mat3_t m3;
if (trans)
VectorCopy (mat + 12, trans);
Mat4toMat3 (mat, m3);
return Mat3Decompose (m3, rot, shear, scale);
}
void
BarycentricCoords (const vec_t **points, int num_points, const vec3_t p,
vec_t *lambda)
{
vec3_t a, b, c, x, ab, bc, ca, n;
vec_t div;
if (num_points > 4)
Sys_Error ("Don't know how to compute the barycentric coordinates "
"for %d points", num_points);
switch (num_points) {
case 1:
lambda[0] = 1;
return;
case 2:
VectorSubtract (p, points[0], x);
VectorSubtract (points[1], points[0], a);
lambda[1] = DotProduct (x, a) / DotProduct (a, a);
lambda[0] = 1 - lambda[1];
return;
case 3:
VectorSubtract (p, points[0], x);
VectorSubtract (points[1], points[0], a);
VectorSubtract (points[2], points[0], b);
CrossProduct (a, b, ab);
div = DotProduct (ab, ab);
CrossProduct (x, b, n);
lambda[1] = DotProduct (n, ab);
CrossProduct (a, x, n);
lambda[2] = DotProduct (n, ab);
lambda[0] = div - lambda[1] - lambda[2];
VectorScale (lambda, 1 / div, lambda);
return;
case 4:
VectorSubtract (p, points[0], x);
VectorSubtract (points[1], points[0], a);
VectorSubtract (points[2], points[0], b);
VectorSubtract (points[3], points[0], c);
CrossProduct (a, b, ab);
CrossProduct (b, c, bc);
CrossProduct (c, a, ca);
div = DotProduct (a, bc);
lambda[1] = DotProduct (x, bc) / div;
lambda[2] = DotProduct (x, ca) / div;
lambda[3] = DotProduct (x, ab) / div;
lambda[0] = 1 - lambda[1] - lambda[2] - lambda[3];
return;
}
Sys_Error ("Not enough points to project or enclose the point");
}
static int
circum_circle (const vec_t **points, int num_points, sphere_t *sphere)
{
vec3_t a, c, b;
vec3_t bc, ca, ab;
vec_t aa, bb, cc;
vec_t div;
vec_t alpha, beta, gamma;
switch (num_points) {
case 1:
VectorCopy (points[0], sphere->center);
return 1;
case 2:
VectorBlend (points[0], points[1], 0.5, sphere->center);
return 1;
case 3:
VectorSubtract (points[0], points[1], a);
VectorSubtract (points[0], points[2], b);
VectorSubtract (points[1], points[2], c);
aa = DotProduct (a, a);
bb = DotProduct (b, b);
cc = DotProduct (c, c);
div = DotProduct (a, c);
div = 2 * (aa * cc - div * div);
if (fabs (div) < EQUAL_EPSILON) {
// degenerate
return 0;
}
alpha = cc * DotProduct (a, b) / div;
beta = -bb * DotProduct (a, c) / div;
gamma = aa * DotProduct (b, c) / div;
VectorScale (points[0], alpha, sphere->center);
VectorMultAdd (sphere->center, beta, points[1], sphere->center);
VectorMultAdd (sphere->center, gamma, points[2], sphere->center);
return 1;
case 4:
VectorSubtract (points[1], points[0], a);
VectorSubtract (points[2], points[0], b);
VectorSubtract (points[3], points[0], c);
CrossProduct (b, c, bc);
CrossProduct (c, a, ca);
CrossProduct (a, b, ab);
div = 2 * DotProduct (a, bc);
if (fabs (div) < EQUAL_EPSILON) {
// degenerate
return 0;
}
aa = DotProduct (a, a) / div;
bb = DotProduct (b, b) / div;
cc = DotProduct (c, c) / div;
VectorScale (bc, aa, sphere->center);
VectorMultAdd (sphere->center, bb, ca, sphere->center);
VectorMultAdd (sphere->center, cc, ab, sphere->center);
VectorAdd (sphere->center, points[0], sphere->center);
return 1;
}
return 0;
}
int
CircumSphere (const vec3_t points[], int num_points, sphere_t *sphere)
{
const vec_t *p[] = {points[0], points[1], points[2], points[3]};
if (num_points > 4)
return 0;
sphere->radius = 0;
if (num_points) {
if (circum_circle (p, num_points, sphere)) {
if (num_points > 1)
sphere->radius = VectorDistance (sphere->center, points[0]);
return 1;
}
return 0;
}
VectorZero (sphere->center);
return 1;
}
static void
closest_affine_point (const vec_t **points, int num_points, const vec3_t x,
vec3_t closest)
{
vec3_t a, b, n, d;
vec_t l;
switch (num_points) {
default:
case 1:
VectorCopy (points[0], closest);
break;
case 2:
VectorSubtract (points[1], points[0], n);
VectorSubtract (x, points[0], d);
l = DotProduct (d, n) / DotProduct (n, n);
VectorMultAdd (points[0], l, n, closest);
break;
case 3:
VectorSubtract (points[1], points[0], a);
VectorSubtract (points[2], points[0], b);
CrossProduct (a, b, n);
VectorSubtract (points[0], x, d);
l = DotProduct (d, n) / DotProduct (n, n);
VectorMultAdd (x, l, n, closest);
break;
}
}
static int
test_support_points(const vec_t **points, int *num_points, const vec3_t center)
{
int in_affine = 0;
int in_convex = 0;
vec3_t v, d, n, a, b;
vec_t nn, dd, vv, dn;
switch (*num_points) {
case 1:
in_affine = VectorCompare (points[0], center);
// the convex hull and affine hull for a single point are the same
in_convex = in_affine;
break;
case 2:
VectorSubtract (points[1], points[0], v);
VectorAdd (points[0], points[1], d);
VectorScale (d, 0.5, d);
VectorSubtract (center, d, d);
CrossProduct (v, d, n);
nn = DotProduct (n, n);
vv = DotProduct (v, v);
in_affine = nn < 1e-5 * vv * vv;
break;
case 3:
VectorSubtract (points[1], points[0], a);
VectorSubtract (points[2], points[0], b);
VectorSubtract (center, points[0], d);
CrossProduct (a, b, n);
dn = DotProduct (d, n);
dd = DotProduct (d, d);
nn = DotProduct (n, n);
in_affine = dn * dn < 1e-5 * dd * nn;
break;
case 4:
in_affine = 1;
break;
default:
Sys_Error ("Invalid number of points (%d) in test_support_points",
*num_points);
}
// if in_convex is not true while in_affine is, then need to test as
// there is more than one dimension for the affine hull (a single support
// point is never dropped as it cannot be redundant)
if (in_affine && !in_convex) {
vec_t lambda[4];
int dropped = 0;
int count = *num_points;
BarycentricCoords (points, count, center, lambda);
for (int i = 0; i < count; i++) {
points[i - dropped] = points[i];
if (lambda[i] < -1e-4) {
dropped++;
(*num_points)--;
}
}
in_convex = !dropped;
if (dropped) {
for (int i = count - dropped; i < count; i++) {
points[i] = 0;
}
}
}
return in_convex;
}
sphere_t
SmallestEnclosingBall (const vec3_t points[], int num_points)
{
set_t was_support = SET_STATIC_INIT (num_points, alloca);
sphere_t sphere;
const vec_t *best;
const vec_t *support[4];
int num_support;
vec_t dist, best_dist;
int i;
int best_i = 0;
int iters = 0;
if (num_points < 1) {
VectorZero (sphere.center);
sphere.radius = 0;
return sphere;
}
for (i = 0; i < 4; i++)
support[i] = 0;
set_empty (&was_support);
VectorCopy (points[0], sphere.center);
best_dist = dist = 0;
best = points[0];
for (i = 1; i < num_points; i++) {
dist = VectorDistance_fast (points[i], sphere.center);
if (dist > best_dist) {
best_dist = dist;
best_i = i;
best = points[i];
}
}
num_support = 1;
support[0] = best;
sphere.radius = best_dist; // note: radius squared until the end
set_add (&was_support, best_i);
while (!test_support_points (support, &num_support, sphere.center)) {
vec3_t affine, v, p, r;
vec_t x, best_x = 0, rr, pv, pp;
int i;
if (iters++ > 2 * num_points)
Sys_Error ("stuck SEB");
closest_affine_point (support, num_support, sphere.center, affine);
VectorSubtract (support[0], affine, r);
rr = DotProduct (r, r);
VectorSubtract (sphere.center, affine, v);
best = 0;
for (i = 0; i < num_points; i++) {
if (SET_TEST_MEMBER (&was_support, i)) {
continue;
}
VectorSubtract (points[i], affine, p);
pp = DotProduct (p, p);
pv = DotProduct (p, v);
if (pp <= rr || pv <= 0 || pv * pv < 1e-6 * rr) {
continue;
}
x = (pp - rr) / (2 * pv);
if (x > best_x) {
best = points[i];
best_i = i;
best_x = x;
}
}
VectorMultAdd (affine, best_x, v, sphere.center);
sphere.radius = VectorDistance_fast (sphere.center, support[0]);
if (best) {
support[num_support++] = best;
set_add (&was_support, best_i);
}
}
best_dist = 0;
for (i = 0; i < num_points; i++) {
dist = VectorDistance_fast (sphere.center, points[i]);
if (dist > best_dist)
best_dist = dist;
}
sphere.radius = sqrt (best_dist);
return sphere;
}