flan/examples/models-box-collisions.fln

114 lines
5.5 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.fln 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.fln 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"
const screen-width = 800
const screen-height = 450
once camera: rl/Camera3D
once player-position: rl/Vector3
once player-size: rl/Vector3
once player-color: rl/Color
once enemy-box-pos: rl/Vector3
once enemy-box-size: rl/Vector3
once enemy-sphere-pos: rl/Vector3
const 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.fln, which is Flan and not C
;; because raymath is `static inline` and has no symbol to bind.
fn 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)}
fn 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 enum
;; exists precisely so that the 0 does not have to be remembered.
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 :projection-perspective}
player-position = rl/Vector3{.x 0.0 .y 1.0 .z 2.0}
player-size = rl/Vector3{.x 1.0 .y 2.0 .z 1.0}
player-color = rl/green
enemy-box-pos = rl/Vector3{.x -4.0 .y 1.0 .z 0.0}
enemy-box-size = rl/Vector3{.x 2.0 .y 2.0 .z 2.0}
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.
if rl/is-key-down(:key-right)
player-position.x += 0.2
elif rl/is-key-down(:key-left)
player-position.x -= 0.2
elif rl/is-key-down(:key-down)
player-position.z += 0.2
elif rl/is-key-down(:key-up)
player-position.z -= 0.2
let player-box = box-around(player-position, player-size)
let hit = rl/check-collision-boxes(player-box, box-around(enemy-box-pos, enemy-box-size)) or rl/check-collision-box-sphere(player-box, enemy-sphere-pos, enemy-sphere-size)
player-color = (if hit then rl/red else rl/green)
;; Draw
rl/with-drawing:
rl/clear-background(rl/raywhite)
rl/with-mode-3d(camera):
rl/draw-cube(enemy-box-pos, enemy-box-size.x, enemy-box-size.y,
enemy-box-size.z, rl/gray)
rl/draw-cube-wires(enemy-box-pos, enemy-box-size.x, enemy-box-size.y,
enemy-box-size.z, 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)