;;;; 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)))