flan/examples/core-world-screen.flan
Joseph Ferano 25f56c24a8 A begin/end pair that cannot come apart, and what it still cannot promise
vendor/raylib/modes.flan: five macros over the five pairs the package binds
-- with-drawing, with-mode-2d, with-mode-3d, with-texture-mode,
with-scissor-mode. A second file with no declare-c in it, split out on
vector.flan's reasoning: raylib.flan is the package's statement about C and
nothing here names C, so nothing here can be made wrong by raylib changing.

Each expands to (do (begin-... args) body... (end-...)) -- the calls the
author used to type, in the order they typed them. No let, no gensym: nothing
binds a name, so there is nothing for a caller's name to collide with.

What it removes is the End* that is missing, wrong, or no longer beside its
Begin*. What it cannot remove is a body leaving through the unwind path: a
return or an invoke-restart skips the rest of the do and the End* with it.
defer is the obvious fix and is refused inside a loop body, which is where a
pair always lives -- checked, not assumed. So sand.flan's discipline stays:
keep the restart boundary outside the pair.

35 call sites converted across examples/ and sand.flan. The one left is
core-scissor-test.flan, whose Begin and End sit in two separate `when`s with
the drawing between them -- a conditional pair is a shape a bracketing macro
cannot express.

test/programs/rl-with.flan covers with-scissor-mode, which no example can,
with a frame function unreachable from main so it needs no libraylib;
rl-with-reject.flan is the arity half.
2026-09-13 18:00:35 +07:00

112 lines
5.0 KiB
Plaintext

;;;; 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 a CameraProjection, so the keyword resolves against the
;; enum's members right here and a misspelt projection is a compile error
;; rather than a 0. raylib's field is `int` and the layout is unchanged —
;; an enum is four bytes either way.
(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 (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/with-drawing
(rl/clear-background rl/raywhite)
(rl/with-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))
;; 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)))))