flan/examples/models-box-collisions.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

130 lines
6.0 KiB
Plaintext

;;;; 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.
;;
;; It used to be eight expressions of field arithmetic here too; it is the
;; half-extent subtracted and added now that the package carries vector
;; arithmetic — see vendor/raylib/vector.flan, which is Flan and not C
;; because raymath is `static inline` and has no symbol to bind.
(defn box-around [centre rl/Vector3 size rl/Vector3] rl/BoundingBox
(let [half (rl/v3-scale size 0.5)]
(rl/BoundingBox {.min (rl/v3-sub centre half)
.max (rl/v3-add centre half)})))
(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/with-drawing
(rl/clear-background rl/raywhite)
(rl/with-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/draw-text "Move player with arrow keys to collide" 220 40 20 rl/gray)
(rl/draw-fps 10 10))))