//////////////////////////////////////////////////////////// //~ 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; } //- Saturate u32 SaturateU32(u32 v) { return v < 0 ? 0 : v > 1 ? 1 : v; } i32 SaturateI32(i32 v) { return v < 0 ? 0 : v > 1 ? 1 : v; } f32 SaturateF32(f32 v) { return v < 0 ? 0 : v > 1 ? 1 : v; } u64 SaturateU64(u64 v) { return v < 0 ? 0 : v > 1 ? 1 : v; } i64 SaturateI64(i64 v) { return v < 0 ? 0 : v > 1 ? 1 : v; } f64 SaturateF64(f64 v) { return v < 0 ? 0 : v > 1 ? 1 : v; } //////////////////////////////////////////////////////////// //~ Numeric ops //- Sign i32 SignF32(f32 v) { return (v >= 0) - (v < 0); } i32 SignF64(f64 v) { return (v >= 0) - (v < 0); } //- Normalize f32 Norm8(u32 v) { return (v & 0xFF) / (f32)0x100; } f32 Norm16(u32 v) { return (v & 0xFFFF) / (f32)0x10000; } f32 Norm24(u32 v) { return (v & 0xFFFFFF) / (f32)0x1000000; } f64 Norm53(u64 v) { return (v & 0x1FFFFFFFFFFFFFull) / (f64)0x20000000000000ull; } //////////////////////////////////////////////////////////// //~ Exponential ops //- Pow u64 // Taken from https://gist.github.com/orlp/3551590 u64 PowU64(u64 v, u8 exp) { PERSIST 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 v > 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 (v == 1) { return 1; } // if (v == -1) // { // return 1 - 2 * (exp & 1); // } return 0; } break; case 6: { if (exp & 1) result *= v; exp >>= 1; v *= v; } FALLTHROUGH; case 5: { if (exp & 1) result *= v; exp >>= 1; v *= v; } FALLTHROUGH; case 4: { if (exp & 1) result *= v; exp >>= 1; v *= v; } FALLTHROUGH; case 3: { if (exp & 1) result *= v; exp >>= 1; v *= v; } FALLTHROUGH; case 2: { if (exp & 1) result *= v; exp >>= 1; v *= v; } FALLTHROUGH; case 1: { if (exp & 1) result *= v; } FALLTHROUGH; default: return result; } } //////////////////////////////////////////////////////////// //~ Align u64 AlignU64(u64 x, u64 align) { align = MaxU64(align, 1); u64 result = (x + (align - 1)); result -= result % align; return result; } u64 NextPow2U64(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; } //////////////////////////////////////////////////////////// //~ 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 a, f32 b, f32 t) { return a + ((b - a) * t); } f64 LerpF64(f64 a, f64 b, f64 t) { return a + ((b - a) * t); } f32 LerpAngleF32(f32 a, f32 b, f32 t) { f32 diff = UnwindAngleF32(b - a); return a + diff * t; } //////////////////////////////////////////////////////////// //~ Int lerp i32 LerpI32(i32 a, i32 b, f32 t) { return a + RoundF32((f32)(b - a) * t); } i64 LerpI64(i64 a, i64 b, f64 t) { return a + RoundF64((f64)(b - a) * t); } i32 LerpU32(u32 a, u32 b, f32 t) { return a + RoundF32((f32)(b - a) * t); } i64 LerpU64(u64 a, u64 b, f64 t) { return a + RoundF64((f64)(b - a) * t); } //////////////////////////////////////////////////////////// //~ Smoothstep f32 SmoothstepF32(f32 a, f32 b, f32 t) { f32 result = 0; if (a == b) { result = (t < a) ? 0 : 1; } else { t = ClampF32((t - a) / (b - a), 0, 1); result = t * t * (3.0f - 2.0f * t); } return result; } f64 SmoothstepF64(f64 a, f64 b, f64 t) { f64 result = 0; if (a == b) { result = (t < a) ? 0 : 1; } else { t = ClampF64((t - a) / (b - a), 0, 1); result = t * t * (3.0f - 2.0f * t); } return result; } //////////////////////////////////////////////////////////// //~ Color u8 MulNormalizedU8(u8 a, u8 b) { u32 t = ((u32)a * (u32)b) + 0x80; return ((t >> 8) + t) >> 8; } f32 SrgbFromLinearF32(f32 lin) { f32 result = 0; if (lin <= 0.0031308f) { result = lin * 12.92f; } else { result = 1.055f * PowF32(lin, 1.0f/2.4f) - 0.055f; } return result; } f32 LinearFromSrgbF32(f32 srgb) { f32 result = 0; if (srgb <= 0.04045f) { result = srgb / 12.92f; } else { result = PowF32((srgb + 0.055f) / 1.055f, 2.4f); } return result; } Vec4 LinearFromSrgb(Vec4 srgb) { Vec4 result = Zi; result.x = LinearFromSrgbF32(srgb.x); result.y = LinearFromSrgbF32(srgb.y); result.z = LinearFromSrgbF32(srgb.z); result.w = srgb.w; return result; } Vec4 SrgbFromLinear(Vec4 lin) { Vec4 result = Zi; result.x = SrgbFromLinearF32(lin.x); result.y = SrgbFromLinearF32(lin.y); result.z = SrgbFromLinearF32(lin.z); result.w = lin.w; return result; } Vec4 SrgbFromHsv(f32 h, f32 s, f32 v) { h = ModF32(h, 360.0f); if (h < 0) { h += 360.0f; } f32 c = v * s; f32 x = c * (1.0f - AbsF32(ModF32(h / 60.0f, 2.0f) - 1.0f)); f32 m = v - c; Vec4 result = VEC4(0, 0, 0, 1); if (h < 60.0f) { result.r = c; result.g = x; result.b = 0; } else if (h < 120.0f) { result.r = x; result.g = c; result.b = 0; } else if (h < 180.0f) { result.r = 0; result.g = c; result.b = x; } else if (h < 240.0f) { result.r = 0; result.g = x; result.b = c; } else if (h < 300.0f) { result.r = x; result.g = 0; result.b = c; } else { result.r = c; result.g = 0; result.b = x; } return result; } Vec4 PremulFromLinear(Vec4 lin) { Vec4 result = Zi; result.x = lin.x * lin.w; result.y = lin.y * lin.w; result.z = lin.z * lin.w; result.w = lin.w; return result; } Vec4 PremulFromSrgb(Vec4 srgb) { Vec4 lin = LinearFromSrgb(srgb); Vec4 premul = PremulFromLinear(lin); return premul; } Vec4 LerpSrgb(Vec4 a, Vec4 b, f32 t) { Vec4 result = Zi; Vec4 a_lin = LinearFromSrgb(a); Vec4 b_lin = LinearFromSrgb(b); Vec4 lerp_lin = LerpVec4(a_lin, b_lin, t); result = SrgbFromLinear(lerp_lin); return result; } //////////////////////////////////////////////////////////// //~ Vec2 b32 IsVec2Zero(Vec2 a) { return a.x == 0 && a.y == 0; } b32 MatchVec2(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)); } Vec2 RecipVec2(Vec2 a) { return VEC2(1.0 / a.x, 1.0 / a.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; } else { a = VEC2(len, 0); } return a; } Vec2 ClampVec2Len(Vec2 a, f32 min, f32 max) { f32 l_sq = a.x * a.x + a.y * a.y; if (l_sq > 0) { if (l_sq > max * max) { f32 denom = max / SqrtF32(l_sq); a.x *= denom; a.y *= denom; } else if (l_sq < min * min) { f32 denom = min / SqrtF32(l_sq); a.x *= denom; a.y *= denom; } } else { a = VEC2(min, 0); } 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); } //- Clamp Vec2 ClampVec2(Vec2 v, Rng2 r) { Vec2 result = Zi; result.x = ClampF32(v.x, r.p0.x, r.p1.x); result.y = ClampF32(v.y, r.p0.y, r.p1.y); return result; } Vec2 SaturateVec2(Vec2 v) { Vec2 result = Zi; result.x = ClampF32(v.x, 0, 1); result.y = ClampF32(v.y, 0, 1); return result; } //- 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)); } Vec2 FloorVec2(Vec2 a) { return VEC2(FloorF32(a.x), FloorF32(a.y)); } Vec2 CeilVec2(Vec2 a) { return VEC2(CeilF32(a.x), CeilF32(a.y)); } //- Rotation // 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 RotateVec2Angle(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 RotateVec2(Vec2 a, Vec2 b) { return VEC2(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x); } Vec2 InvertRot(Vec2 r) { r.y = -r.y; return r; } Vec2 Vec2FromAngle(f32 a) { return VEC2(CosF32(a), SinF32(a)); } f32 AngleFromVec2(Vec2 v) { return ArcTan2F32(v.y, v.x); } //- Area f32 AreaFromVec2(Vec2 v) { return AbsF32(v.x * v.y); } //- 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 a, Vec2 b, f32 t) { return VEC2(LerpF32(a.x, b.x, t), LerpF32(a.y, b.y, t)); } Vec2 LerpVec2Vec2(Vec2 a, Vec2 b, Vec2 t) { return VEC2(LerpF32(a.x, b.x, t.x), LerpF32(a.y, b.y, t.y)); } // Interpolate direction vectors (spherical lerp) Vec2 SlerpVec2(Vec2 a, Vec2 b, f32 t) { f32 rot = LerpAngleF32(AngleFromVec2(a), AngleFromVec2(b), t); f32 len = LerpF32(Vec2Len(a), Vec2Len(b), t); return MulVec2(Vec2FromAngle(rot), len); } //////////////////////////////////////////////////////////// //~ Vec2I32 b32 MatchVec2I32(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); } Vec2I32 MulVec2I32Vec2I32(Vec2I32 a, Vec2I32 b) { return VEC2I32(a.x * b.x, a.y * b.y); } Vec2I32 DivVec2I32Vec2I32(Vec2I32 a, Vec2I32 b) { return VEC2I32(a.x / b.x, a.y / b.y); } //////////////////////////////////////////////////////////// //~ Vec4 //- Mul Vec4 MulVec4(Vec4 v, f32 s) { return VEC4(v.x * s, v.y * s, v.z * s, v.w * s); } Vec4 MulVec4Vec4(Vec4 a, Vec4 b) { return VEC4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); } //- Lerp Vec4 LerpVec4(Vec4 a, Vec4 b, f32 t) { Vec4 result = Zi; result.x = LerpF32(a.x, b.x, t); result.y = LerpF32(a.y, b.y, t); result.z = LerpF32(a.z, b.z, t); result.w = LerpF32(a.w, b.w, t); return result; } //- Conversion Vec4 Vec4FromU32(u32 v) { Vec4 result = Zi; result.x = ((v >> 0) & 0xFF) / 255.0; result.y = ((v >> 8) & 0xFF) / 255.0; result.z = ((v >> 16) & 0xFF) / 255.0; result.w = ((v >> 24) & 0xFF) / 255.0; return result; } u32 U32FromVec4(Vec4 v) { u32 result = 0; result |= (((u32)(v.x * 255.0)) & 0xFF) << 0; result |= (((u32)(v.y * 255.0)) & 0xFF) << 8; result |= (((u32)(v.z * 255.0)) & 0xFF) << 16; result |= (((u32)(v.w * 255.0)) & 0xFF) << 24; return result; } //////////////////////////////////////////////////////////// //~ Range //- Rng1 f32 NormRng(Rng r, f32 v) { return (r.max - r.min) / v; } //- Rng2 b32 IsRng2Empty(Rng2 r) { return r.p0.x >= r.p1.x || r.p0.y >= r.p1.y; } Vec2 DimsFromRng2(Rng2 r) { Vec2 result = Zi; result.x = r.p1.x - r.p0.x; result.y = r.p1.y - r.p0.y; return result; } Vec2 CenterFromRng2(Rng2 r) { Vec2 result = Zi; Vec2 dims = DimsFromRng2(r); result.x = r.p0.x + dims.x * 0.5; result.y = r.p0.y + dims.y * 0.5; return result; } Vec2 NormRng2(Rng2 r, Vec2 v) { Vec2 result = Zi; Vec2 dims = SubVec2(r.p1, r.p0); result.x = v.x / dims.x; result.y = v.y / dims.y; return result; } Rng2 UnionRng2(Rng2 a, Rng2 b) { Rng2 result = Zi; result.p0.x = MinF32(a.p0.x, b.p0.x); result.p0.y = MinF32(a.p0.y, b.p0.y); result.p1.x = MaxF32(a.p1.x, b.p1.x); result.p1.y = MaxF32(a.p1.y, b.p1.y); return result; } Rng2 IntersectRng2(Rng2 a, Rng2 b) { Rng2 result = Zi; result.p0.x = MaxF32(a.p0.x, b.p0.x); result.p0.y = MaxF32(a.p0.y, b.p0.y); result.p1.x = MinF32(a.p1.x, b.p1.x); result.p1.y = MinF32(a.p1.y, b.p1.y); return result; } b32 IsIntersectingRng2(Rng2 a, Rng2 b) { return !IsRng2Empty(IntersectRng2(a, b)); } Rng2 AddRng2Vec2(Rng2 r, Vec2 v) { Rng2 result = Zi; result.p0 = AddVec2(r.p0, v); result.p1 = AddVec2(r.p1, v); return result; } Rng2 MulRng2Vec2(Rng2 r, Vec2 v) { Rng2 result = Zi; result.p0 = MulVec2Vec2(r.p0, v); result.p1 = MulVec2Vec2(r.p1, v); return result; } Rng2 DivRng2Vec2(Rng2 r, Vec2 v) { Rng2 result = Zi; result.p0 = DivVec2Vec2(r.p0, v); result.p1 = DivVec2Vec2(r.p1, v); return result; } //- Rng2I32 b32 IsRng2I32Empty(Rng2I32 r) { return r.p0.x >= r.p1.x || r.p0.y >= r.p1.y; } Vec2I32 DimsFromRng2I32(Rng2I32 r) { Vec2I32 result = Zi; result.x = r.p1.x - r.p0.x; result.y = r.p1.y - r.p0.y; return result; } Vec2I32 CenterFromRng2I32(Rng2I32 r) { Vec2I32 result = Zi; Vec2I32 dims = DimsFromRng2I32(r); result.x = r.p0.x + dims.x * 0.5; result.y = r.p0.y + dims.y * 0.5; return result; } Rng2I32 UnionRng2I32(Rng2I32 a, Rng2I32 b) { Rng2I32 result = Zi; result.p0.x = MinI32(a.p0.x, b.p0.x); result.p0.y = MinI32(a.p0.y, b.p0.y); result.p1.x = MaxI32(a.p1.x, b.p1.x); result.p1.y = MaxI32(a.p1.y, b.p1.y); return result; } Rng2I32 IntersectRng2I32(Rng2I32 a, Rng2I32 b) { Rng2I32 result = Zi; result.p0.x = MaxI32(a.p0.x, b.p0.x); result.p0.y = MaxI32(a.p0.y, b.p0.y); result.p1.x = MinI32(a.p1.x, b.p1.x); result.p1.y = MinI32(a.p1.y, b.p1.y); return result; } b32 IsIntersectingRng2I32(Rng2I32 a, Rng2I32 b) { return !IsRng2I32Empty(IntersectRng2I32(a, b)); } Rng2I32 AddRng2I32Vec2I32(Rng2I32 r, Vec2I32 v) { Rng2I32 result = Zi; result.p0 = AddVec2I32(r.p0, v); result.p1 = AddVec2I32(r.p1, v); return result; } Rng2I32 MulRng2I32Vec2I32(Rng2I32 r, Vec2I32 v) { Rng2I32 result = Zi; result.p0 = MulVec2I32Vec2I32(r.p0, v); result.p1 = MulVec2I32Vec2I32(r.p1, v); return result; } Rng2I32 DivRng2I32Vec2I32(Rng2I32 r, Vec2I32 v) { Rng2I32 result = Zi; result.p0 = DivVec2I32Vec2I32(r.p0, v); result.p1 = DivVec2I32Vec2I32(r.p1, v); return result; } //////////////////////////////////////////////////////////// //~ Affine b32 MatchAffine(Affine af0, Affine af1) { return MatchVec2(af0.og, af1.og) && MatchVec2(af0.bx, af1.bx) && MatchVec2(af0.by, af1.by); } //- Initialization Affine AffineFromTranslation(Vec2 v) { Affine af; af.bx = VEC2(1, 0); af.by = VEC2(0, 1); af.og = v; return af; } Affine AffineFromRot(Vec2 r) { Affine result; result.bx = VEC2(r.x, r.y); result.by = VEC2(-r.y, r.x); result.og = VEC2(0, 0); return result; } Affine AffineFromAngle(f32 r) { Affine 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; } Affine AffineFromScale(Vec2 scale) { Affine result; result.bx = VEC2(scale.x, 0); result.by = VEC2(0, scale.y); result.og = VEC2(0, 0); return result; } //- Translation Affine TranslateAffine(Affine af, Vec2 v) { af.og = VEC2(af.bx.x * v.x + af.by.x * v.y + af.og.x, af.bx.y * v.x + af.by.y * v.y + af.og.y); return af; } Affine PreTranslateAffine(Affine af, Vec2 v) { af.og = AddVec2(af.og, v); return af; } //- Rotation Affine RotateAffine(Affine af, Vec2 r) { return MulAffine(af, AffineFromRot(r)); } Affine PreRotateAffine(Affine af, Vec2 r) { return MulAffine(AffineFromRot(r), af); } //- Scale Affine ScaleAffine(Affine af, Vec2 scale) { af.bx = MulVec2(af.bx, scale.x); af.by = MulVec2(af.by, scale.y); return af; } Affine PreScaleAffine(Affine af, Vec2 scale) { Affine result; result.bx = MulVec2Vec2(af.bx, scale); result.by = MulVec2Vec2(af.by, scale); result.og = MulVec2Vec2(af.og, scale); return result; } //- Lerp Affine LerpAffine(Affine a, Affine b, f32 t) { Affine 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 Affine InvertAffine(Affine af) { f32 det = DeterminantFromAffine(af); f32 inv_det = 1.0f / det; f32 old_bx_x = af.bx.x; af.bx.x = af.by.y; af.by.y = old_bx_x; af.bx = MulVec2Vec2(af.bx, VEC2(inv_det, -inv_det)); af.by = MulVec2Vec2(af.by, VEC2(-inv_det, inv_det)); af.og = MulAffineBasisVec2(af, NegVec2(af.og)); return af; } //- Mul Affine MulAffine(Affine a, Affine b) { Affine 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 = MulAffineVec2(a, b.og); return result; } Vec2 MulAffineBasisVec2(Affine af, Vec2 v) { return VEC2( af.bx.x * v.x + af.by.x * v.y, af.bx.y * v.x + af.by.y * v.y ); } Vec2 MulAffineVec2(Affine af, Vec2 v) { Vec2 result = MulAffineBasisVec2(af, v); result = AddVec2(result, af.og); return result; } Affine MulAffineXform(Affine af, Xform xf) { Affine result; result.bx.x = af.bx.x * xf.r.x + af.by.x * xf.r.y; result.bx.y = af.bx.y * xf.r.x + af.by.y * xf.r.y; result.by.x = af.bx.x * -xf.r.y + af.by.x * xf.r.x; result.by.y = af.bx.y * -xf.r.y + af.by.y * xf.r.x; result.og = MulAffineVec2(af, xf.t); return result; } //- Helpers Affine BasisFromAffine(Affine af) { Affine result = Zi; result.bx = af.bx; result.by = af.by; return result; } f32 DeterminantFromAffine(Affine af) { return WedgeVec2(af.bx, af.by); } Vec2 RightFromAffine(Affine af) { return af.bx; } Vec2 LeftFromAffine(Affine af) { return NegVec2(af.bx); } Vec2 UpFromAffine(Affine af) { return NegVec2(af.by); } Vec2 DownFromAffine(Affine af) { return af.by; } f32 AngleFromAffine(Affine af) { return AngleFromVec2(af.bx); } Vec2 ScaleFromAffine(Affine af) { f32 det = DeterminantFromAffine(af); i32 det_sign = (det >= 0) - (det < 0); return VEC2(Vec2Len(af.bx), det_sign * Vec2Len(af.by)); } //////////////////////////////////////////////////////////// //~ Xform Xform XformTR(Vec2 t, Vec2 r) { Xform xf; xf.t = t; xf.r = r; return xf; } Xform XformRT(Vec2 r, Vec2 t) { Xform xf; xf.t = VEC2(0, 0); xf.r = r; xf = TranslateXform(xf, t); return xf; } Xform MulXform(Xform a, Xform b) { Xform result; result.r.x = a.r.x * b.r.x + -a.r.y * b.r.y; result.r.y = a.r.y * b.r.x + a.r.x * b.r.y; result.t.x = a.r.x * b.t.x + -a.r.y * b.t.y; result.t.y = a.r.y * b.t.x + a.r.x * b.t.y; result.t = AddVec2(result.t, a.t); return result; } Xform TranslateXform(Xform xf, Vec2 t) { xf.t = VEC2(xf.r.x * t.x + -xf.r.y * t.y + xf.t.x, xf.r.y * t.x + xf.r.x * t.y + xf.t.y); return xf; } Xform RotateXform(Xform xf, Vec2 r) { xf.r = RotateVec2(xf.r, r); return xf; } Xform InvertXform(Xform xf) { xf.t = NegVec2(xf.t); xf.r.y = -xf.r.y; return xf; } Vec2 MulXformVec2(Xform xf, Vec2 v) { Vec2 result = Zi; result = AddVec2(RotateVec2(v, xf.r), xf.t); return result; } Xform NormXform(Xform xf) { xf.r = NormVec2(xf.r); return xf; } //////////////////////////////////////////////////////////// //~ Line Vec2 IntersectLines(Vec2 a0, Vec2 b0, Vec2 a1, Vec2 b1) { Vec2 vab0 = SubVec2(b0, a0); Vec2 vab1 = SubVec2(b1, a1); Vec2 va0a1 = SubVec2(a1, a0); f32 denom = WedgeVec2(vab0, vab1); f32 t0 = WedgeVec2(va0a1, vab1) / denom; Vec2 result = AddVec2(a0, MulVec2(vab0, t0)); return result; } //////////////////////////////////////////////////////////// //~ Spring // 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 Mat4x4 Mat4x4FromAffine(Affine af) { return (Mat4x4) { .e = { {af.bx.x, af.bx.y, 0, 0}, {af.by.x, af.by.y, 0, 0}, {0, 0, 1, 0}, {af.og.x, af.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 m0, Mat4x4 m1) { f32 a00 = m0.e[0][0], a01 = m0.e[0][1], a02 = m0.e[0][2], a03 = m0.e[0][3], a10 = m0.e[1][0], a11 = m0.e[1][1], a12 = m0.e[1][2], a13 = m0.e[1][3], a20 = m0.e[2][0], a21 = m0.e[2][1], a22 = m0.e[2][2], a23 = m0.e[2][3], a30 = m0.e[3][0], a31 = m0.e[3][1], a32 = m0.e[3][2], a33 = m0.e[3][3], b00 = m1.e[0][0], b01 = m1.e[0][1], b02 = m1.e[0][2], b03 = m1.e[0][3], b10 = m1.e[1][0], b11 = m1.e[1][1], b12 = m1.e[1][2], b13 = m1.e[1][3], b20 = m1.e[2][0], b21 = m1.e[2][1], b22 = m1.e[2][2], b23 = m1.e[2][3], b30 = m1.e[3][0], b31 = m1.e[3][1], b32 = m1.e[3][2], b33 = m1.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(Affine view, f32 viewport_width, f32 viewport_height) { Mat4x4 projection = Mat4x4FromOrtho(0.0, viewport_width, viewport_height, 0.0, -1.0, 1.0); Mat4x4 view4x4 = Mat4x4FromAffine(view); return MulMat4x4(projection, view4x4); }