diff --git a/NEXT.md b/NEXT.md index d179b35..2f96044 100644 --- a/NEXT.md +++ b/NEXT.md @@ -238,16 +238,54 @@ would cost. ## ~~To discuss: five gaps the raylib examples hit and could not close~~ — **three closed, two live elsewhere** -- **Closed:** an enum-typed `defstruct` field is no longer refused by the layout check (an enum *is* an `i32`, and - the predicate is symmetric now, so the enum may be on either side); the header check reaches `defconst` and - `defenum` through name-mapping directives in `vendor/raylib/bindings`, so a wrong flag bit is no longer silent; - and the `Ptr`-indexing gap has its own queue entry at the top of this file, because it now blocks three - examples rather than being a note. -- **Still open, and both are here rather than in a queue entry because neither has a customer pressing:** - **raymath is `static inline`**, so `Clamp`, `Vector2Add` and `Remap` have no symbol to `declare-c` at all — a lane - measured the alternative and found writing the arithmetic in Flan cost nothing, which is what makes this a - non-problem rather than a gap; and **function-pointer parameters** are refused (`SetTraceLogCallback`, the audio - stream processors), which is the callback direction of the FFI and nothing has needed it yet. +Found by the lane that ported `core-2d-camera`, `core-scissor-test`, `core-window-flags`, +`core-world-screen` and `core-window-should-close`. None blocked those five. All five blocked +something else, and each is a language or checker question rather than a missing binding, which is +why they are here and not in a binding list. + +1. **A `(Ptr T)` returned from C cannot be indexed.** `indexed` in `lib/check.ml` accepts `Array` + and `Slice` only, so a C function answering `int*` is readable at element 0 through `deref` and + nowhere else. `LoadRandomSequence` is the case that hit it and `core_random_sequence` is + unportable until it moves. The question is what the answer should be: a length has to come from + somewhere before a pointer can become a slice, and C does not supply one. Possibly a + `(slice-from-ptr p n)` where the caller states the length and owns being right about it. + **This has its own queue entry at the top of this file now** — it blocks three more examples than this + section knew about, and both candidate shapes are written out there. + +2. ~~**An enum-typed `defstruct` field is refused by the layout check.**~~ **Closed.** The layout + check now accepts an enum where the header says `int`, symmetrically, and still refuses + anything that is not four bytes. `Camera3D.projection` is a `CameraProjection` again and the + `rl/camera-projection` helper is gone; `.projection :perspective` resolves at the construction + site, so the keyword half of the problem went away with it. See BUILT.md. + +3. ~~**The header check does not reach `defconst` or `defenum`.**~~ **Closed.** It reaches both. + `bindings` gained `enum`, `const` and `constant` lines that say what a Flan constant is called in + C; every mapped name is compared by value, and a name the mapping cannot find, a rule that + 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.**~~ **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 + 3D-collision families), and **function-pointer parameters** (`SetTraceLogCallback`, which blocks + `core_custom_logging`, and the audio stream processors). The first three are ordinary widening — + write the `defstruct` and they import. The function-pointer one is not, and is the interesting + one: it is the callback direction of the FFI, which nothing has needed yet. ## Queued, 2026-09-13 (second session) — everything four lanes left behind @@ -382,12 +420,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** @@ -661,7 +721,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 diff --git a/examples/core-3d-picking.flan b/examples/core-3d-picking.flan index d96d561..f1831ce 100644 --- a/examples/core-3d-picking.flan +++ b/examples/core-3d-picking.flan @@ -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 diff --git a/examples/models-box-collisions.flan b/examples/models-box-collisions.flan index 74ab36f..1f0e2f2 100644 --- a/examples/models-box-collisions.flan +++ b/examples/models-box-collisions.flan @@ -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 diff --git a/examples/shapes-following-eyes.flan b/examples/shapes-following-eyes.flan index d30c9d6..71da0d8 100644 --- a/examples/shapes-following-eyes.flan +++ b/examples/shapes-following-eyes.flan @@ -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)))}))))) diff --git a/examples/text-input-box.flan b/examples/text-input-box.flan index 8a4cb91..35f58c3 100644 --- a/examples/text-input-box.flan +++ b/examples/text-input-box.flan @@ -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)) diff --git a/vendor/raylib/bindings b/vendor/raylib/bindings index 325dee7..f428bfb 100644 --- a/vendor/raylib/bindings +++ b/vendor/raylib/bindings @@ -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 every member of that defenum diff --git a/vendor/raylib/generated.flan b/vendor/raylib/generated.flan index 35a7cbc..482ddc6 100644 --- a/vendor/raylib/generated.flan +++ b/vendor/raylib/generated.flan @@ -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") diff --git a/vendor/raylib/raylib.flan b/vendor/raylib/raylib.flan index 62622ae..6e1158d 100644 --- a/vendor/raylib/raylib.flan +++ b/vendor/raylib/raylib.flan @@ -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,95 @@ (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. +;; +;; `bindings` makes the opposite argument a few lines above its own list — +;; that hand-writing the variants of a family "would widen the half that has +;; to be maintained by hand for nothing the examples ask for" — and it is +;; right there and does not reach here. That paragraph is about hand-written +;; `declare-c` lines, which are exactly the half a header change can falsify. +;; None of these eleven is one: each is a `name` directive, so the generated +;; declaration keeps the C symbol and its checked signature and gives up only +;; its Flan name. The hand-maintained half does not widen at all — what is +;; written below is Flan calling Flan, and it cannot disagree with raylib. 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 diff --git a/vendor/raylib/vector.flan b/vendor/raylib/vector.flan new file mode 100644 index 0000000..55022ca --- /dev/null +++ b/vendor/raylib/vector.flan @@ -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))))}))