An idiomatic layer over the raylib bindings, and raymath in Flan

Three kinds of Flan face over the generated set, which stays honest to C
because that is what makes it checkable against the header.

A slice where C takes a pointer and a count: the eleven vector-array
drawing calls, all eleven rather than the three anybody calls, since a
subset has its hole where the next caller looks. Each guards the empty
slice, which is the part a hand-written call site gets wrong rather than
merely writes out -- raylib takes a count of 0 happily, but taking the
address of element 0 of an empty slice is out of bounds before raylib is
reached.

An Option where C signals with a sentinel: get-key-pressed and
get-char-pressed, raylib's two input queues, both of which say "empty"
with 0. What that buys is in text-input-box.flan, which read the queue in
two places -- once to prime the loop, once at the bottom of the body --
and now reads it in one.

Both of those use the `name` directive in bindings, so the generated
declaration keeps the symbol and gives up the name: nothing about the C
signature is hand-written and the generated half keeps its
agreement-by-construction with the header.

An enum where the header says int: key-up?, key-pressed-repeat?,
mouse-button-up?. These are NOT wrappers -- a C enum parameter has an
int's ABI, so the hand-written declare-c with the Flan type is the whole
fix. They were holes in families whose other halves already took a Key,
so (rl/key-down? :space) compiled and (rl/key-up? :space) did not.

Not built: with-drawing and with-mode-2d. A macro cannot live in a
package -- the expander collects defmacros from the prelude and from the
file being compiled, and one in an imported package is refused by name.
test/programs/pkg-macro.flan is that refusal.

And vendor/raylib/vector.flan, which is raymath written in Flan because
raymath is static inline and has no symbol to bind. A file of its own,
split on declare-c and not on "idiomatic": there is not one declaration
in it, so it is not part of the surface the header check reads, and
raylib.flan is 1300 lines already. clamp and lerp are deliberately absent
-- the prelude has both, and a second lerp would not even be the same
function, since the prelude writes (1-t)a + tb where raymath writes
a + t*(b - a).

Examples: the identical eight-expression box-around helper in
core-3d-picking and models-box-collisions is a half-extent subtracted and
added. shapes-following-eyes keeps its measurement and gives up one line
to v2-sub, which is the honest size of the gap in a file that is nothing
but vector maths.
This commit is contained in:
Joseph Ferano 2026-09-13 15:16:40 +07:00
parent 8472c50d57
commit 5a1c2745b0
9 changed files with 543 additions and 72 deletions

57
NEXT.md
View File

@ -286,12 +286,21 @@ why they are here and not in a binding list.
reaches nothing, and a `defenum` with no line at all are each reported rather than skipped. All
eight raylib enums and all 16 `ConfigFlags` bits check out against 5.5. See BUILT.md.
4. **raymath is `static inline`, so there is no symbol to bind.** `Clamp`, `Vector2Add`, `Remap` and
the rest exist only in the header. `declare-c` has nothing to name. rlgl's matrix stack is
unbound for a different reason. Together they make `core_2d_camera_mouse_zoom` unfaithful rather
than merely awkward, so it was skipped. The options are writing the arithmetic in Flan, which is
what the prelude would do anyway, or compiling a small C file that re-exports them as real
symbols.
4. ~~**raymath is `static inline`, so there is no symbol to bind.**~~ **Closed for the vector half.**
`Clamp`, `Vector2Add`, `Remap` and the rest exist only in the header and `declare-c` has nothing
to name, so the arithmetic is written in Flan: `vendor/raylib/vector.flan`, a package file with
no `declare-c` in it at all — which is why it is a file of its own rather than more of
`raylib.flan`, the file the header check reads hand-written signatures out of. Vector2 and
Vector3 add/sub/mul/scale/negate/dot/length/distance/normalize/lerp, `v2-angle`, `v2-rotate`,
`v3-cross`, and `remap`, `inverse-lerp`, `wrap-f32` on scalars. raymath's semantics exactly,
including the zero-length guard in `normalize`. **`clamp` and `lerp` are deliberately absent**:
both are already in the prelude, and a second `lerp` would not even be the same function —
the prelude writes `(1-t)a + tb`, raymath writes `a + t*(b - a)`, and shipping both under names
one letter apart is a footgun. The C-shim alternative was rejected on the measurement
`examples/shapes-following-eyes.flan` already took: it would buy identical arithmetic for a
compilation unit in the build and a second place raylib's semantics are written down. rlgl's
matrix stack is still unbound for a different reason, so `core_2d_camera_mouse_zoom` is still
skipped.
5. **Four families are still refused by the importer for want of a `defstruct`.** `FilePathList`
(a `char**`, blocks `core_drop_files`), `Model`/`Mesh`/`Ray`/`BoundingBox` (the model and
@ -433,12 +442,34 @@ Interactively the controls are now `r` and left-mouse only. `test_web.ml`'s asse
the wasm module went with the embed: `web-files.flan` is web-built and *run* under node and asserts the embedded
bytes print, which is the same property checked harder.
## Queued: an idiomatic layer over the generated bindings
## ~~Queued: an idiomatic layer over the generated bindings~~ — **landed**
Thin Flan-shaped wrappers **over** the generated bindings, not instead of them. The generated set stays honest to C —
that is what makes it checkable against the header — and the layer is where a Flan-shaped API lives. Two of these
already exist by hand in `vendor/raylib/raylib.flan` and are the shape to copy: `collision-point-poly?` takes a slice
and `collision-lines` answers with an `Option`, each wrapping a `-raw` binding of the same name.
Thin Flan-shaped wrappers **over** the generated bindings, not instead of them. Built as three kinds, listed in the
header of `vendor/raylib/raylib.flan`:
- **A slice where C takes a pointer and a count.** The eleven vector-array drawing calls — `draw-line-strip`, the two
triangle batches, the five splines, the two `image-draw-triangle-*`, `draw-triangle-strip-3d` — under one section.
All eleven and not the three anybody calls: a subset puts the hole where the next caller looks. Each also guards the
empty slice, which is the part a hand-written call site gets wrong rather than merely writes out — raylib takes a
count of 0 happily, but `(addr (at pts 0))` is out of bounds before raylib is reached.
- **An `Option` where C signals with a sentinel.** `get-key-pressed` and `get-char-pressed`, raylib's two input
queues, both of which say "empty" with 0. What it buys is in `examples/text-input-box.flan`: the C shape reads the
queue in two places, once to prime the loop and once at the bottom of the body, and the Option shape reads it in
one.
- **An enum where the header says `int`.** `key-up?`, `key-pressed-repeat?`, `mouse-button-up?` — holes in families
whose other halves already took a `Key` or a `MouseButton`, so `(rl/key-down? :space)` compiled and
`(rl/key-up? :space)` did not. **These are not wrappers.** A C enum parameter has an int's ABI, so the hand-written
`declare-c` with the Flan type on it is the whole fix and a `defn` around it would be a rename.
The mechanism for the first two is the `name` directive in `vendor/raylib/bindings`: the generated declaration keeps
the symbol and gives up the name, so nothing about the C signature is hand-written and the generated half keeps its
agreement-by-construction with the header. The third is an `exclude` plus a hand-written line, exactly as `SetExitKey`
and `SetMouseCursor` already were.
**Not built: `with-drawing` and `with-mode-2d`.** An unbalanced begin/end is a real bug and a macro removes it, but a
macro cannot live in a package — the expander collects `defmacro`s from the prelude and from the file being compiled,
and one in an imported package is refused by name. `test/programs/pkg-macro.flan` is that refusal and its whole content
is the case. They have to be written in the program that uses them, or wait for macros to be importable.
## ~~Queued: a restart is not a transaction, and the docs must say so~~ — **landed**
@ -712,7 +743,9 @@ second.
One smaller thing found and worth not re-deriving: an enum parameter imports as `i32`, because the header says
`KeyboardKey` and nothing tells the importer the package calls that `Key`. The ABI is identical, the face is worse,
and it is why `(rl/key-down? :space)` keeps its hand-written line.
and it is why `(rl/key-down? :space)` keeps its hand-written line. The idiomatic-layer lane closed the three holes
this left — `key-up?`, `key-pressed-repeat?` and `mouse-button-up?` were generated and therefore took an `i32`, so the
sibling of a call that worked did not — by excluding them and hand-writing the enum type, which is all it takes.
### Landed 2026-09-12 — six tracks, one session

View File

@ -54,16 +54,17 @@
;; The same centre/size to min/max conversion as in
;; examples/models-box-collisions.flan. Written out here rather than shared
;; because an example is a single file the reader can follow end to end, and
;; a two-file port for eight expressions would cost more than it saves.
;; because an example is a single file the reader can follow end to end.
;;
;; It used to be eight expressions of field arithmetic; it is the half-extent
;; subtracted and added, which is what the C means, now that the package
;; carries vector arithmetic — see vendor/raylib/vector.flan. That file
;; exists because raymath is `static inline` and has no symbol to bind, so
;; v3-sub and v3-add are Flan and not C.
(defn box-around [centre rl/Vector3 size rl/Vector3] rl/BoundingBox
(rl/BoundingBox
{.min (rl/Vector3 {.x (- (.x centre) (/ (.x size) 2.0))
.y (- (.y centre) (/ (.y size) 2.0))
.z (- (.z centre) (/ (.z size) 2.0))})
.max (rl/Vector3 {.x (+ (.x centre) (/ (.x size) 2.0))
.y (+ (.y centre) (/ (.y size) 2.0))
.z (+ (.z centre) (/ (.z size) 2.0))})}))
(let [half (rl/v3-scale size 0.5)]
(rl/BoundingBox {.min (rl/v3-sub centre half)
.max (rl/v3-add centre half)})))
(defn main [] ()
(rl/init-window screen-width screen-height

View File

@ -54,14 +54,15 @@
;; min-corner/max-corner, not centre/size, and every collision call in the
;; family takes the corner form — so this is the conversion the C writes out
;; longhand at each of its two call sites.
;;
;; It used to be eight expressions of field arithmetic here too; it is the
;; half-extent subtracted and added now that the package carries vector
;; arithmetic — see vendor/raylib/vector.flan, which is Flan and not C
;; because raymath is `static inline` and has no symbol to bind.
(defn box-around [centre rl/Vector3 size rl/Vector3] rl/BoundingBox
(rl/BoundingBox
{.min (rl/Vector3 {.x (- (.x centre) (/ (.x size) 2.0))
.y (- (.y centre) (/ (.y size) 2.0))
.z (- (.z centre) (/ (.z size) 2.0))})
.max (rl/Vector3 {.x (+ (.x centre) (/ (.x size) 2.0))
.y (+ (.y centre) (/ (.y size) 2.0))
.z (+ (.z centre) (/ (.z size) 2.0))})}))
(let [half (rl/v3-scale size 0.5)]
(rl/BoundingBox {.min (rl/v3-sub centre half)
.max (rl/v3-add centre half)})))
(defn main [] ()
(rl/init-window screen-width screen-height

View File

@ -17,6 +17,16 @@
;;;; 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
@ -56,9 +66,8 @@
;; 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)]
(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)))})))))

View File

@ -7,11 +7,19 @@
;;;; state — key-down?, key-pressed?, the mouse position — and this is the
;;;; only example that drains a *queue*: raylib buffers the characters the
;;;; platform produced since the last frame, already through the keyboard
;;;; layout and the dead keys, and hands them back one at a time until it
;;;; answers 0. The loop that empties it is the point of the example, and a
;;;; layout and the dead keys, and hands them back one at a time until it is
;;;; empty. The loop that empties it is the point of the example, and a
;;;; program that read the queue once per frame instead would silently drop
;;;; the second character of a fast pair. It is a plain i32 out of the
;;;; generated bindings; nothing needed adding for it.
;;;; the second character of a fast pair.
;;;;
;;;; raylib says "empty" by answering 0, and rl/get-char-pressed is now a
;;;; Flan wrapper that says it with None instead — see raylib.flan, "Draining
;;;; raylib's two input queues". What that buys is visible below: the C
;;;; shape, which this file had, reads the queue in *two* places, once to
;;;; prime the loop and once at the bottom of the body, and a `> 0` in
;;;; between that a reader has to know raylib's convention to trust. The
;;;; Option shape reads it in one place, and the case where there is no
;;;; character is a branch the checker knows about rather than a comparison.
;;;;
;;;; The second is set-mouse-cursor, which needed a new defenum. raylib's
;;;; header says `int cursor` and means one of eleven MOUSE_CURSOR_ values, so
@ -75,19 +83,22 @@
(do
(rl/set-mouse-cursor :ibeam)
;; Drain the character queue. raylib answers 0 when it is empty, and
;; more than one character can arrive in a single frame — holding a
;; key with the platform's repeat on is the ordinary way that happens.
(let [key (rl/get-char-pressed)]
(while (> key 0)
;; 32..125 is the printable ASCII range the C accepts. Anything
;; outside it — an accented letter, a control character — is
;; dropped rather than stored, because the buffer is bytes and a
;; codepoint above 127 would need more than one of them.
(when (and (>= key 32) (<= key 125) (< letter-count max-input-chars))
(set (at name letter-count) (u8 key))
(set letter-count (+ letter-count 1)))
(set key (rl/get-char-pressed))))
;; Drain the character queue: more than one character can arrive in
;; a single frame — holding a key with the platform's repeat on is
;; the ordinary way that happens — and None is the end of it.
(let [draining true]
(while draining
(match (rl/get-char-pressed)
None (set draining false)
;; 32..125 is the printable ASCII range the C accepts. Anything
;; outside it — an accented letter, a control character — is
;; dropped rather than stored, because the buffer is bytes and a
;; codepoint above 127 would need more than one of them.
(Some key)
(when (and (>= key 32) (<= key 125)
(< letter-count max-input-chars))
(set (at name letter-count) (u8 key))
(set letter-count (+ letter-count 1))))))
(when (rl/key-pressed? :backspace)
(set letter-count (- letter-count 1))

View File

@ -57,9 +57,6 @@ name IsWindowFocused window-focused?
name IsWindowResized window-resized?
name IsWindowState window-state?
name IsCursorOnScreen cursor-on-screen?
name IsKeyUp key-up?
name IsKeyPressedRepeat key-pressed-repeat?
name IsMouseButtonUp mouse-button-up?
name IsFileDropped file-dropped?
name IsFileExtension file-extension?
name IsFileNameValid file-name-valid?
@ -139,6 +136,53 @@ exclude DrawSphereWires
exclude DrawRay
exclude GetScreenToWorldRay
# ── The idiomatic layer, which is what these last two blocks are for ──
#
# Three kinds of C signature get a Flan face in raylib.flan rather than the
# generated one, and each kind is a directive here so that the generated file
# does not also define the name. See the "An idiomatic layer" section of
# raylib.flan for what each wrapper buys at the call site.
#
# 1. An `int` parameter the package already has a defenum for. These are
# excluded and hand-written with the enum type, exactly as SetExitKey and
# SetMouseCursor already are, and for the same reason: a keyword resolves
# against the members at compile time and a typo is an error there. What
# makes these three different from those is that they are *holes in a
# family that already exists* — key-down? takes a Key and key-up? took an
# i32, so `(rl/key-up? :space)` did not compile while `(rl/key-down?
# :space)` did. A wrapper would be a pure rename; the fix is the
# declaration.
exclude IsKeyUp
exclude IsKeyPressedRepeat
exclude IsMouseButtonUp
# 2. A sentinel return, wrapped by a Flan defn that answers an Option. The
# generated declaration is still what calls C and is still checked against
# the header — only its *name* moves aside, which is what `name` is for.
# Nothing about the signature is wrong, so there is no reason to hand-write
# it and lose the generated half's by-construction agreement.
name GetCharPressed get-char-pressed-raw
name GetKeyPressed get-key-pressed-raw
# 3. A pointer-and-count pair where Flan has a slice. Same treatment and the
# same reason: the C signature is right, the Flan face is a slice, so the
# generated line keeps the symbol and the wrapper takes the name. This is
# every raylib entry point that takes an array of vectors as pointer plus
# count, and it is the whole family on purpose — a subset would put the
# hole exactly where the next caller looks, which is the argument
# raylib.flan makes about ConfigFlags.
name DrawLineStrip draw-line-strip-raw
name DrawTriangleFan draw-triangle-fan-raw
name DrawTriangleStrip draw-triangle-strip-raw
name DrawTriangleStrip3D draw-triangle-strip-3d-raw
name DrawSplineLinear draw-spline-linear-raw
name DrawSplineBasis draw-spline-basis-raw
name DrawSplineCatmullRom draw-spline-catmull-rom-raw
name DrawSplineBezierQuadratic draw-spline-bezier-quadratic-raw
name DrawSplineBezierCubic draw-spline-bezier-cubic-raw
name ImageDrawTriangleFan image-draw-triangle-fan-raw
name ImageDrawTriangleStrip image-draw-triangle-strip-raw
# ── What the package's constants are called in C ────────────────────
#
# enum <FlanEnum> <C_PREFIX> every member of that defenum

View File

@ -1,4 +1,4 @@
;;;; Generated from raylib.h by `flan generate-c`. Do not edit this file.
;;;; Generated from raylib-5.5.h by `flan generate-c`. Do not edit this file.
;;;;
;;;; Every line here was read out of the C header named by `headers`, and
;;;; the next regeneration overwrites the file — so a correction made here
@ -10,7 +10,7 @@
;;;;
;;;; Regenerating compares the package against the header first and
;;;; refuses to write when they disagree, so this file and the
;;;; hand-written declarations beside it agreed with raylib.h when it was made.
;;;; hand-written declarations beside it agreed with raylib-5.5.h when it was made.
(declare-c window-fullscreen? [] bool "IsWindowFullscreen")
(declare-c window-hidden? [] bool "IsWindowHidden")
@ -93,13 +93,10 @@
(declare-c set-automation-event-base-frame [frame i32] "SetAutomationEventBaseFrame")
(declare-c start-automation-event-recording [] "StartAutomationEventRecording")
(declare-c stop-automation-event-recording [] "StopAutomationEventRecording")
(declare-c key-pressed-repeat? [key i32] bool "IsKeyPressedRepeat")
(declare-c key-up? [key i32] bool "IsKeyUp")
(declare-c get-key-pressed [] i32 "GetKeyPressed")
(declare-c get-char-pressed [] i32 "GetCharPressed")
(declare-c get-key-pressed-raw [] i32 "GetKeyPressed")
(declare-c get-char-pressed-raw [] i32 "GetCharPressed")
(declare-c set-gamepad-mappings [mappings string] i32 "SetGamepadMappings")
(declare-c set-gamepad-vibration [gamepad i32 left-motor f32 right-motor f32 duration f32] "SetGamepadVibration")
(declare-c mouse-button-up? [button i32] bool "IsMouseButtonUp")
(declare-c get-mouse-x [] i32 "GetMouseX")
(declare-c get-mouse-y [] i32 "GetMouseY")
(declare-c get-mouse-delta [] Vector2 "GetMouseDelta")
@ -108,7 +105,7 @@
(declare-c set-mouse-scale [scale-x f32 scale-y f32] "SetMouseScale")
(declare-c get-mouse-wheel-move-v [] Vector2 "GetMouseWheelMoveV")
(declare-c update-camera-pro [camera (Ptr Camera3D) movement Vector3 rotation Vector3 zoom f32] "UpdateCameraPro")
(declare-c draw-line-strip [points (Ptr Vector2) point-count i32 color Color] "DrawLineStrip")
(declare-c draw-line-strip-raw [points (Ptr Vector2) point-count i32 color Color] "DrawLineStrip")
(declare-c draw-line-bezier [start-pos Vector2 end-pos Vector2 thick f32 color Color] "DrawLineBezier")
(declare-c draw-circle-sector [center Vector2 radius f32 start-angle f32 end-angle f32 segments i32 color Color] "DrawCircleSector")
(declare-c draw-circle-sector-lines [center Vector2 radius f32 start-angle f32 end-angle f32 segments i32 color Color] "DrawCircleSectorLines")
@ -117,16 +114,16 @@
(declare-c draw-rectangle-gradient-v [pos-x i32 pos-y i32 width i32 height i32 top Color bottom Color] "DrawRectangleGradientV")
(declare-c draw-rectangle-gradient-h [pos-x i32 pos-y i32 width i32 height i32 left Color right Color] "DrawRectangleGradientH")
(declare-c draw-rectangle-gradient-ex [rec Rectangle top-left Color bottom-left Color top-right Color bottom-right Color] "DrawRectangleGradientEx")
(declare-c draw-triangle-fan [points (Ptr Vector2) point-count i32 color Color] "DrawTriangleFan")
(declare-c draw-triangle-strip [points (Ptr Vector2) point-count i32 color Color] "DrawTriangleStrip")
(declare-c draw-triangle-fan-raw [points (Ptr Vector2) point-count i32 color Color] "DrawTriangleFan")
(declare-c draw-triangle-strip-raw [points (Ptr Vector2) point-count i32 color Color] "DrawTriangleStrip")
(declare-c draw-poly [center Vector2 sides i32 radius f32 rotation f32 color Color] "DrawPoly")
(declare-c draw-poly-lines [center Vector2 sides i32 radius f32 rotation f32 color Color] "DrawPolyLines")
(declare-c draw-poly-lines-ex [center Vector2 sides i32 radius f32 rotation f32 line-thick f32 color Color] "DrawPolyLinesEx")
(declare-c draw-spline-linear [points (Ptr Vector2) point-count i32 thick f32 color Color] "DrawSplineLinear")
(declare-c draw-spline-basis [points (Ptr Vector2) point-count i32 thick f32 color Color] "DrawSplineBasis")
(declare-c draw-spline-catmull-rom [points (Ptr Vector2) point-count i32 thick f32 color Color] "DrawSplineCatmullRom")
(declare-c draw-spline-bezier-quadratic [points (Ptr Vector2) point-count i32 thick f32 color Color] "DrawSplineBezierQuadratic")
(declare-c draw-spline-bezier-cubic [points (Ptr Vector2) point-count i32 thick f32 color Color] "DrawSplineBezierCubic")
(declare-c draw-spline-linear-raw [points (Ptr Vector2) point-count i32 thick f32 color Color] "DrawSplineLinear")
(declare-c draw-spline-basis-raw [points (Ptr Vector2) point-count i32 thick f32 color Color] "DrawSplineBasis")
(declare-c draw-spline-catmull-rom-raw [points (Ptr Vector2) point-count i32 thick f32 color Color] "DrawSplineCatmullRom")
(declare-c draw-spline-bezier-quadratic-raw [points (Ptr Vector2) point-count i32 thick f32 color Color] "DrawSplineBezierQuadratic")
(declare-c draw-spline-bezier-cubic-raw [points (Ptr Vector2) point-count i32 thick f32 color Color] "DrawSplineBezierCubic")
(declare-c draw-spline-segment-linear [p-1 Vector2 p-2 Vector2 thick f32 color Color] "DrawSplineSegmentLinear")
(declare-c draw-spline-segment-basis [p-1 Vector2 p-2 Vector2 p-3 Vector2 p-4 Vector2 thick f32 color Color] "DrawSplineSegmentBasis")
(declare-c draw-spline-segment-catmull-rom [p-1 Vector2 p-2 Vector2 p-3 Vector2 p-4 Vector2 thick f32 color Color] "DrawSplineSegmentCatmullRom")
@ -197,8 +194,8 @@
(declare-c image-draw-triangle [dst (Ptr Image) v-1 Vector2 v-2 Vector2 v-3 Vector2 color Color] "ImageDrawTriangle")
(declare-c image-draw-triangle-ex [dst (Ptr Image) v-1 Vector2 v-2 Vector2 v-3 Vector2 c-1 Color c-2 Color c-3 Color] "ImageDrawTriangleEx")
(declare-c image-draw-triangle-lines [dst (Ptr Image) v-1 Vector2 v-2 Vector2 v-3 Vector2 color Color] "ImageDrawTriangleLines")
(declare-c image-draw-triangle-fan [dst (Ptr Image) points (Ptr Vector2) point-count i32 color Color] "ImageDrawTriangleFan")
(declare-c image-draw-triangle-strip [dst (Ptr Image) points (Ptr Vector2) point-count i32 color Color] "ImageDrawTriangleStrip")
(declare-c image-draw-triangle-fan-raw [dst (Ptr Image) points (Ptr Vector2) point-count i32 color Color] "ImageDrawTriangleFan")
(declare-c image-draw-triangle-strip-raw [dst (Ptr Image) points (Ptr Vector2) point-count i32 color Color] "ImageDrawTriangleStrip")
(declare-c image-draw [dst (Ptr Image) src Image src-rec Rectangle dst-rec Rectangle tint Color] "ImageDraw")
(declare-c image-draw-text [dst (Ptr Image) text string pos-x i32 pos-y i32 font-size i32 color Color] "ImageDrawText")
(declare-c image-draw-text-ex [dst (Ptr Image) font Font text string position Vector2 font-size f32 spacing f32 tint Color] "ImageDrawTextEx")
@ -245,7 +242,7 @@
(declare-c draw-point-3d [position Vector3 color Color] "DrawPoint3D")
(declare-c draw-circle-3d [center Vector3 radius f32 rotation-axis Vector3 rotation-angle f32 color Color] "DrawCircle3D")
(declare-c draw-triangle-3d [v-1 Vector3 v-2 Vector3 v-3 Vector3 color Color] "DrawTriangle3D")
(declare-c draw-triangle-strip-3d [points (Ptr Vector3) point-count i32 color Color] "DrawTriangleStrip3D")
(declare-c draw-triangle-strip-3d-raw [points (Ptr Vector3) point-count i32 color Color] "DrawTriangleStrip3D")
(declare-c draw-cube-wires-v [position Vector3 size Vector3 color Color] "DrawCubeWiresV")
(declare-c draw-sphere-ex [center-pos Vector3 radius f32 rings i32 slices i32 color Color] "DrawSphereEx")
(declare-c draw-cylinder [position Vector3 radius-top f32 radius-bottom f32 height f32 slices i32 color Color] "DrawCylinder")

View File

@ -27,9 +27,29 @@
;;;; f64 where raylib says float now emits `double` in the generated
;;;; prototype, and raylib reads garbage.
;;;;
;;;; Two bindings keep a hand-written Flan wrapper, both because their Flan
;;;; face is deliberately not raylib's: collision-point-poly? takes a slice,
;;;; and collision-lines answers with an Option. Both wrappers are Flan.
;;;; Some bindings keep a hand-written Flan wrapper, because their Flan face
;;;; is deliberately not raylib's. Three shapes of that, and every wrapper in
;;;; this file is one of them:
;;;;
;;;; - a slice where C takes a pointer and a count — collision-point-poly?,
;;;; load-image-from-memory, load-font-ex, and the eleven vector-array
;;;; drawing calls under "A slice where raylib wants a pointer and a
;;;; count";
;;;; - an Option where C signals failure by a bool out-parameter or a
;;;; sentinel — collision-lines, get-key-pressed, get-char-pressed;
;;;; - an enum where the header says `int`. These are NOT wrappers: a C
;;;; enum parameter has an int's ABI, so the hand-written declare-c with
;;;; the Flan type on it is the whole fix, and set-exit-key,
;;;; set-mouse-cursor, key-up? and mouse-button-up? are all that.
;;;;
;;;; What is NOT here, and was asked for: with-drawing and with-mode-2d over
;;;; raylib's begin/end pairs. An unbalanced pair is a real bug and a macro
;;;; removes it, but a macro cannot live in a package — the expander collects
;;;; defmacros from the prelude and from the file being compiled, and a
;;;; defmacro in an imported package is refused by name
;;;; (test/programs/pkg-macro.flan, an acceptance case whose whole content is
;;;; the refusal). So these have to be written in the program that uses them,
;;;; or wait for macros to be importable, and neither is this file's to do.
;; Layouts are C's — no object headers anywhere — so these are exactly
;; raylib's structs and nothing marshals.
@ -133,6 +153,18 @@
(declare-c key-down? [key Key] bool "IsKeyDown")
(declare-c key-released? [key Key] bool "IsKeyReleased")
;; The other two halves of that family, hand-written for exactly the reason
;; above and added late: they were generated, so they took an i32, so
;; `(rl/key-up? :space)` did not compile while `(rl/key-down? :space)` did.
;; That is a hole in a family rather than a missing convenience — a caller
;; who has used key-down? has no reason to expect the sibling to be spelled
;; differently, and what they get instead of a keyword is a number nobody
;; checks. Nothing wraps these: the ABI of a C enum parameter is the ABI of
;; an int, so the declaration IS the fix and a defn around it would only be
;; a rename.
(declare-c key-up? [key Key] bool "IsKeyUp")
(declare-c key-pressed-repeat? [key Key] bool "IsKeyPressedRepeat")
(declare-c mouse-button-pressed?
[button MouseButton] bool
"IsMouseButtonPressed")
@ -140,6 +172,37 @@
(declare-c mouse-button-released?
[button MouseButton] bool
"IsMouseButtonReleased")
(declare-c mouse-button-up? [button MouseButton] bool "IsMouseButtonUp")
;; ── Draining raylib's two input queues ──────────────────────────────
;;
;; Both of these answer "nothing left" with 0, and 0 is also a value the
;; caller could otherwise have to think about — KEY_NULL for one, the NUL
;; byte for the other. An Option says which of the two it is in the type, so
;; the loop that drains the queue cannot read the sentinel as a key or as a
;; character: `while (> key 0)` is a comparison a reader has to know the
;; convention to trust, and `(while-some ...)` — or the `if-let` shape the
;; examples use — is one a reader can check.
;;
;; The generated declarations are still what call C; only their names moved
;; aside, to -raw, via the `name` lines in `bindings`. Nothing about the C
;; signature was wrong, so hand-writing it would have taken the generated
;; half's agreement-by-construction with the header and given nothing back.
;;
;; get-key-pressed answers an i32 and not a Key. A Key is a *closed* set the
;; package names a subset of, and this queue reports every key on the
;; keyboard including the ones no member covers, so the enum would be a
;; promise the value does not keep. Comparing the answer against `:space`
;; would be the reason to want it, and that is what key-pressed? is for.
(defn get-key-pressed [] (Option i32)
(let [k (get-key-pressed-raw)]
(if (= k 0) None (Some k))))
;; Unicode codepoint, not a byte: raylib decodes the platform's input, so a
;; value above 127 is a real codepoint and not the first byte of one.
(defn get-char-pressed [] (Option i32)
(let [c (get-char-pressed-raw)]
(if (= c 0) None (Some c))))
(declare-c get-mouse-position [] Vector2 "GetMousePosition")
@ -793,6 +856,85 @@
(declare-c draw-rectangle-rounded-lines-ex [rec Rectangle roundness f32
segments i32 thick f32 color Color] "DrawRectangleRoundedLinesEx")
;; ── A slice where raylib wants a pointer and a count ─────────────────
;;
;; Eleven entry points take an array of vectors as a pointer plus an `int`
;; count. A Flan slice already carries both, so every call site that does not
;; go through a wrapper has to take the slice apart itself — `(addr (at pts
;; 0))` and `(len pts)`, twice, in the right order — and the compiler cannot
;; check that the two halves came from the same slice. The wrapper is where
;; that idiom lives, which is the rule collision-point-poly? set.
;;
;; It also guards the empty case, which is the part a hand-written call site
;; gets wrong rather than merely writes out. raylib takes a count of 0 and
;; draws nothing, but `(at pts 0)` on an empty slice is out of bounds before
;; raylib is ever reached: the safe call is "do not call at all", and it is
;; written once here instead of at every use.
;;
;; All eleven and not the three anybody has called. A subset would have its
;; hole exactly where the next caller looks, which is the argument this file
;; already makes about ConfigFlags, and the eleven are one family — there is
;; no line to draw between DrawSplineLinear and DrawSplineBasis that a reader
;; would predict. They sit together here rather than each in its own section
;; for the same reason: the justification above is one argument about a shape
;; that cuts across Shapes, Images and 3D, and splitting the family would
;; mean writing it three times or leaving two thirds of it unexplained.
;;
;; Each -raw below is a generated declaration whose name moved aside; see the
;; `name` lines at the foot of `bindings`.
(defn draw-line-strip [points [Vector2] color Color] ()
(when (> (len points) 0)
(draw-line-strip-raw (addr (at points 0)) (len points) color)))
(defn draw-triangle-fan [points [Vector2] color Color] ()
(when (> (len points) 0)
(draw-triangle-fan-raw (addr (at points 0)) (len points) color)))
(defn draw-triangle-strip [points [Vector2] color Color] ()
(when (> (len points) 0)
(draw-triangle-strip-raw (addr (at points 0)) (len points) color)))
(defn draw-triangle-strip-3d [points [Vector3] color Color] ()
(when (> (len points) 0)
(draw-triangle-strip-3d-raw (addr (at points 0)) (len points) color)))
;; The five spline drawers. raylib reads the same point array five different
;; ways; the only difference between these wrappers is which one it calls.
(defn draw-spline-linear [points [Vector2] thick f32 color Color] ()
(when (> (len points) 0)
(draw-spline-linear-raw (addr (at points 0)) (len points) thick color)))
(defn draw-spline-basis [points [Vector2] thick f32 color Color] ()
(when (> (len points) 0)
(draw-spline-basis-raw (addr (at points 0)) (len points) thick color)))
(defn draw-spline-catmull-rom [points [Vector2] thick f32 color Color] ()
(when (> (len points) 0)
(draw-spline-catmull-rom-raw (addr (at points 0)) (len points) thick color)))
(defn draw-spline-bezier-quadratic [points [Vector2] thick f32 color Color] ()
(when (> (len points) 0)
(draw-spline-bezier-quadratic-raw
(addr (at points 0)) (len points) thick color)))
(defn draw-spline-bezier-cubic [points [Vector2] thick f32 color Color] ()
(when (> (len points) 0)
(draw-spline-bezier-cubic-raw
(addr (at points 0)) (len points) thick color)))
;; The same two into an Image rather than the frame. `dst` stays a pointer:
;; it is the thing being written, not an array, and raylib's convention for
;; an in-place Image is the whole Image* family in this file.
(defn image-draw-triangle-fan [dst (Ptr Image) points [Vector2] color Color] ()
(when (> (len points) 0)
(image-draw-triangle-fan-raw dst (addr (at points 0)) (len points) color)))
(defn image-draw-triangle-strip
[dst (Ptr Image) points [Vector2] color Color] ()
(when (> (len points) 0)
(image-draw-triangle-strip-raw dst (addr (at points 0)) (len points) color)))
;; ── Text ────────────────────────────────────────────────────────────
;;
;; Both of these use raylib's built-in font, and both therefore need

233
vendor/raylib/vector.flan vendored Normal file
View File

@ -0,0 +1,233 @@
;;;; Vector arithmetic over raylib's Vector2 and Vector3, in Flan.
;;;;
;;;; This is raymath, and raymath is the one part of raylib that cannot be
;;;; bound at all. raymath.h defines every one of its functions `static
;;;; inline` (RMAPI expands to it), so Vector2Add and Clamp and Remap have no
;;;; symbol in libraylib for `declare-c` to name — not a signature the
;;;; importer gets wrong, not a struct the package has not described, but
;;;; nothing to link against. NEXT.md item 4 records it and names the two
;;;; ways out: write the arithmetic in Flan, or compile a small C file that
;;;; re-exports the inlines as real symbols.
;;;;
;;;; It is written in Flan, and the measurement that decided it was already
;;;; taken: examples/shapes-following-eyes.flan is an example whose every
;;;; line is vector maths, ported without a vector library, and its own
;;;; header reports that this cost nothing — the C does not use raymath there
;;;; either. A C shim would buy identical arithmetic at the price of a
;;;; compilation unit in the build, a second place raylib's semantics are
;;;; written down, and a third target's worth of it for the web build.
;;;;
;;;; Every function here is raymath's, semantics included, and the ones where
;;;; that is not obvious say so. The one worth knowing without reading: at
;;;; zero length, v2-normalize and v3-normalize answer the zero vector rather
;;;; than dividing and producing NaNs. raymath makes that choice and a caller
;;;; who has raymath in mind would be surprised by the other one.
;;;;
;;;; ── Why this is a file of its own ───────────────────────────────────
;;;;
;;;; The split is on `declare-c`, not on "idiomatic". raylib.flan is the
;;;; package's statement about C: every line in it is a declaration or a thin
;;;; wrapper over one, it is the file the header check reads hand-written
;;;; signatures out of, and a wrong line in it stops the build. There is not
;;;; one `declare-c` below and there never will be, because there is nothing
;;;; to declare — so nothing here can be checked against a header, and
;;;; nothing here can be made wrong by raylib changing. A reader who wants to
;;;; know what the package claims about C should not have to walk past four
;;;; hundred lines of float arithmetic to find out, and 1300 lines of
;;;; raylib.flan is already the argument against adding to it.
;;;;
;;;; A package is a directory, so this is simply another .flan beside the
;;;; others and is qualified `rl/` like the rest of it.
;;;;
;;;; ── Names ───────────────────────────────────────────────────────────
;;;;
;;;; `v2-` and `v3-`, not `vector2-`. These appear nested inside each other —
;;;; `(rl/v2-add p (rl/v2-scale d t))` is the ordinary shape — and the longer
;;;; spelling puts more characters between the reader and the arithmetic than
;;;; it puts meaning. The prefix still says the type, which is the part a
;;;; language without generics needs it to say.
;;;;
;;;; ── What is NOT here, on purpose ────────────────────────────────────
;;;;
;;;; `clamp` and `lerp`. Both are already in the prelude — clamp as a macro
;;;; (prelude.ml, "clamp is a macro and not a function"), lerp as a function
;;;; — and both are unqualified names every program already has;
;;;; examples/textures-fog-of-war.flan calls the prelude's clamp today. A
;;;; second `rl/lerp` would not even be the same function: the prelude writes
;;;; the weighted sum `(1-t)a + tb`, which returns b exactly at t = 1.0,
;;;; where raymath writes `a + t*(b - a)`, which does not once rounding is
;;;; involved. Shipping both under names one letter apart is a bug waiting
;;;; for whoever picks the wrong one. So: use the prelude's, and what is
;;;; added below is the neighbours the prelude does not have.
;; ── f32, the scalars raymath has and the prelude does not ───────────
;; Where `value` falls between `start` and `end`, as 0.0 at start and 1.0 at
;; end. raymath spells this `Normalize`, which collides with the vector
;; normalize two sections down and means something unrelated to it; it is the
;; inverse of lerp and is named for that. start = end is a division by zero,
;; as it is in raymath: an empty range has no answer and inventing one would
;; hide the caller's bug.
(defn inverse-lerp [value f32 start f32 end f32] f32
(/ (- value start) (- end start)))
;; raymath's Remap, to the character: inverse-lerp on the input range, then
;; lerp on the output range, and NOT clamped to either. A value outside the
;; input range maps outside the output range, which is what makes it usable
;; for extrapolation — a caller who wants it bounded writes the prelude's
;; clamp around it and can see that they did.
;;
;; Written as one expression rather than as (lerp out-start out-end
;; (inverse-lerp ...)) because the prelude's lerp is the weighted-sum form
;; and raymath's Remap is the a + t*(b - a) form; composing them would be a
;; different function in the last bit.
(defn remap [value f32 in-start f32 in-end f32
out-start f32 out-end f32] f32
(+ (* (/ (- value in-start) (- in-end in-start))
(- out-end out-start))
out-start))
;; raymath's Wrap. Brings a value into [min, max) by subtracting whole spans
;; of it — an angle past 2π, a scrolling offset past the tile width. floor
;; and not truncation, so a value below min wraps up instead of sticking.
(defn wrap-f32 [value f32 lo f32 hi f32] f32
(- value (* (- hi lo) (floor-f32 (/ (- value lo) (- hi lo))))))
;; ── Vector2 ─────────────────────────────────────────────────────────
(defn v2-add [a Vector2 b Vector2] Vector2
(Vector2 {.x (+ (.x a) (.x b)) .y (+ (.y a) (.y b))}))
(defn v2-sub [a Vector2 b Vector2] Vector2
(Vector2 {.x (- (.x a) (.x b)) .y (- (.y a) (.y b))}))
;; Componentwise, which is raymath's Vector2Multiply and is not a dot product
;; or anything else that deserves the word "multiply" unqualified. It is what
;; a non-uniform scale is written as.
(defn v2-mul [a Vector2 b Vector2] Vector2
(Vector2 {.x (* (.x a) (.x b)) .y (* (.y a) (.y b))}))
(defn v2-scale [v Vector2 k f32] Vector2
(Vector2 {.x (* (.x v) k) .y (* (.y v) k)}))
(defn v2-negate [v Vector2] Vector2
(Vector2 {.x (- 0.0 (.x v)) .y (- 0.0 (.y v))}))
(defn v2-dot [a Vector2 b Vector2] f32
(+ (* (.x a) (.x b)) (* (.y a) (.y b))))
;; The squared forms are not micro-optimisation dressed up: comparing two
;; distances, or a distance against a radius, is the common case and neither
;; needs the square root. raymath has both for the same reason.
(defn v2-length-sqr [v Vector2] f32
(+ (* (.x v) (.x v)) (* (.y v) (.y v))))
(defn v2-length [v Vector2] f32
(sqrt-f32 (+ (* (.x v) (.x v)) (* (.y v) (.y v)))))
(defn v2-distance-sqr [a Vector2 b Vector2] f32
(let [dx (- (.x a) (.x b))
dy (- (.y a) (.y b))]
(+ (* dx dx) (* dy dy))))
(defn v2-distance [a Vector2 b Vector2] f32
(let [dx (- (.x a) (.x b))
dy (- (.y a) (.y b))]
(sqrt-f32 (+ (* dx dx) (* dy dy)))))
;; Zero in, zero out — raymath's Vector2Normalize guards on `length > 0` and
;; returns {0, 0}, and this does the same. The alternative is dividing by
;; zero and answering a vector of NaNs, which then propagates through every
;; subsequent frame's arithmetic and reports itself somewhere else entirely.
;; The guard is the whole reason this is a function and not two divisions
;; written at the call site.
(defn v2-normalize [v Vector2] Vector2
(let [length (sqrt-f32 (+ (* (.x v) (.x v)) (* (.y v) (.y v))))]
(if (> length 0.0)
(let [inv (/ 1.0 length)]
(Vector2 {.x (* (.x v) inv) .y (* (.y v) inv)}))
(Vector2 {.x 0.0 .y 0.0}))))
;; The signed angle from a to b, in radians, via atan2 of the 2D cross
;; product over the dot. Signed and not absolute, so it says which way to
;; turn; raymath's Vector2Angle is this and not the acos form.
(defn v2-angle [a Vector2 b Vector2] f32
(atan2-f32 (- (* (.x a) (.y b)) (* (.y a) (.x b)))
(+ (* (.x a) (.x b)) (* (.y a) (.y b)))))
;; a + t*(b - a) componentwise, which is raymath's Vector2Lerp exactly. The
;; note in the file header applies: the prelude's scalar lerp is the
;; weighted-sum form and this is not, so the two do not agree in the last bit
;; at t = 1.0. raymath's is kept here because a vector path that disagrees
;; with raylib's own would be the surprise.
(defn v2-lerp [a Vector2 b Vector2 t f32] Vector2
(Vector2 {.x (+ (.x a) (* t (- (.x b) (.x a))))
.y (+ (.y a) (* t (- (.y b) (.y a))))}))
;; Counter-clockwise by `angle` radians in raylib's screen space, which has y
;; growing downward — so on screen it turns the other way from the way the
;; maths reads. raymath's Vector2Rotate, unchanged.
(defn v2-rotate [v Vector2 angle f32] Vector2
(let [c (cos-f32 angle)
s (sin-f32 angle)]
(Vector2 {.x (- (* (.x v) c) (* (.y v) s))
.y (+ (* (.x v) s) (* (.y v) c))})))
;; ── Vector3 ─────────────────────────────────────────────────────────
(defn v3-add [a Vector3 b Vector3] Vector3
(Vector3 {.x (+ (.x a) (.x b)) .y (+ (.y a) (.y b)) .z (+ (.z a) (.z b))}))
(defn v3-sub [a Vector3 b Vector3] Vector3
(Vector3 {.x (- (.x a) (.x b)) .y (- (.y a) (.y b)) .z (- (.z a) (.z b))}))
(defn v3-mul [a Vector3 b Vector3] Vector3
(Vector3 {.x (* (.x a) (.x b)) .y (* (.y a) (.y b)) .z (* (.z a) (.z b))}))
(defn v3-scale [v Vector3 k f32] Vector3
(Vector3 {.x (* (.x v) k) .y (* (.y v) k) .z (* (.z v) k)}))
(defn v3-negate [v Vector3] Vector3
(Vector3 {.x (- 0.0 (.x v)) .y (- 0.0 (.y v)) .z (- 0.0 (.z v))}))
(defn v3-dot [a Vector3 b Vector3] f32
(+ (+ (* (.x a) (.x b)) (* (.y a) (.y b))) (* (.z a) (.z b))))
;; Right-handed, which is the convention raylib's camera uses: the cross of
;; the x axis with the y axis is the z axis.
(defn v3-cross [a Vector3 b Vector3] Vector3
(Vector3 {.x (- (* (.y a) (.z b)) (* (.z a) (.y b)))
.y (- (* (.z a) (.x b)) (* (.x a) (.z b)))
.z (- (* (.x a) (.y b)) (* (.y a) (.x b)))}))
(defn v3-length-sqr [v Vector3] f32
(+ (+ (* (.x v) (.x v)) (* (.y v) (.y v))) (* (.z v) (.z v))))
(defn v3-length [v Vector3] f32
(sqrt-f32 (+ (+ (* (.x v) (.x v)) (* (.y v) (.y v))) (* (.z v) (.z v)))))
(defn v3-distance-sqr [a Vector3 b Vector3] f32
(let [dx (- (.x a) (.x b))
dy (- (.y a) (.y b))
dz (- (.z a) (.z b))]
(+ (+ (* dx dx) (* dy dy)) (* dz dz))))
(defn v3-distance [a Vector3 b Vector3] f32
(let [dx (- (.x a) (.x b))
dy (- (.y a) (.y b))
dz (- (.z a) (.z b))]
(sqrt-f32 (+ (+ (* dx dx) (* dy dy)) (* dz dz)))))
;; Zero in, zero out, exactly as v2-normalize and for the same reason.
(defn v3-normalize [v Vector3] Vector3
(let [length (sqrt-f32 (+ (+ (* (.x v) (.x v)) (* (.y v) (.y v)))
(* (.z v) (.z v))))]
(if (> length 0.0)
(let [inv (/ 1.0 length)]
(Vector3 {.x (* (.x v) inv) .y (* (.y v) inv) .z (* (.z v) inv)}))
(Vector3 {.x 0.0 .y 0.0 .z 0.0}))))
(defn v3-lerp [a Vector3 b Vector3 t f32] Vector3
(Vector3 {.x (+ (.x a) (* t (- (.x b) (.x a))))
.y (+ (.y a) (* t (- (.y b) (.y a))))
.z (+ (.z a) (* t (- (.z b) (.z a))))}))