sand-img/main.odin
2025-03-07 22:38:13 +13:00

166 lines
4.5 KiB
Odin

package main
import "core:encoding/cbor"
import "core:fmt"
import "core:image/png"
import "core:math/rand"
import "core:os"
import "core:strings"
import "core:time"
import rl "vendor:raylib"
MAKE_CONVERTER :: #config(MAKE_CONVERTER, true)
Pixel :: struct {
id: int,
pos: rl.Vector2,
already_moved: bool,
color: rl.Color,
}
State :: struct {
seed: u64,
colors: map[int]rl.Color,
}
state: State
SIZE :: 800
SCALE :: 20
DIVIDED :: SIZE / SCALE
pixels: [dynamic]Pixel
get_pixel :: proc(pos: rl.Vector2) -> Maybe(^Pixel) {
for &pixel in pixels {
if pixel.pos == pos {
return &pixel
}
}
return nil
}
is_finished :: proc() -> bool {
return len(pixels) >= DIVIDED * DIVIDED
}
main :: proc() {
rl.SetTraceLogLevel(.ERROR)
when MAKE_CONVERTER {
if len(os.args) < 3 {
fmt.eprintln("Please provide input and output filenames!")
return
}
state.seed = u64(time.time_to_unix(time.now()))
rand.reset(state.seed)
rl.SetConfigFlags({.WINDOW_HIDDEN})
fmt.println("Beginning conversion...")
} else {
if len(os.args) < 2 {
fmt.eprintln("Please provide a sand image!")
return
}
bytes, err := os.read_entire_file_or_err(os.args[1])
if err != nil {
fmt.eprintln("Failed to read sand image:", err)
return
}
unmarshal_err := cbor.unmarshal(string(bytes), &state)
if unmarshal_err != nil {
fmt.eprintln("Failed to unmarshal sand image:", unmarshal_err)
}
rand.reset(state.seed)
}
rl.InitWindow(SIZE, SIZE, strings.clone_to_cstring(os.args[1]))
when MAKE_CONVERTER {
img := rl.LoadImage(strings.clone_to_cstring(os.args[1]))
rl.ImageResize(&img, DIVIDED, DIVIDED)
} else {
rl.SetTargetFPS(120)
}
for !rl.WindowShouldClose() {
for &pixel in pixels {
pixel.already_moved = false
}
if !is_finished() {
pos := rl.Vector2{f32(rand.int_max(DIVIDED)), 0}
if _, found := get_pixel(pos).?; !found {
color: rl.Color
id := len(pixels)
when !MAKE_CONVERTER {
color = state.colors[id]
}
append(&pixels, Pixel{id = id, pos = pos, color = color})
}
}
when !MAKE_CONVERTER {
rl.BeginDrawing()
rl.ClearBackground(rl.BLACK)
}
for &pixel in pixels {
when !MAKE_CONVERTER {
rl.DrawRectangleV(pixel.pos * SCALE, {SCALE, SCALE}, pixel.color)
}
if pixel.already_moved do continue
if pixel.pos.y >= DIVIDED - 1 do continue
down := pixel.pos + {0, 1}
left := pixel.pos + {-1, 1}
right := pixel.pos + {1, 1}
if _, found := get_pixel(down).?; !found {
pixel.pos = down
pixel.already_moved = true
} else if _, found := get_pixel(left).?; !found {
if pixel.pos.x > 0 {
pixel.pos = left
pixel.already_moved = true
}
}
if _, found := get_pixel(right).?; !found && !pixel.already_moved {
pixel.already_moved = true
if pixel.pos.x >= DIVIDED - 1 do continue
pixel.pos = right
}
pixel.already_moved = true
}
when !MAKE_CONVERTER {
rl.EndDrawing()
}
when MAKE_CONVERTER {
if is_finished() {
for x in 0 ..< DIVIDED {
for y in 0 ..< DIVIDED {
pixel := get_pixel(rl.Vector2{f32(x), f32(y)}).?
color := rl.GetImageColor(img, i32(x), i32(y))
pixel.color = color
state.colors[pixel.id] = color
}
}
bytes, err := cbor.marshal(state)
if err != nil {
fmt.eprintln("Failed to marshal sand image:", err)
return
}
write_err := os.write_entire_file_or_err(os.args[2], bytes)
if write_err != nil {
fmt.eprintln("Failed to write sand image:", write_err)
return
}
fmt.println("Conversion complete")
rl.CloseWindow()
}
}
}
}