power_play/src/base/base_math.c
2025-08-24 18:58:53 -05:00

1449 lines
30 KiB
C

/* Math functions are default 32 bit (f32, i32, etc) unless specified */
////////////////////////////////
//~ Min / max
//- Min
u8 MinU8(u8 a, u8 b) { return a <= b ? a : b; }
i8 MinI8(i8 a, i8 b) { return a <= b ? a : b; }
u32 MinU32(u32 a, u32 b) { return a <= b ? a : b; }
i32 MinI32(i32 a, i32 b) { return a <= b ? a : b; }
f32 MinF32(f32 a, f32 b) { return a <= b ? a : b; }
u64 MinU64(u64 a, u64 b) { return a <= b ? a : b; }
i64 MinI64(i64 a, i64 b) { return a <= b ? a : b; }
f64 MinF64(f64 a, f64 b) { return a <= b ? a : b; }
//- Max
u8 MaxU8(u8 a, u8 b) { return a >= b ? a : b; }
i8 MaxI8(i8 a, i8 b) { return a >= b ? a : b; }
u32 MaxU32(u32 a, u32 b) { return a >= b ? a : b; }
i32 MaxI32(i32 a, i32 b) { return a >= b ? a : b; }
f32 MaxF32(f32 a, f32 b) { return a >= b ? a : b; }
u64 MaxU64(u64 a, u64 b) { return a >= b ? a : b; }
i64 MaxI64(i64 a, i64 b) { return a >= b ? a : b; }
f64 MaxF64(f64 a, f64 b) { return a >= b ? a : b; }
//- Clamp
u32 ClampU32(u32 v, u32 min, u32 max) { return v < min ? min : v > max ? max : v; }
i32 ClampI32(i32 v, i32 min, i32 max) { return v < min ? min : v > max ? max : v; }
f32 ClampF32(f32 v, f32 min, f32 max) { return v < min ? min : v > max ? max : v; }
u64 ClampU64(u64 v, u64 min, u64 max) { return v < min ? min : v > max ? max : v; }
i64 ClampI64(i64 v, i64 min, i64 max) { return v < min ? min : v > max ? max : v; }
f64 ClampF64(f64 v, f64 min, f64 max) { return v < min ? min : v > max ? max : v; }
////////////////////////////////
//~ Rounding ops
//- Round
f32 RoundF32(f32 f)
{
return IxRoundF32ToF32(f);
}
f64 RoundF64(f64 f)
{
return IxRoundF64ToF64(f);
}
i32 RoundF32ToI32(f32 f)
{
return IxRoundF32ToI32(f);
}
i64 RoundF64ToI64(f64 f)
{
return IxRoundF64ToI64(f);
}
//- Floor
f32 FloorF32(f32 f)
{
return IxFloorF32ToF32(f);
}
f64 FloorF64(f64 f)
{
return IxFloorF64ToF64(f);
}
i32 FloorF32ToI32(f32 f)
{
return IxFloorF32ToI32(f);
}
i64 FloorF64ToI64(f64 f)
{
return IxFloorF64ToI64(f);
}
//- Ceil
f32 CeilF32(f32 f)
{
return IxCeilF32ToF32(f);
}
f64 CeilF64(f64 f)
{
return IxCeilF64ToF64(f);
}
i32 CeilF32ToI32(f32 f)
{
return IxCeilF32ToI32(f);
}
i64 CeilF64ToI64(f64 f)
{
return IxCeilF64ToI64(f);
}
//- Trunc
f32 TruncF32(f32 f)
{
return IxTruncF32ToF32(f);
}
f64 TruncF64(f64 f)
{
return IxTruncF64ToF64(f);
}
////////////////////////////////
//~ Fmod
f32 ModF32(f32 x, f32 m)
{
return x - m * TruncF32(x / m);
}
f64 ModF64(f64 x, f64 m)
{
return x - m * TruncF64(x / m);
}
////////////////////////////////
//~ Floating point sign
f32 AbsF32(f32 f)
{
u32 truncated = *(u32 *)&f & 0x7FFFFFFF;
return *(f32 *)&truncated;
}
f64 AbsF64(f64 f)
{
u64 truncated = *(u64 *)&f & 0x7FFFFFFFFFFFFFFFULL;
return *(f64 *)&truncated;
}
u32 AbsI32(i32 v)
{
return (u32)(v * ((v >= 0) - (v < 0)));
}
u64 AbsI64(i64 v)
{
return (u64)(v * ((v >= 0) - (v < 0)));
}
i32 SignF32(f32 f)
{
u32 sign_bit = (*(u32 *)&f >> 31) & 1;
return 1 + -((i32)(sign_bit << 1));
}
i64 SignF64(f64 f)
{
u64 sign_bit = (*(u64 *)&f >> 63) & 1;
return 1 + -((i64)(sign_bit << 1));
}
////////////////////////////////
//~ U64 pow
/* Taken from https://gist.github.com/orlp/3551590 */
u64 PowU64(u64 base, u8 exp)
{
LocalPersist const u8 highest_bit_set[] = {
0, 1, 2, 2, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 255, /* Anything past 63 is a guaranteed overflow with base > 1 */
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
};
u64 result = 1;
switch (highest_bit_set[exp])
{
case 255:
{
/* 255 = overflow, return 0 */
if (base == 1)
{
return 1;
}
// if (base == -1)
// {
// return 1 - 2 * (exp & 1);
// }
return 0;
} break;
case 6:
{
if (exp & 1) result *= base;
exp >>= 1;
base *= base;
} FALLTHROUGH;
case 5:
{
if (exp & 1) result *= base;
exp >>= 1;
base *= base;
} FALLTHROUGH;
case 4:
{
if (exp & 1) result *= base;
exp >>= 1;
base *= base;
} FALLTHROUGH;
case 3:
{
if (exp & 1) result *= base;
exp >>= 1;
base *= base;
} FALLTHROUGH;
case 2:
{
if (exp & 1) result *= base;
exp >>= 1;
base *= base;
} FALLTHROUGH;
case 1:
{
if (exp & 1) result *= base;
} FALLTHROUGH;
default: return result;
}
}
////////////////////////////////
//~ Align up
u64 AlignU64Pow2(u64 x)
{
u64 result = 0;
if (x > 0)
{
result = x - 1;
result |= result >> 1;
result |= result >> 2;
result |= result >> 4;
result |= result >> 8;
result |= result >> 16;
result |= result >> 32;
++result;
}
return result;
}
////////////////////////////////
//~ Logn
/* Based on FreeBSD's implementation
* https://github.com/freebsd/freebsd-src/blob/main/lib/msun/src/e_logf.c */
f32 LnF32(f32 x)
{
LocalPersist const f32 ln2_hi = 6.9313812256e-01f;
LocalPersist const f32 ln2_lo = 9.0580006145e-06f;
i32 x_int = *(i32 *)&x;
i32 k = 0;
if (x_int < 0x00800000)
{
f32 two_p25 = 3.3554432000e+07;
if ((x_int & 0x7fffffff) == 0)
{
/* Return -inf if x is 0 */
return -two_p25 / 0;
}
else if (x_int < 0)
{
/* Return NaN if x is negative */
return (x - x) / 0;
}
k -= 25;
x *= two_p25;
x_int = *(i32 *)&x;
}
else if (x_int >= 0x7f800000)
{
return x + x;
}
k += (x_int >> 23) - 127;
x_int &= 0x007fffff;
i32 i = (x_int + (0x95f64 << 3)) & 0x800000;
i32 x_int_normalized = x_int | (i ^ 0x3f800000);
x = *(f32 *)&x_int_normalized - 1.0f;
k += (i >> 23);
if ((0x007fffff & (0x8000 + x_int)) < 0xc000)
{
if (x == 0)
{
if (k == 0)
{
return 0;
}
else
{
return (f32)k * ln2_hi + (f32)k * ln2_lo;
}
}
f32 r = x * x * (0.5f - 0.33333333333333333f * x);
if (k == 0)
{
return x - r;
}
else
{
return (f32)k * ln2_hi - ((r - (f32)k * ln2_lo) - x);
}
}
f32 s = x / (2.0f + x);
f32 z = s * s;
f32 w = z * z;
f32 r = (z * (0.66666662693f + w * 0.28498786688f)) + (w * (0.40000972152f + w * 0.24279078841f));
if (((x_int - (0x6147a << 3)) | ((0x6b851 << 3) - x_int)) > 0)
{
f32 hfsq = 0.5f * x * x;
if (k == 0)
{
return x - (hfsq - s * (hfsq + r));
}
else
{
return (f32)k * ln2_hi - ((hfsq - (s * (hfsq + r) + (f32)k * ln2_lo)) - x);
}
}
else
{
if (k == 0)
{
return x - s * (x - r);
}
else
{
return (f32)k * ln2_hi - ((s * (x - r) - (f32)k * ln2_lo) - x);
}
}
}
////////////////////////////////
//~ Exp
/* Based on FreeBSD's implementation
* https://github.com/freebsd/freebsd-src/blob/main/lib/msun/src/e_expf.c */
f32 ExpF32(f32 x)
{
LocalPersist const f32 half[2] = { 0.5, -0.5 };
LocalPersist const f32 o_threshold = 8.8721679688e+01f;
LocalPersist const f32 u_threshold = -1.0397208405e+02f;
LocalPersist const f32 ln2_hi[2] = { 6.9314575195e-01f, -6.9314575195e-01f };
LocalPersist const f32 ln2_lo[2] = { 1.4286067653e-06f, -1.4286067653e-06f };
LocalPersist const f32 inv_ln2 = 1.4426950216e+00f;
LocalPersist const f32 huge = 1.0e+30f;
LocalPersist const f32 two_m100 = 7.8886090522e-31f;
u32 x_uint = *(u32 *)&x;
i32 x_sign_bit = (i32)((x_uint >> 31) & 1);
x_uint &= 0x7fffffff;
/* Filter out non-finite argument */
if (x_uint >= 0x42b17218)
{ /* if |x|>=88.721... */
if (x_uint > 0x7f800000)
{
return x + x; /* NaN */
}
else if (x_uint == 0x7f800000)
{
return (x_sign_bit == 0) ? x : 0;
}
if (x > o_threshold)
{
/* Overflow */
return F32Infinity;
}
else if (x < u_threshold)
{
/* Underflow */
return two_m100 * two_m100;
}
}
/* Argument reduction */
i32 k = 0;
f32 hi = 0;
f32 lo = 0;
if (x_uint > 0x3eb17218)
{
if (x_uint < 0x3F851592)
{
hi = x - ln2_hi[x_sign_bit];
lo = ln2_lo[x_sign_bit];
k = 1 - x_sign_bit - x_sign_bit;
}
else
{
k = (i32)(inv_ln2 * x + half[x_sign_bit]);
hi = x - (f32)k * ln2_hi[0];
lo = (f32)k * ln2_lo[0];
}
x = hi - lo;
}
else if (x_uint < 0x39000000)
{
if (huge + x > 1.0f)
{
return 1.0f + x;
}
}
else
{
k = 0;
}
f32 two_pk;
if (k >= -125)
{
u32 temp = ((u32)(0x7f + k)) << 23;
two_pk = *(f32 *)&temp;
}
else
{
u32 temp = ((u32)(0x7f + (k + 100))) << 23;
two_pk = *(f32 *)&temp;
}
f32 t = x * x;
f32 c = x - t * (1.6666625440e-1f + t * -2.7667332906e-3f);
if (k == 0)
{
return 1.0f - ((x * c) / (c - 2.0f) - x);
}
else
{
f32 y = 1.0f - ((lo - (x * c) / (2.0f - c)) - hi);
if (k >= -125)
{
if (k == 128)
{
u32 temp = 0x7f800000;
return y * 2.0f * (*(f32 *)&temp);
}
return y * two_pk;
}
else
{
return y * two_pk * two_m100;
}
}
}
////////////////////////////////
//~ Pow
f32 PowF32(f32 a, f32 b)
{
if (a >= 0)
{
/* a is positive */
return ExpF32(LnF32(a) * b);
}
else
{
/* a is negative */
f32 res_sign = RoundF32ToI32(b) % 2 == 0 ? 1 : -1;
return ExpF32(LnF32(-a) * b) * res_sign;
}
}
////////////////////////////////
//~ Sqrt
f32 SqrtF32(f32 x)
{
return IxSqrtF32(x);
}
f64 SqrtF64(f64 x)
{
return IxSqrtF64(x);
}
f32 RSqrtF32(f32 x)
{
return IxRsqrtF32(x);
}
////////////////////////////////
//~ Trig
/* Functions based on Cephes implementation (https://www.netlib.org/cephes/):
* - SinApproxF32
* - CosApproxF32
* - ReduceToPio4
* - ArcTanF32
*/
//- Reduce
/* Reduce postive x to range [0, Pi/4] (Cody-Waite argument reduction).
* Returns 0 if x > (2^24 - 1);
* Sets octant_out=-1 on error. */
f32 ReduceToPio4(f32 x, i32 *octant_out)
{
i32 octant = -1;
if (x <= ((1 << 24) - 1))
{
octant = x * (4 / Pi); /* Integer part of x/(Pi/4) */
f32 y = octant;
if (octant & 1)
{
octant += 1;
y += 1.0;
}
/* Modulo 360 degrees */
octant &= 7;
if (x > 8192)
{
x = x - y * (Pi / 4);
}
else
{
/* Extended precision modular arithmetic */
x = ((x - y * 0.78515625) - y * 2.4187564849853515625e-4) - y * 3.77489497744594108e-8;
}
}
*octant_out = octant;
return x;
}
//- Sin approximation
/* Approximate sin in range [0, Pi/4] */
f32 SinApproxF32(f32 x)
{
f32 x_sq = x * x;
return (((-1.9515295891E-4 * x_sq + 8.3321608736E-3) * x_sq - 1.6666654611E-1) * x_sq * x) + x;
}
//- Cos approximation
f32 CosApproxF32(f32 x)
{
f32 x_sq = x * x;
return (((2.443315711809948E-005 * x_sq - 1.388731625493765E-003) * x_sq + 4.166664568298827E-002) * x_sq * x_sq) - (x_sq * 0.5) + 1;
}
//- Sin
f32 SinF32(f32 x)
{
f32 sign = 1;
if (IsF32Nan(x))
{
return 0;
}
else if (x < 0)
{
sign = -1;
x = -x;
}
i32 octant;
x = ReduceToPio4(x, &octant);
/* Reflect in x axis */
if (octant > 3)
{
sign = -sign;
octant -= 4;
}
switch (octant)
{
case -1: return 0;
case 1: case 2: return CosApproxF32(x) * sign;
default: return SinApproxF32(x) * sign;
}
}
//- Cos
f32 CosF32(f32 x)
{
f32 sign = 1;
if (IsF32Nan(x))
{
return 0;
}
else if (x < 0)
{
x = -x;
}
i32 octant;
x = ReduceToPio4(x, &octant);
/* Reflect in x axis */
if (octant > 3)
{
sign = -sign;
octant -= 4;
}
if (octant > 1)
{
sign = -sign;
}
switch (octant)
{
case -1: return 0;
case 1: case 2: return SinApproxF32(x) * sign;
default: return CosApproxF32(x) * sign;
}
}
//- ArcTan
f32 ArcTanF32(f32 x)
{
f32 sign = 1;
if (x < 0)
{
sign = -1;
x = -x;
}
/* Reduce range */
f32 y;
if (x > 2.414213562373095)
{ /* tan((Pi / 8) * 3) */
y = Pi / 2;
x = -(1.f / x);
}
else if (x > 0.4142135623730950)
{ /* tan(Pi / 8) */
y = Pi / 4;
x = (x - 1.f) / (x + 1.f);
}
else
{
y = 0;
}
f32 x_sq = x * x;
y += ((((8.05374449538e-2 * x_sq - 1.38776856032E-1) * x_sq + 1.99777106478E-1) * x_sq - 3.33329491539E-1) * x_sq * x + x);
return y * sign;
}
//- ArcTan2
f32 ArcTan2F32(f32 y, f32 x)
{
f32 result;
if (x == 0)
{
if (y < 0)
{
result = 3 * Pi / 2;
}
else if (y == 0)
{
result = 0;
}
else
{
result = Pi / 2;
}
}
else if (y == 0)
{
if (x < 0)
{
result = Pi;
}
else
{
result = 0;
}
}
else
{
f32 offset;
if (x < 0)
{
offset = Pi;
}
else if (y < 0)
{
offset = Pi * 2;
}
else
{
offset = 0;
}
result = ArcTanF32(y / x) + offset;
}
return result;
}
//- ArcSin
f32 ArcSinF32(f32 x)
{
/* TODO: Dedicated arcsin approximation */
return ArcTan2F32(x, SqrtF32(1.0f - (x * x)));
}
//- ArcCos
f32 ArcCosF32(f32 x)
{
/* TODO: Dedicated arccos approximation */
return (Pi / 2.0f) - ArcTan2F32(x, SqrtF32(1.0f - (x * x)));
}
////////////////////////////////
//~ Angle unwind
/* Returns angle in range [-Pi, Pi] */
f32 UnwindAngleF32(f32 a)
{
f32 d = ModF32(a, Tau);
return ModF32(2.0f * d, Tau) - d;
}
////////////////////////////////
//~ Float lerp
f32 LerpF32(f32 val0, f32 val1, f32 t)
{
return val0 + ((val1 - val0) * t);
}
f64 LerpF64(f64 val0, f64 val1, f64 t)
{
return val0 + ((val1 - val0) * t);
}
f32 LerpAngleF32(f32 a, f32 b, f32 t)
{
f32 diff = UnwindAngleF32(b - a);
return a + diff * t;
}
////////////////////////////////
//~ Int lerp
i32 LerpI32(i32 val0, i32 val1, f32 t)
{
return val0 + RoundF32ToI32(((f32)(val1 - val0) * t));
}
//- Lerp i64
i64 LerpI64(i64 val0, i64 val1, f64 t)
{
return val0 + RoundF64ToI64(((f64)(val1 - val0) * t));
}
////////////////////////////////
//~ Vec2 operations
b32 IsVec2Zero(Vec2 a)
{
return a.x == 0 && a.y == 0;
}
b32 EqVec2(Vec2 a, Vec2 b)
{
return a.x == b.x && a.y == b.y;
}
//- Mul
Vec2 MulVec2(Vec2 a, f32 s)
{
return VEC2(a.x * s, a.y * s);
}
Vec2 MulVec2Vec2(Vec2 a, Vec2 b)
{
return VEC2(a.x * b.x, a.y * b.y);
}
Vec2 NegVec2(Vec2 a)
{
return VEC2(-a.x, -a.y);
}
//- Div
Vec2 DivVec2(Vec2 a, f32 s)
{
f32 d = 1 / s;
return VEC2(a.x * d, a.y * d);
}
Vec2 DivVec2Vec2(Vec2 a, Vec2 b)
{
return VEC2(a.x * (1 / b.x), a.y * (1 / b.y));
}
//- Add
Vec2 AddVec2(Vec2 a, Vec2 b)
{
return VEC2(a.x + b.x, a.y + b.y);
}
Vec2 SubVec2(Vec2 a, Vec2 b)
{
return VEC2(a.x - b.x, a.y - b.y);
}
//- Len
f32 Vec2Len(Vec2 a)
{
return SqrtF32(a.x * a.x + a.y * a.y);
}
f32 Vec2LenSq(Vec2 a)
{
return a.x * a.x + a.y * a.y;
}
Vec2 Vec2WithLen(Vec2 a, f32 len)
{
f32 l_sq = a.x * a.x + a.y * a.y;
if (l_sq != 0)
{
f32 denom = len / SqrtF32(l_sq);
a.x *= denom;
a.y *= denom;
}
return a;
}
Vec2 ClampVec2Len(Vec2 a, f32 max)
{
f32 l_sq = a.x * a.x + a.y * a.y;
if (l_sq > max * max)
{
f32 denom = max / SqrtF32(l_sq);
a.x *= denom;
a.y *= denom;
}
return a;
}
f32 Vec2Distance(Vec2 a, Vec2 b)
{
f32 dx = b.x - a.x;
f32 dy = b.y - a.y;
return SqrtF32(dx * dx + dy * dy);
}
Vec2 NormVec2(Vec2 a)
{
return Vec2WithLen(a, 1.f);
}
//- Dot
f32 DotVec2(Vec2 a, Vec2 b)
{
return a.x * b.x + a.y * b.y;
}
/* Returns signed area between vectors (positive in clockwise direction)
* AKA perpendicular dot product
* AKA 2d cross-product */
f32 WedgeVec2(Vec2 a, Vec2 b)
{
return a.x * b.y - a.y * b.x;
}
/* Clockwise (right) perpendicular vector */
Vec2 PerpVec2(Vec2 a)
{
return VEC2(-a.y, a.x);
}
/* Clockwise (right) perpendicular vector scaled by s */
Vec2 MulPerpVec2(Vec2 a, f32 s)
{
return VEC2(s * -a.y, s * a.x);
}
Vec2 PerpVec2TowardsDir(Vec2 v, Vec2 dir)
{
f32 wedge = WedgeVec2(v, dir);
return MulPerpVec2(v, (wedge >= 0) - (wedge < 0));
}
//- Round / floor / ceil
Vec2 RoundVec2(Vec2 a)
{
return VEC2(RoundF32(a.x), RoundF32(a.y));
}
Vec2I32 RoundVec2ToVec2I32(Vec2 a)
{
return VEC2I32(RoundF32ToI32(a.x), RoundF32ToI32(a.y));
}
Vec2 FloorVec2(Vec2 a)
{
return VEC2(FloorF32(a.x), FloorF32(a.y));
}
Vec2 CeilVec2(Vec2 a)
{
return VEC2(CeilF32(a.x), CeilF32(a.y));
}
//- Angle
/* Returns 1 if winding between vectors a & b is clockwise or straight, -1 if counter-clockwise */
i32 WindingFromVec2(Vec2 a, Vec2 b)
{
f32 w = WedgeVec2(a, b);
return (w >= 0) - (w < 0);
}
Vec2 RotateVec2(Vec2 v, f32 a)
{
f32 c = CosF32(a);
f32 s = SinF32(a);
return VEC2(v.x * c - v.y * s, v.x * s + v.y * c);
}
Vec2 Vec2FromAngle(f32 a)
{
return VEC2(CosF32(a), SinF32(a));
}
f32 AngleFromVec2(Vec2 v)
{
return ArcTan2F32(v.y, v.x);
}
f32 AngleFromVec2Dirs(Vec2 dir1, Vec2 dir2)
{
return ArcTan2F32(WedgeVec2(dir1, dir2), DotVec2(dir1, dir2));
}
f32 AngleFromVec2Points(Vec2 pt1, Vec2 pt2)
{
return AngleFromVec2(SubVec2(pt2, pt1));
}
//- Closest point
Vec2 ClosestPointFromRay(Vec2 ray_pos, Vec2 ray_dir_norm, Vec2 p)
{
Vec2 ray_p_dir = SubVec2(p, ray_pos);
f32 dot = DotVec2(ray_dir_norm, ray_p_dir);
Vec2 ray_dir_closest = MulVec2(ray_dir_norm, dot);
return AddVec2(ray_pos, ray_dir_closest);
}
//- Lerp
/* Interpolate position vectors */
Vec2 LerpVec2(Vec2 val0, Vec2 val1, f32 t)
{
return VEC2(LerpF32(val0.x, val1.x, t), LerpF32(val0.y, val1.y, t));
}
/* Interpolate direction vectors (spherical lerp) */
Vec2 SlerpVec2(Vec2 val0, Vec2 val1, f32 t)
{
f32 rot = LerpAngleF32(AngleFromVec2(val0), AngleFromVec2(val1), t);
f32 len = LerpF32(Vec2Len(val0), Vec2Len(val1), t);
return MulVec2(Vec2FromAngle(rot), len);
}
////////////////////////////////
//~ Vec2I32 Operations
b32 EqVec2I32(Vec2I32 a, Vec2I32 b)
{
return a.x == b.x && a.y == b.y;
}
Vec2I32 NegVec2I32(Vec2I32 a)
{
return VEC2I32(-a.x, -a.y);
}
Vec2I32 AddVec2I32(Vec2I32 a, Vec2I32 b)
{
return VEC2I32(a.x + b.x, a.y + b.y);
}
Vec2I32 SubVec2I32(Vec2I32 a, Vec2I32 b)
{
return VEC2I32(a.x - b.x, a.y - b.y);
}
////////////////////////////////
//~ Xform operations
b32 EqXform(Xform xf1, Xform xf2)
{
return EqVec2(xf1.og, xf2.og) && EqVec2(xf1.bx, xf2.bx) && EqVec2(xf1.by, xf2.by);
}
//- Initialization
Xform XformFromPos(Vec2 v)
{
Xform xf;
xf.bx = VEC2(1, 0);
xf.by = VEC2(0, 1);
xf.og = v;
return xf;
}
Xform XformFromRot(f32 r)
{
Xform result;
f32 c = CosF32(r);
f32 s = SinF32(r);
result.bx = VEC2(c, s);
result.by = VEC2(-s, c);
result.og = VEC2(0, 0);
return result;
}
Xform XformFromScale(Vec2 scale)
{
Xform result;
result.bx = VEC2(scale.x, 0);
result.by = VEC2(0, scale.y);
result.og = VEC2(0, 0);
return result;
}
Xform XformFromTrs(Trs trs)
{
Xform xf = XformFromPos(trs.t);
xf = RotateXform(xf, trs.r);
xf = ScaleXform(xf, trs.s);
return xf;
}
Xform XformFromRect(Rect rect)
{
Vec2 half_size = MulVec2(rect.size, 0.5);
Xform xf = ZI;
xf.bx = VEC2(rect.size.x, 0);
xf.by = VEC2(0, rect.size.y);
xf.og = AddVec2(rect.pos, half_size);
return xf;
}
//- Translation
Xform TranslateXform(Xform xf, Vec2 v)
{
xf.og = VEC2(xf.bx.x * v.x + xf.by.x * v.y + xf.og.x, xf.bx.y * v.x + xf.by.y * v.y + xf.og.y);
return xf;
}
Xform WorldTranslateXform(Xform xf, Vec2 v)
{
xf.og = AddVec2(xf.og, v);
return xf;
}
//- Rotation
Xform RotateXform(Xform xf, f32 r)
{
return MulXform(xf, XformFromRot(r));
}
Xform WorldRotateXform(Xform xf, f32 r)
{
return MulXform(XformFromRot(r), xf);
}
Xform WorldRotateXformBasis(Xform xf, f32 r)
{
f32 diff = r;
f32 c = CosF32(diff);
f32 s = SinF32(diff);
xf.bx = VEC2(xf.bx.x * c - xf.bx.y * s, xf.bx.x * s + xf.bx.y * c);
xf.by = VEC2(xf.by.x * c - xf.by.y * s, xf.by.x * s + xf.by.y * c);
return xf;
}
Xform XformWIthWorldRotation(Xform xf, f32 r)
{
return WorldRotateXformBasis(xf, r - RotationFromXform(xf));
}
//- Scale
Xform ScaleXform(Xform xf, Vec2 scale)
{
xf.bx = MulVec2(xf.bx, scale.x);
xf.by = MulVec2(xf.by, scale.y);
return xf;
}
Xform WorldScaleXform(Xform xf, Vec2 scale)
{
Xform result;
result.bx = MulVec2Vec2(xf.bx, scale);
result.by = MulVec2Vec2(xf.by, scale);
result.og = MulVec2Vec2(xf.og, scale);
return result;
}
//- Lerp
Xform LerpXform(Xform a, Xform b, f32 t)
{
Xform result;
result.bx = SlerpVec2(a.bx, b.bx, t);
result.by = SlerpVec2(a.by, b.by, t);
result.og = LerpVec2(a.og, b.og, t);
return result;
}
//- Invert
Xform InvertXform(Xform xf)
{
f32 det = DeterminantFromXform(xf);
f32 inv_det = 1.0f / det;
f32 old_bx_x = xf.bx.x;
xf.bx.x = xf.by.y;
xf.by.y = old_bx_x;
xf.bx = MulVec2Vec2(xf.bx, VEC2(inv_det, -inv_det));
xf.by = MulVec2Vec2(xf.by, VEC2(-inv_det, inv_det));
xf.og = MulXformBasisV2(xf, NegVec2(xf.og));
return xf;
}
//- Mul
Xform MulXform(Xform a, Xform b)
{
Xform result;
result.bx.x = a.bx.x * b.bx.x + a.by.x * b.bx.y;
result.bx.y = a.bx.y * b.bx.x + a.by.y * b.bx.y;
result.by.x = a.bx.x * b.by.x + a.by.x * b.by.y;
result.by.y = a.bx.y * b.by.x + a.by.y * b.by.y;
result.og = MulXformV2(a, b.og);
return result;
}
Vec2 MulXformBasisV2(Xform xf, Vec2 v)
{
return VEC2(
xf.bx.x * v.x + xf.by.x * v.y,
xf.bx.y * v.x + xf.by.y * v.y
);
}
Vec2 MulXformV2(Xform xf, Vec2 v)
{
Vec2 result = MulXformBasisV2(xf, v);
result = AddVec2(result, xf.og);
return result;
}
Quad MulXformQuad(Xform xf, Quad quad)
{
Quad result;
result.p0 = MulXformV2(xf, quad.p0);
result.p1 = MulXformV2(xf, quad.p1);
result.p2 = MulXformV2(xf, quad.p2);
result.p3 = MulXformV2(xf, quad.p3);
return result;
}
Vec2 InvertXformBasisMulV2(Xform xf, Vec2 v)
{
Xform inv = InvertXform(xf);
Vec2 result = MulXformBasisV2(inv, v);
return result;
}
/* TODO: Get rid of this? Just force caller to use invert manually since it's expensive. */
Vec2 InvertXformMulV2(Xform xf, Vec2 v)
{
Xform inv = InvertXform(xf);
return MulXformV2(inv, v);
}
//- Helpers
Xform BasisFromXform(Xform xf)
{
Xform result = ZI;
result.bx = xf.bx;
result.by = xf.by;
return result;
}
f32 DeterminantFromXform(Xform xf)
{
return WedgeVec2(xf.bx, xf.by);
}
Vec2 RightFromXform(Xform xf)
{
return xf.bx;
}
Vec2 LeftFromXform(Xform xf)
{
return NegVec2(xf.bx);
}
Vec2 UpFromXform(Xform xf)
{
return NegVec2(xf.by);
}
Vec2 DownFromXform(Xform xf)
{
return xf.by;
}
f32 RotationFromXform(Xform xf)
{
return AngleFromVec2(xf.bx);
}
Vec2 ScaleFromXform(Xform xf)
{
f32 det_sign = SignF32(DeterminantFromXform(xf));
return VEC2(Vec2Len(xf.bx), det_sign * Vec2Len(xf.by));
}
////////////////////////////////
//~ Quad operations
Quad QuadFromRect(Rect rect)
{
Quad result;
result.p0 = VEC2(rect.x, rect.y); /* Top left */
result.p1 = VEC2(rect.x + rect.width, rect.y); /* Top right */
result.p2 = VEC2(rect.x + rect.width, rect.y + rect.height); /* Bottom right */
result.p3 = VEC2(rect.x, rect.y + rect.height); /* Bottom left */
return result;
}
Quad QuadFromAabb(Aabb aabb)
{
Quad result;
result.p0 = VEC2(aabb.p0.x, aabb.p0.y); /* Top left */
result.p1 = VEC2(aabb.p1.x, aabb.p0.y); /* Top right */
result.p2 = VEC2(aabb.p1.x, aabb.p1.y); /* Bottom right */
result.p3 = VEC2(aabb.p0.x, aabb.p1.y); /* Bottom left */
return result;
}
Quad QuadFromLine(Vec2 start, Vec2 end, f32 thickness)
{
f32 width = thickness / 2.f;
Vec2 dir = NormVec2(SubVec2(end, start));
Vec2 dir_perp = PerpVec2(dir);
Vec2 left = MulVec2(dir_perp, -width);
Vec2 right = MulVec2(dir_perp, width);
Quad result;
result.p0 = AddVec2(start, left);
result.p1 = AddVec2(start, right);
result.p2 = AddVec2(end, right);
result.p3 = AddVec2(end, left);
return result;
}
Quad QuadFromRay(Vec2 pos, Vec2 rel, f32 thickness)
{
Vec2 end = AddVec2(pos, rel);
return QuadFromLine(pos, end, thickness);
}
Quad ScaleQuad(Quad q, f32 s)
{
q.p0 = MulVec2(q.p0, s);
q.p1 = MulVec2(q.p1, s);
q.p2 = MulVec2(q.p2, s);
q.p3 = MulVec2(q.p3, s);
return q;
}
Quad RoundQuad(Quad quad)
{
Quad result;
result.p0 = RoundVec2(quad.p0);
result.p1 = RoundVec2(quad.p1);
result.p2 = RoundVec2(quad.p2);
result.p3 = RoundVec2(quad.p3);
return result;
}
Quad FloorQuad(Quad quad)
{
Quad result;
result.p0 = FloorVec2(quad.p0);
result.p1 = FloorVec2(quad.p1);
result.p2 = FloorVec2(quad.p2);
result.p3 = FloorVec2(quad.p3);
return result;
}
////////////////////////////////
//~ Spring operations
/* https://box2d.org/files/ErinCatto_SoftConstraints_GDC2011.pdf */
SoftSpring MakeSpring(f32 hertz, f32 damping_ratio, f32 dt)
{
SoftSpring result;
if (hertz == 0.0f)
{
result.bias_rate = 0;
result.mass_scale = 1;
result.impulse_scale = 0;
}
else
{
f32 angular_frequency = Tau * hertz;
f32 a = 2 * damping_ratio + angular_frequency * dt;
f32 b = angular_frequency * a * dt;
f32 c = 1 / (b + 1);
result.bias_rate = angular_frequency / a;
result.mass_scale = b * c;
result.impulse_scale = c;
}
return result;
}
////////////////////////////////
//~ Mat4x4 operations
Mat4x4 Mat4x4FromXform(Xform xf)
{
return (Mat4x4)
{
.e = {
{xf.bx.x, xf.bx.y, 0, 0},
{xf.by.x, xf.by.y, 0, 0},
{0, 0, 1, 0},
{xf.og.x, xf.og.y, 0, 1},
}
};
}
Mat4x4 Mat4x4FromOrtho(f32 left, f32 right, f32 bottom, f32 top, f32 near_z, f32 far_z)
{
Mat4x4 m = ZI;
f32 rl = 1.0f / (right - left);
f32 tb = 1.0f / (top - bottom);
f32 fn = -1.0f / (far_z - near_z);
m.e[0][0] = 2.0f * rl;
m.e[1][1] = 2.0f * tb;
m.e[2][2] = 2.0f * fn;
m.e[3][0] = -(right + left) * rl;
m.e[3][1] = -(top + bottom) * tb;
m.e[3][2] = (far_z + near_z) * fn;
m.e[3][3] = 1.0f;
return m;
}
Mat4x4 MulMat4x4(Mat4x4 m1, Mat4x4 m2)
{
f32 a00 = m1.e[0][0], a01 = m1.e[0][1], a02 = m1.e[0][2], a03 = m1.e[0][3],
a10 = m1.e[1][0], a11 = m1.e[1][1], a12 = m1.e[1][2], a13 = m1.e[1][3],
a20 = m1.e[2][0], a21 = m1.e[2][1], a22 = m1.e[2][2], a23 = m1.e[2][3],
a30 = m1.e[3][0], a31 = m1.e[3][1], a32 = m1.e[3][2], a33 = m1.e[3][3],
b00 = m2.e[0][0], b01 = m2.e[0][1], b02 = m2.e[0][2], b03 = m2.e[0][3],
b10 = m2.e[1][0], b11 = m2.e[1][1], b12 = m2.e[1][2], b13 = m2.e[1][3],
b20 = m2.e[2][0], b21 = m2.e[2][1], b22 = m2.e[2][2], b23 = m2.e[2][3],
b30 = m2.e[3][0], b31 = m2.e[3][1], b32 = m2.e[3][2], b33 = m2.e[3][3];
Mat4x4 result;
result.e[0][0] = a00 * b00 + a10 * b01 + a20 * b02 + a30 * b03;
result.e[0][1] = a01 * b00 + a11 * b01 + a21 * b02 + a31 * b03;
result.e[0][2] = a02 * b00 + a12 * b01 + a22 * b02 + a32 * b03;
result.e[0][3] = a03 * b00 + a13 * b01 + a23 * b02 + a33 * b03;
result.e[1][0] = a00 * b10 + a10 * b11 + a20 * b12 + a30 * b13;
result.e[1][1] = a01 * b10 + a11 * b11 + a21 * b12 + a31 * b13;
result.e[1][2] = a02 * b10 + a12 * b11 + a22 * b12 + a32 * b13;
result.e[1][3] = a03 * b10 + a13 * b11 + a23 * b12 + a33 * b13;
result.e[2][0] = a00 * b20 + a10 * b21 + a20 * b22 + a30 * b23;
result.e[2][1] = a01 * b20 + a11 * b21 + a21 * b22 + a31 * b23;
result.e[2][2] = a02 * b20 + a12 * b21 + a22 * b22 + a32 * b23;
result.e[2][3] = a03 * b20 + a13 * b21 + a23 * b22 + a33 * b23;
result.e[3][0] = a00 * b30 + a10 * b31 + a20 * b32 + a30 * b33;
result.e[3][1] = a01 * b30 + a11 * b31 + a21 * b32 + a31 * b33;
result.e[3][2] = a02 * b30 + a12 * b31 + a22 * b32 + a32 * b33;
result.e[3][3] = a03 * b30 + a13 * b31 + a23 * b32 + a33 * b33;
return result;
}
Mat4x4 ProjectMat4x4View(Xform view, f32 viewport_width, f32 viewport_height)
{
Mat4x4 projection = Mat4x4FromOrtho(0.0, viewport_width, viewport_height, 0.0, -1.0, 1.0);
Mat4x4 view4x4 = Mat4x4FromXform(view);
return MulMat4x4(projection, view4x4);
}