quakeforge/libs/util/mathlib.c
Bill Currie 7240d2dc80 [model] Move plane info into mnode_t, and visframe out
The main goal was to get visframe out of mnode_t to make it thread-safe
(each thread can have its own visframe array), but moving the plane info
into mnode_t made for better data access patters when traversing the bsp
tree as the plane is right there with the child indices. Nicely, the
size of mnode_t is the same as before (64 bytes due to alignment), with
4 bytes wasted.

Performance-wise, there seems to be very little difference. Maybe
slightly slower.

The unfortunate thing about the change is the plane distance is negated,
possibly leading to some confusion, particularly since the box and
sphere culling functions were affected. However, this is so point-plane
distance calculations can be done with a single 4d dot product.
2022-05-22 12:41:23 +09:00

1494 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: these matrices have forward, left and up vectors horizontal rather
than vertical and are thus the inverse of the matrices to produce the
actual rotation.
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
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;
}