flan/examples/textures-image-generation.fln

112 lines
5.8 KiB
Plaintext

;;;; raylib [textures] example - procedural images generation
;;;;
;;;; examples/textures/textures_image_generation.c. The strongest single pick
;;;; in the textures category and the reason is arithmetic: eight of the nine
;;;; textures come from a Gen* call that no program in this tree had ever
;;;; made, and none of the nine needs a file on disk. Every other textures
;;;; example loads a .png out of its resources directory; this one builds all
;;;; of its pixels.
;;;;
;;;; What it puts under load that nothing else does. An Image is the one
;;;; struct in raylib.flan that carries a pointer to memory raylib owns —
;;;; `data (Ptr u8)` — and it is returned by value, so every one of these nine
;;;; calls hands back a 24-byte aggregate through the shim's out-pointer with
;;;; a live heap block inside it. The corpus had crossed an Image before
;;;; (image-from-image has a headless acceptance case) but never nine in a row
;;;; and never on the load-then-upload-then-free path a real program uses:
;;;; gen, load-texture-from-image to get it onto the GPU, unload-image to give
;;;; the CPU copy back. Getting that order wrong is a leak rather than a
;;;; crash, which is exactly the sort of thing a ported example is for.
;;;;
;;;; Nothing needed adding. All nine generators and load-texture-from-image
;;;; came out of the generated half of the bindings; the only parameter that
;;;; looks like it wants an enum is gen-image-gradient-linear's `direction`,
;;;; which is an angle in degrees and not a flag, so an i32 is its true face.
;;;;
;;;; The C's `switch (currentTexture)` is a `cond` here. Flan has no switch
;;;; and the chain reads the same; the C's `default: break` is the `else`
;;;; branch (`:else`), unreachable because the index is taken modulo the count.
import rl "vendor:raylib"
const screen-width = 800
const screen-height = 450
;; Nine and not eight: the linear gradient appears three times, at three
;; angles, because a vertical, a horizontal and a diagonal gradient are the
;; same generator with a different `direction`.
const num-textures = 9
once textures: [9 rl/Texture2D]
once current-texture: i32
;; Generate on the CPU, upload, drop the pixels. The texture holds a GL name
;; and nothing of the Image, so the CPU copy can go as soon as the upload is
;; done — which is why the C frees all nine in a block immediately after the
;; nine uploads and this does it one at a time. Holding all nine 800x450 RGBA
;; images at once would be 14 MB for no reason.
;;
;; A top-level defn and not a local closure: Flan's `fn` takes its types from
;; the position it is written in, so it needs a parameter of (Fn [T ...] R) to
;; land in, and a `let` binding is not one.
fn upload(img: rl/Image) -> rl/Texture2D
let t = rl/load-texture-from-image(img)
rl/unload-image(img)
t
fn main() -> ()
rl/init-window(screen-width, screen-height,
"raylib [textures] example - procedural images generation")
defer rl/close-window()
textures = array(num-textures, rl/Texture2D)
;; 0 degrees runs the gradient top to bottom, 90 left to right, 45 corner
;; to corner.
textures[0] = upload(rl/gen-image-gradient-linear(screen-width, screen-height, 0, rl/red, rl/blue))
textures[1] = upload(rl/gen-image-gradient-linear(screen-width, screen-height, 90, rl/red, rl/blue))
textures[2] = upload(rl/gen-image-gradient-linear(screen-width, screen-height, 45, rl/red, rl/blue))
;; density 0 means the falloff reaches the edge of the image.
textures[3] = upload(rl/gen-image-gradient-radial(screen-width, screen-height, 0.0, rl/white, rl/black))
textures[4] = upload(rl/gen-image-gradient-square(screen-width, screen-height, 0.0, rl/white, rl/black))
;; 32 checks each way, so each square is 25 by 14 pixels.
textures[5] = upload(rl/gen-image-checked(screen-width, screen-height, 32, 32, rl/red, rl/blue))
;; `factor` is the fraction of pixels that come out white.
textures[6] = upload(rl/gen-image-white-noise(screen-width, screen-height, 0.5))
textures[7] = upload(rl/gen-image-perlin-noise(screen-width, screen-height, 50, 50, 4.0))
;; `tile-size` is the cell spacing, in pixels.
textures[8] = upload(rl/gen-image-cellular(screen-width, screen-height, 32))
defer dotimes([i num-textures], rl/unload-texture(textures[i]))
current-texture = 0
rl/set-target-fps(60)
until rl/window-should-close()
;; Update
if rl/is-mouse-button-pressed(:mouse-left) or rl/is-key-pressed(:key-right)
current-texture = (current-texture + 1) % num-textures
;; Draw
rl/with-drawing:
rl/clear-background(rl/raywhite)
rl/draw-texture(textures[current-texture], 0, 0, rl/white)
rl/draw-rectangle(30, 400, 325, 30, rl/fade(rl/skyblue, 0.5))
rl/draw-rectangle-lines(30, 400, 325, 30, rl/fade(rl/white, 0.5))
rl/draw-text("MOUSE LEFT BUTTON to CYCLE PROCEDURAL TEXTURES", 40, 410,
10, rl/white)
;; The C's switch. Each label is positioned so its right edge lands in the
;; same place, which is why the x differs per line.
if current-texture == 0
rl/draw-text("VERTICAL GRADIENT", 560, 10, 20, rl/raywhite)
elif current-texture == 1
rl/draw-text("HORIZONTAL GRADIENT", 540, 10, 20, rl/raywhite)
elif current-texture == 2
rl/draw-text("DIAGONAL GRADIENT", 540, 10, 20, rl/raywhite)
elif current-texture == 3
rl/draw-text("RADIAL GRADIENT", 580, 10, 20, rl/lightgray)
elif current-texture == 4
rl/draw-text("SQUARE GRADIENT", 580, 10, 20, rl/lightgray)
elif current-texture == 5
rl/draw-text("CHECKED", 680, 10, 20, rl/raywhite)
elif current-texture == 6
rl/draw-text("WHITE NOISE", 640, 10, 20, rl/red)
elif current-texture == 7
rl/draw-text("PERLIN NOISE", 640, 10, 20, rl/red)
else
rl/draw-text("CELLULAR", 670, 10, 20, rl/raywhite)