package tiny_libs
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
From-scratch libraries for teaching: graphics, audio, compression, crypto, networking and more
Install
dune-project
Dependency
Authors
Maintainers
Sources
0.3.6.tar.gz
md5=7c636383d146d30ac6f2fa234a6253c8
sha512=c79f3823c5f8f57e5038eb640d487c61168b84aa07c61999d6622ef9fd0c890e2b03b4c6a7cdbbe9352a49e25dda00ac7bb14693cee8e3d7beeed251351a2af0
doc/src/tiny_libs.physics_2d/Collide.ml.html
Source file Collide.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219(* Claude Code * * Copyright (C) 2026 Yoann Padioleau * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License * (LGPL) as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. *) (* See Collide.mli *) (*****************************************************************************) (* The tests *) (*****************************************************************************) let circles ((c1, r1) : Vec2.t * float) ((c2, r2) : Vec2.t * float) : Contact.t option = let d = Vec2.sub c2 c1 in let dist = Vec2.length d in if dist >= r1 +. r2 then None else (* the same center: any direction will do *) let normal = if dist = 0. then (1., 0.) else Vec2.scale (1. /. dist) d in Some { normal; depth = r1 +. r2 -. dist; point = Vec2.add c1 (Vec2.scale r1 normal) } let bounds_overlap (((ax0, ay0), (ax1, ay1)) : Vec2.t * Vec2.t) (((bx0, by0), (bx1, by1)) : Vec2.t * Vec2.t) : bool = ax0 <= bx1 && bx0 <= ax1 && ay0 <= by1 && by0 <= ay1 let point_in_polygon ((px, py) : Vec2.t) (corners : Vec2.t list) : bool = (* the edges crossing the horizontal ray from p to the right: an edge * whose ends are on both sides of the ray's line, crossing it right of * p. (A corner exactly on the line counts for one of its two edges * only: one end strictly above, the other not.) *) Shape.edges corners |> List.fold_left (fun inside ((x1, y1), (x2, y2)) -> if y1 > py <> (y2 > py) && px < x1 +. ((py -. y1) *. (x2 -. x1) /. (y2 -. y1)) then not inside else inside) false (* on which side of the line through a and b is p: > 0 left, < 0 right *) let side (a : Vec2.t) (b : Vec2.t) (p : Vec2.t) : float = Vec2.cross (Vec2.sub b a) (Vec2.sub p a) (* do the intervals [a1, a2] and [b1, b2] (in any order) overlap *) let overlap_1d a1 a2 b1 b2 = Float.max (Float.min a1 a2) (Float.min b1 b2) <= Float.min (Float.max a1 a2) (Float.max b1 b2) let segments_cross ((a, b) : Vec2.t * Vec2.t) ((c, d) : Vec2.t * Vec2.t) : bool = (* <= 0.: touching counts, an end on the other segment *) side a b c *. side a b d <= 0. && side c d a *. side c d b <= 0. (* ... but two segments on the same line only if they overlap *) && (side a b c <> 0. || side a b d <> 0. || (overlap_1d (fst a) (fst b) (fst c) (fst d) && overlap_1d (snd a) (snd b) (snd c) (snd d))) let polygons_touch (p : Vec2.t list) (q : Vec2.t list) : bool = List.exists (fun e -> List.exists (fun f -> segments_cross e f) (Shape.edges q)) (Shape.edges p) || (match p with v :: _ -> point_in_polygon v q | [] -> false) || match q with v :: _ -> point_in_polygon v p | [] -> false (* the shadow of a polygon on an axis: the min and max of its corners' * projections *) let shadow (axis : Vec2.t) (corners : Vec2.t list) : float * float = List.fold_left (fun (lo, hi) v -> let d = Vec2.dot axis v in (Float.min lo d, Float.max hi d)) (infinity, neg_infinity) corners let centroid (corners : Vec2.t list) : Vec2.t = Vec2.scale (1. /. float_of_int (List.length corners)) (List.fold_left Vec2.add (0., 0.) corners) (* where segments ab and cd cross, if they do (not when parallel) *) let crossing ((a, b) : Vec2.t * Vec2.t) ((c, d) : Vec2.t * Vec2.t) : Vec2.t option = let ab = Vec2.sub b a and cd = Vec2.sub d c and ac = Vec2.sub c a in let denom = Vec2.cross ab cd in if denom = 0. then None else (* a + t ab = c + u cd *) let t = Vec2.cross ac cd /. denom and u = Vec2.cross ac ab /. denom in if t >= 0. && t <= 1. && u >= 0. && u <= 1. then Some (Vec2.add a (Vec2.scale t ab)) else None let overlap_middle (p : Vec2.t list) (q : Vec2.t list) : Vec2.t option = (* the corners of the overlap region: the corners of each inside the * other, and where their edges cross *) let inside = List.filter (fun v -> point_in_polygon v q) p @ List.filter (fun v -> point_in_polygon v p) q in let crossings = List.concat_map (fun e -> List.filter_map (crossing e) (Shape.edges q)) (Shape.edges p) in match inside @ crossings with [] -> None | points -> Some (centroid points) let sat (p : Vec2.t list) (q : Vec2.t list) : Contact.t option = (* the axes: perpendicular to every edge of both *) let axes = List.map (fun (a, b) -> Vec2.normalize (Vec2.perp (Vec2.sub b a))) (Shape.edges p @ Shape.edges q) in (* the least overlap, and its axis; None as soon as one separates *) let rec go best = function | [] -> best | axis :: rest -> ( let (p0, p1) = shadow axis p and (q0, q1) = shadow axis q in let overlap = Float.min p1 q1 -. Float.max p0 q0 in if overlap <= 0. then None else match best with | Some (_, o) when o <= overlap -> go best rest | _ -> go (Some (axis, overlap)) rest) in match go None axes with | None -> None | Some (axis, depth) -> (* the normal from p to q *) let normal = if Vec2.dot axis (Vec2.sub (centroid q) (centroid p)) < 0. then Vec2.scale (-1.) axis else axis in (* the middle of the overlap region; failing that (they only * touch along an edge, no area), q's corner deepest in p *) let deepest () = List.fold_left (fun best v -> if Vec2.dot normal v < Vec2.dot normal best then v else best) (List.hd q) q in let point = match overlap_middle p q with Some m -> m | None -> deepest () in Some { normal; depth; point } (* the point of segment ab nearest to p *) let nearest_on_segment (p : Vec2.t) ((a, b) : Vec2.t * Vec2.t) : Vec2.t = let ab = Vec2.sub b a in let len2 = Vec2.dot ab ab in if len2 = 0. then a else let t = Float.max 0. (Float.min 1. (Vec2.dot (Vec2.sub p a) ab /. len2)) in Vec2.add a (Vec2.scale t ab) (* the point of the polygon's outline nearest to p *) let nearest_on_outline (p : Vec2.t) (corners : Vec2.t list) : Vec2.t = let points = List.map (nearest_on_segment p) (Shape.edges corners) in List.fold_left (fun best q -> if Vec2.length (Vec2.sub q p) < Vec2.length (Vec2.sub best p) then q else best) (List.hd points) points let circle_polygon ((c, r) : Vec2.t * float) (corners : Vec2.t list) : bool = point_in_polygon c corners || Vec2.length (Vec2.sub (nearest_on_outline c corners) c) <= r let circle_convex ((c, r) : Vec2.t * float) (corners : Vec2.t list) : Contact.t option = let q = nearest_on_outline c corners in let d = Vec2.length (Vec2.sub q c) in if point_in_polygon c corners then (* the center inside: the polygon must go past the center, away from * the nearest edge *) let normal = if d = 0. then (1., 0.) else Vec2.scale (1. /. d) (Vec2.sub c q) in Some { normal; depth = r +. d; point = q } else if d < r then let normal = if d = 0. then (1., 0.) else Vec2.scale (1. /. d) (Vec2.sub q c) in Some { normal; depth = r -. d; point = q } else None (* the swept tests, for things too fast to be caught overlapping *) let segment_polygon ((a, b) : Vec2.t * Vec2.t) (corners : Vec2.t list) : Vec2.t option = if point_in_polygon a corners then Some a else (* where it crosses the outline, the nearest to a *) List.filter_map (crossing (a, b)) (Shape.edges corners) |> List.fold_left (fun best p -> match best with | Some q when Vec2.length (Vec2.sub q a) <= Vec2.length (Vec2.sub p a) -> best | _ -> Some p) None let segment_circle ((a, b) : Vec2.t * Vec2.t) ((c, r) : Vec2.t * float) : Vec2.t option = let p = nearest_on_segment c (a, b) in if Vec2.length (Vec2.sub p c) <= r then Some p else None (*****************************************************************************) (* Any two hitboxes *) (*****************************************************************************) let touching (a : Shape.placed) (b : Shape.placed) : bool = bounds_overlap (Shape.bounds a) (Shape.bounds b) && match (a, b) with | Point_at p, Point_at q -> p = q | Point_at p, Circle_at (c, r) | Circle_at (c, r), Point_at p -> Vec2.length (Vec2.sub p c) <= r | Point_at p, Polygon_at q | Polygon_at q, Point_at p -> point_in_polygon p q | Circle_at (c1, r1), Circle_at (c2, r2) -> Vec2.length (Vec2.sub c2 c1) <= r1 +. r2 | Circle_at (c, r), Polygon_at q | Polygon_at q, Circle_at (c, r) -> circle_polygon (c, r) q | Polygon_at p, Polygon_at q -> polygons_touch p q let flip (c : Contact.t option) : Contact.t option = Option.map (fun (c : Contact.t) -> { c with normal = Vec2.scale (-1.) c.normal }) c let contact (a : Shape.placed) (b : Shape.placed) : Contact.t option = (* a point is a circle of radius 0 *) let circle = function Shape.Point_at p -> Some (p, 0.) | Circle_at (c, r) -> Some (c, r) | Polygon_at _ -> None in match (a, b) with | Polygon_at p, Polygon_at q -> if Shape.convex p && Shape.convex q then sat p q else None | Polygon_at p, other -> ( match circle other with Some c when Shape.convex p -> flip (circle_convex c p) | _ -> None) | other, Polygon_at q -> ( match circle other with Some c when Shape.convex q -> circle_convex c q | _ -> None) | _ -> ( match (circle a, circle b) with Some c1, Some c2 -> circles c1 c2 | _ -> None) (* the corners of each polygon inside the other, each with its own * depth along the normal (from p to q), the two farthest apart kept *) let polygon_manifold (p : Vec2.t list) (q : Vec2.t list) : Contact.t list = match sat p q with | None -> [] | Some c -> let n = c.normal in let along v = Vec2.dot n v in (* how far p reaches towards q, and q back towards p *) let p_front = List.fold_left (fun m v -> Float.max m (along v)) neg_infinity p in let q_front = List.fold_left (fun m v -> Float.min m (along v)) infinity q in let points = List.filter_map (fun v -> if point_in_polygon v p then Some (v, p_front -. along v) else None) q @ List.filter_map (fun v -> if point_in_polygon v q then Some (v, along v -. q_front) else None) p |> List.filter (fun (_, depth) -> depth > 0.) in let contact (point, depth) : Contact.t = { normal = n; depth; point } in (match points with | [] -> [ c ] | [ _ ] | [ _; _ ] -> List.map contact points | first :: _ -> (* the two extremes along the contact's surface: the widest * support *) let t v = Vec2.cross n v in let pick better = List.fold_left (fun m x -> if better (t (fst x)) (t (fst m)) then x else m) first points in [ contact (pick ( < )); contact (pick ( > )) ]) let manifold (a : Shape.placed) (b : Shape.placed) : Contact.t list = match (a, b) with | Polygon_at p, Polygon_at q when Shape.convex p && Shape.convex q -> polygon_manifold p q | _ -> Option.to_list (contact a b)
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>