Rect :: #type,distinct Vec(4, RECT_TYPE); Circle :: struct { x,y,r: float; #place x; pos: Vec2; } Line :: struct { x0,y0,x1,y1: float; #place x0; a: Vec2; #place x1; b: Vec2; #place x0; e: [2]Vec2; } Triangle :: [3]Vec2; ORIGIN :: Vec2.{.[0, 0]}; 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; } 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; } 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; } 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; } 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; } 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; } inside :: (t: Triangle, p: Vec2) -> bool #symmetric { cay := t[2].y - t[0].y; pay := p.y - t[0].y; cax := t[2].x - t[0].x; bay := t[1].y - t[0].y; bax := t[1].x - t[0].x; denom := bay*cax - bax*cay; w1 := t[0].x*cay + pay*cax - p.x*cay; w1 /= denom; w2 := (p.y - t[0].y - w1*bay)/cay; return w1 >= 0 && w2 >= 0 && w1 + w2 <= 1; } 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 the center of the circle acting as the origin (0, 0) r_center: Vec2 = get_center(r); p := r_center - c.pos; d: Vec2 = abs(p) - Vec2.{.[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 but instead of the origin we check if the other rectangle center collides :: (r1: Rect, r2: Rect) -> bool { r: Rect = 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); } // Cohen-sutherland's line clipping algorithm collides :: (r: Rect, l: Line) -> bool #symmetric { INSIDE :: 0b0000; LEFT :: 0b0001; RIGHT :: 0b0010; BOTTOM :: 0b0100; TOP :: 0b1000; compute_out_code :: (x: float, y: float) -> int #expand { code := INSIDE; if x < xmin code |= LEFT; else if x > xmax code |= RIGHT; if y < ymin code |= BOTTOM; else if y > ymax code |= TOP; return code; } xmin := r.x; xmax := r.x + r.width; ymin := r.y; ymax := r.y + r.height; x0 := l.a.x; y0 := l.a.y; x1 := l.b.x; y1 := l.b.y; oc0 := compute_out_code(l.a.x, l.a.y); oc1 := compute_out_code(l.b.x, l.b.y); accept: bool; while true { if !(oc0 | oc1) { accept = true; break; } else if oc0 & oc1 { break; } else { x, y: float = ---; oc_out := ifx oc1 > oc0 then oc1 else oc0; // find intersection point; // slope = (y1 - y0)/(x1 - x0) // x = x0 + (1/slope)*(ym - y0), ym is ymin or ymax // y = y0 + slope*(xm - x0), xm is xmin or xmax // outcode bit guarantees the denom is non-zero if oc_out & TOP { x = x0 + (x1 - x0)*(ymax - y0)/(y1 - y0); y = ymax; } else if oc_out & BOTTOM { x = x0 + (x1 - x0)*(ymin - y0)/(y1 - y0); y = ymin; } else if oc_out & RIGHT { y = y0 + (y1 - y0)*(xmax - x0)/(x1 - x0); x = xmax; } else { y = y0 + (y1 - y0)*(xmin - x0)/(x1 - x0); x = xmin; } // Move outside points to intersection point if oc_out == oc0 { x0 = x; y0 = y; oc0 = compute_out_code(x0, y0); } else { x1 = x; y1 = y; oc1 = compute_out_code(x1, y1); } } } return accept; } // Triangle SDF with the circle position inplace of the origin and a circle expansion of the sdf collides :: (t: Triangle, c: Circle) -> bool #symmetric { e0 := t[1] - t[0]; e1 := t[2] - t[1]; e2 := t[0] - t[2]; v0 := c.pos - t[0]; v1 := c.pos - t[1]; v2 := c.pos - t[2]; pq0 := v0 - e0*min(max(dot(v0,e0)/dot(e0,e0), 0.0), 1.0); pq1 := v1 - e1*min(max(dot(v1,e1)/dot(e1,e1), 0.0), 1.0); pq2 := v2 - e2*min(max(dot(v2,e2)/dot(e2,e2), 0.0), 1.0); s := sign(e0.x*e2.y - e0.y*e2.x); d := min(min(Vec2.{.[dot(pq0,pq0), s*(v0.x*e0.y - v0.y*e0.x)]}, Vec2.{.[dot(pq1,pq1), s*(v1.x*e1.y - v1.y*e1.x)]}), Vec2.{.[dot(pq2,pq2), s*(v2.x*e2.y - v2.y*e2.x)]}); dist := -math.sqrt(d.x)*sign(d.y); return dist < c.r; } collides :: (t: Triangle, r: Rect) -> 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]}; rt1: Triangle = .[p1, p4, p2]; rt2: Triangle = .[p1, p3, p4]; if collides(t, rt1) || collides(t, rt2) return true; return false; } collides :: (t1: Triangle, t2: Triangle) -> bool { return inline gjk(t1, t2); } collides :: (t: Triangle, l: Line) -> bool #symmetric { return inline gjk(t, l.e); } // Note(Jesse): 2D gjk :: (s1: []Vec2, s2: []Vec2) -> bool { S: Simplex2D; S.a = gjk_support(s1, s2, s1[0] - s2[0]); d := -S.a; // This makes a vector from S.a towards the origin (ORIGIN - S.a) S.points = 1; while true { A := gjk_support(s1, s2, d); if !same_dir(A, d) // If A is even towards the origin. Otherwise there's no way they intersect return false; S.points += 1; S.c = S.b; // we want S.a to be the newest point. S.c is the oldest S.b = S.a; S.a = A; if gjk_do_simplex(*S, *d) return true; } return false; } #scope_file basic :: #import "Basic"; // Used with gjk, not for general use Simplex2D :: struct { a,b,c: Vec2; points: int; } // Used with gjk. Simplified for 2D triple_cross :: inline (a: Vec2, b: Vec2, c: Vec2) -> Vec2 { // return cross(cross(v3(a, 0), v3(b, 0)), v3(c, 0)).xy; return Vec2.{.[-a.x*b.y*c.y + a.y*b.x*c.y, a.x*b.y*c.x - a.y*b.x*c.x]}; } // Sets the direction for the next support function based on the simplex we have and how it's orientated // to the origin: 0, 0. Because we know which point was added last (S.a) we can deduce directions we don't // have to check gjk_do_simplex :: (S: *Simplex2D, dir: *Vec2) -> bool { if S.points == { case 2; AB := S.b - S.a; AO := -S.a; if same_dir(AB, AO) { dir.* = triple_cross(AB, AO, AB); } else { dir.* = AO; S.points = 1; } case 3; AO := -S.a; // Because this is 2D, we don't need a large 3 point simplex case, or a 4 point case AB := S.b - S.a; AC := S.c - S.a; abperp := triple_cross(AC, AB, AB); acperp := triple_cross(AB, AC, AC); if same_dir(abperp, AO) { S.points = 2; dir.* = abperp; } else if same_dir(acperp, AO) { S.b = S.c; S.points = 2; dir.* = acperp; } else { return true; // origin must be inside the triangle } } return false; } // Returns the vertex furthest along the dir vector for two polygons. We could split it up // to take something that can't be represented by a list of points, like a circle. gjk_support :: inline (a: []Vec2, b: []Vec2, dir: Vec2) -> Vec2 { helper :: (t: []Vec2, dir: Vec2) -> Vec2 { p := t[0]; best := dot(t[0], dir); for 1..t.count - 1 { v := t[it]; if dot(t[it], dir) > best { best = dot(v, dir); p = v; } } return p; } v := helper(a, dir); w := helper(b, -dir); return v - w; } /////// // Helpers and operators get_center :: (r: Rect) -> Vec2 { return v2f(r.x + r.width/2, r.y + r.height/2); } #if #exists(RUN_TESTS) #run { // Just a formality to get proper compile errors b: bool; r1 := Rect.{.[100, 150, 250, 250]}; c1 := Circle.{150, 500, 50}; l1 := Line.{400, 100, 650, 150}; t1: Triangle; t1[0] = v2f(600.0, 500.0); t1[1] = v2f(650.0, 250.0); t1[2] = v2f(625.0, 200.0); p: Vec2 = v2f(100.0, 100.0); r2 := Rect.{.[100, 100, 10, 10]}; c2 := Circle.{100, 100, 10}; l2 := Line.{100, 100, 200, 200}; t2: Triangle; t2[0] = v2f(50.0, 50.0 + 20.0); t2[1] = v2f(50.0 + 20.0, 50.0 - 20.0); t2[2] = v2f(50.0 - 20.0, 50.0 - 20.0); b |= inside(r1, p); b |= collides(r1, r2); b |= collides(r1, l2); b |= collides(r1, c2); b |= collides(r1, t2); b |= inside(c1, p); b |= collides(c1, r2); b |= collides(c1, l2); b |= collides(c1, c2); b |= collides(c1, t2); b |= collides(l1, r2); b |= collides(l1, l2); b |= collides(l1, c2); b |= collides(l1, t2); b |= inside(t1, p); b |= collides(t1, r2); b |= collides(t1, l2); b |= collides(t1, c2); b |= collides(t1, t2); }