Merge branch 'worktree-agent-ad330000e84e78a2f' into dev-loop

This commit is contained in:
Joseph Ferano 2026-09-13 15:01:35 +07:00
commit 8472c50d57
13 changed files with 1552 additions and 13 deletions

View File

@ -0,0 +1,157 @@
;;;; raylib [core] example - 3d picking
;;;;
;;;; examples/core/core_3d_picking.c. The one `core` example in this batch,
;;;; and it is here for surface and not for category: it is the only example
;;;; anywhere upstream that uses Ray and RayCollision without also loading a
;;;; Model, so it is the only way those two structs get a caller at all.
;;;;
;;;; Picking is the round trip the corpus was missing. core-world-screen.flan
;;;; already runs get-world-to-screen — a point in the scene to a pixel. This
;;;; is the inverse and it is a different shape: a pixel does not name a
;;;; point, it names a *line* through the scene, which is what a Ray is, and
;;;; the answer to "what did I click on" is where that line first meets
;;;; something, which is what a RayCollision is. Two structs the package could
;;;; not describe, for one operation that cannot be written without them.
;;;;
;;;; What was added, all in vendor/raylib/raylib.flan:
;;;;
;;;; - `Ray` — two Vector3s, origin and direction.
;;;; - `RayCollision` — a C `bool`, a float and two Vector3s. This is the
;;;; one of the three new structs a layout check can actually fail:
;;;; `hit` is one byte and `distance` is four, so there are three padding
;;;; bytes between them that a permutation destroys. BoundingBox's two
;;;; Vector3s are interchangeable and nothing would catch swapping them.
;;;; - `BoundingBox` — shared with examples/models-box-collisions.flan.
;;;; - get-screen-to-world-ray and draw-ray, hand-written beside
;;;; get-world-to-screen and the cube draws for the reasons `bindings`
;;;; gives: the first reads the same camera as its inverse, and the second
;;;; is inside a frame.
;;;;
;;;; get-ray-collision-box came free out of the generated half the moment Ray
;;;; and BoundingBox existed, along with the sphere, triangle and quad forms.
;;;; get-ray-collision-mesh is still refused and will stay refused: Mesh owns
;;;; seven arrays and a GPU handle, and describing it is a different job.
;;;;
;;;; raylib 5.5 renamed GetMouseRay to GetScreenToWorldRay and left the old
;;;; name as a #define. A #define is not a symbol, so there is nothing to bind
;;;; it to and nothing lost by not trying.
(import rl "vendor:raylib")
(defconst screen-width 800)
(defconst screen-height 450)
(defvar camera rl/Camera3D)
(defvar cube-position rl/Vector3)
(defvar cube-size rl/Vector3)
;; The picking ray, kept between frames because draw-ray draws it every frame
;; whether or not it hit anything — that is how the example shows where the
;; click went.
(defvar ray rl/Ray)
(defvar collision rl/RayCollision)
;; 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.
(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))})}))
(defn main [] ()
(rl/init-window screen-width screen-height
"raylib [core] example - 3d picking")
(defer (rl/close-window))
(set camera (rl/Camera3D {.position (rl/Vector3 {.x 10.0 .y 10.0 .z 10.0})
.target (rl/Vector3 {.x 0.0 .y 0.0 .z 0.0})
.up (rl/Vector3 {.x 0.0 .y 1.0 .z 0.0})
.fovy 45.0
.projection :perspective}))
(set cube-position (rl/Vector3 {.x 0.0 .y 1.0 .z 0.0}))
(set cube-size (rl/Vector3 {.x 2.0 .y 2.0 .z 2.0}))
;; Both start zeroed, which is the C's `= { 0 }`. A zero Ray draws as a
;; degenerate line at the origin and a zero RayCollision has hit false, so
;; neither needs a "not yet" flag beside it.
(set ray (rl/Ray {.position (rl/Vector3 {.x 0.0 .y 0.0 .z 0.0})
.direction (rl/Vector3 {.x 0.0 .y 0.0 .z 0.0})}))
(set collision (rl/RayCollision {.hit false .distance 0.0
.point (rl/Vector3 {.x 0.0 .y 0.0 .z 0.0})
.normal (rl/Vector3 {.x 0.0 .y 0.0 .z 0.0})}))
(rl/set-target-fps 60)
(until (rl/window-should-close?)
;; Update. The first-person controls only run while the cursor is
;; captured, which is what the right button toggles — otherwise the mouse
;; could not be used to aim at anything.
(when (rl/cursor-hidden?) (rl/update-camera (addr camera) :first-person))
(when (rl/mouse-button-pressed? :right)
(if (rl/cursor-hidden?) (rl/enable-cursor) (rl/disable-cursor)))
(when (rl/mouse-button-pressed? :left)
(if (not (.hit collision))
;; The pixel under the pointer, as a line through the scene, tested
;; against the cube's bounds.
(do
(set ray (rl/get-screen-to-world-ray (rl/get-mouse-position) camera))
(set collision
(rl/get-ray-collision-box ray (box-around cube-position cube-size))))
;; A second click deselects. Only `hit` is cleared; the ray stays put
;; and keeps being drawn, as in the C.
(set (.hit collision) false)))
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
(rl/begin-mode-3d camera)
(if (.hit collision)
(do
(rl/draw-cube cube-position (.x cube-size) (.y cube-size) (.z cube-size)
rl/red)
(rl/draw-cube-wires cube-position (.x cube-size) (.y cube-size)
(.z cube-size) rl/maroon)
;; A slightly larger wireframe around the selection, which is the
;; whole visual feedback of the example.
(rl/draw-cube-wires cube-position (+ (.x cube-size) 0.2)
(+ (.y cube-size) 0.2) (+ (.z cube-size) 0.2)
rl/green))
(do
(rl/draw-cube cube-position (.x cube-size) (.y cube-size) (.z cube-size)
rl/gray)
(rl/draw-cube-wires cube-position (.x cube-size) (.y cube-size)
(.z cube-size) rl/darkgray)))
;; raylib draws the ray a thousand units long, so it reads as a line
;; crossing the scene rather than as a segment with an end.
(rl/draw-ray ray rl/maroon)
(rl/draw-grid 10 1.0)
(rl/end-mode-3d)
(rl/draw-text "Try clicking on the box with your mouse!" 240 10 20
rl/darkgray)
(when (.hit collision)
(rl/draw-text "BOX SELECTED"
(/ (- screen-width (rl/measure-text "BOX SELECTED" 30)) 2)
(i32 (* (f32 screen-height) 0.1)) 30 rl/green))
(rl/draw-text "Right click mouse to toggle camera controls" 10 430 10
rl/gray)
(rl/draw-fps 10 10)
(rl/end-drawing)))

View File

@ -0,0 +1,133 @@
;;;; raylib [models] example - box collisions
;;;;
;;;; examples/models/models_box_collisions.c. The previous lane's finding was
;;;; that the whole models category is refused by the importer for want of a
;;;; defstruct, and that a models example is therefore mostly a binding
;;;; exercise. That is true of the twenty that load a Model — but not of this
;;;; one, which is why it is here: nothing in it is a Model, a Mesh or a
;;;; Material. It is two cubes, a sphere and one aggregate the package did not
;;;; describe.
;;;;
;;;; That aggregate is BoundingBox, and it is two Vector3s and nothing else —
;;;; no owned pointer, no array, no lifetime. One `defstruct` in
;;;; vendor/raylib/raylib.flan un-refuses four raylib functions at once
;;;; (CheckCollisionBoxes, CheckCollisionBoxSphere, DrawBoundingBox and
;;;; GetRayCollisionBox), and that is the cheapest widening of the 3D surface
;;;; available anywhere in the tree. Ray and RayCollision went in beside it
;;;; for examples/core-3d-picking.flan and for the same reason.
;;;;
;;;; So the answer to "is a models example just a binding exercise" is: the
;;;; ones that need Model are, and this one is the counterexample. What
;;;; separates them is not the category, it is whether the struct owns memory.
;;;;
;;;; What it also exercises, and what needed hand-writing rather than
;;;; generating: draw-cube-v, draw-sphere and draw-sphere-wires. All three
;;;; were in the generated half already, and all three moved to the
;;;; hand-written one here on `bindings`'s own rule — they are inside a frame,
;;;; in an example that ships in examples/. draw-cube-v in particular was
;;;; named there as generated *because nothing called it*; something calls it
;;;; now.
;;;;
;;;; The collision maths is written twice in the C, once per test, building
;;;; the player's box inline both times. It is a function here for the reason
;;;; the eyes example gives: two copies of the same eight expressions cannot
;;;; be checked against each other by reading them.
(import rl "vendor:raylib")
(defconst screen-width 800)
(defconst screen-height 450)
(defvar camera rl/Camera3D)
(defvar player-position rl/Vector3)
(defvar player-size rl/Vector3)
(defvar player-color rl/Color)
(defvar enemy-box-pos rl/Vector3)
(defvar enemy-box-size rl/Vector3)
(defvar enemy-sphere-pos rl/Vector3)
(defconst enemy-sphere-size f32 1.5)
;; A box centred on `centre` with the given extents. raylib's BoundingBox is
;; 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.
(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))})}))
(defn main [] ()
(rl/init-window screen-width screen-height
"raylib [models] example - box collisions")
(defer (rl/close-window))
;; The C writes this camera as a brace initialiser with a trailing 0 for the
;; projection, which is CAMERA_PERSPECTIVE. Named here, because the defenum
;; exists precisely so that the 0 does not have to be remembered.
(set camera (rl/Camera3D {.position (rl/Vector3 {.x 0.0 .y 10.0 .z 10.0})
.target (rl/Vector3 {.x 0.0 .y 0.0 .z 0.0})
.up (rl/Vector3 {.x 0.0 .y 1.0 .z 0.0})
.fovy 45.0
.projection :perspective}))
(set player-position (rl/Vector3 {.x 0.0 .y 1.0 .z 2.0}))
(set player-size (rl/Vector3 {.x 1.0 .y 2.0 .z 1.0}))
(set player-color rl/green)
(set enemy-box-pos (rl/Vector3 {.x -4.0 .y 1.0 .z 0.0}))
(set enemy-box-size (rl/Vector3 {.x 2.0 .y 2.0 .z 2.0}))
(set enemy-sphere-pos (rl/Vector3 {.x 4.0 .y 0.0 .z 0.0}))
(rl/set-target-fps 60)
(until (rl/window-should-close?)
;; Update. The arrow keys move on the XZ plane, and the C's else-if chain
;; means only one direction applies per frame — diagonal movement is not
;; possible, deliberately.
(cond
(rl/key-down? :right) (set (.x player-position) (+ (.x player-position) 0.2))
(rl/key-down? :left) (set (.x player-position) (- (.x player-position) 0.2))
(rl/key-down? :down) (set (.z player-position) (+ (.z player-position) 0.2))
(rl/key-down? :up) (set (.z player-position) (- (.z player-position) 0.2)))
(let [player-box (box-around player-position player-size)
hit (or (rl/check-collision-boxes player-box
(box-around enemy-box-pos enemy-box-size))
(rl/check-collision-box-sphere player-box enemy-sphere-pos
enemy-sphere-size))]
(set player-color (if hit rl/red rl/green)))
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
(rl/begin-mode-3d camera)
(rl/draw-cube enemy-box-pos (.x enemy-box-size) (.y enemy-box-size)
(.z enemy-box-size) rl/gray)
(rl/draw-cube-wires enemy-box-pos (.x enemy-box-size) (.y enemy-box-size)
(.z enemy-box-size) rl/darkgray)
;; draw-sphere is draw-sphere-ex at 16 rings and 16 slices; the wires form
;; takes them because the tessellation is only visible in the wireframe.
(rl/draw-sphere enemy-sphere-pos enemy-sphere-size rl/gray)
(rl/draw-sphere-wires enemy-sphere-pos enemy-sphere-size 16 16 rl/darkgray)
(rl/draw-cube-v player-position player-size player-color)
(rl/draw-grid 10 1.0)
(rl/end-mode-3d)
(rl/draw-text "Move player with arrow keys to collide" 220 40 20 rl/gray)
(rl/draw-fps 10 10)
(rl/end-drawing)))

View File

@ -0,0 +1,102 @@
;;;; raylib [shapes] example - basic shapes drawing
;;;;
;;;; examples/shapes/shapes_basic_shapes.c. Ported first of the shapes batch
;;;; because it is the widest single file in the category: six draw families
;;;; the corpus had never called — draw-circle-gradient, the two rectangle
;;;; gradients, draw-triangle and draw-triangle-lines, and all three of the
;;;; poly draws — in one frame, beside the four the core examples already
;;;; exercise. Every one of them came out of the generated half of the
;;;; bindings and none needed a new line anywhere; that is the result worth
;;;; recording, because the whole point of a committed generated.flan is that
;;;; a build with no FLAN_RAYLIB_H set can draw with all of it.
;;;;
;;;; What it is actually a test of. The gradient calls are the first thing in
;;;; the corpus to pass *two* Colors in one call, and draw-triangle is the
;;;; first to pass three Vector2s. A struct argument crosses the FFI by
;;;; pointer into a generated shim (vendor/raylib/raylib.flan's header note),
;;;; so the arity of that copying is what a call with several small structs in
;;;; a row puts under load. Nothing here can be asserted headless — every line
;;;; needs a GL context — so the check is the link and the screen, which is
;;;; what the Shapes comment in raylib.flan already says about this family.
;;;;
;;;; The one deliberate difference from the C: the C writes `screenWidth/4*2`
;;;; and `screenWidth/4.0f*3.0f` for the same column and gets 400 and 600 out
;;;; of integer and float division respectively. Both are written here as the
;;;; arithmetic they are, in the type the call wants, rather than copied with
;;;; a cast on the end — `(/ screen-width 5)` is an i32 division for the i32
;;;; parameters of draw-circle, and the poly centre is built from f32
;;;; literals because a Vector2 holds f32.
(import rl "vendor:raylib")
(defconst screen-width 800)
(defconst screen-height 450)
;; The three columns the C lays the shapes out in. The middle one is
;; screenWidth/4*2 and the right one screenWidth/4*3, which is 400 and 600.
(defconst col-left 160) ; screen-width / 5
(defconst col-mid 400)
(defconst col-right 600)
(defvar rotation f32)
(defn main [] ()
(rl/init-window screen-width screen-height
"raylib [shapes] example - basic shapes drawing")
(defer (rl/close-window))
(set rotation 0.0)
(rl/set-target-fps 60)
(until (rl/window-should-close?)
;; Update. The polygons spin; nothing else moves.
(set rotation (+ rotation 0.2))
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
(rl/draw-text "some basic shapes available on raylib" 20 20 20 rl/darkgray)
;; Circles. The gradient one takes an inner and an outer colour and is the
;; first call in the corpus to pass two Colors at once.
(rl/draw-circle col-left 120 35.0 rl/darkblue)
(rl/draw-circle-gradient col-left 220 60.0 rl/green rl/skyblue)
(rl/draw-circle-lines col-left 340 80.0 rl/darkblue)
;; Rectangles. draw-rectangle-gradient-h runs the colour left to right;
;; the -v form runs it top to bottom and the -ex form takes all four
;; corners. The C uses the horizontal one here.
(rl/draw-rectangle (- col-mid 60) 100 120 60 rl/red)
(rl/draw-rectangle-gradient-h (- col-mid 90) 170 180 130 rl/maroon rl/gold)
;; raylib draws this with quads internally rather than with lines, which
;; is why its thickness does not follow the line width anywhere.
(rl/draw-rectangle-lines (- col-mid 40) 320 80 60 rl/orange)
;; Triangles. raylib wants the three vertices in counter-clockwise order
;; and draws nothing at all for a clockwise one, so the order here is not
;; cosmetic.
(rl/draw-triangle (rl/Vector2 {.x 600.0 .y 80.0})
(rl/Vector2 {.x 540.0 .y 150.0})
(rl/Vector2 {.x 660.0 .y 150.0})
rl/violet)
(rl/draw-triangle-lines (rl/Vector2 {.x 600.0 .y 160.0})
(rl/Vector2 {.x 580.0 .y 230.0})
(rl/Vector2 {.x 620.0 .y 230.0})
rl/darkblue)
;; Polygons, all three at the same centre and the same rotation, at three
;; radii, so the filled hexagon sits inside the two outlines.
(let [centre (rl/Vector2 {.x (f32 col-right) .y 330.0})]
(rl/draw-poly centre 6 80.0 rotation rl/brown)
(rl/draw-poly-lines centre 6 90.0 rotation rl/brown)
(rl/draw-poly-lines-ex centre 6 85.0 rotation 6.0 rl/beige))
;; The C draws every LINES-based shape together so raylib can batch them
;; into one pass. This last line is part of that comment and not an
;; afterthought, so it stays where the C has it.
(rl/draw-line 18 42 (- screen-width 18) 42 rl/black)
(rl/end-drawing)))

View File

@ -0,0 +1,129 @@
;;;; raylib [shapes] example - collision area
;;;;
;;;; examples/shapes/shapes_collision_area.c. Picked for one call:
;;;; get-collision-rec, which is the only binding in the package that takes
;;;; two Rectangles and answers a third. Everything else in the corpus that
;;;; crosses a struct either passes one in (Camera2D, Color) or gets one back
;;;; (get-mouse-position, fade) — this is the first per-frame call doing both
;;;; at once, and the returned struct comes back through the shim's `out`
;;;; pointer rather than in registers, which is a different lowering again.
;;;;
;;;; It is also the only headless-adjacent thing in the file: both
;;;; collision-recs? and get-collision-rec are pure arithmetic over their
;;;; arguments and need no window at all. The acceptance table already asserts
;;;; the pair; what this adds is the same two calls on a frame path with one
;;;; rectangle driven by the mouse, where a permuted Rectangle would put the
;;;; green overlap patch somewhere the two boxes are not.
;;;;
;;;; Both TextFormats go through examples/digits.flan the way the core ports
;;;; do it: draw the literal part, then the number at x plus its width.
(import rl "vendor:raylib")
(import d "digits.flan")
(defconst screen-width 800)
(defconst screen-height 450)
;; The top strip the collision message is drawn into, and the floor box B is
;; not allowed above.
(defconst screen-upper-limit 40)
(defvar box-a rl/Rectangle) ; moves by itself, bounces off the sides
(defvar box-b rl/Rectangle) ; follows the mouse
(defvar box-collision rl/Rectangle) ; their overlap, valid only while touching
(defvar box-a-speed-x i32)
(defvar paused bool)
(defvar collision bool)
(defn main [] ()
(rl/init-window screen-width screen-height
"raylib [shapes] example - collision area")
(defer (rl/close-window))
(set box-a (rl/Rectangle {.x 10.0
.y (- (/ (f32 screen-height) 2.0) 50.0)
.width 200.0 .height 100.0}))
(set box-a-speed-x 4)
(set box-b (rl/Rectangle {.x (- (/ (f32 screen-width) 2.0) 30.0)
.y (- (/ (f32 screen-height) 2.0) 30.0)
.width 60.0 .height 60.0}))
;; A fresh Rectangle is four zeroes, which is the C's `= { 0 }`. It is never
;; drawn before the first collision sets it.
(set box-collision (rl/Rectangle {.x 0.0 .y 0.0 .width 0.0 .height 0.0}))
(set paused false)
(set collision false)
(rl/set-target-fps 60)
(until (rl/window-should-close?)
;; Update
(when (not paused)
(set (.x box-a) (+ (.x box-a) (f32 box-a-speed-x))))
;; Bounce at either edge. The test is on the far edge going right and on
;; the near one going left, so the box reverses on whichever it reaches.
(when (or (>= (+ (.x box-a) (.width box-a)) (f32 (rl/get-screen-width)))
(<= (.x box-a) 0.0))
(set box-a-speed-x (* box-a-speed-x -1)))
;; Box B is centred on the mouse and then clamped back inside the play
;; area — which is the window minus the top strip.
(set (.x box-b) (- (f32 (rl/get-mouse-x)) (/ (.width box-b) 2.0)))
(set (.y box-b) (- (f32 (rl/get-mouse-y)) (/ (.height box-b) 2.0)))
(if (>= (+ (.x box-b) (.width box-b)) (f32 (rl/get-screen-width)))
(set (.x box-b) (- (f32 (rl/get-screen-width)) (.width box-b)))
(when (<= (.x box-b) 0.0) (set (.x box-b) 0.0)))
(if (>= (+ (.y box-b) (.height box-b)) (f32 (rl/get-screen-height)))
(set (.y box-b) (- (f32 (rl/get-screen-height)) (.height box-b)))
(when (<= (.y box-b) (f32 screen-upper-limit))
(set (.y box-b) (f32 screen-upper-limit))))
(set collision (rl/collision-recs? box-a box-b))
;; Only meaningful while they touch: raylib answers a zero rectangle for
;; two boxes that do not, and the C leaves the previous one in place
;; rather than reading that back. This follows it.
(when collision (set box-collision (rl/get-collision-rec box-a box-b)))
(when (rl/key-pressed? :space) (set paused (not paused)))
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
(rl/draw-rectangle 0 0 screen-width screen-upper-limit
(if collision rl/red rl/black))
(rl/draw-rectangle-rec box-a rl/gold)
(rl/draw-rectangle-rec box-b rl/blue)
(when collision
;; The overlap itself, drawn over both boxes. This patch landing
;; anywhere other than where the gold and the blue cross is what a
;; permuted Rectangle would look like.
(rl/draw-rectangle-rec box-collision rl/lime)
(rl/draw-text "COLLISION!"
(- (/ (rl/get-screen-width) 2)
(/ (rl/measure-text "COLLISION!" 20) 2))
(- (/ screen-upper-limit 2) 10) 20 rl/black)
;; The C's TextFormat("Collision Area: %i", ...). Both dimensions are
;; truncated to int before multiplying, as the C's two casts do.
(let [x (- (/ (rl/get-screen-width) 2) 100)
y (+ screen-upper-limit 10)]
(set x (+ x (d/draw-piece "Collision Area: " x y 20 rl/black)))
(d/draw-int (* (i32 (.width box-collision)) (i32 (.height box-collision)))
x y 20 rl/black)))
(rl/draw-text "Press SPACE to PAUSE/RESUME" 20 (- screen-height 35) 20
rl/lightgray)
(rl/draw-fps 10 10)
(rl/end-drawing)))

View File

@ -0,0 +1,103 @@
;;;; raylib [shapes] example - following eyes
;;;;
;;;; examples/shapes/shapes_following_eyes.c. Ported for the thing it needs
;;;; that raylib does not supply: the whole update is vector arithmetic —
;;;; subtract two points, take the angle, walk back out along it — and every
;;;; one of those steps is raymath. raymath is `static inline` in
;;;; raymath.h, so Vector2Subtract and Vector2Angle have no symbol in
;;;; libraylib at all and there is nothing for declare-c to bind. NEXT.md
;;;; names this as one of the three open gaps.
;;;;
;;;; What that costs here is nothing, because the C does not use raymath
;;;; either — it writes the four subtractions out and calls atan2f, cosf and
;;;; sinf from libm, and Flan's prelude already declares all three as
;;;; atan2-f32, cos-f32 and sin-f32. So this file is the *measurement* of the
;;;; gap rather than a workaround for it: an example whose every line is
;;;; vector maths, written without a vector library, in the same shape the C
;;;; has. It reads fine. A Vector2Add would have saved two lines in the whole
;;;; file.
;;;;
;;;; The raylib call it does exercise is collision-point-circle?, which no
;;;; ported example had called, on a frame path, with the point coming
;;;; straight out of get-mouse-position — one struct out of raylib and back
;;;; into it untouched.
;;;;
;;;; The eye geometry, since the C hides it in a subtraction: the iris is
;;;; pinned inside a circle of (sclera - iris) radius about the sclera's
;;;; centre, so the iris disc is tangent to the sclera's edge at the extreme
;;;; rather than hanging over it.
(import rl "vendor:raylib")
(defconst screen-width 800)
(defconst screen-height 450)
(defconst sclera-radius f32 80.0)
(defconst iris-radius f32 24.0)
(defvar sclera-left rl/Vector2)
(defvar sclera-right rl/Vector2)
(defvar iris-left rl/Vector2)
(defvar iris-right rl/Vector2)
;; One eye's pupil, given where the mouse is and where the eye is. Answers the
;; mouse position unchanged while it is inside the eye, and the point on the
;; limit circle in that direction when it is outside.
;;
;; The C repeats this block twice with different variables; it is one function
;; here because the two copies are identical and a difference between them
;; would be invisible.
(defn track [mouse rl/Vector2 centre rl/Vector2] rl/Vector2
(let [limit (- sclera-radius iris-radius)]
(if (rl/collision-point-circle? mouse centre limit)
mouse
;; Outside: the direction from the eye to the mouse, and then `limit`
;; units back out along it. atan2 then cos/sin rather than a normalise,
;; because that is what the C does and because it needs no guard for a
;; zero-length vector — atan2(0,0) is 0 and the pupil sits at the right
;; of the eye, which is unreachable anyway since (0,0) is inside.
(let [dx (- (.x mouse) (.x centre))
dy (- (.y mouse) (.y centre))
angle (atan2-f32 dy dx)]
(rl/Vector2 {.x (+ (.x centre) (* limit (cos-f32 angle)))
.y (+ (.y centre) (* limit (sin-f32 angle)))})))))
(defn main [] ()
(rl/init-window screen-width screen-height
"raylib [shapes] example - following eyes")
(defer (rl/close-window))
;; The C reads these off get-screen-width/get-screen-height after the window
;; is open rather than off the constants, and so does this: on a high-DPI
;; display the two can differ.
(set sclera-left (rl/Vector2 {.x (- (/ (f32 (rl/get-screen-width)) 2.0) 100.0)
.y (/ (f32 (rl/get-screen-height)) 2.0)}))
(set sclera-right (rl/Vector2 {.x (+ (/ (f32 (rl/get-screen-width)) 2.0) 100.0)
.y (/ (f32 (rl/get-screen-height)) 2.0)}))
(set iris-left sclera-left)
(set iris-right sclera-right)
(rl/set-target-fps 60)
(until (rl/window-should-close?)
;; Update
(let [mouse (rl/get-mouse-position)]
(set iris-left (track mouse sclera-left))
(set iris-right (track mouse sclera-right)))
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
;; White of the eye, iris, pupil — in that order, each over the last.
(rl/draw-circle-v sclera-left sclera-radius rl/lightgray)
(rl/draw-circle-v iris-left iris-radius rl/brown)
(rl/draw-circle-v iris-left 10.0 rl/black)
(rl/draw-circle-v sclera-right sclera-radius rl/lightgray)
(rl/draw-circle-v iris-right iris-radius rl/darkgreen)
(rl/draw-circle-v iris-right 10.0 rl/black)
(rl/draw-fps 10 10)
(rl/end-drawing)))

View File

@ -0,0 +1,140 @@
;;;; raylib [text] example - input box
;;;;
;;;; examples/text/text_input_box.c. Two things in this file exist nowhere
;;;; else in the corpus.
;;;;
;;;; The first is get-char-pressed. Everything ported so far reads input as
;;;; 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
;;;; 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 is set-mouse-cursor, which needed a new defenum. raylib's
;;;; header says `int cursor` and means one of eleven MOUSE_CURSOR_ values, so
;;;; the Flan face is `MouseCursor` — hand-written in raylib.flan beside the
;;;; other cursor calls, excluded from the generated half, and mapped in
;;;; `bindings` so the eleven members are checked against the header. See the
;;;; comment there: this call is made on every frame off a hover test, and the
;;;; failure an i32 would allow is not a crash but a pointer in the wrong
;;;; shape, which nothing reports.
;;;;
;;;; The text buffer, and what it says about strings. The C keeps a
;;;; `char name[MAX_INPUT_CHARS + 1]` and maintains the NUL itself; here it is
;;;; a `[9 u8]` with no terminator, and every use is `(string (slice name 0
;;;; letter-count))`. A Flan string is a pointer and a length, and the shim
;;;; that calls DrawText copies it and NUL-terminates the copy
;;;; (lib/shim.ml) — so the extra byte the C needs has no counterpart here and
;;;; the buffer is exactly as long as the characters it can hold. The two
;;;; draws and the measure all take the same slice, so they cannot disagree
;;;; about where the text ends.
;;;;
;;;; Dropped from the C: its IsAnyKeyPressed helper, which is defined at the
;;;; bottom of the file and called from nowhere.
(import rl "vendor:raylib")
(import d "digits.flan")
(defconst screen-width 800)
(defconst screen-height 450)
(defconst max-input-chars 9)
;; No +1: see the header comment. There is no NUL to leave room for.
(defvar name [9 u8])
(defvar letter-count i32)
(defvar text-box rl/Rectangle)
(defvar mouse-on-text bool)
(defvar frames-counter i32)
(defn main [] ()
(rl/init-window screen-width screen-height
"raylib [text] example - input box")
(defer (rl/close-window))
(set name (array max-input-chars u8))
(set letter-count 0)
(set text-box (rl/Rectangle {.x (- (/ (f32 screen-width) 2.0) 100.0)
.y 180.0 .width 225.0 .height 50.0}))
(set mouse-on-text false)
(set frames-counter 0)
(rl/set-target-fps 60)
(until (rl/window-should-close?)
;; Update
(set mouse-on-text
(rl/collision-point-rec? (rl/get-mouse-position) text-box))
(if mouse-on-text
(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))))
(when (rl/key-pressed? :backspace)
(set letter-count (- letter-count 1))
(when (< letter-count 0) (set letter-count 0))))
(rl/set-mouse-cursor :default))
;; The blink phase only runs while the box has focus, so the underscore is
;; always visible on the frame the pointer arrives.
(if mouse-on-text
(set frames-counter (+ frames-counter 1))
(set frames-counter 0))
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
(rl/draw-text "PLACE MOUSE OVER INPUT BOX!" 240 140 20 rl/gray)
(rl/draw-rectangle-rec text-box rl/lightgray)
(rl/draw-rectangle-lines (i32 (.x text-box)) (i32 (.y text-box))
(i32 (.width text-box)) (i32 (.height text-box))
(if mouse-on-text rl/red rl/darkgray))
(let [typed (string (slice name 0 letter-count))]
(rl/draw-text typed (+ (i32 (.x text-box)) 5) (+ (i32 (.y text-box)) 8)
40 rl/maroon)
;; The C's TextFormat("INPUT CHARS: %i/%i", ...), reassembled out of
;; examples/digits.flan the way the other ports do it. `typed` is a view
;; of `name` and not of the runtime's shared format buffer, so it
;; survives the two numbers being formatted — which a value from
;; i64->bytes would not.
(let [x 315]
(set x (+ x (d/draw-piece "INPUT CHARS: " x 250 20 rl/darkgray)))
(set x (+ x (d/draw-int letter-count x 250 20 rl/darkgray)))
(set x (+ x (d/draw-piece "/" x 250 20 rl/darkgray)))
(d/draw-int max-input-chars x 250 20 rl/darkgray))
(when mouse-on-text
(if (< letter-count max-input-chars)
;; Twenty frames on, twenty frames off, at the pen position after
;; whatever has been typed.
(when (= (% (/ frames-counter 20) 2) 0)
(rl/draw-text "_"
(+ (i32 (.x text-box)) 8 (rl/measure-text typed 40))
(+ (i32 (.y text-box)) 12) 40 rl/maroon))
(rl/draw-text "Press BACKSPACE to delete chars..." 230 300 20
rl/gray))))
(rl/end-drawing)))

View File

@ -0,0 +1,76 @@
;;;; raylib [text] example - text writing anim
;;;;
;;;; examples/text/text_writing_anim.c. Sixty lines of C whose whole substance
;;;; is one call this package cannot bind:
;;;;
;;;; DrawText(TextSubtext(message, 0, framesCounter/10), ...)
;;;;
;;;; TextSubtext is in raylib.h and is not in the bindings, on the rule
;;;; raylib.flan states for the whole Text* family: it answers a `char *` into
;;;; a rotating static buffer, and declare-c refuses a returned pointer to
;;;; memory the caller does not own. There is no missing line to add — the
;;;; binding would be wrong at any signature.
;;;;
;;;; And it does not matter, because Flan has the operation as a primitive.
;;;; `(slice a lo hi)` takes a view of an array, `(string b)` reinterprets the
;;;; bytes as a string at no cost, and `(string (slice message 0 n))` is
;;;; TextSubtext with the static buffer removed — no copy, no shared state, no
;;;; rotation to run out of. Porting the example is therefore how the gap gets
;;;; *closed* rather than reported: the C's workaround for not having slices
;;;; is the thing Flan did not need.
;;;;
;;;; The one place they differ, and it is why the clamp below is here rather
;;;; than in the C: TextSubtext clips its length to the string, so the C can
;;;; pass a counter that runs past the end for ever and see nothing happen.
;;;; `slice` does not clip — an out-of-range bound is an error, and a bound
;;;; computed at run time is checked at run time — so the length has to be
;;;; brought inside the string before the call. That is a better failure than
;;;; the C's, and it costs one `min`.
;;;;
;;;; The message is a `[u8]` and not a `string` because `slice` takes an array
;;;; or a slice; `(bytes "…")` is the bridge in the other direction from
;;;; `(string …)` and costs nothing either. The embedded newline is written as
;;;; an escape, and raylib's draw-text breaks the line on it.
(import rl "vendor:raylib")
(defconst screen-width 800)
(defconst screen-height 450)
(defconst message
"This sample illustrates a text writing\nanimation effect! Check it out! ;)")
(defvar frames-counter i32)
(defn main [] ()
(rl/init-window screen-width screen-height
"raylib [text] example - text writing anim")
(defer (rl/close-window))
(set frames-counter 0)
(rl/set-target-fps 60)
(until (rl/window-should-close?)
;; Update. Holding space runs the counter eight times faster; enter starts
;; it over.
(if (rl/key-down? :space)
(set frames-counter (+ frames-counter 8))
(set frames-counter (+ frames-counter 1)))
(when (rl/key-pressed? :enter) (set frames-counter 0))
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
;; One character every ten frames. The clamp is the whole difference from
;; the C — see the header comment.
(let [b (bytes message)
n (min (i32 (len b)) (/ frames-counter 10))]
(rl/draw-text (string (slice b 0 n)) 210 160 20 rl/maroon))
(rl/draw-text "PRESS [ENTER] to RESTART!" 240 260 20 rl/lightgray)
(rl/draw-text "HOLD [SPACE] to SPEED UP!" 239 300 20 rl/lightgray)
(rl/end-drawing)))

View File

@ -0,0 +1,197 @@
;;;; raylib [textures] example - fog of war
;;;;
;;;; examples/textures/textures_fog_of_war.c. Picked because it is the only
;;;; example in the whole upstream tree where a *texture filter* is the point
;;;; rather than a detail. The fog is drawn into a render texture one pixel
;;;; per tile — 25 by 15 — and then stretched to 800x450 on the way to the
;;;; screen, and the smooth edge between seen and unseen is entirely the
;;;; bilinear sampler doing the interpolation. With the default :point filter
;;;; the same program draws 32-pixel squares.
;;;;
;;;; That is what needed adding: `TextureFilter`, a new defenum in
;;;; vendor/raylib/raylib.flan, with set-texture-filter moved from the
;;;; generated half to the hand-written one and mapped in `bindings` so its
;;;; six members are checked against raylib.h. The header says `int filter`
;;;; and there is nothing in an `int` to say that 1 is the interesting value;
;;;; `:bilinear` says it.
;;;;
;;;; The three other things it exercises, none of which needed a line:
;;;;
;;;; - A render texture used as a *scratch surface* rather than as a
;;;; post-processing pass. core-scissor-test clipped the real framebuffer;
;;;; this draws a whole second frame into a 25x15 target and then samples
;;;; it. begin-texture-mode/end-texture-mode were already bound.
;;;; - draw-texture-pro with a NEGATIVE source height. A render texture's
;;;; rows come out of GL bottom-up, so every program that samples one has
;;;; to flip it, and raylib's convention for that is a source rectangle
;;;; with a negative height rather than a separate flag. Nothing in the
;;;; corpus had passed one. A Rectangle whose `height` field had been
;;;; permuted with `width` would draw the fog mirrored and the wrong way
;;;; up at once.
;;;; - clear-background with `blank` — alpha 0 — inside a texture mode, so
;;;; the untouched parts of the fog target are transparent and the map
;;;; shows through.
;;;;
;;;; The C's Map struct with its two calloc'd `unsigned char *` is two `[375
;;;; u8]` globals here. The dimensions are compile-time constants in the C
;;;; too (the #defines and the two assignments right after), so nothing is
;;;; lost by fixing them, and a global cannot hold a Vec in any case
;;;; (PORTING.md §3). 375 is 25*15, written out because Flan's array length
;;;; must be a literal.
(import rl "vendor:raylib")
(import d "digits.flan")
(defconst screen-width 800)
(defconst screen-height 450)
(defconst map-tile-size 32)
(defconst player-size 16)
;; How far the player sees, in tiles. The C's loop runs from -this to +this
;; exclusive, so the lit patch is 2*this tiles across and not 2*this+1 — an
;; asymmetry in the original that is kept rather than tidied, because tidying
;; it would change the picture.
(defconst player-tile-visibility 2)
(defconst tiles-x 25)
(defconst tiles-y 15)
;; 0 or 1, picked once: which of the two blues a tile is drawn in.
(defvar tile-ids [375 u8])
;; 0 = never seen (solid black), 1 = visible now (no fog), 2 = seen before
;; (mostly black). The three-way state is why this is a byte per tile and not
;; a bit.
(defvar tile-fog [375 u8])
(defvar player-position rl/Vector2)
(defvar player-tile-x i32)
(defvar player-tile-y i32)
(defvar fog-of-war rl/RenderTexture2D)
(defn main [] ()
(rl/init-window screen-width screen-height
"raylib [textures] example - fog of war")
(defer (rl/close-window))
(set tile-ids (array 375 u8))
(set tile-fog (array 375 u8))
(dotimes [i (* tiles-x tiles-y)]
(set (at tile-ids i) (u8 (rl/get-random-value 0 1))))
(set player-position (rl/Vector2 {.x 180.0 .y 130.0}))
(set player-tile-x 0)
(set player-tile-y 0)
;; One texel per tile. The stretch to full size on draw is where the
;; smoothing happens, which is why the target is this small on purpose.
(set fog-of-war (rl/load-render-texture tiles-x tiles-y))
(defer (rl/unload-render-texture fog-of-war))
(rl/set-texture-filter (.texture fog-of-war) :bilinear)
(rl/set-target-fps 60)
(until (rl/window-should-close?)
;; Update
(when (rl/key-down? :right) (set (.x player-position) (+ (.x player-position) 5.0)))
(when (rl/key-down? :left) (set (.x player-position) (- (.x player-position) 5.0)))
(when (rl/key-down? :down) (set (.y player-position) (+ (.y player-position) 5.0)))
(when (rl/key-down? :up) (set (.y player-position) (- (.y player-position) 5.0)))
;; Keep the player inside the tilemap. The far edge is measured against
;; the player's far side, which is why player-size is subtracted.
(let [max-x (f32 (- (* tiles-x map-tile-size) player-size))
max-y (f32 (- (* tiles-y map-tile-size) player-size))]
(set (.x player-position) (clamp (.x player-position) 0.0 max-x))
(set (.y player-position) (clamp (.y player-position) 0.0 max-y)))
;; Everything lit on the previous frame drops to "seen before". The lit
;; tiles are then set back to 1 below, so a tile still in view never
;; spends a frame dimmed.
(dotimes [i (* tiles-x tiles-y)]
(when (= (at tile-fog i) 1) (set (at tile-fog i) 2)))
;; Which tile the player's centre is standing on.
(set player-tile-x
(i32 (/ (+ (.x player-position) (f32 (/ map-tile-size 2)))
(f32 map-tile-size))))
(set player-tile-y
(i32 (/ (+ (.y player-position) (f32 (/ map-tile-size 2)))
(f32 map-tile-size))))
;; Light the square around the player, skipping anything off the map.
;; Without that test this reads and writes outside the array, which in the
;; C is undefined and here is a bounds trap — the same bug, reported.
(let [y (- player-tile-y player-tile-visibility)]
(while (< y (+ player-tile-y player-tile-visibility))
(let [x (- player-tile-x player-tile-visibility)]
(while (< x (+ player-tile-x player-tile-visibility))
(when (and (>= x 0) (< x tiles-x) (>= y 0) (< y tiles-y))
(set (at tile-fog (+ (* y tiles-x) x)) 1))
(set x (+ x 1))))
(set y (+ y 1))))
;; Draw the fog into its own little target first, at one pixel per tile.
;; blank is alpha 0, so a tile that is neither unseen nor remembered
;; leaves nothing behind and the map below shows through unmodified.
(rl/begin-texture-mode fog-of-war)
(rl/clear-background rl/blank)
(dotimes [y tiles-y]
(dotimes [x tiles-x]
;; A tile in view (1) draws nothing at all and stays transparent.
(let [f (at tile-fog (+ (* y tiles-x) x))]
(when (!= f 1)
(rl/draw-rectangle x y 1 1
(if (= f 0) rl/black (rl/fade rl/black 0.8)))))))
(rl/end-texture-mode)
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
;; The map itself, in full size.
(dotimes [y tiles-y]
(dotimes [x tiles-x]
(rl/draw-rectangle (* x map-tile-size) (* y map-tile-size)
map-tile-size map-tile-size
(if (= (at tile-ids (+ (* y tiles-x) x)) 0)
rl/blue
(rl/fade rl/blue 0.9)))
(rl/draw-rectangle-lines (* x map-tile-size) (* y map-tile-size)
map-tile-size map-tile-size
(rl/fade rl/darkblue 0.5))))
(rl/draw-rectangle-v player-position
(rl/Vector2 {.x (f32 player-size) .y (f32 player-size)})
rl/red)
;; The fog, stretched over the whole map. The negative source height is
;; the flip — see the header comment.
(rl/draw-texture-pro
(.texture fog-of-war)
(rl/Rectangle {.x 0.0 .y 0.0
.width (f32 (.width (.texture fog-of-war)))
.height (- 0.0 (f32 (.height (.texture fog-of-war))))})
(rl/Rectangle {.x 0.0 .y 0.0
.width (f32 (* tiles-x map-tile-size))
.height (f32 (* tiles-y map-tile-size))})
(rl/Vector2 {.x 0.0 .y 0.0})
0.0
rl/white)
;; The C's TextFormat("Current tile: [%i,%i]", ...). Each number is drawn
;; before the next is formatted, which examples/digits.flan requires:
;; both share one static buffer in the runtime.
(let [x 10]
(set x (+ x (d/draw-piece "Current tile: [" x 10 20 rl/raywhite)))
(set x (+ x (d/draw-int player-tile-x x 10 20 rl/raywhite)))
(set x (+ x (d/draw-piece "," x 10 20 rl/raywhite)))
(set x (+ x (d/draw-int player-tile-y x 10 20 rl/raywhite)))
(d/draw-piece "]" x 10 20 rl/raywhite))
(rl/draw-text "ARROW KEYS to move" 10 (- screen-height 25) 20 rl/raywhite)
(rl/end-drawing)))

View File

@ -0,0 +1,130 @@
;;;; raylib [textures] example - procedural images generation
;;;;
;;;; examples/textures/textures_image_generation.c. The strongest single pick
;;;; in the textures category and the reason is arithmetic: eight of the nine
;;;; textures come from a Gen* call that no program in this tree had ever
;;;; made, and none of the nine needs a file on disk. Every other textures
;;;; example loads a .png out of its resources directory; this one builds all
;;;; of its pixels.
;;;;
;;;; What it puts under load that nothing else does. An Image is the one
;;;; struct in raylib.flan that carries a pointer to memory raylib owns —
;;;; `data (Ptr u8)` — and it is returned by value, so every one of these nine
;;;; calls hands back a 24-byte aggregate through the shim's out-pointer with
;;;; a live heap block inside it. The corpus had crossed an Image before
;;;; (image-from-image has a headless acceptance case) but never nine in a row
;;;; and never on the load-then-upload-then-free path a real program uses:
;;;; gen, load-texture-from-image to get it onto the GPU, unload-image to give
;;;; the CPU copy back. Getting that order wrong is a leak rather than a
;;;; crash, which is exactly the sort of thing a ported example is for.
;;;;
;;;; Nothing needed adding. All nine generators and load-texture-from-image
;;;; came out of the generated half of the bindings; the only parameter that
;;;; looks like it wants an enum is gen-image-gradient-linear's `direction`,
;;;; which is an angle in degrees and not a flag, so an i32 is its true face.
;;;;
;;;; The C's `switch (currentTexture)` is a `cond` here. Flan has no switch
;;;; and the chain reads the same; the C's `default: break` is the `else`
;;;; branch (`:else`), unreachable because the index is taken modulo the count.
(import rl "vendor:raylib")
(defconst screen-width 800)
(defconst screen-height 450)
;; Nine and not eight: the linear gradient appears three times, at three
;; angles, because a vertical, a horizontal and a diagonal gradient are the
;; same generator with a different `direction`.
(defconst num-textures 9)
(defvar textures [9 rl/Texture2D])
(defvar current-texture i32)
;; Generate on the CPU, upload, drop the pixels. The texture holds a GL name
;; and nothing of the Image, so the CPU copy can go as soon as the upload is
;; done — which is why the C frees all nine in a block immediately after the
;; nine uploads and this does it one at a time. Holding all nine 800x450 RGBA
;; images at once would be 14 MB for no reason.
;;
;; A top-level defn and not a local closure: Flan's `fn` takes its types from
;; the position it is written in, so it needs a parameter of (Fn [T ...] R) to
;; land in, and a `let` binding is not one.
(defn upload [img rl/Image] rl/Texture2D
(let [t (rl/load-texture-from-image img)]
(rl/unload-image img)
t))
(defn main [] ()
(rl/init-window screen-width screen-height
"raylib [textures] example - procedural images generation")
(defer (rl/close-window))
(set textures (array num-textures rl/Texture2D))
;; 0 degrees runs the gradient top to bottom, 90 left to right, 45 corner
;; to corner.
(set (at textures 0)
(upload (rl/gen-image-gradient-linear screen-width screen-height 0
rl/red rl/blue)))
(set (at textures 1)
(upload (rl/gen-image-gradient-linear screen-width screen-height 90
rl/red rl/blue)))
(set (at textures 2)
(upload (rl/gen-image-gradient-linear screen-width screen-height 45
rl/red rl/blue)))
;; density 0 means the falloff reaches the edge of the image.
(set (at textures 3)
(upload (rl/gen-image-gradient-radial screen-width screen-height 0.0
rl/white rl/black)))
(set (at textures 4)
(upload (rl/gen-image-gradient-square screen-width screen-height 0.0
rl/white rl/black)))
;; 32 checks each way, so each square is 25 by 14 pixels.
(set (at textures 5)
(upload (rl/gen-image-checked screen-width screen-height 32 32
rl/red rl/blue)))
;; `factor` is the fraction of pixels that come out white.
(set (at textures 6)
(upload (rl/gen-image-white-noise screen-width screen-height 0.5)))
(set (at textures 7)
(upload (rl/gen-image-perlin-noise screen-width screen-height 50 50 4.0)))
;; `tile-size` is the cell spacing, in pixels.
(set (at textures 8)
(upload (rl/gen-image-cellular screen-width screen-height 32)))
(defer (dotimes [i num-textures] (rl/unload-texture (at textures i))))
(set current-texture 0)
(rl/set-target-fps 60)
(until (rl/window-should-close?)
;; Update
(when (or (rl/mouse-button-pressed? :left) (rl/key-pressed? :right))
(set current-texture (% (+ current-texture 1) num-textures)))
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
(rl/draw-texture (at textures current-texture) 0 0 rl/white)
(rl/draw-rectangle 30 400 325 30 (rl/fade rl/skyblue 0.5))
(rl/draw-rectangle-lines 30 400 325 30 (rl/fade rl/white 0.5))
(rl/draw-text "MOUSE LEFT BUTTON to CYCLE PROCEDURAL TEXTURES"
40 410 10 rl/white)
;; The C's switch. Each label is positioned so its right edge lands in the
;; same place, which is why the x differs per line.
(cond
(= current-texture 0) (rl/draw-text "VERTICAL GRADIENT" 560 10 20 rl/raywhite)
(= current-texture 1) (rl/draw-text "HORIZONTAL GRADIENT" 540 10 20 rl/raywhite)
(= current-texture 2) (rl/draw-text "DIAGONAL GRADIENT" 540 10 20 rl/raywhite)
(= current-texture 3) (rl/draw-text "RADIAL GRADIENT" 580 10 20 rl/lightgray)
(= current-texture 4) (rl/draw-text "SQUARE GRADIENT" 580 10 20 rl/lightgray)
(= current-texture 5) (rl/draw-text "CHECKED" 680 10 20 rl/raywhite)
(= current-texture 6) (rl/draw-text "WHITE NOISE" 640 10 20 rl/red)
(= current-texture 7) (rl/draw-text "PERLIN NOISE" 640 10 20 rl/red)
:else (rl/draw-text "CELLULAR" 670 10 20 rl/raywhite))
(rl/end-drawing)))

View File

@ -0,0 +1,255 @@
;;;; raylib [textures] example - mouse painting
;;;;
;;;; examples/textures/textures_mouse_painting.c. The one example in the
;;;; category that takes a picture back OFF the GPU and writes it to disk, and
;;;; that round trip is why it is here:
;;;;
;;;; load-image-from-texture → image-flip-vertical → export-image
;;;;
;;;; Nothing in the corpus had run it. image-from-image has a headless
;;;; acceptance case and sand.flan loads images the other way, but the path
;;;; that reads a render target's pixels back into CPU memory, corrects for
;;;; GL's bottom-up rows and encodes a PNG had never been exercised at all.
;;;; All three came out of the generated half of the bindings; nothing needed
;;;; adding for this file.
;;;;
;;;; The flip is the same fact as the negative source height in
;;;; examples/textures-fog-of-war.flan, met from the other side: on screen the
;;;; canvas is drawn with a negative-height source rectangle so GL's bottom-up
;;;; rows come out the right way up, and on save there is no source rectangle
;;;; to negate, so the pixels are turned over in memory instead. A program
;;;; that did one and not the other would show a correct picture and save an
;;;; upside-down one — which is exactly the bug the C's comment warns about.
;;;;
;;;; The other thing this puts under load is the render texture as *persistent
;;;; state*. The fog-of-war target is rebuilt from nothing every frame; this
;;;; one is the document — it is cleared once before the loop and every stroke
;;;; after that accumulates in it. Between the two the corpus has both ways a
;;;; render texture gets used.
;;;;
;;;; Two deviations from the C, both in the same place and both deliberate.
;;;; The C computes colorMouseHover with a `for` whose `else` clause resets it
;;;; on every miss and whose `break` leaves it set on a hit, so the value that
;;;; survives depends on the loop exiting early — it works, but only because
;;;; of the break. Here the reset is before the loop and the loop only ever
;;;; sets it, which is the same result said plainly. And "no colour hovered"
;;;; is -1 in both, tested with `>= 0` before it is used as an index.
(import rl "vendor:raylib")
(defconst screen-width 800)
(defconst screen-height 450)
(defconst max-colors-count 23)
;; The palette strip along the top. colors[0] is also the canvas's clear
;; colour and the "eraser", which is why raywhite is first and not simply
;; another entry.
(defvar colors [23 rl/Color])
(defvar colors-recs [23 rl/Rectangle])
(defvar color-selected i32)
(defvar color-selected-prev i32)
(defvar color-mouse-hover i32)
(defvar brush-size f32)
(defvar mouse-was-pressed bool)
(defvar btn-save-rec rl/Rectangle)
(defvar btn-save-mouse-hover bool)
(defvar show-save-message bool)
(defvar save-message-counter i32)
(defvar target rl/RenderTexture2D)
(defn main [] ()
(rl/init-window screen-width screen-height
"raylib [textures] example - mouse painting")
(defer (rl/close-window))
(set colors (array max-colors-count rl/Color))
(set (at colors 0) rl/raywhite)
(set (at colors 1) rl/yellow)
(set (at colors 2) rl/gold)
(set (at colors 3) rl/orange)
(set (at colors 4) rl/pink)
(set (at colors 5) rl/red)
(set (at colors 6) rl/maroon)
(set (at colors 7) rl/green)
(set (at colors 8) rl/lime)
(set (at colors 9) rl/darkgreen)
(set (at colors 10) rl/skyblue)
(set (at colors 11) rl/blue)
(set (at colors 12) rl/darkblue)
(set (at colors 13) rl/purple)
(set (at colors 14) rl/violet)
(set (at colors 15) rl/darkpurple)
(set (at colors 16) rl/beige)
(set (at colors 17) rl/brown)
(set (at colors 18) rl/darkbrown)
(set (at colors 19) rl/lightgray)
(set (at colors 20) rl/gray)
(set (at colors 21) rl/darkgray)
(set (at colors 22) rl/black)
;; 30 wide with a 2-pixel gap, starting 10 from the left.
(set colors-recs (array max-colors-count rl/Rectangle))
(dotimes [i max-colors-count]
(set (at colors-recs i)
(rl/Rectangle {.x (+ 10.0 (* 32.0 (f32 i)))
.y 10.0 .width 30.0 .height 30.0})))
(set color-selected 0)
(set color-selected-prev 0)
(set color-mouse-hover 0)
(set brush-size 20.0)
(set mouse-was-pressed false)
(set btn-save-rec (rl/Rectangle {.x 750.0 .y 10.0 .width 40.0 .height 30.0}))
(set btn-save-mouse-hover false)
(set show-save-message false)
(set save-message-counter 0)
;; The canvas. It is the document, not a scratch buffer: nothing clears it
;; again until the user presses C.
(set target (rl/load-render-texture screen-width screen-height))
(defer (rl/unload-render-texture target))
(rl/begin-texture-mode target)
(rl/clear-background (at colors 0))
(rl/end-texture-mode)
;; 120 and not 60: the stroke is a circle stamped at the mouse position once
;; per frame, so the gaps between stamps during a fast drag are a function
;; of the frame rate. The C's comment says as much.
(rl/set-target-fps 120)
(until (rl/window-should-close?)
;; Update
(let [mouse-pos (rl/get-mouse-position)]
(if (rl/key-pressed? :right)
(set color-selected (+ color-selected 1))
(when (rl/key-pressed? :left)
(set color-selected (- color-selected 1))))
(set color-selected (clamp color-selected 0 (- max-colors-count 1)))
;; Which swatch the pointer is over, or -1. See the header comment: the
;; reset is here rather than in an else branch inside the loop.
(set color-mouse-hover -1)
(dotimes [i max-colors-count]
(when (rl/collision-point-rec? mouse-pos (at colors-recs i))
(set color-mouse-hover i)
(break)))
(when (and (>= color-mouse-hover 0) (rl/mouse-button-pressed? :left))
(set color-selected color-mouse-hover)
(set color-selected-prev color-selected))
(set brush-size (clamp (+ brush-size (* (rl/get-mouse-wheel-move) 5.0))
2.0 50.0))
(when (rl/key-pressed? :c)
(rl/begin-texture-mode target)
(rl/clear-background (at colors 0))
(rl/end-texture-mode))
;; Paint. The gesture test is what makes the example work on a
;; touchscreen, where there is no mouse button to hold.
(when (or (rl/mouse-button-down? :left)
(= (rl/get-gesture-detected) :drag))
(rl/begin-texture-mode target)
;; Above y=50 is the palette strip, and a stroke there would paint
;; under the toolbar where it could never be seen.
(when (> (.y mouse-pos) 50.0)
(rl/draw-circle (i32 (.x mouse-pos)) (i32 (.y mouse-pos))
brush-size (at colors color-selected)))
(rl/end-texture-mode))
;; Right button erases, which is painting in the clear colour. The
;; selected swatch is parked while the button is held so the toolbar
;; shows what is being drawn, and restored on release.
(if (rl/mouse-button-down? :right)
(do
(when (not mouse-was-pressed)
(set color-selected-prev color-selected)
(set color-selected 0))
(set mouse-was-pressed true)
(rl/begin-texture-mode target)
(when (> (.y mouse-pos) 50.0)
(rl/draw-circle (i32 (.x mouse-pos)) (i32 (.y mouse-pos))
brush-size (at colors 0)))
(rl/end-texture-mode))
(when (and (rl/mouse-button-released? :right) mouse-was-pressed)
(set color-selected color-selected-prev)
(set mouse-was-pressed false)))
(set btn-save-mouse-hover (rl/collision-point-rec? mouse-pos btn-save-rec))
;; The round trip. See the header comment for why the flip is here and
;; not on the draw.
(when (or (and btn-save-mouse-hover (rl/mouse-button-released? :left))
(rl/key-pressed? :s))
(let [image (rl/load-image-from-texture (.texture target))]
(rl/image-flip-vertical (addr image))
(rl/export-image image "my_amazing_texture_painting.png")
(rl/unload-image image))
(set show-save-message true))
(when show-save-message
(set save-message-counter (+ save-message-counter 1))
(when (> save-message-counter 240)
(set show-save-message false)
(set save-message-counter 0)))
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
;; The canvas, flipped by the negative source height.
(rl/draw-texture-rec (.texture target)
(rl/Rectangle {.x 0.0 .y 0.0
.width (f32 (.width (.texture target)))
.height (- 0.0 (f32 (.height (.texture target))))})
(rl/Vector2 {.x 0.0 .y 0.0})
rl/white)
;; The brush preview, drawn on the screen and not into the canvas.
(when (> (.y mouse-pos) 50.0)
(if (rl/mouse-button-down? :right)
(rl/draw-circle-lines (i32 (.x mouse-pos)) (i32 (.y mouse-pos))
brush-size rl/gray)
(rl/draw-circle (rl/get-mouse-x) (rl/get-mouse-y)
brush-size (at colors color-selected))))
;; The toolbar, over the canvas.
(rl/draw-rectangle 0 0 (rl/get-screen-width) 50 rl/raywhite)
(rl/draw-line 0 50 (rl/get-screen-width) 50 rl/lightgray)
(dotimes [i max-colors-count]
(rl/draw-rectangle-rec (at colors-recs i) (at colors i)))
;; The first swatch is raywhite on raywhite, so it needs an outline to
;; be visible at all.
(rl/draw-rectangle-lines 10 10 30 30 rl/lightgray)
(when (>= color-mouse-hover 0)
(rl/draw-rectangle-rec (at colors-recs color-mouse-hover)
(rl/fade rl/white 0.6)))
(let [r (at colors-recs color-selected)]
(rl/draw-rectangle-lines-ex
(rl/Rectangle {.x (- (.x r) 2.0) .y (- (.y r) 2.0)
.width (+ (.width r) 4.0) .height (+ (.height r) 4.0)})
2.0 rl/black))
(rl/draw-rectangle-lines-ex btn-save-rec 2.0
(if btn-save-mouse-hover rl/red rl/black))
(rl/draw-text "SAVE!" 755 20 10
(if btn-save-mouse-hover rl/red rl/black))
(when show-save-message
(rl/draw-rectangle 0 0 (rl/get-screen-width) (rl/get-screen-height)
(rl/fade rl/raywhite 0.8))
(rl/draw-rectangle 0 150 (rl/get-screen-width) 80 rl/black)
(rl/draw-text "IMAGE SAVED: my_amazing_texture_painting.png"
150 180 20 rl/raywhite))
(rl/end-drawing))))

View File

@ -90,13 +90,16 @@ exclude IsWindowReady
# generated i32 would take. GetWorldToScreen goes with BeginMode3D
# because they read the same camera.
#
# What this line splits, and deliberately: draw-cube is here and draw-cube-v
# is generated, get-world-to-screen is here and get-world-to-screen-ex is
# generated, update-camera is here and update-camera-pro is generated. Every
# other family in raylib.flan — the texture draws, the circle draws — sits
# together, so the split is worth naming: what is hand-written is what the
# ported examples call, and hand-writing the variants as well would widen the
# half that has to be maintained by hand for nothing the examples ask for.
# What this line splits, and deliberately: get-world-to-screen is here and
# get-world-to-screen-ex is generated, update-camera is here and
# update-camera-pro is generated. (draw-cube-v was named here as the third
# such pair and no longer is — see the DrawCubeV line further down, which is
# what happens to a split of this kind when an example starts calling the
# other half.) Every other family in raylib.flan — the texture draws, the
# circle draws — sits together, so the split is worth naming: what is
# hand-written is what the ported examples call, and hand-writing the variants
# as well would widen the half that has to be maintained by hand for nothing
# the examples ask for.
#
# What is deliberately NOT here: the window-state family (IsWindowState,
# SetWindowState, ClearWindowState, ToggleFullscreen, Minimize/Maximize/
@ -115,6 +118,27 @@ exclude SetExitKey
exclude UpdateCamera
exclude GetWorldToScreen
# The second batch of ported examples widened both halves of that split, and
# each of these is here for one of the two reasons above and no new one.
#
# - SetMouseCursor and SetTextureFilter take an `int` in the header and one
# of a closed set of names in fact, so their Flan face is a defenum and
# not the C signature — the same trade SetExitKey makes.
# - DrawCubeV, DrawSphere, DrawSphereWires and DrawRay are inside a frame,
# in examples/models-box-collisions.flan and examples/core-3d-picking.flan.
# draw-cube-v moving here is the one place this batch *narrows* the split
# the paragraph above names: it was generated because nothing called it,
# and something calls it now.
# - GetScreenToWorldRay goes with GetWorldToScreen for the reason given
# there. They are inverses over the same camera.
exclude SetMouseCursor
exclude SetTextureFilter
exclude DrawCubeV
exclude DrawSphere
exclude DrawSphereWires
exclude DrawRay
exclude GetScreenToWorldRay
# ── What the package's constants are called in C ────────────────────
#
# enum <FlanEnum> <C_PREFIX> every member of that defenum
@ -146,6 +170,8 @@ enum CameraMode CAMERA_
enum GamepadButton GAMEPAD_BUTTON_
enum GamepadAxis GAMEPAD_AXIS_
enum Gesture GESTURE_
enum MouseCursor MOUSE_CURSOR_
enum TextureFilter TEXTURE_FILTER_
# raylib writes GESTURE_DOUBLETAP as one word where every other member of that
# enum is underscored. This is the narrow exception and not a general escape

View File

@ -60,6 +60,7 @@
(declare-c begin-blend-mode [mode i32] "BeginBlendMode")
(declare-c end-blend-mode [] "EndBlendMode")
(declare-c end-vr-stereo-mode [] "EndVrStereoMode")
(declare-c get-screen-to-world-ray-ex [position Vector2 camera Camera3D width i32 height i32] Ray "GetScreenToWorldRayEx")
(declare-c get-world-to-screen-ex [position Vector3 camera Camera3D width i32 height i32] Vector2 "GetWorldToScreenEx")
(declare-c swap-screen-buffer [] "SwapScreenBuffer")
(declare-c poll-input-events [] "PollInputEvents")
@ -106,7 +107,6 @@
(declare-c set-mouse-offset [offset-x i32 offset-y i32] "SetMouseOffset")
(declare-c set-mouse-scale [scale-x f32 scale-y f32] "SetMouseScale")
(declare-c get-mouse-wheel-move-v [] Vector2 "GetMouseWheelMoveV")
(declare-c set-mouse-cursor [cursor i32] "SetMouseCursor")
(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-bezier [start-pos Vector2 end-pos Vector2 thick f32 color Color] "DrawLineBezier")
@ -206,7 +206,6 @@
(declare-c update-texture [texture Texture2D pixels (Ptr u8)] "UpdateTexture")
(declare-c update-texture-rec [texture Texture2D rec Rectangle pixels (Ptr u8)] "UpdateTextureRec")
(declare-c gen-texture-mipmaps [texture (Ptr Texture2D)] "GenTextureMipmaps")
(declare-c set-texture-filter [texture Texture2D filter i32] "SetTextureFilter")
(declare-c set-texture-wrap [texture Texture2D wrap i32] "SetTextureWrap")
(declare-c color-is-equal [col-1 Color col-2 Color] bool "ColorIsEqual")
(declare-c color-to-int [color Color] i32 "ColorToInt")
@ -247,11 +246,8 @@
(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-cube-v [position Vector3 size Vector3 color Color] "DrawCubeV")
(declare-c draw-cube-wires-v [position Vector3 size Vector3 color Color] "DrawCubeWiresV")
(declare-c draw-sphere [center-pos Vector3 radius f32 color Color] "DrawSphere")
(declare-c draw-sphere-ex [center-pos Vector3 radius f32 rings i32 slices i32 color Color] "DrawSphereEx")
(declare-c draw-sphere-wires [center-pos Vector3 radius f32 rings i32 slices i32 color Color] "DrawSphereWires")
(declare-c draw-cylinder [position Vector3 radius-top f32 radius-bottom f32 height f32 slices i32 color Color] "DrawCylinder")
(declare-c draw-cylinder-ex [start-pos Vector3 end-pos Vector3 start-radius f32 end-radius f32 sides i32 color Color] "DrawCylinderEx")
(declare-c draw-cylinder-wires [position Vector3 radius-top f32 radius-bottom f32 height f32 slices i32 color Color] "DrawCylinderWires")
@ -259,10 +255,17 @@
(declare-c draw-capsule [start-pos Vector3 end-pos Vector3 radius f32 slices i32 rings i32 color Color] "DrawCapsule")
(declare-c draw-capsule-wires [start-pos Vector3 end-pos Vector3 radius f32 slices i32 rings i32 color Color] "DrawCapsuleWires")
(declare-c draw-plane [center-pos Vector3 size Vector2 color Color] "DrawPlane")
(declare-c draw-bounding-box [box BoundingBox color Color] "DrawBoundingBox")
(declare-c draw-billboard [camera Camera3D texture Texture2D position Vector3 scale f32 tint Color] "DrawBillboard")
(declare-c draw-billboard-rec [camera Camera3D texture Texture2D source Rectangle position Vector3 size Vector2 tint Color] "DrawBillboardRec")
(declare-c draw-billboard-pro [camera Camera3D texture Texture2D source Rectangle position Vector3 up Vector3 size Vector2 origin Vector2 rotation f32 tint Color] "DrawBillboardPro")
(declare-c check-collision-spheres [center-1 Vector3 radius-1 f32 center-2 Vector3 radius-2 f32] bool "CheckCollisionSpheres")
(declare-c check-collision-boxes [box-1 BoundingBox box-2 BoundingBox] bool "CheckCollisionBoxes")
(declare-c check-collision-box-sphere [box BoundingBox center Vector3 radius f32] bool "CheckCollisionBoxSphere")
(declare-c get-ray-collision-sphere [ray Ray center Vector3 radius f32] RayCollision "GetRayCollisionSphere")
(declare-c get-ray-collision-box [ray Ray box BoundingBox] RayCollision "GetRayCollisionBox")
(declare-c get-ray-collision-triangle [ray Ray p-1 Vector3 p-2 Vector3 p-3 Vector3] RayCollision "GetRayCollisionTriangle")
(declare-c get-ray-collision-quad [ray Ray p-1 Vector3 p-2 Vector3 p-3 Vector3 p-4 Vector3] RayCollision "GetRayCollisionQuad")
(declare-c load-wave-from-memory [file-type string file-data (Ptr u8) data-size i32] Wave "LoadWaveFromMemory")
(declare-c update-sound [sound Sound data (Ptr u8) sample-count i32] "UpdateSound")
(declare-c export-wave-as-code [wave Wave file-name string] bool "ExportWaveAsCode")

View File

@ -153,11 +153,31 @@
;; The cursor's visibility is window state and not input, but it is read and
;; written by the same code that reads the mouse, so it sits here.
;; hide-cursor only hides it; it does not lock it to the window, which is
;; what raylib's separate DisableCursor does and which nothing here needs.
;; what raylib's separate DisableCursor does. Those two — DisableCursor and
;; EnableCursor — are in the generated half rather than here: they take no
;; arguments at all, so there is no Flan face for a hand-written line to
;; improve, which is the same rule that leaves GetMouseX there.
;; examples/core-3d-picking.flan toggles them from the right mouse button.
(declare-c show-cursor [] "ShowCursor")
(declare-c hide-cursor [] "HideCursor")
(declare-c cursor-hidden? [] bool "IsCursorHidden")
;; The shape the pointer takes. raylib's SetMouseCursor says `int` and means
;; one of these eleven, so the Flan face is the enum for the same reason
;; set-exit-key takes a Key: the call is made every frame from a hover test,
;; and `(rl/set-mouse-cursor :ibeam)` is checked against the members where an
;; i32 would take any number at all — including the one off-by-one that picks
;; the arrow instead of the I-beam and looks like nothing at all went wrong.
;;
;; All eleven are here and not a subset, unlike Key: the enum is closed and
;; eleven members is the whole of it.
(defenum MouseCursor
[default 0 arrow 1 ibeam 2 crosshair 3 pointing-hand 4
resize-ew 5 resize-ns 6 resize-nwse 7 resize-nesw 8 resize-all 9
not-allowed 10])
(declare-c set-mouse-cursor [cursor MouseCursor] "SetMouseCursor")
;; ── Colours ─────────────────────────────────────────────────────────
;;
;; A Color is four bytes in RGBA order, so it is *not* the little-endian
@ -352,10 +372,57 @@
[position Vector3 width f32 height f32 length f32 color Color]
"DrawCubeWires")
(declare-c draw-cube-v [position Vector3 size Vector3 color Color] "DrawCubeV")
;; DrawSphere is DrawSphereEx with rings and slices fixed at 16; the wires
;; form takes them because the wireframe is the only place the tessellation is
;; visible. Both are here rather than generated for the reason above: they are
;; inside a frame, in an example that ships in examples/.
(declare-c draw-sphere
[center Vector3 radius f32 color Color]
"DrawSphere")
(declare-c draw-sphere-wires
[center Vector3 radius f32 rings i32 slices i32 color Color]
"DrawSphereWires")
;; `slices` squares each way from the origin, `spacing` world units apart, on
;; the XZ plane.
(declare-c draw-grid [slices i32 spacing f32] "DrawGrid")
;; ── Rays and boxes ──────────────────────────────────────────────────
;;
;; Three small aggregates, added together because the picking call produces
;; all three: a Ray goes in, a BoundingBox says what to test it against, and
;; a RayCollision comes back. Every field is read off raylib.h 5.5 and every
;; one of them is a Vector3 or a scalar — there is no owned memory anywhere
;; here, which is exactly what separates these three from Model and Mesh, and
;; why these could be described and those still cannot.
;;
;; What the layout check can and cannot say about them, on the same rule the
;; Camera3D note states: BoundingBox's two Vector3s are the same type, so a
;; permuted BoundingBox has the identical layout and nothing would catch it.
;; RayCollision is the opposite and is the one worth having checked — `hit` is
;; a C `bool`, one byte, and `distance` is a float, so the three padding bytes
;; between them are a real claim about the struct that a permutation breaks.
(defstruct BoundingBox [min Vector3 max Vector3])
(defstruct Ray [position Vector3 direction Vector3])
(defstruct RayCollision
[hit bool distance f32 point Vector3 normal Vector3])
;; The inverse of get-world-to-screen above, and hand-written beside it for
;; the reason stated there: the two read the same camera and the same window
;; dimensions, so they belong to the same half of the file. raylib 5.5 keeps
;; GetMouseRay as a #define onto this name; the define is not a symbol and
;; there is nothing to bind it to.
(declare-c get-screen-to-world-ray
[position Vector2 camera Camera3D] Ray
"GetScreenToWorldRay")
;; Per-frame and inside BeginMode3D, like the cube draws. raylib draws the
;; ray as a line a thousand units long, so it is a debugging aid and not
;; geometry with an end.
(declare-c draw-ray [ray Ray color Color] "DrawRay")
;; ── Shapes ──────────────────────────────────────────────────────────
;;
;; Rectangle intersection, which raylib computes from all four fields in
@ -459,6 +526,27 @@
(declare-c unload-texture [texture Texture2D] "UnloadTexture")
;; How a texture is sampled when it is drawn at anything other than its own
;; size. The header says `int` on SetTextureFilter and means one of these six.
;;
;; The value of the enum face here is not a typo caught at the call site so
;; much as a *readable* one: the fog-of-war example renders its fog into a
;; 25x15 render texture and scales it to 800x450, and the entire visual point
;; of the example is that `:bilinear` smooths the tile edges where the
;; default `:point` would show 32-pixel squares. A bare `1` in that call says
;; nothing; the member name says the whole thing.
;;
;; TEXTURE_FILTER_ANISOTROPIC_* are the mipmapped modes and need a texture
;; with mipmaps generated, which nothing here makes — they are listed because
;; the enum is closed, not because anything calls them.
(defenum TextureFilter
[point 0 bilinear 1 trilinear 2
anisotropic-4x 3 anisotropic-8x 4 anisotropic-16x 5])
(declare-c set-texture-filter
[texture Texture2D filter TextureFilter]
"SetTextureFilter")
(declare-c draw-texture
[texture Texture2D x i32 y i32 tint Color]
"DrawTexture")