From 1df6f7a642692b8a2d3aaeae81295dac67b06160 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sun, 13 Sep 2026 12:49:28 +0700 Subject: [PATCH] Five more core examples, and the 3D half of the bindings they wanted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit core-2d-camera, core-scissor-test, core-window-flags, core-world-screen and core-window-should-close, ported from raylib 5.5's examples/core. What each one asked of vendor/raylib: 2d-camera nothing. A whole Camera2D by value, per frame, into the call that actually draws with it — the layout the acceptance table pins through arithmetic, now going through the path it was bound for. scissor-test begin-scissor-mode / end-scissor-mode, hand-written. window-flags twelve more ConfigFlags constants; raylib.flan carried the four sand.flan sets and this reads eleven. world-screen the 3D surface did not exist: no Vector3, no Camera3D, and the importer refused every 3D function in raylib.h by name for want of them. Two defstructs, two defenums, begin/end-mode-3d, update-camera, get-world-to-screen, draw-cube, draw-cube-wires, draw-grid — and 24 more 3D lines the importer can generate now that the types are described. window-should-close set-exit-key, and Key/null to pass it. The hand-written/generated line, written down in vendor/raylib/bindings: a drawing pair inside a frame is hand-written, and so is anything whose Flan face is not the C signature — set-exit-key takes a Key, update-camera takes a (Ptr Camera3D) and a CameraMode. The window-state family and get-mouse-x/y stay generated: plain scalars in and bool out, with nothing for a hand-written line to add. Camera3D's projection field stays i32, because the header says int and the layout check holds this file to that; rl/camera-projection is the conversion, and still takes a keyword. --- examples/core-2d-camera.flan | 142 ++++++++++++++++++++ examples/core-scissor-test.flan | 72 ++++++++++ examples/core-window-flags.flan | 179 +++++++++++++++++++++++++ examples/core-window-should-close.flan | 68 ++++++++++ examples/core-world-screen.flan | 114 ++++++++++++++++ vendor/raylib/bindings | 38 ++++++ vendor/raylib/generated.flan | 29 +++- vendor/raylib/raylib.flan | 140 +++++++++++++++++-- 8 files changed, 766 insertions(+), 16 deletions(-) create mode 100644 examples/core-2d-camera.flan create mode 100644 examples/core-scissor-test.flan create mode 100644 examples/core-window-flags.flan create mode 100644 examples/core-window-should-close.flan create mode 100644 examples/core-world-screen.flan diff --git a/examples/core-2d-camera.flan b/examples/core-2d-camera.flan new file mode 100644 index 0000000..4ba9217 --- /dev/null +++ b/examples/core-2d-camera.flan @@ -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))) diff --git a/examples/core-scissor-test.flan b/examples/core-scissor-test.flan new file mode 100644 index 0000000..f0672e4 --- /dev/null +++ b/examples/core-scissor-test.flan @@ -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))) diff --git a/examples/core-window-flags.flan b/examples/core-window-flags.flan new file mode 100644 index 0000000..4bedaaa --- /dev/null +++ b/examples/core-window-flags.flan @@ -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))) diff --git a/examples/core-window-should-close.flan b/examples/core-window-should-close.flan new file mode 100644 index 0000000..946b427 --- /dev/null +++ b/examples/core-window-should-close.flan @@ -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))) diff --git a/examples/core-world-screen.flan b/examples/core-world-screen.flan new file mode 100644 index 0000000..6c30f40 --- /dev/null +++ b/examples/core-world-screen.flan @@ -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)))) diff --git a/vendor/raylib/bindings b/vendor/raylib/bindings index b933b12..2fad847 100644 --- a/vendor/raylib/bindings +++ b/vendor/raylib/bindings @@ -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 diff --git a/vendor/raylib/generated.flan b/vendor/raylib/generated.flan index 760027f..e68a5e5 100644 --- a/vendor/raylib/generated.flan +++ b/vendor/raylib/generated.flan @@ -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") diff --git a/vendor/raylib/raylib.flan b/vendor/raylib/raylib.flan index c7df385..e07d2a1 100644 --- a/vendor/raylib/raylib.flan +++ b/vendor/raylib/raylib.flan @@ -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