From c143b4d7894621bfb2bfb29b04adec5f14897d55 Mon Sep 17 00:00:00 2001 From: jesse Date: Sat, 5 Jul 2025 03:52:42 -0700 Subject: [PATCH] Added common shape collision detection Triangle will probably be the next one. I decided against using rays and did line segments instead. Probably much more broadly viable. --- math/common.jai | 10 +++ math/shape.jai | 169 +++++++++++++++++++++++++++++++++++++++--------- math/vec.jai | 27 ++++++-- 3 files changed, 169 insertions(+), 37 deletions(-) diff --git a/math/common.jai b/math/common.jai index c2f1f57..b610979 100644 --- a/math/common.jai +++ b/math/common.jai @@ -74,6 +74,16 @@ asin :: (ang: float) -> float #expand { return from_rad(math.asin(ang)); } +max :: (x: float, y: float) -> float { + if x < y return y; + return x; +} + +min :: (x: float, y: float) -> float { + if x < y return x; + return y; +} + abs :: (v: $T) -> T #modify { return meta.type_is_scalar(T), "Only accepting scalar types, integer and float"; } { return ifx v < 0 then -v else v; diff --git a/math/shape.jai b/math/shape.jai index e6fde84..9b51a1d 100644 --- a/math/shape.jai +++ b/math/shape.jai @@ -1,51 +1,160 @@ Rect :: #type,distinct Vec(4, RECT_TYPE); +Circle :: struct { + x,y: float; + r: float; + #place x; + pos: Vec2; +} + +Line :: struct { + x0,y0,x1,y1: float; + #place x0; + a: Vec2; + #place x1; + b: Vec2; +} + make_rect :: (x: RECT_TYPE, y: RECT_TYPE, w: RECT_TYPE, h: RECT_TYPE) -> Rect { - r: Rect = ---; - r.x = x; - r.y = y; - r.width = w; - r.height = h; - return r; + r: Rect = ---; + r.x = x; + r.y = y; + r.width = w; + r.height = h; + return r; } cut_left :: (rect: *Rect, want: RECT_TYPE) -> Rect { - amnt := basic.min(want, rect.width); - r := rect.*; - r.width = amnt; - rect.x += amnt; - rect.width -= amnt; - return r; + amnt := basic.min(want, rect.width); + r := rect.*; + r.width = amnt; + rect.x += amnt; + rect.width -= amnt; + return r; } cut_right :: (rect: *Rect, want: RECT_TYPE) -> Rect { - amnt := basic.min(want, rect.width); - r := rect.*; - r.x += rect.width - amnt; - r.width = amnt; - rect.width -= r.width; - return r; + amnt := basic.min(want, rect.width); + r := rect.*; + r.x += rect.width - amnt; + r.width = amnt; + rect.width -= r.width; + return r; } cut_top :: (rect: *Rect, want: RECT_TYPE) -> Rect { - amnt := basic.min(rect.height, want); - r := rect.*; - r.height -= amnt; - rect.y += amnt; - rect.height -= amnt; - return r; + amnt := basic.min(rect.height, want); + r := rect.*; + r.height -= amnt; + rect.y += amnt; + rect.height -= amnt; + return r; } cut_bottom :: (rect: *Rect, want: RECT_TYPE) -> Rect { - amnt := basic.min(want, rect.height); - r := rect.*; - r.y += r.height - amnt; - r.height = amnt; - rect.height -= amnt; - return r; + amnt := basic.min(want, rect.height); + r := rect.*; + r.y += r.height - amnt; + r.height = amnt; + rect.height -= amnt; + return r; +} + +area :: (r: Rect) -> float { + return r.width*r.height; +} + +area :: (c: Circle) -> float { + return PI*c.r*c.r; +} + +inside :: (r: Rect, p: Vec2) -> bool #symmetric { + return p.x >= r.x && p.x <= r.x + r.width && + p.y >= r.y && p.y <= r.y + r.height; +} + +inside :: (c: Circle, p: Vec2) -> bool #symmetric { + dp := p - c.pos; + return dp.x*dp.x + dp.y*dp.y <= c.r*c.r; +} + +collides :: (c1: Circle, c2: Circle) -> bool { + dp := c2.pos - c1.pos; + return c1.r + c2.r >= length(dp); +} + +// Note(Jesse): This is using sdfs. Very elegant +collides :: (c: Circle, r: Rect) -> bool #symmetric { + // We need to 'transpose' all the math to the origin (centered at 0,0) + r_center: Vec2 = get_center(r); + p := r_center - c.pos; + d: Vec2 = abs(p) - v2f(r.width/2, r.height/2); + dist := length(max(d, 0.0)) + min(max(d.x, d.y), 0.0); + dist -= c.r; // 'adding' the distance of the circle + return dist <= 0.0; +} + +// Note(Jesse): Minkowski difference +collides :: (r1: Rect, r2: Rect) -> bool { + r: Rect = make_rect(r1.x - r2.width/2, r1.y - r2.width/2, r1.width + r2.width, r1.height + r2.height); + return inside(r, get_center(r2)); +} + +collides :: (c: Circle, seg: Line) -> bool #symmetric { + pa := c.pos - seg.a; + ba := seg.b - seg.a; + // Note(Jesse): Using clamp here will cause small segments to collide even when they don't + h: float = min(max(dot(pa,ba)/dot(ba,ba), 0.0), 1.0); + dist := length(pa - ba*h); + return dist - c.r <= 0.0; +} + +collides :: (s1: Line, s2: Line) -> bool { + denom := ((s2.y1 - s2.y0)*(s1.x1 - s1.x0) - (s2.x1 - s2.x0)*(s1.y1 - s1.y0)); + + a := ((s2.x1 - s2.x0)*(s1.y0 - s2.y0) - (s2.y1 - s2.y0)*(s1.x0 - s2.x0)); + b := ((s1.x1 - s1.x0)*(s1.y0 - s2.y0) - (s1.y1 - s1.y0)*(s1.x0 - s2.x0)); + + if denom == 0.0 + return false; + + a /= denom; + b /= denom; + + return (a >= 0 && a <= 1) && (b >= 0 && b <= 1); +} + +// Todo(Jesse): I want to find a better line segment rectangle algorithm. +// one option is a line clipping algorithm. Cohen-Sutherland algorithm +// would be the best option. It does accepts/rejects fastest +// but still has a while in it, and a few ifs. +collides :: (r: Rect, seg: Line) -> bool #symmetric { + p1 := Vec2.{r.x, r.y}; + p2 := Vec2.{r.x + r.width, r.y}; + p3 := Vec2.{r.x, r.y + r.height}; + p4 := Vec2.{r.x + r.width, r.y + r.height}; + + l1 := Line.{a=p1, b=p2}; + l2 := Line.{a=p1, b=p3}; + l3 := Line.{a=p3, b=p4}; + l4 := Line.{a=p2, b=p4}; + + return collides(seg, l1) || collides(seg, l2) || collides(seg, l3) || collides(seg, l4); +} + +/////// +// Helpers and operators + +get_center :: (r: Rect) -> Vec2 { + return v2f(r.x + r.width/2, r.y + r.height/2); } #scope_file basic :: #import "Basic"; + +#if RUN_TESTS #run,stallable { + test.run(basic.tprint("%: Vec2", UNITS), t => { + }); +} diff --git a/math/vec.jai b/math/vec.jai index ba71d26..a12384c 100644 --- a/math/vec.jai +++ b/math/vec.jai @@ -434,13 +434,26 @@ length :: (v: Vec) -> float #no_abc { abs :: (v: Vec) -> Vec(v.N, v.T) #no_abc { r: Vec(v.N, v.T) = ---; - n := v.N - 1; - while n >= 0 { - if v[n] < 0 - r[n] = -v[n]; - else - r[n] = v[n]; - n -= 1; + #if v.N <= 4 { + r = v; + if r.x < 0 r.x = -r.x; + if r.y < 0 r.y = -r.y; + #if v.N >= 3 { + if r.z < 0 r.z = -r.z; + } + #if v.N == 4 { + if r.w < 0 r.w = -r.w; + } + } + else { + n := v.N - 1; + while n >= 0 { + if v[n] < 0 + r[n] = -v[n]; + else + r[n] = v[n]; + n -= 1; + } } return r; }