;;;; 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) (defvar camera rl/Camera3D) (defvar cube-position rl/Vector3) (defvar 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. (defvar ray rl/Ray) (defvar 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? :right) (if (rl/cursor-hidden?) (rl/enable-cursor) (rl/disable-cursor))) (when (rl/mouse-button-pressed? :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/begin-drawing) (rl/clear-background rl/raywhite) (rl/begin-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/end-mode-3d) (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) (rl/end-drawing)))