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

This commit is contained in:
Joseph Ferano 2026-09-13 13:46:47 +07:00
commit 03804a8045
8 changed files with 766 additions and 16 deletions

View File

@ -0,0 +1,142 @@
;;;; raylib [core] example - 2d camera
;;;;
;;;; examples/core/core_2d_camera.c. Needed no new binding at all, which is
;;;; the reason to port it: Camera2D, begin-mode-2d and end-mode-2d have been
;;;; in vendor/raylib/raylib.flan since the beginning and nothing in the
;;;; corpus had ever passed a whole Camera2D across the FFI on a frame's path.
;;;; Two Vector2s, a float and a float, by value, sixty times a second — the
;;;; acceptance table pins that layout with get-screen-to-world-2d, and this
;;;; is the same struct going into the call that actually draws with it.
;;;;
;;;; The hundred buildings are a `defvar` of fixed arrays rather than a Vec:
;;;; a global cannot hold a Vec (PORTING.md §3) and does not need to here,
;;;; because the count is a constant in the C too. `(array n T)` is the zeroed
;;;; fixed array, and the C's `= { 0 }` is exactly that.
;;;;
;;;; get-random-value comes from the generated half of the bindings. It is on
;;;; no frame's path — it runs once, before the loop — and its Flan face is
;;;; the C one, so PORTING.md §1's rule does not reach it.
;;;;
;;;; One deliberate difference from the C: `(- camera.rotation 1.0)` and the
;;;; clamp after it are written with the prelude's `clamp` macro rather than
;;;; two ifs. It is the same arithmetic; the C spells it out because C has no
;;;; clamp.
(import rl "vendor:raylib")
(defconst screen-width 800)
(defconst screen-height 450)
(defconst max-buildings 100)
(defvar buildings [100 rl/Rectangle])
(defvar build-colors [100 rl/Color])
(defvar player rl/Rectangle)
(defvar camera rl/Camera2D)
(defn main [] ()
(rl/init-window screen-width screen-height
"raylib [core] example - 2d camera")
(defer (rl/close-window))
(set player (rl/Rectangle {.x 400.0 .y 280.0 .width 40.0 .height 40.0}))
(set buildings (array max-buildings rl/Rectangle))
(set build-colors (array max-buildings rl/Color))
;; The skyline: each building is as wide as the last one left room for, so
;; the spacing accumulates and the row starts 6000 units to the left.
(let [spacing 0]
(dotimes [i max-buildings]
(let [w (f32 (rl/get-random-value 50 200))
h (f32 (rl/get-random-value 100 800))]
(set (at buildings i)
(rl/Rectangle {.x (+ -6000.0 (f32 spacing))
.y (- (- (f32 screen-height) 130.0) h)
.width w
.height h}))
(set spacing (+ spacing (i32 w)))
(set (at build-colors i)
(rl/Color {.r (u8 (rl/get-random-value 200 240))
.g (u8 (rl/get-random-value 200 240))
.b (u8 (rl/get-random-value 200 250))
.a 255})))))
;; A fresh Camera2D is all zeroes and a zoom of 0 makes the transform
;; singular, so the zoom is the one field that must be set — raylib.flan's
;; own comment on the struct says so.
(set camera (rl/Camera2D {.offset (rl/Vector2 {.x (/ (f32 screen-width) 2.0)
.y (/ (f32 screen-height) 2.0)})
.target (rl/Vector2 {.x (+ (.x player) 20.0)
.y (+ (.y player) 20.0)})
.rotation 0.0
.zoom 1.0}))
(rl/set-target-fps 60)
(until (rl/window-should-close?)
;; Update
;; The C's `if (RIGHT) ... else if (LEFT) ...`: both held cancels to the
;; right, which is the else-if and not an accident.
(if (rl/key-down? :right)
(set (.x player) (+ (.x player) 2.0))
(when (rl/key-down? :left) (set (.x player) (- (.x player) 2.0))))
;; The camera follows the player's centre.
(set (.target camera) (rl/Vector2 {.x (+ (.x player) 20.0)
.y (+ (.y player) 20.0)}))
(if (rl/key-down? :a)
(set (.rotation camera) (- (.rotation camera) 1.0))
(when (rl/key-down? :s)
(set (.rotation camera) (+ (.rotation camera) 1.0))))
(set (.rotation camera) (clamp (.rotation camera) -40.0 40.0))
(set (.zoom camera) (+ (.zoom camera) (* (rl/get-mouse-wheel-move) 0.05)))
(set (.zoom camera) (clamp (.zoom camera) 0.1 3.0))
(when (rl/key-pressed? :r)
(set (.zoom camera) 1.0)
(set (.rotation camera) 0.0))
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
;; Everything between these two is in world space and goes through the
;; camera. The ground and the crosshair lines are drawn far outside the
;; window on purpose: at zoom 0.1 they still have to reach the edges.
(rl/begin-mode-2d camera)
(rl/draw-rectangle -6000 320 13000 8000 rl/darkgray)
(dotimes [i max-buildings]
(rl/draw-rectangle-rec (at buildings i) (at build-colors i)))
(rl/draw-rectangle-rec player rl/red)
(rl/draw-line (i32 (.x (.target camera))) (* screen-height -10)
(i32 (.x (.target camera))) (* screen-height 10) rl/green)
(rl/draw-line (* screen-width -10) (i32 (.y (.target camera)))
(* screen-width 10) (i32 (.y (.target camera))) rl/green)
(rl/end-mode-2d)
;; And everything after it is in screen space again — the frame drawn
;; around the window is how the example shows the difference.
(rl/draw-text "SCREEN AREA" 640 10 20 rl/red)
(rl/draw-rectangle 0 0 screen-width 5 rl/red)
(rl/draw-rectangle 0 5 5 (- screen-height 10) rl/red)
(rl/draw-rectangle (- screen-width 5) 5 5 (- screen-height 10) rl/red)
(rl/draw-rectangle 0 (- screen-height 5) screen-width 5 rl/red)
(rl/draw-rectangle 10 10 250 113 (rl/fade rl/skyblue 0.5))
(rl/draw-rectangle-lines 10 10 250 113 rl/blue)
(rl/draw-text "Free 2d camera controls:" 20 20 10 rl/black)
(rl/draw-text "- Right/Left to move Offset" 40 40 10 rl/darkgray)
(rl/draw-text "- Mouse Wheel to Zoom in-out" 40 60 10 rl/darkgray)
(rl/draw-text "- A / S to Rotate" 40 80 10 rl/darkgray)
(rl/draw-text "- R to reset Zoom and Rotation" 40 100 10 rl/darkgray)
(rl/end-drawing)))

View File

@ -0,0 +1,72 @@
;;;; raylib [core] example - scissor test
;;;;
;;;; examples/core/core_scissor_test.c. Needed begin-scissor-mode and
;;;; end-scissor-mode, now hand-written in vendor/raylib/raylib.flan: they are
;;;; a begin/end pair inside a frame, which is the class PORTING.md §1 says
;;;; belongs in the hand-written half and not in the importer's. get-mouse-x
;;;; and get-mouse-y come from the generated half — their Flan face is the C
;;;; one, two plain i32s, and there is nothing for a hand-written line to say
;;;; about them that the header does not already say.
;;;;
;;;; The scissor rectangle is a Rectangle of floats in the C and stays one
;;;; here, even though every use of it casts back to i32, because the one call
;;;; that takes it whole — draw-rectangle-lines-ex — wants a Rectangle. That
;;;; is also why the mouse is read through get-mouse-x/get-mouse-y rather than
;;;; get-mouse-position: the C does the arithmetic in floats from integer
;;;; readings, and doing it the other way round would round differently on the
;;;; odd pixel.
;;;;
;;;; What this example is really a test of, and what no headless case could
;;;; be: raylib's scissor rectangle is in screen pixels with y growing DOWN,
;;;; and the GL call underneath measures from the bottom. raylib flips it. If
;;;; the binding or the flip were wrong the clip would appear mirrored about
;;;; the middle of the window — a perfectly plausible picture, in the wrong
;;;; place — so the only check is the outline drawn around it afterwards
;;;; landing on the revealed patch.
(import rl "vendor:raylib")
(defconst screen-width 800)
(defconst screen-height 450)
(defvar scissor rl/Rectangle)
(defvar scissor-mode bool)
(defn main [] ()
(rl/init-window screen-width screen-height
"raylib [core] example - scissor test")
(defer (rl/close-window))
(set scissor (rl/Rectangle {.x 0.0 .y 0.0 .width 300.0 .height 300.0}))
(set scissor-mode true)
(rl/set-target-fps 60)
(until (rl/window-should-close?)
;; Update
(when (rl/key-pressed? :s) (set scissor-mode (not scissor-mode)))
;; Centre the scissor area on the mouse.
(set (.x scissor) (- (f32 (rl/get-mouse-x)) (/ (.width scissor) 2.0)))
(set (.y scissor) (- (f32 (rl/get-mouse-y)) (/ (.height scissor) 2.0)))
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
(when scissor-mode
(rl/begin-scissor-mode (i32 (.x scissor)) (i32 (.y scissor))
(i32 (.width scissor)) (i32 (.height scissor))))
;; A full-screen rectangle and a line of text. Only the part inside the
;; scissor rectangle reaches the framebuffer, which is the whole example.
(rl/draw-rectangle 0 0 (rl/get-screen-width) (rl/get-screen-height) rl/red)
(rl/draw-text "Move the mouse around to reveal this text!" 190 200 20
rl/lightgray)
(when scissor-mode (rl/end-scissor-mode))
(rl/draw-rectangle-lines-ex scissor 1.0 rl/black)
(rl/draw-text "Press S to toggle scissor test" 10 10 20 rl/black)
(rl/end-drawing)))

View File

@ -0,0 +1,179 @@
;;;; raylib [core] example - window flags
;;;;
;;;; examples/core/core_window_flags.c. The reason to port this one is the
;;;; flags themselves: raylib.flan carried four of ConfigFlags' sixteen
;;;; members — the four sand.flan sets before the window exists — and this
;;;; example reads and writes eleven of them after it exists. All sixteen are
;;;; in raylib.flan now, read off raylib.h 5.5, in the header's order and not
;;;; in bit order (FLAG_VSYNC_HINT is 0x40 and FLAG_FULLSCREEN_MODE is 0x02,
;;;; which is the kind of thing nobody remembers correctly).
;;;;
;;;; They are `defconst u32`s and not a `defenum` for the reason the file
;;;; already gives about gestures: raylib wants the OR of several and a
;;;; keyword can only ever name one member.
;;;;
;;;; window-state?, set-window-state, clear-window-state, toggle-fullscreen,
;;;; minimize-window, maximize-window and restore-window all come from the
;;;; generated half. That is the line drawn in vendor/raylib/bindings and it
;;;; is worth saying why it falls here: the rule in PORTING.md §1 exists so
;;;; that a build with no header set can still draw, and generated.flan is
;;;; committed, so it can. What a hand-written line adds on top of that is a
;;;; Flan face the C signature does not have — a Key instead of an int, a
;;;; (Ptr Camera3D) instead of a raw pointer — and these seven have no such
;;;; face to add. They are u32 in and bool out in both languages.
;;;;
;;;; Not faithful in one place, deliberately: the C draws twelve lines of
;;;; on/off state through TextFormat and a two-branch if each time. Flan has
;;;; no TextFormat, so the pair is one helper here — which also removes the
;;;; C's own copy-paste bug, where the UNFOCUSED line says "[G]" when it is on
;;;; and "[U]" when it is off.
(import rl "vendor:raylib")
(import d "digits.flan")
(defconst screen-width 800)
(defconst screen-height 450)
;; Frames the window has been hidden or minimized for. H and N both put the
;; window somewhere it cannot be typed at, so the only way back is a count.
(defconst restore-after 240)
(defvar ball-pos rl/Vector2)
(defvar ball-speed rl/Vector2)
(defvar frames i32)
(defconst ball-radius f32 20.0)
;; One line of the state list: the label, then "on" or "off" in lime or
;; maroon, at the x the label ended. The C writes two whole DrawText calls per
;; flag with the text repeated; this says it once.
(defn draw-flag [label string flag u32 y i32] ()
(rl/draw-text label 10 y 10 rl/gray)
(let [x (+ 10 (rl/measure-text label 10))]
(if (rl/window-state? flag)
(rl/draw-text " on" x y 10 rl/lime)
(rl/draw-text " off" x y 10 rl/maroon))))
;; A flag the program owns: pressing the key turns it off if it is on and on
;; if it is off. Nine of the C's eleven key handlers are exactly this.
(defn toggle-flag [flag u32] ()
(if (rl/window-state? flag)
(rl/clear-window-state flag)
(rl/set-window-state flag)))
(defn main [] ()
;; The C leaves its SetConfigFlags call commented out, so that every flag
;; the list shows starts off. Kept that way: the point of the example is
;; which of them can be changed afterwards.
(rl/init-window screen-width screen-height
"raylib [core] example - window flags")
(defer (rl/close-window))
(set ball-pos (rl/Vector2 {.x (/ (f32 (rl/get-screen-width)) 2.0)
.y (/ (f32 (rl/get-screen-height)) 2.0)}))
(set ball-speed (rl/Vector2 {.x 5.0 .y 4.0}))
;; No set-target-fps, as in the C: with FLAG_VSYNC_HINT among the flags the
;; example toggles, a frame limiter on top of it would hide what V-Sync
;; does to the number draw-fps reports.
(until (rl/window-should-close?)
;; Update
(when (rl/key-pressed? :f) (rl/toggle-fullscreen))
(when (rl/key-pressed? :r) (toggle-flag rl/flag-window-resizable))
(when (rl/key-pressed? :d) (toggle-flag rl/flag-window-undecorated))
(when (rl/key-pressed? :u) (toggle-flag rl/flag-window-unfocused))
(when (rl/key-pressed? :t) (toggle-flag rl/flag-window-topmost))
(when (rl/key-pressed? :a) (toggle-flag rl/flag-window-always-run))
(when (rl/key-pressed? :v) (toggle-flag rl/flag-vsync-hint))
;; Hidden and minimized are not toggles: the window they hide is also the
;; window the key would have to be pressed in, so each comes back on a
;; timer instead.
(when (rl/key-pressed? :h)
(unless (rl/window-state? rl/flag-window-hidden)
(rl/set-window-state rl/flag-window-hidden))
(set frames 0))
(when (rl/window-state? rl/flag-window-hidden)
(set frames (+ frames 1))
(when (>= frames restore-after)
(rl/clear-window-state rl/flag-window-hidden)))
(when (rl/key-pressed? :n)
(unless (rl/window-state? rl/flag-window-minimized)
(rl/minimize-window))
(set frames 0))
(when (rl/window-state? rl/flag-window-minimized)
(set frames (+ frames 1))
(when (>= frames restore-after) (rl/restore-window)))
;; Maximize needs FLAG_WINDOW_RESIZABLE first, which is what R is for.
(when (rl/key-pressed? :m)
(if (rl/window-state? rl/flag-window-maximized)
(rl/restore-window)
(rl/maximize-window)))
;; The ball bounces off whatever the window is right now, so resizing,
;; maximizing and going fullscreen all show up in its path.
(set (.x ball-pos) (+ (.x ball-pos) (.x ball-speed)))
(set (.y ball-pos) (+ (.y ball-pos) (.y ball-speed)))
(when (or (>= (.x ball-pos) (- (f32 (rl/get-screen-width)) ball-radius))
(<= (.x ball-pos) ball-radius))
(set (.x ball-speed) (* (.x ball-speed) -1.0)))
(when (or (>= (.y ball-pos) (- (f32 (rl/get-screen-height)) ball-radius))
(<= (.y ball-pos) ball-radius))
(set (.y ball-speed) (* (.y ball-speed) -1.0)))
;; Draw
(rl/begin-drawing)
;; A transparent framebuffer wants a transparent clear; anything else
;; paints over the desktop the flag exists to show.
(if (rl/window-state? rl/flag-window-transparent)
(rl/clear-background rl/blank)
(rl/clear-background rl/raywhite))
(rl/draw-circle-v ball-pos ball-radius rl/maroon)
(rl/draw-rectangle-lines-ex
(rl/Rectangle {.x 0.0 .y 0.0
.width (f32 (rl/get-screen-width))
.height (f32 (rl/get-screen-height))})
4.0 rl/raywhite)
(rl/draw-circle-v (rl/get-mouse-position) 10.0 rl/darkblue)
(rl/draw-fps 10 10)
;; The C's TextFormat("Screen Size: [%i, %i]", ...), reassembled out of
;; literals and examples/digits.flan the way the other ports do it: `x`
;; walks along the line, and draw-int hands back the width it drew.
(let [x 10]
(rl/draw-text "Screen Size: [" x 40 10 rl/green)
(set x (+ x (rl/measure-text "Screen Size: [" 10)))
(set x (+ x (d/draw-int (rl/get-screen-width) x 40 10 rl/green)))
(rl/draw-text ", " x 40 10 rl/green)
(set x (+ x (rl/measure-text ", " 10)))
(set x (+ x (d/draw-int (rl/get-screen-height) x 40 10 rl/green)))
(rl/draw-text "]" x 40 10 rl/green))
(rl/draw-text "Following flags can be set after window creation:"
10 60 10 rl/gray)
(draw-flag "[F] FLAG_FULLSCREEN_MODE:" rl/flag-fullscreen-mode 80)
(draw-flag "[R] FLAG_WINDOW_RESIZABLE:" rl/flag-window-resizable 100)
(draw-flag "[D] FLAG_WINDOW_UNDECORATED:" rl/flag-window-undecorated 120)
(draw-flag "[H] FLAG_WINDOW_HIDDEN:" rl/flag-window-hidden 140)
(draw-flag "[N] FLAG_WINDOW_MINIMIZED:" rl/flag-window-minimized 160)
(draw-flag "[M] FLAG_WINDOW_MAXIMIZED:" rl/flag-window-maximized 180)
(draw-flag "[U] FLAG_WINDOW_UNFOCUSED:" rl/flag-window-unfocused 200)
(draw-flag "[T] FLAG_WINDOW_TOPMOST:" rl/flag-window-topmost 220)
(draw-flag "[A] FLAG_WINDOW_ALWAYS_RUN:" rl/flag-window-always-run 240)
(draw-flag "[V] FLAG_VSYNC_HINT:" rl/flag-vsync-hint 260)
(rl/draw-text "Following flags can only be set before window creation:"
10 300 10 rl/gray)
(draw-flag "FLAG_WINDOW_HIGHDPI:" rl/flag-window-highdpi 320)
(draw-flag "FLAG_WINDOW_TRANSPARENT:" rl/flag-window-transparent 340)
(draw-flag "FLAG_MSAA_4X_HINT:" rl/flag-msaa-4x-hint 360)
(rl/end-drawing)))

View File

@ -0,0 +1,68 @@
;;;; raylib [core] example - window should close
;;;;
;;;; examples/core/core_window_should_close.c. Needed set-exit-key, and one
;;;; enum member to go with it: raylib.flan's `Key` now has `null 0`, read off
;;;; KEY_NULL in raylib.h. The binding is hand-written rather than taken from
;;;; the generated half because the Flan face differs — raylib declares
;;;; `void SetExitKey(int key)` and the generated line therefore takes an i32,
;;;; where this one takes a Key, so `(rl/set-exit-key :null)` is checked
;;;; against the enum and `:nul` is a compile error instead of a 0.
;;;;
;;;; The whole example is about what window-should-close? means. It is not a
;;;; flag raylib latches: it is "the close button was clicked, or the exit key
;;;; is down", recomputed each frame, and it goes back to false on its own.
;;;; That is what lets this program ask for confirmation and then carry on —
;;;; and it is also why the loop is driven by a `defvar` of its own rather
;;;; than by the predicate, which is the one structural difference from every
;;;; other example in this directory.
;;;;
;;;; `(set-exit-key :null)` takes ESC away from raylib so the program can read
;;;; it itself. The window's X button still works and is still what
;;;; window-should-close? reports.
(import rl "vendor:raylib")
(defconst screen-width 800)
(defconst screen-height 450)
(defvar exit-requested bool)
(defvar exiting bool)
(defn main [] ()
(rl/init-window screen-width screen-height
"raylib [core] example - window should close")
(defer (rl/close-window))
;; No key closes the window any more. ESC is an ordinary key from here on.
(rl/set-exit-key :null)
(rl/set-target-fps 60)
(until exiting
;; Update. Either way of asking to leave raises the question; only Y and N
;; answer it. window-should-close? is false again on the next frame, so
;; the request has to be remembered in a variable.
(when (or (rl/window-should-close?) (rl/key-pressed? :escape))
(set exit-requested true))
;; The C's `if (Y) ... else if (N) ...`, which is an `if` with a `when`
;; in its else and not a `cond`: a `cond` needs an `:else` arm and there
;; is nothing to do when neither key was pressed.
(when exit-requested
(if (rl/key-pressed? :y)
(set exiting true)
(when (rl/key-pressed? :n) (set exit-requested false))))
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
(if exit-requested
(do
(rl/draw-rectangle 0 100 screen-width 200 rl/black)
(rl/draw-text "Are you sure you want to exit program? [Y/N]" 40 180 30
rl/white))
(rl/draw-text "Try to close the window to get confirmation message!"
120 200 20 rl/lightgray))
(rl/end-drawing)))

View File

@ -0,0 +1,114 @@
;;;; raylib [core] example - world to screen
;;;;
;;;; examples/core/core_world_screen.c, and the first thing in this directory
;;;; to draw in 3D at all. That is why it is here: before it, vendor/raylib
;;;; described no Vector3, no Camera3D and not one call that takes either, so
;;;; the importer refused every 3D function in raylib.h by name — "Vector3 is
;;;; a struct the package does not describe". Two defstructs later it refuses
;;;; none of them, and the generated half grew the cubes, spheres, cylinders
;;;; and billboards along with the eight lines this example needs.
;;;;
;;;; Hand-written in raylib.flan rather than generated, and why each:
;;;;
;;;; begin-mode-3d / end-mode-3d a begin/end pair inside a frame, the
;;;; class PORTING.md §1 names, and taken
;;;; together the way begin-mode-2d is
;;;; draw-cube / draw-cube-wires / draw-grid drawn every frame
;;;; update-camera the Flan face differs: (Ptr Camera3D),
;;;; because it mutates, and a CameraMode
;;;; rather than the header's int
;;;; get-world-to-screen it reads the same camera as the mode
;;;;
;;;; What is NOT pinned by anything, and cannot be: Camera3D's first three
;;;; fields are all Vector3, so every permutation of position, target and up
;;;; has the identical layout and no computed test can tell them apart. A
;;;; camera looking from the wrong place is a picture, not a number. The same
;;;; is true of Vector3's own x, y and z. Only the screen catches those.
;;;;
;;;; update-camera in :third-person mode also takes the mouse, which is why
;;;; the C calls DisableCursor: the cursor is locked to the window and its
;;;; movement becomes camera rotation rather than a pointer. disable-cursor
;;;; comes from the generated half — it is called once, before the loop.
;;;;
;;;; The C's two TextFormats go through examples/digits.flan, as in the other
;;;; ports, because Flan has no TextFormat and i64->bytes has no field width.
(import rl "vendor:raylib")
(import d "digits.flan")
(defconst screen-width 800)
(defconst screen-height 450)
(defvar camera rl/Camera3D)
(defvar cube rl/Vector3)
(defn main [] ()
(rl/init-window screen-width screen-height
"raylib [core] example - core world screen")
(defer (rl/close-window))
;; `projection` is an i32 and not a CameraProjection, because the header
;; says the field is `int` and the layout check holds this file to that.
;; rl/camera-projection is the conversion, and it still takes a keyword, so
;; a misspelt projection is a compile error rather than a 0.
(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 (rl/camera-projection :perspective)}))
(set cube (rl/Vector3 {.x 0.0 .y 0.0 .z 0.0}))
;; The mouse becomes the camera's, not a pointer's.
(rl/disable-cursor)
(rl/set-target-fps 60)
(until (rl/window-should-close?)
;; Update. The camera is passed by address because update-camera writes
;; to it — a global is an assignable place, so it has an address to take.
(rl/update-camera (addr camera) :third-person)
;; Where the point 2.5 units above the cube lands on the screen. This is
;; the whole example: the same transform raylib is about to draw with,
;; asked in advance, so a label can be put on a thing in the world.
(let [label-pos (rl/get-world-to-screen
(rl/Vector3 {.x (.x cube)
.y (+ (.y cube) 2.5)
.z (.z cube)})
camera)]
;; Draw
(rl/begin-drawing)
(rl/clear-background rl/raywhite)
(rl/begin-mode-3d camera)
(rl/draw-cube cube 2.0 2.0 2.0 rl/red)
(rl/draw-cube-wires cube 2.0 2.0 2.0 rl/maroon)
(rl/draw-grid 10 1.0)
(rl/end-mode-3d)
;; And the 2D half, drawn after end-mode-3d and therefore on top of the
;; cube whatever the depth buffer says — which is the second half of
;; what the example demonstrates.
(rl/draw-text "Enemy: 100 / 100"
(- (i32 (.x label-pos))
(/ (rl/measure-text "Enemy: 100/100" 20) 2))
(i32 (.y label-pos)) 20 rl/black)
(let [x 10]
(rl/draw-text "Cube position in screen space coordinates: [" x 10 20
rl/lime)
(set x (+ x (rl/measure-text
"Cube position in screen space coordinates: [" 20)))
(set x (+ x (d/draw-int (i32 (.x label-pos)) x 10 20 rl/lime)))
(rl/draw-text ", " x 10 20 rl/lime)
(set x (+ x (rl/measure-text ", " 20)))
(set x (+ x (d/draw-int (i32 (.y label-pos)) x 10 20 rl/lime)))
(rl/draw-text "]" x 10 20 rl/lime))
(rl/draw-text "Text 2d should be always on top of the cube" 10 40 20
rl/gray)
(rl/end-drawing))))

View File

@ -76,3 +76,41 @@ name IsAudioStreamProcessed audio-stream-processed?
exclude DrawTexturePro
exclude ImageFromImage
exclude IsWindowReady
# The scissor pair and the 3D surface the ported examples in examples/ draw
# through, excluded here for the same two reasons in different proportions.
#
# - BeginScissorMode/EndScissorMode, BeginMode3D/EndMode3D, DrawCube,
# DrawCubeWires, DrawGrid are inside a frame. The rule above is about the
# per-frame path, and a begin/end pair is taken together: hand-writing one
# half and generating the other is the asymmetry that reads as a mistake.
# - SetExitKey, UpdateCamera and GetWorldToScreen are excluded because their
# Flan face is not the C signature. SetExitKey takes a Key and not an int,
# UpdateCamera takes a CameraMode, and both of those refuse a typo the
# 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 is deliberately NOT here: the window-state family (IsWindowState,
# SetWindowState, ClearWindowState, ToggleFullscreen, Minimize/Maximize/
# RestoreWindow), GetMouseX/GetMouseY and GetRandomValue. Those are called
# per frame by the examples too, but their Flan face IS the C one — plain
# scalars, nothing an enum or a pointer improves — and generated.flan is
# committed, so the default build has them with no header set either way.
exclude BeginScissorMode
exclude EndScissorMode
exclude BeginMode3D
exclude EndMode3D
exclude DrawCube
exclude DrawCubeWires
exclude DrawGrid
exclude SetExitKey
exclude UpdateCamera
exclude GetWorldToScreen

View File

@ -56,13 +56,11 @@
(declare-c enable-cursor [] "EnableCursor")
(declare-c disable-cursor [] "DisableCursor")
(declare-c cursor-on-screen? [] bool "IsCursorOnScreen")
(declare-c end-mode-3d [] "EndMode3D")
(declare-c end-shader-mode [] "EndShaderMode")
(declare-c begin-blend-mode [mode i32] "BeginBlendMode")
(declare-c end-blend-mode [] "EndBlendMode")
(declare-c begin-scissor-mode [x i32 y i32 width i32 height i32] "BeginScissorMode")
(declare-c end-scissor-mode [] "EndScissorMode")
(declare-c end-vr-stereo-mode [] "EndVrStereoMode")
(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")
(declare-c wait-time [seconds f64] "WaitTime")
@ -98,7 +96,6 @@
(declare-c key-up? [key i32] bool "IsKeyUp")
(declare-c get-key-pressed [] i32 "GetKeyPressed")
(declare-c get-char-pressed [] i32 "GetCharPressed")
(declare-c set-exit-key [key i32] "SetExitKey")
(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")
@ -110,6 +107,7 @@
(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")
(declare-c draw-circle-sector [center Vector2 radius f32 start-angle f32 end-angle f32 segments i32 color Color] "DrawCircleSector")
@ -212,6 +210,7 @@
(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")
(declare-c color-to-hsv [color Color] Vector3 "ColorToHSV")
(declare-c color-from-hsv [hue f32 saturation f32 value f32] Color "ColorFromHSV")
(declare-c color-tint [color Color tint Color] Color "ColorTint")
(declare-c color-brightness [color Color factor f32] Color "ColorBrightness")
@ -243,7 +242,27 @@
(declare-c text-find-index [text string find string] i32 "TextFindIndex")
(declare-c text-to-integer [text string] i32 "TextToInteger")
(declare-c text-to-float [text string] f32 "TextToFloat")
(declare-c draw-grid [slices i32 spacing f32] "DrawGrid")
(declare-c draw-line-3d [start-pos Vector3 end-pos Vector3 color Color] "DrawLine3D")
(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-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")
(declare-c draw-cylinder-wires-ex [start-pos Vector3 end-pos Vector3 start-radius f32 end-radius f32 sides i32 color Color] "DrawCylinderWiresEx")
(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-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 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

@ -33,7 +33,13 @@
;; Layouts are C's — no object headers anywhere — so these are exactly
;; raylib's structs and nothing marshals.
;; Vector3 sits here rather than in the 3D section below because it is a
;; vector before it is a camera's business: nothing about it is 3D-only. Three
;; floats in x/y/z order, and — like the three Vector3 fields of Camera3D —
;; nothing in the acceptance table can tell a permutation of them apart,
;; because every permutation has the same layout.
(defstruct Vector2 [x f32 y f32])
(defstruct Vector3 [x f32 y f32 z f32])
(defstruct Color [r u8 g u8 b u8 a u8])
;; Texture2D is five 4-byte fields in a row, which is the layout most likely
@ -53,7 +59,10 @@
s 83 t 84 u 85 v 86 w 87 x 88 y 89 z 90
escape 256 enter 257 tab 258 backspace 259
right 262 left 263 down 264 up 265
left-shift 340])
left-shift 340
;; KEY_NULL is not a key. It is the value set-exit-key takes to mean "no
;; key closes the window", which is the only thing that ever passes it.
null 0])
(defenum MouseButton
[left 0 right 1 middle 2 side 3 extra 4 forward 5 back 6])
@ -79,21 +88,47 @@
;; the OR of several and a keyword can only ever name one member, so the
;; parameter is a u32 and the members are defconsts rather than a defenum.
;;
;; The part that is NOT obvious from the signature: this has to be called
;; BEFORE init-window. raylib stores the flags and reads them while creating
;; the context, so setting them afterwards is accepted, logged at INFO, and
;; does nothing to the window that already exists — which looks exactly like
;; a binding that did not work. Only the four the examples ask for are here;
;; the rest are one line each when something calls them.
(defconst flag-fullscreen-mode u32 2)
(defconst flag-window-resizable u32 4)
(defconst flag-msaa-4x-hint u32 32)
(defconst flag-vsync-hint u32 64)
;; The part that is NOT obvious from the signature: set-config-flags has to
;; be called BEFORE init-window. raylib stores the flags and reads them while
;; creating the context, so setting them afterwards is accepted, logged at
;; INFO, and does nothing to the window that already exists — which looks
;; exactly like a binding that did not work. Afterwards is what the generated
;; set-window-state, clear-window-state and window-state? are for, and they
;; take these same bits, which is why the four sand.flan sets grew into the
;; whole of ConfigFlags: a subset has its hole exactly where the next caller
;; looks. Every value below was read off raylib.h 5.5.
;;
;; The values are NOT in bit order in the header and are not reordered here:
;; FLAG_VSYNC_HINT is 0x40 and FLAG_FULLSCREEN_MODE is 0x02.
(defconst flag-vsync-hint u32 64)
(defconst flag-fullscreen-mode u32 2)
(defconst flag-window-resizable u32 4)
(defconst flag-window-undecorated u32 8)
(defconst flag-window-hidden u32 128)
(defconst flag-window-minimized u32 512)
(defconst flag-window-maximized u32 1024)
(defconst flag-window-unfocused u32 2048)
(defconst flag-window-topmost u32 4096)
(defconst flag-window-always-run u32 256)
(defconst flag-window-transparent u32 16)
(defconst flag-window-highdpi u32 8192)
(defconst flag-window-mouse-passthrough u32 16384)
(defconst flag-borderless-windowed-mode u32 32768)
(defconst flag-msaa-4x-hint u32 32)
(defconst flag-interlaced-hint u32 65536)
(declare-c set-config-flags [flags u32] "SetConfigFlags")
;; ── Input ───────────────────────────────────────────────────────────
;; Which key closes the window, ESCAPE by default. Hand-written rather than
;; left to the generated half because the Flan face is the difference: raylib
;; declares it `void SetExitKey(int key)` and the generated line therefore
;; takes an i32, where this one takes a Key and so accepts :escape and refuses
;; a typo. `:null` is how a program says "no key does that" and takes the
;; close over itself.
(declare-c set-exit-key [key Key] "SetExitKey")
(declare-c key-pressed? [key Key] bool "IsKeyPressed")
(declare-c key-down? [key Key] bool "IsKeyDown")
(declare-c key-released? [key Key] bool "IsKeyReleased")
@ -189,6 +224,20 @@
[x i32 y i32 width i32 height i32 color Color]
"DrawRectangle")
;; Scissor: everything drawn between these two is clipped to the rectangle,
;; in screen pixels with y down from the top — which is NOT the GL convention
;; underneath, and raylib flips it for you. It is drawing state like
;; begin-mode-2d is, so the pair is hand-written for the same reason: both
;; halves of a begin/end pair sit together, and both are on a frame's path.
;;
;; Nothing headless can assert this. A clip that is off by the window height
;; — the flip not applied — draws a perfectly plausible picture in the wrong
;; half of the screen, and only looking catches it.
(declare-c begin-scissor-mode
[x i32 y i32 width i32 height i32]
"BeginScissorMode")
(declare-c end-scissor-mode [] "EndScissorMode")
;; ── Shapes texture ──────────────────────────────────────────────────
;;
;; raylib draws every shape from one atlas texture, and this pair sets and
@ -243,6 +292,75 @@
[position Vector2 camera Camera2D] Vector2
"GetWorldToScreen2D")
;; ── Camera3D ────────────────────────────────────────────────────────
;;
;; raylib's `Camera` is a typedef for this, and every 3D call takes it by
;; value: position, the point it looks at, an up vector, a vertical
;; field-of-view in degrees, and which projection to build. Field order is
;; read off raylib.h 5.5 and is position/target/up/fovy/projection.
;;
;; What nothing here can pin, said the way the Texture2D note above says it:
;; the first three fields are the same type and the same size, so a permuted
;; Camera3D has the identical layout and every acceptance case that could
;; exist would pass. A camera that looks from the wrong place is a picture,
;; not a number. `projection` is `int` in the header and i32 here rather than
;; CameraProjection, because the layout is what a defstruct states and no
;; other field in this file is enum-typed; the enum below is for the value a
;; caller writes into it.
(defenum CameraProjection [perspective 0 orthographic 1])
;; A member's value, as the i32 the field above is. Writing the field's type
;; as CameraProjection instead was tried first and is refused by the check
;; that compares this file against raylib.h — "field projection is
;; CameraProjection in the defstruct and i32 (int)" — which is the right
;; answer even though the two have the same representation: a defstruct is a
;; statement about a C layout and the C says `int`. And a bare `:perspective`
;; cannot be written into an i32 field either, because a keyword only resolves
;; where an enum type is expected. So the conversion is said out loud, once,
;; here: (camera-projection :perspective) is 0 and a typo is still an error.
(defn camera-projection [p CameraProjection] i32 (i32 p))
;; UpdateCamera's mode. :custom means it does nothing and the program moves
;; the camera itself.
(defenum CameraMode
[custom 0 free 1 orbital 2 first-person 3 third-person 4])
(defstruct Camera3D
[position Vector3 target Vector3 up Vector3 fovy f32 projection i32])
;; The mode's built-in controls, applied to the camera in place — hence
;; (Ptr Camera3D), on the rule the Images section states: a call that mutates
;; takes a pointer at the Flan face. It reads the mouse and the keyboard
;; itself, which is what makes it a per-frame call and not a setup one.
(declare-c update-camera
[camera (Ptr Camera3D) mode CameraMode]
"UpdateCamera")
(declare-c begin-mode-3d [camera Camera3D] "BeginMode3D")
(declare-c end-mode-3d [] "EndMode3D")
;; Where a world point lands on the screen, for the current camera and the
;; current window size. Unlike get-world-to-screen-2d this one is not pure
;; arithmetic over its arguments — it reads the window's dimensions — so it
;; needs a window, and headless it answers about a 0x0 screen.
(declare-c get-world-to-screen
[position Vector3 camera Camera3D] Vector2
"GetWorldToScreen")
;; The three 3D draws the examples use. All of them are immediate-mode
;; geometry and all of them need a GL context, so like the shapes above they
;; are link-checked and nothing more.
(declare-c draw-cube
[position Vector3 width f32 height f32 length f32 color Color]
"DrawCube")
(declare-c draw-cube-wires
[position Vector3 width f32 height f32 length f32 color Color]
"DrawCubeWires")
;; `slices` squares each way from the origin, `spacing` world units apart, on
;; the XZ plane.
(declare-c draw-grid [slices i32 spacing f32] "DrawGrid")
;; ── Shapes ──────────────────────────────────────────────────────────
;;
;; Rectangle intersection, which raylib computes from all four fields in