FIX.org, NEXT.md, DISCUSS.org, docs/DISCUSS.md and the session handoff at the root are one TODO.org now: 293 entries under seven subsystem headings, each carrying an org keyword that says where it stands. A DONE entry is a few lines saying what was decided and what that rules out; the reasoning that would not compress — the embedding spike and the four reports the hand-written x86 backend was built from — moved into docs/BUILT.md instead, and its entries point there in one line. Every entry was checked against the tree before it got a keyword, and the prose was wrong in both directions. Things the deleted files called open were built: the first-evaluation stall, main being redefinable, macro parameter lists, the type-limit constants, the array constructors, the byte fills, inc/dec, the discard's fontification, the Emacs buffers, rt_die's _exit, the backtrace surface, and the acceptance failure that could print and still exit zero. Things they called done were not: the backend reports' no-plan buckets had gone stale in the other direction, the value-dependent defvar was superseded rather than built, and macro-expansion source locations are on an unmerged lane, so that entry is NEXT and names the branch. Every comment that cited one of the five by name now cites a heading that exists, in TODO.org or in docs/BUILT.md. The session reports under docs/handoffs/ keep naming the files they worked on, because rewriting them would falsify what those sessions did; each carries a note saying where the content went.
234 lines
12 KiB
Plaintext
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. TODO.org, "raymath is written in Flan, because
|
|
;;;; static inline has no symbol", records it and names the two ways out: write
|
|
;;;; the arithmetic in Flan, or a small C file re-exporting them as 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))))}))
|