;;;; raylib [shapes] example - following eyes ;;;; ;;;; examples/shapes/shapes_following_eyes.c. Ported for the thing it needs ;;;; that raylib does not supply: the whole update is vector arithmetic — ;;;; subtract two points, take the angle, walk back out along it — and every ;;;; one of those steps is raymath. raymath is `static inline` in ;;;; raymath.h, so Vector2Subtract and Vector2Angle have no symbol in ;;;; libraylib at all and there is nothing for declare-c to bind. NEXT.md ;;;; names this as one of the three open gaps. ;;;; ;;;; What that costs here is nothing, because the C does not use raymath ;;;; either — it writes the four subtractions out and calls atan2f, cosf and ;;;; sinf from libm, and Flan's prelude already declares all three as ;;;; atan2-f32, cos-f32 and sin-f32. So this file is the *measurement* of the ;;;; gap rather than a workaround for it: an example whose every line is ;;;; vector maths, written without a vector library, in the same shape the C ;;;; has. It reads fine. A Vector2Add would have saved two lines in the whole ;;;; file. ;;;; ;;;; That measurement is what decided the answer, and the answer landed: ;;;; vendor/raylib/vector.flan is raymath written in Flan, since a C shim ;;;; re-exporting the inlines would have bought identical arithmetic at the ;;;; price of a compilation unit and a second place raylib's semantics live. ;;;; The measurement is left standing rather than rewritten away — one ;;;; subtraction below is `rl/v2-sub` and the rest of the file is unchanged, ;;;; including the atan2/cos/sin path, which is deliberate: it needs no guard ;;;; for a zero-length vector where a normalise would. One line saved, in a ;;;; file that is nothing but vector maths, is the honest size of the gap. ;;;; ;;;; The raylib call it does exercise is collision-point-circle?, which no ;;;; ported example had called, on a frame path, with the point coming ;;;; straight out of get-mouse-position — one struct out of raylib and back ;;;; into it untouched. ;;;; ;;;; The eye geometry, since the C hides it in a subtraction: the iris is ;;;; pinned inside a circle of (sclera - iris) radius about the sclera's ;;;; centre, so the iris disc is tangent to the sclera's edge at the extreme ;;;; rather than hanging over it. (import rl "vendor:raylib") (defconst screen-width 800) (defconst screen-height 450) (defconst sclera-radius f32 80.0) (defconst iris-radius f32 24.0) (defonce sclera-left rl/Vector2) (defonce sclera-right rl/Vector2) (defonce iris-left rl/Vector2) (defonce iris-right rl/Vector2) ;; One eye's pupil, given where the mouse is and where the eye is. Answers the ;; mouse position unchanged while it is inside the eye, and the point on the ;; limit circle in that direction when it is outside. ;; ;; The C repeats this block twice with different variables; it is one function ;; here because the two copies are identical and a difference between them ;; would be invisible. (defn track [mouse rl/Vector2 centre rl/Vector2] rl/Vector2 (let [limit (- sclera-radius iris-radius)] (if (rl/collision-point-circle? mouse centre limit) mouse ;; Outside: the direction from the eye to the mouse, and then `limit` ;; units back out along it. atan2 then cos/sin rather than a normalise, ;; because that is what the C does and because it needs no guard for a ;; zero-length vector — atan2(0,0) is 0 and the pupil sits at the right ;; of the eye, which is unreachable anyway since (0,0) is inside. (let [d (rl/v2-sub mouse centre) angle (atan2-f32 (.y d) (.x d))] (rl/Vector2 {.x (+ (.x centre) (* limit (cos-f32 angle))) .y (+ (.y centre) (* limit (sin-f32 angle)))}))))) (defn main [] () (rl/init-window screen-width screen-height "raylib [shapes] example - following eyes") (defer (rl/close-window)) ;; The C reads these off get-screen-width/get-screen-height after the window ;; is open rather than off the constants, and so does this: on a high-DPI ;; display the two can differ. (set sclera-left (rl/Vector2 {.x (- (/ (f32 (rl/get-screen-width)) 2.0) 100.0) .y (/ (f32 (rl/get-screen-height)) 2.0)})) (set sclera-right (rl/Vector2 {.x (+ (/ (f32 (rl/get-screen-width)) 2.0) 100.0) .y (/ (f32 (rl/get-screen-height)) 2.0)})) (set iris-left sclera-left) (set iris-right sclera-right) (rl/set-target-fps 60) (until (rl/window-should-close?) ;; Update (let [mouse (rl/get-mouse-position)] (set iris-left (track mouse sclera-left)) (set iris-right (track mouse sclera-right))) ;; Draw (rl/with-drawing (rl/clear-background rl/raywhite) ;; White of the eye, iris, pupil — in that order, each over the last. (rl/draw-circle-v sclera-left sclera-radius rl/lightgray) (rl/draw-circle-v iris-left iris-radius rl/brown) (rl/draw-circle-v iris-left 10.0 rl/black) (rl/draw-circle-v sclera-right sclera-radius rl/lightgray) (rl/draw-circle-v iris-right iris-radius rl/darkgreen) (rl/draw-circle-v iris-right 10.0 rl/black) (rl/draw-fps 10 10))))