The trio the author decided on 2026-09-20 is now all built: def is CL's defparameter — its initialiser runs on every daemon re-run, unguarded, so an edited initialiser repaints the same storage on C-c C-c plus re-run — defonce (Clojure's name for CL's defvar, per the author) initialises once behind the .init~once. flag, and defconst stays the image. One parse arm reads both forms; the difference is Ast.reinit, carried to Tast.global's grerun. Emit.startup_plan gives a def no guard flag, and Check.check_global lifts every def initialiser — zero and literal included — into global/<n>, so the host's startup reaches it through the function cell and a re-evaluated def swaps it (Session's def_inits; Emit.redefinition declares the cell for a non-sibling target). The old defvar spelling is refused with the rename and both compiling spellings, and every program, test, doc and editor list is swept — except sand.flan, the author's live WIP, whose seven defvar lines are flagged in FIX.org and keep its three dependent tests red on this branch.
154 lines
7.1 KiB
Plaintext
154 lines
7.1 KiB
Plaintext
;;;; raylib [core] example - 3d picking
|
|
;;;;
|
|
;;;; examples/core/core_3d_picking.c. The one `core` example in this batch,
|
|
;;;; and it is here for surface and not for category: it is the only example
|
|
;;;; anywhere upstream that uses Ray and RayCollision without also loading a
|
|
;;;; Model, so it is the only way those two structs get a caller at all.
|
|
;;;;
|
|
;;;; Picking is the round trip the corpus was missing. core-world-screen.flan
|
|
;;;; already runs get-world-to-screen — a point in the scene to a pixel. This
|
|
;;;; is the inverse and it is a different shape: a pixel does not name a
|
|
;;;; point, it names a *line* through the scene, which is what a Ray is, and
|
|
;;;; the answer to "what did I click on" is where that line first meets
|
|
;;;; something, which is what a RayCollision is. Two structs the package could
|
|
;;;; not describe, for one operation that cannot be written without them.
|
|
;;;;
|
|
;;;; What was added, all in vendor/raylib/raylib.flan:
|
|
;;;;
|
|
;;;; - `Ray` — two Vector3s, origin and direction.
|
|
;;;; - `RayCollision` — a C `bool`, a float and two Vector3s. This is the
|
|
;;;; one of the three new structs a layout check can actually fail:
|
|
;;;; `hit` is one byte and `distance` is four, so there are three padding
|
|
;;;; bytes between them that a permutation destroys. BoundingBox's two
|
|
;;;; Vector3s are interchangeable and nothing would catch swapping them.
|
|
;;;; - `BoundingBox` — shared with examples/models-box-collisions.flan.
|
|
;;;; - get-screen-to-world-ray and draw-ray, hand-written beside
|
|
;;;; get-world-to-screen and the cube draws for the reasons `bindings`
|
|
;;;; gives: the first reads the same camera as its inverse, and the second
|
|
;;;; is inside a frame.
|
|
;;;;
|
|
;;;; get-ray-collision-box came free out of the generated half the moment Ray
|
|
;;;; and BoundingBox existed, along with the sphere, triangle and quad forms.
|
|
;;;; get-ray-collision-mesh is still refused and will stay refused: Mesh owns
|
|
;;;; seven arrays and a GPU handle, and describing it is a different job.
|
|
;;;;
|
|
;;;; raylib 5.5 renamed GetMouseRay to GetScreenToWorldRay and left the old
|
|
;;;; name as a #define. A #define is not a symbol, so there is nothing to bind
|
|
;;;; it to and nothing lost by not trying.
|
|
|
|
(import rl "vendor:raylib")
|
|
|
|
(defconst screen-width 800)
|
|
(defconst screen-height 450)
|
|
|
|
(defonce camera rl/Camera3D)
|
|
|
|
(defonce cube-position rl/Vector3)
|
|
(defonce cube-size rl/Vector3)
|
|
|
|
;; The picking ray, kept between frames because draw-ray draws it every frame
|
|
;; whether or not it hit anything — that is how the example shows where the
|
|
;; click went.
|
|
(defonce ray rl/Ray)
|
|
(defonce collision rl/RayCollision)
|
|
|
|
;; The same centre/size to min/max conversion as in
|
|
;; examples/models-box-collisions.flan. Written out here rather than shared
|
|
;; because an example is a single file the reader can follow end to end.
|
|
;;
|
|
;; It used to be eight expressions of field arithmetic; it is the half-extent
|
|
;; subtracted and added, which is what the C means, now that the package
|
|
;; carries vector arithmetic — see vendor/raylib/vector.flan. That file
|
|
;; exists because raymath is `static inline` and has no symbol to bind, so
|
|
;; v3-sub and v3-add are Flan and not C.
|
|
(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 [core] example - 3d picking")
|
|
(defer (rl/close-window))
|
|
|
|
(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-position (rl/Vector3 {.x 0.0 .y 1.0 .z 0.0}))
|
|
(set cube-size (rl/Vector3 {.x 2.0 .y 2.0 .z 2.0}))
|
|
|
|
;; Both start zeroed, which is the C's `= { 0 }`. A zero Ray draws as a
|
|
;; degenerate line at the origin and a zero RayCollision has hit false, so
|
|
;; neither needs a "not yet" flag beside it.
|
|
(set ray (rl/Ray {.position (rl/Vector3 {.x 0.0 .y 0.0 .z 0.0})
|
|
.direction (rl/Vector3 {.x 0.0 .y 0.0 .z 0.0})}))
|
|
(set collision (rl/RayCollision {.hit false .distance 0.0
|
|
.point (rl/Vector3 {.x 0.0 .y 0.0 .z 0.0})
|
|
.normal (rl/Vector3 {.x 0.0 .y 0.0 .z 0.0})}))
|
|
|
|
(rl/set-target-fps 60)
|
|
|
|
(until (rl/window-should-close?)
|
|
;; Update. The first-person controls only run while the cursor is
|
|
;; captured, which is what the right button toggles — otherwise the mouse
|
|
;; could not be used to aim at anything.
|
|
(when (rl/cursor-hidden?) (rl/update-camera (addr camera) :first-person))
|
|
|
|
(when (rl/mouse-button-pressed? :mouse-right)
|
|
(if (rl/cursor-hidden?) (rl/enable-cursor) (rl/disable-cursor)))
|
|
|
|
(when (rl/mouse-button-pressed? :mouse-left)
|
|
(if (not (.hit collision))
|
|
;; The pixel under the pointer, as a line through the scene, tested
|
|
;; against the cube's bounds.
|
|
(do
|
|
(set ray (rl/get-screen-to-world-ray (rl/get-mouse-position) camera))
|
|
(set collision
|
|
(rl/get-ray-collision-box ray (box-around cube-position cube-size))))
|
|
;; A second click deselects. Only `hit` is cleared; the ray stays put
|
|
;; and keeps being drawn, as in the C.
|
|
(set (.hit collision) false)))
|
|
|
|
;; Draw
|
|
(rl/with-drawing
|
|
(rl/clear-background rl/raywhite)
|
|
|
|
(rl/with-mode-3d camera
|
|
(if (.hit collision)
|
|
(do
|
|
(rl/draw-cube cube-position (.x cube-size) (.y cube-size) (.z cube-size)
|
|
rl/red)
|
|
(rl/draw-cube-wires cube-position (.x cube-size) (.y cube-size)
|
|
(.z cube-size) rl/maroon)
|
|
;; A slightly larger wireframe around the selection, which is the
|
|
;; whole visual feedback of the example.
|
|
(rl/draw-cube-wires cube-position (+ (.x cube-size) 0.2)
|
|
(+ (.y cube-size) 0.2) (+ (.z cube-size) 0.2)
|
|
rl/green))
|
|
(do
|
|
(rl/draw-cube cube-position (.x cube-size) (.y cube-size) (.z cube-size)
|
|
rl/gray)
|
|
(rl/draw-cube-wires cube-position (.x cube-size) (.y cube-size)
|
|
(.z cube-size) rl/darkgray)))
|
|
|
|
;; raylib draws the ray a thousand units long, so it reads as a line
|
|
;; crossing the scene rather than as a segment with an end.
|
|
(rl/draw-ray ray rl/maroon)
|
|
(rl/draw-grid 10 1.0))
|
|
|
|
(rl/draw-text "Try clicking on the box with your mouse!" 240 10 20
|
|
rl/darkgray)
|
|
|
|
(when (.hit collision)
|
|
(rl/draw-text "BOX SELECTED"
|
|
(/ (- screen-width (rl/measure-text "BOX SELECTED" 30)) 2)
|
|
(i32 (* (f32 screen-height) 0.1)) 30 rl/green))
|
|
|
|
(rl/draw-text "Right click mouse to toggle camera controls" 10 430 10
|
|
rl/gray)
|
|
|
|
(rl/draw-fps 10 10))))
|