The trio the author decided on 2026-09-20 is now all built: def is CL's defparameter — its initialiser runs on every daemon re-run, unguarded, so an edited initialiser repaints the same storage on C-c C-c plus re-run — defonce (Clojure's name for CL's defvar, per the author) initialises once behind the .init~once. flag, and defconst stays the image. One parse arm reads both forms; the difference is Ast.reinit, carried to Tast.global's grerun. Emit.startup_plan gives a def no guard flag, and Check.check_global lifts every def initialiser — zero and literal included — into global/<n>, so the host's startup reaches it through the function cell and a re-evaluated def swaps it (Session's def_inits; Emit.redefinition declares the cell for a non-sibling target). The old defvar spelling is refused with the rename and both compiling spellings, and every program, test, doc and editor list is swept — except sand.flan, the author's live WIP, whose seven defvar lines are flagged in FIX.org and keep its three dependent tests red on this branch.
138 lines
6.0 KiB
Plaintext
138 lines
6.0 KiB
Plaintext
;;;; 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 `defonce` of fixed arrays rather than a Vec:
|
|
;;;; a global cannot hold a Vec (docs/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 docs/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)
|
|
|
|
(defonce buildings [100 rl/Rectangle])
|
|
(defonce build-colors [100 rl/Color])
|
|
(defonce player rl/Rectangle)
|
|
(defonce 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? :key-right)
|
|
(set (.x player) (+ (.x player) 2.0))
|
|
(when (rl/key-down? :key-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? :key-a)
|
|
(set (.rotation camera) (- (.rotation camera) 1.0))
|
|
(when (rl/key-down? :key-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? :key-r)
|
|
(set (.zoom camera) 1.0)
|
|
(set (.rotation camera) 0.0))
|
|
|
|
;; Draw
|
|
(rl/with-drawing
|
|
(rl/clear-background rl/raywhite)
|
|
|
|
;; Everything inside with-mode-2d 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/with-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))
|
|
|
|
;; 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))))
|