Three kinds of Flan face over the generated set, which stays honest to C because that is what makes it checkable against the header. A slice where C takes a pointer and a count: the eleven vector-array drawing calls, all eleven rather than the three anybody calls, since a subset has its hole where the next caller looks. Each guards the empty slice, which is the part a hand-written call site gets wrong rather than merely writes out -- raylib takes a count of 0 happily, but taking the address of element 0 of an empty slice is out of bounds before raylib is reached. An Option where C signals with a sentinel: get-key-pressed and get-char-pressed, raylib's two input queues, both of which say "empty" with 0. What that buys is in text-input-box.flan, which read the queue in two places -- once to prime the loop, once at the bottom of the body -- and now reads it in one. Both of those use the `name` directive in bindings, so the generated declaration keeps the symbol and gives up the name: nothing about the C signature is hand-written and the generated half keeps its agreement-by-construction with the header. An enum where the header says int: key-up?, key-pressed-repeat?, mouse-button-up?. These are NOT wrappers -- a C enum parameter has an int's ABI, so the hand-written declare-c with the Flan type is the whole fix. They were holes in families whose other halves already took a Key, so (rl/key-down? :space) compiled and (rl/key-up? :space) did not. Not built: with-drawing and with-mode-2d. A macro cannot live in a package -- the expander collects defmacros from the prelude and from the file being compiled, and one in an imported package is refused by name. test/programs/pkg-macro.flan is that refusal. And vendor/raylib/vector.flan, which is raymath written in Flan because raymath is static inline and has no symbol to bind. A file of its own, split on declare-c and not on "idiomatic": there is not one declaration in it, so it is not part of the surface the header check reads, and raylib.flan is 1300 lines already. clamp and lerp are deliberately absent -- the prelude has both, and a second lerp would not even be the same function, since the prelude writes (1-t)a + tb where raymath writes a + t*(b - a). Examples: the identical eight-expression box-around helper in core-3d-picking and models-box-collisions is a half-extent subtracted and added. shapes-following-eyes keeps its measurement and gives up one line to v2-sub, which is the honest size of the gap in a file that is nothing but vector maths.
135 lines
6.0 KiB
Plaintext
135 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/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)))
|