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/Joint2d.ml.html
Source file Joint2d.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(* 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 Joint2d.mli *) type kind = | Pin of { motor : (float * float) option } | Rod of { length : float } | Rope of { length : float } | Pulley of { ground_a : Vec2.t; ground_b : Vec2.t; length : float } type t = { a : int; b : int; anchor_a : Vec2.t; anchor_b : Vec2.t; kind : kind } (* a vector turned by [angle] radians, counterclockwise *) let rotate (angle : float) ((x, y) : Vec2.t) : Vec2.t = let c = cos angle and s = sin angle in ((c *. x) -. (s *. y), (s *. x) +. (c *. y)) (* the world point [p] in body [i]'s own frame, and back *) let to_local (bodies : Body.t array) (angles : float array) (i : int) (p : Vec2.t) : Vec2.t = rotate (-.angles.(i)) (Vec2.sub p bodies.(i).pos) let arm (angles : float array) (i : int) (local : Vec2.t) : Vec2.t = rotate angles.(i) local let anchors (bodies : Body.t array) (angles : float array) (j : t) : Vec2.t * Vec2.t = (Vec2.add bodies.(j.a).pos (arm angles j.a j.anchor_a), Vec2.add bodies.(j.b).pos (arm angles j.b j.anchor_b)) let pin bodies angles a b ~at ?motor () = { a; b; anchor_a = to_local bodies angles a at; anchor_b = to_local bodies angles b at; kind = Pin { motor } } let rod bodies angles a b ~at_a ~at_b () = { a; b; anchor_a = to_local bodies angles a at_a; anchor_b = to_local bodies angles b at_b; kind = Rod { length = Vec2.length (Vec2.sub at_b at_a) } } let rope bodies angles a b ~at_a ~at_b ?length () = let length = match length with Some l -> l | None -> Vec2.length (Vec2.sub at_b at_a) in { a; b; anchor_a = to_local bodies angles a at_a; anchor_b = to_local bodies angles b at_b; kind = Rope { length } } let pulley bodies angles a b ~at_a ~at_b ~ground_a ~ground_b () = let length = Vec2.length (Vec2.sub at_a ground_a) +. Vec2.length (Vec2.sub at_b ground_b) in { a; b; anchor_a = to_local bodies angles a at_a; anchor_b = to_local bodies angles b at_b; kind = Pulley { ground_a; ground_b; length } } let length_now bodies angles (j : t) : float = let pa, pb = anchors bodies angles j in match j.kind with | Pin _ -> 0. | Rod _ | Rope _ -> Vec2.length (Vec2.sub pb pa) | Pulley { ground_a; ground_b; _ } -> Vec2.length (Vec2.sub pa ground_a) +. Vec2.length (Vec2.sub pb ground_b) (* A row: the speed along it is lin_a . va + ang_a * wa + lin_b . vb + * ang_b * wb; the solver wants it to be [target]. [mass] is 1 over the * row's resistance: the impulse that changes that speed by 1. *) type row = { ra : int; rb : int; lin_a : Vec2.t; ang_a : float; lin_b : Vec2.t; ang_b : float; mass : float; target : float; lo : float; hi : float; mutable total : float; } let make_row (bodies : Body.t array) ra rb lin_a ang_a lin_b ang_b ~target ~lo ~hi : row option = let a = bodies.(ra) and b = bodies.(rb) in let k = (Resolve.inverse_mass a *. Vec2.dot lin_a lin_a) +. (Resolve.inverse_inertia a *. ang_a *. ang_a) +. (Resolve.inverse_mass b *. Vec2.dot lin_b lin_b) +. (Resolve.inverse_inertia b *. ang_b *. ang_b) in if k = 0. then None else Some { ra; rb; lin_a; ang_a; lin_b; ang_b; mass = 1. /. k; target; lo; hi; total = 0. } (* A row keeping the two anchors' distance along [n] at [gap] = 0: the * speed along it is n . (vb + wb x rb - va - wa x ra), and in 2D w x r * . n is w (r x n), so each body's lever is r x n *) let along (bodies : Body.t array) (j : t) ((ra_, rb_) : Vec2.t * Vec2.t) (n : Vec2.t) ~(bias : float) ~lo ~hi : row option = make_row bodies j.a j.b (Vec2.scale (-1.) n) (-.Vec2.cross ra_ n) n (Vec2.cross rb_ n) ~target:(-.bias) ~lo ~hi let rows ~(beta : float) ~(dt : float) (bodies : Body.t array) (angles : float array) (j : t) : row list = let arm_a = arm angles j.a j.anchor_a and arm_b = arm angles j.b j.anchor_b in let pa = Vec2.add bodies.(j.a).pos arm_a and pb = Vec2.add bodies.(j.b).pos arm_b in let gap = Vec2.sub pb pa in match j.kind with | Pin { motor } -> (* x and y, each driven towards closing its part of the gap *) let axis n = along bodies j (arm_a, arm_b) n ~bias:(beta /. dt *. Vec2.dot gap n) ~lo:neg_infinity ~hi:infinity in let turning = match motor with | None -> None | Some (speed, torque) -> make_row bodies j.a j.b (0., 0.) (-1.) (0., 0.) 1. ~target:speed ~lo:(-.torque *. dt) ~hi:(torque *. dt) in List.filter_map Fun.id [ axis (1., 0.); axis (0., 1.); turning ] | Rod { length } | Rope { length } -> let d = Vec2.length gap in if d < 1e-9 then [] else let n = Vec2.scale (1. /. d) gap in let stretch = d -. length in let rope = match j.kind with Rope _ -> true | _ -> false in (* a rope is a row only when taut, and then it may only pull: * its impulse, along n from a to b, never positive *) if rope && stretch < 0. then [] else Option.to_list (along bodies j (arm_a, arm_b) n ~bias:(beta /. dt *. stretch) ~lo:neg_infinity ~hi:(if rope then 0. else infinity)) | Pulley { ground_a; ground_b; length } -> let side p g = let v = Vec2.sub p g in let l = Vec2.length v in (l, if l < 1e-9 then (0., 0.) else Vec2.scale (1. /. l) v) in let la, ua = side pa ground_a and lb, ub = side pb ground_b in let stretch = la +. lb -. length in if stretch < 0. then [] else (* the total length grows at ua . (point a's velocity) + ub . * (point b's): both bodies on the "plus" side of the row; it * may only pull *) Option.to_list (make_row bodies j.a j.b ua (Vec2.cross arm_a ua) ub (Vec2.cross arm_b ub) ~target:(-.beta /. dt *. stretch) ~lo:neg_infinity ~hi:0.) let solve_row (bodies : Body.t array) (r : row) : unit = let a = bodies.(r.ra) and b = bodies.(r.rb) in let speed = Vec2.dot r.lin_a a.vel +. (r.ang_a *. a.spin) +. Vec2.dot r.lin_b b.vel +. (r.ang_b *. b.spin) in let before = r.total in r.total <- Float.max r.lo (Float.min r.hi (before +. (r.mass *. (r.target -. speed)))); let p = r.total -. before in let push (x : Body.t) lin ang = { x with vel = Vec2.add x.vel (Vec2.scale (p *. Resolve.inverse_mass x) lin); spin = x.spin +. (p *. Resolve.inverse_inertia x *. ang) } in bodies.(r.ra) <- push bodies.(r.ra) r.lin_a r.ang_a; bodies.(r.rb) <- push bodies.(r.rb) r.lin_b r.ang_b
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>