Three shapes, two text, three textures, one models and one core, picked for binding surface rather than for how they look. shapes-basic-shapes brings in six draw families nothing had called — the circle and rectangle gradients, the triangles, all three poly draws — and is the first call in the corpus to pass two Colors or three Vector2s at once. shapes-collision-area is get-collision-rec, the only binding that takes two Rectangles and answers a third, on a frame path. shapes-following-eyes is the raymath gap measured rather than worked around: every line of it is vector arithmetic written without a vector library, the way the C writes it. text-input-box drains get-char-pressed's queue, which no example had read, and needed a MouseCursor defenum for set-mouse-cursor. text-writing-anim replaces TextSubtext — unbindable, it answers a pointer into a rotating static buffer — with (string (slice b 0 n)), which is the same operation without the shared state. textures-image-generation runs nine Gen* calls and the gen/upload/unload-image path, all procedural, no file on disk. textures-fog-of-war needed a TextureFilter defenum: the smooth fog edge is entirely :bilinear on a 25x15 render texture, and it is also the first draw-texture-pro with a negative source height. textures-mouse-painting is the same render texture used as a document rather than as scratch, plus the round trip back off the GPU — load-image-from-texture, image-flip-vertical, export-image — which nothing had run. models-box-collisions is the counterexample to "a models example is a binding exercise": nothing in it is a Model, and one BoundingBox defstruct un-refuses four functions. core-3d-picking is the only caller anywhere for Ray and RayCollision, and picking is the inverse of the get-world-to-screen the corpus already had. Added to vendor/raylib: defstructs BoundingBox, Ray and RayCollision; defenums MouseCursor and TextureFilter with their mapping lines in bindings; hand-written declare-c for SetMouseCursor, SetTextureFilter, DrawCubeV, DrawSphere, DrawSphereWires, DrawRay and GetScreenToWorldRay, each excluded from the generated half on the rule bindings already states. generated.flan regenerated against raylib 5.5: 272 declarations, 117 refused, every defstruct, hand-written declare-c and mapped constant agreeing with the header.
104 lines
4.5 KiB
Plaintext
104 lines
4.5 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.
|
|
;;;;
|
|
;;;; 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)
|
|
|
|
(defvar sclera-left rl/Vector2)
|
|
(defvar sclera-right rl/Vector2)
|
|
(defvar iris-left rl/Vector2)
|
|
(defvar 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 [dx (- (.x mouse) (.x centre))
|
|
dy (- (.y mouse) (.y centre))
|
|
angle (atan2-f32 dy dx)]
|
|
(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/begin-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)
|
|
|
|
(rl/end-drawing)))
|