flan/examples/shapes-following-eyes.flan
Joseph Ferano a4c6b996ff def re-runs its initialiser, and defvar is renamed defonce
The trio the author decided on 2026-09-20 is now all built: def is CL's
defparameter — its initialiser runs on every daemon re-run, unguarded, so
an edited initialiser repaints the same storage on C-c C-c plus re-run —
defonce (Clojure's name for CL's defvar, per the author) initialises once
behind the .init~once. flag, and defconst stays the image.

One parse arm reads both forms; the difference is Ast.reinit, carried to
Tast.global's grerun. Emit.startup_plan gives a def no guard flag, and
Check.check_global lifts every def initialiser — zero and literal
included — into global/<n>, so the host's startup reaches it through the
function cell and a re-evaluated def swaps it (Session's def_inits;
Emit.redefinition declares the cell for a non-sibling target). The old
defvar spelling is refused with the rename and both compiling spellings,
and every program, test, doc and editor list is swept — except sand.flan,
the author's live WIP, whose seven defvar lines are flagged in FIX.org
and keep its three dependent tests red on this branch.
2026-09-21 07:12:04 +07:00

111 lines
5.1 KiB
Plaintext

;;;; 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))))