Three shapes, two text, three textures, one models and one core, picked for binding surface rather than for how they look. shapes-basic-shapes brings in six draw families nothing had called — the circle and rectangle gradients, the triangles, all three poly draws — and is the first call in the corpus to pass two Colors or three Vector2s at once. shapes-collision-area is get-collision-rec, the only binding that takes two Rectangles and answers a third, on a frame path. shapes-following-eyes is the raymath gap measured rather than worked around: every line of it is vector arithmetic written without a vector library, the way the C writes it. text-input-box drains get-char-pressed's queue, which no example had read, and needed a MouseCursor defenum for set-mouse-cursor. text-writing-anim replaces TextSubtext — unbindable, it answers a pointer into a rotating static buffer — with (string (slice b 0 n)), which is the same operation without the shared state. textures-image-generation runs nine Gen* calls and the gen/upload/unload-image path, all procedural, no file on disk. textures-fog-of-war needed a TextureFilter defenum: the smooth fog edge is entirely :bilinear on a 25x15 render texture, and it is also the first draw-texture-pro with a negative source height. textures-mouse-painting is the same render texture used as a document rather than as scratch, plus the round trip back off the GPU — load-image-from-texture, image-flip-vertical, export-image — which nothing had run. models-box-collisions is the counterexample to "a models example is a binding exercise": nothing in it is a Model, and one BoundingBox defstruct un-refuses four functions. core-3d-picking is the only caller anywhere for Ray and RayCollision, and picking is the inverse of the get-world-to-screen the corpus already had. Added to vendor/raylib: defstructs BoundingBox, Ray and RayCollision; defenums MouseCursor and TextureFilter with their mapping lines in bindings; hand-written declare-c for SetMouseCursor, SetTextureFilter, DrawCubeV, DrawSphere, DrawSphereWires, DrawRay and GetScreenToWorldRay, each excluded from the generated half on the rule bindings already states. generated.flan regenerated against raylib 5.5: 272 declarations, 117 refused, every defstruct, hand-written declare-c and mapped constant agreeing with the header.
158 lines
7.0 KiB
Plaintext
158 lines
7.0 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)
|
|
|
|
(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, and
|
|
;; a two-file port for eight expressions would cost more than it saves.
|
|
(defn box-around [centre rl/Vector3 size rl/Vector3] rl/BoundingBox
|
|
(rl/BoundingBox
|
|
{.min (rl/Vector3 {.x (- (.x centre) (/ (.x size) 2.0))
|
|
.y (- (.y centre) (/ (.y size) 2.0))
|
|
.z (- (.z centre) (/ (.z size) 2.0))})
|
|
.max (rl/Vector3 {.x (+ (.x centre) (/ (.x size) 2.0))
|
|
.y (+ (.y centre) (/ (.y size) 2.0))
|
|
.z (+ (.z centre) (/ (.z size) 2.0))})}))
|
|
|
|
(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)))
|