flan/vendor/raylib/vector.flan
Joseph Ferano 5a1c2745b0 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.
2026-09-13 15:16:40 +07:00

234 lines
12 KiB
Plaintext

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