From d7ceec448eba0acf2e48b3147edb41ba44afc060 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sun, 13 Sep 2026 14:42:49 +0700 Subject: [PATCH] Ten more raylib examples, and the three small structs 3D needed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three shapes, two text, three textures, one models and one core, picked for binding surface rather than for how they look. shapes-basic-shapes brings in six draw families nothing had called — the circle and rectangle gradients, the triangles, all three poly draws — and is the first call in the corpus to pass two Colors or three Vector2s at once. shapes-collision-area is get-collision-rec, the only binding that takes two Rectangles and answers a third, on a frame path. shapes-following-eyes is the raymath gap measured rather than worked around: every line of it is vector arithmetic written without a vector library, the way the C writes it. text-input-box drains get-char-pressed's queue, which no example had read, and needed a MouseCursor defenum for set-mouse-cursor. text-writing-anim replaces TextSubtext — unbindable, it answers a pointer into a rotating static buffer — with (string (slice b 0 n)), which is the same operation without the shared state. textures-image-generation runs nine Gen* calls and the gen/upload/unload-image path, all procedural, no file on disk. textures-fog-of-war needed a TextureFilter defenum: the smooth fog edge is entirely :bilinear on a 25x15 render texture, and it is also the first draw-texture-pro with a negative source height. textures-mouse-painting is the same render texture used as a document rather than as scratch, plus the round trip back off the GPU — load-image-from-texture, image-flip-vertical, export-image — which nothing had run. models-box-collisions is the counterexample to "a models example is a binding exercise": nothing in it is a Model, and one BoundingBox defstruct un-refuses four functions. core-3d-picking is the only caller anywhere for Ray and RayCollision, and picking is the inverse of the get-world-to-screen the corpus already had. Added to vendor/raylib: defstructs BoundingBox, Ray and RayCollision; defenums MouseCursor and TextureFilter with their mapping lines in bindings; hand-written declare-c for SetMouseCursor, SetTextureFilter, DrawCubeV, DrawSphere, DrawSphereWires, DrawRay and GetScreenToWorldRay, each excluded from the generated half on the rule bindings already states. generated.flan regenerated against raylib 5.5: 272 declarations, 117 refused, every defstruct, hand-written declare-c and mapped constant agreeing with the header. --- examples/core-3d-picking.flan | 157 +++++++++++++++ examples/models-box-collisions.flan | 133 ++++++++++++ examples/shapes-basic-shapes.flan | 102 ++++++++++ examples/shapes-collision-area.flan | 129 ++++++++++++ examples/shapes-following-eyes.flan | 103 ++++++++++ examples/text-input-box.flan | 140 +++++++++++++ examples/text-writing-anim.flan | 76 +++++++ examples/textures-fog-of-war.flan | 197 ++++++++++++++++++ examples/textures-image-generation.flan | 130 ++++++++++++ examples/textures-mouse-painting.flan | 255 ++++++++++++++++++++++++ vendor/raylib/bindings | 23 +++ vendor/raylib/generated.flan | 13 +- vendor/raylib/raylib.flan | 84 ++++++++ 13 files changed, 1537 insertions(+), 5 deletions(-) create mode 100644 examples/core-3d-picking.flan create mode 100644 examples/models-box-collisions.flan create mode 100644 examples/shapes-basic-shapes.flan create mode 100644 examples/shapes-collision-area.flan create mode 100644 examples/shapes-following-eyes.flan create mode 100644 examples/text-input-box.flan create mode 100644 examples/text-writing-anim.flan create mode 100644 examples/textures-fog-of-war.flan create mode 100644 examples/textures-image-generation.flan create mode 100644 examples/textures-mouse-painting.flan diff --git a/examples/core-3d-picking.flan b/examples/core-3d-picking.flan new file mode 100644 index 0000000..d96d561 --- /dev/null +++ b/examples/core-3d-picking.flan @@ -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))) diff --git a/examples/models-box-collisions.flan b/examples/models-box-collisions.flan new file mode 100644 index 0000000..74ab36f --- /dev/null +++ b/examples/models-box-collisions.flan @@ -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))) diff --git a/examples/shapes-basic-shapes.flan b/examples/shapes-basic-shapes.flan new file mode 100644 index 0000000..1f6c667 --- /dev/null +++ b/examples/shapes-basic-shapes.flan @@ -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))) diff --git a/examples/shapes-collision-area.flan b/examples/shapes-collision-area.flan new file mode 100644 index 0000000..4fdf73d --- /dev/null +++ b/examples/shapes-collision-area.flan @@ -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))) diff --git a/examples/shapes-following-eyes.flan b/examples/shapes-following-eyes.flan new file mode 100644 index 0000000..d30c9d6 --- /dev/null +++ b/examples/shapes-following-eyes.flan @@ -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))) diff --git a/examples/text-input-box.flan b/examples/text-input-box.flan new file mode 100644 index 0000000..8a4cb91 --- /dev/null +++ b/examples/text-input-box.flan @@ -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))) diff --git a/examples/text-writing-anim.flan b/examples/text-writing-anim.flan new file mode 100644 index 0000000..3d67c54 --- /dev/null +++ b/examples/text-writing-anim.flan @@ -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))) diff --git a/examples/textures-fog-of-war.flan b/examples/textures-fog-of-war.flan new file mode 100644 index 0000000..d17879f --- /dev/null +++ b/examples/textures-fog-of-war.flan @@ -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))) diff --git a/examples/textures-image-generation.flan b/examples/textures-image-generation.flan new file mode 100644 index 0000000..af9616e --- /dev/null +++ b/examples/textures-image-generation.flan @@ -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))) diff --git a/examples/textures-mouse-painting.flan b/examples/textures-mouse-painting.flan new file mode 100644 index 0000000..03cb3b2 --- /dev/null +++ b/examples/textures-mouse-painting.flan @@ -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)))) diff --git a/vendor/raylib/bindings b/vendor/raylib/bindings index 8d01bb6..39dd78f 100644 --- a/vendor/raylib/bindings +++ b/vendor/raylib/bindings @@ -115,6 +115,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 every member of that defenum @@ -146,6 +167,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 diff --git a/vendor/raylib/generated.flan b/vendor/raylib/generated.flan index e68a5e5..35a7cbc 100644 --- a/vendor/raylib/generated.flan +++ b/vendor/raylib/generated.flan @@ -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") diff --git a/vendor/raylib/raylib.flan b/vendor/raylib/raylib.flan index 47eae98..4d69d6b 100644 --- a/vendor/raylib/raylib.flan +++ b/vendor/raylib/raylib.flan @@ -158,6 +158,22 @@ (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 +368,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 +522,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")