🍯 Glaze

package main

import "core:log"
import "core:math"
import "core:math/rand"
import "core:slice"

pallette := []u32 {
	0x29009e,
	0x0029b4,
	0x003ec5,
	0x004ed1,
	0x005cd7,
	0x0067d7,
	0x0072d2,
	0x007cc9,
	0x0085bc,
	0x008ead,
	0x00969b,
	0x009f8a,
	0x00a678,
	0x00ad67,
	0x00b457,
	0x4cb94a,
}
steps := f32(len(pallette) - 1) / 255

App :: struct {
	balls:      [dynamic][4]int,
	cell_size:  int,
	grid_width: int,
	cells:      []u8,
	next_cells: []u8,
}

app_init :: proc() -> ^App {
	log.debugf(
		"pallette test: %d %02x%02x%02x==29009e\n",
		len(pallette),
		pallette[0] >> 16 & 0xff,
		pallette[0] >> 8 & 0xff,
		pallette[0] & 0xff,
	)

	app := new(App)
	app.cell_size = 8
	app.grid_width = 70
	app.cells = make([]u8, app.grid_width * app.grid_width)
	app.next_cells = make([]u8, app.grid_width * app.grid_width)

	app.balls = make([dynamic][4]int)
	append(&app.balls, [4]int{10, 10, 1, 1})
	append(&app.balls, [4]int{60, 60, 1, -1})
	append(&app.balls, [4]int{60, 10, 1, 1})

	for _, i in app.cells {
		//app.cells[i] = u8(rand.int_max(len(pallette))) if u8(rand.int_max(2)) == 1 else 0
		//app.cells[i] = u8(rand.int_max(2))
		pos_x := i % app.grid_width
		pos_y := i / app.grid_width

		cell: int
		for ball in app.balls {
			dist := 1 / math.sqrt(math.pow(f32(ball.x - pos_x), 2) + math.pow(f32(ball.y - pos_y), 2))
			cell += int(dist * 255)
		}
		app.cells[i] = u8(cell / len(app.balls))
	}

	return app
}

draw_cells :: proc(app: ^App, bitmap: ^Bitmap) {
	left_x := (bitmap.width / 2) - ((app.grid_width / 2) * app.cell_size)
	right_x := (bitmap.width / 2) + ((app.grid_width / 2) * app.cell_size)
	top_y := (bitmap.height / 2) - ((app.grid_width / 2) * app.cell_size)
	bottom_y := (bitmap.height / 2) + ((app.grid_width / 2) * app.cell_size)
	/*for i in 0 ..= app.grid_width {
		offset := i * app.cell_size
		draw_line(bitmap, left_x, top_y + offset, right_x, top_y + offset, 0xffffffff)
		draw_line(bitmap, left_x + offset, top_y, left_x + offset, bottom_y, 0xffffffff)
	}*/

	for cell, i in app.cells {
		if cell > 0 {
			pos_x := i % app.grid_width
			pos_y := i / app.grid_width
			draw_rect_filled(
				bitmap,
				left_x + (pos_x * app.cell_size),
				top_y + (pos_y * app.cell_size),
				left_x + (pos_x * app.cell_size) + app.cell_size,
				top_y + (pos_y * app.cell_size) + app.cell_size,
				pallette[cell],
				pallette[cell],
			)
		}
	}

	draw_rect(bitmap, left_x, top_y, right_x, bottom_y, 0xffffffff)
}

threshold :: proc(val, thresh: u8) -> u8 {
	return 1 if val >= thresh else 0
}

get_cell_state :: proc(app: ^App, x, y: int, thresh: u8) -> u8 {
	state := threshold(app.cells[(y * app.grid_width) + x], thresh) << 3
	state += threshold(app.cells[(y * app.grid_width) + x + 1], thresh) << 2
	state += threshold(app.cells[((y + 1) * app.grid_width) + x + 1], thresh) << 1
	state += threshold(app.cells[((y + 1) * app.grid_width) + x], thresh)
	return state
}

draw_marching_squares :: proc(app: ^App, bitmap: ^Bitmap) {
	left_x := (bitmap.width / 2) - ((app.grid_width / 2) * app.cell_size)
	right_x := (bitmap.width / 2) + ((app.grid_width / 2) * app.cell_size)
	top_y := (bitmap.height / 2) - ((app.grid_width / 2) * app.cell_size)
	bottom_y := (bitmap.height / 2) + ((app.grid_width / 2) * app.cell_size)

	half_size := app.cell_size / 2
	for y in 0 ..< app.grid_width {
		for x in 0 ..< app.grid_width {

			pos_x := x * app.cell_size
			pos_y := y * app.cell_size

			if x < app.grid_width - 1 && y < app.grid_width - 1 {
				a := [2]int{left_x + pos_x + half_size, top_y + pos_y}
				b := [2]int{left_x + pos_x + app.cell_size, top_y + pos_y + half_size}
				c := [2]int{left_x + pos_x + half_size, top_y + pos_y + app.cell_size}
				d := [2]int{left_x + pos_x, top_y + pos_y + half_size}
				state := get_cell_state(app, x, y, 18)

				switch state {
				case 1:
					draw_line(bitmap, c.x, c.y, d.x, d.y, 0xffffffff)
				case 2:
					draw_line(bitmap, b.x, b.y, c.x, c.y, 0xffffffff)
				case 3:
					draw_line(bitmap, b.x, b.y, d.x, d.y, 0xffffffff)
				case 4:
					draw_line(bitmap, a.x, a.y, b.x, b.y, 0xffffffff)
				case 5:
					draw_line(bitmap, a.x, a.y, d.x, d.y, 0xffffffff)
					draw_line(bitmap, b.x, b.y, c.x, c.y, 0xffffffff)
				case 6:
					draw_line(bitmap, a.x, a.y, c.x, c.y, 0xffffffff)
				case 7:
					draw_line(bitmap, a.x, a.y, d.x, d.y, 0xffffffff)
				case 8:
					draw_line(bitmap, a.x, a.y, d.x, d.y, 0xffffffff)
				case 9:
					draw_line(bitmap, a.x, a.y, c.x, c.y, 0xffffffff)
				case 10:
					draw_line(bitmap, a.x, a.y, b.x, b.y, 0xffffffff)
					draw_line(bitmap, c.x, c.y, d.x, d.y, 0xffffffff)
				case 11:
					draw_line(bitmap, a.x, a.y, b.x, b.y, 0xffffffff)
				case 12:
					draw_line(bitmap, b.x, b.y, d.x, d.y, 0xffffffff)
				case 13:
					draw_line(bitmap, b.x, b.y, c.x, c.y, 0xffffffff)
				case 14:
					draw_line(bitmap, c.x, c.y, d.x, d.y, 0xffffffff)
				}
			}

			/*cell := threshold(app.cells[(y * app.grid_width) + x], 24)
			if cell > 0 {
				draw_rect_filled(
					bitmap,
					left_x + pos_x - 1,
					top_y + pos_y - 1,
					left_x + pos_x + 1,
					top_y + pos_y + 1,
					0xff00ff00,
					0xff00ff00,
				)
			} else {
				draw_rect_filled(
					bitmap,
					left_x + pos_x - 1,
					top_y + pos_y - 1,
					left_x + pos_x + 1,
					top_y + pos_y + 1,
					0xffff0000,
					0xffff0000,
				)
			}*/
		}
	}

	draw_rect(bitmap, left_x - half_size, top_y - half_size, right_x - half_size, bottom_y - half_size, 0xffffffff)
}

do_xor :: proc(app: ^App, bitmap: ^Bitmap) {
	@(static)
	offset: int

	i: int
	for y in 0 ..< bitmap.height {
		for x in 0 ..< bitmap.width {
			c := int(f32(u8((x + offset) ~ (y + offset))) * steps)
			bitmap.buffer[i + 0] = u8(pallette[c] & 0xff) / 10
			bitmap.buffer[i + 1] = u8(pallette[c] >> 8 & 0xff) / 10
			bitmap.buffer[i + 2] = u8(pallette[c] >> 16 & 0xff) / 10
			bitmap.buffer[i + 3] = 255
			i += 4
		}
	}
	offset += 1
}

do_sand :: proc(app: ^App, bitmap: ^Bitmap) {
	@(static)
	count: int
	@(static)
	pallette_color: u8 = 1
	if count % 20 == 0 {
		pallette_color = (1 + (pallette_color + 1)) % u8(len(pallette))
	}
	app.cells[rand.int_max(40) - 20 + (app.grid_width / 2)] = pallette_color
	app.cells[rand.int_max(40) - 20 + (app.grid_width / 2)] = pallette_color
	app.cells[rand.int_max(40) - 20 + (app.grid_width / 2)] = pallette_color
	app.cells[rand.int_max(40) - 20 + (app.grid_width / 2)] = pallette_color
	count += 1

	slice.fill(app.next_cells, 0)
	for cell, i in app.cells {
		if cell > 0 {
			if i >= len(app.cells) - app.grid_width {
				app.next_cells[i] = cell
				continue
			}

			next_cell := i + app.grid_width
			if (app.cells[next_cell] == 0) {
				app.next_cells[next_cell] = cell
			} else {
				left_cell := next_cell - 1
				right_cell := next_cell + 1

				cell_x := next_cell % app.grid_width
				if cell_x == 0 {
					left_cell += app.grid_width
				}
				if cell_x == app.grid_width - 1 {
					right_cell -= app.grid_width
				}

				check_cells := [2]int{left_cell, right_cell}
				if rand.int_max(2) == 1 {
					check_cells[0] = right_cell
					check_cells[1] = left_cell
				}

				if app.cells[check_cells[0]] == 0 {
					app.next_cells[check_cells[0]] = cell
				} else if app.cells[check_cells[1]] == 0 {
					app.next_cells[check_cells[1]] = cell
				} else {
					app.next_cells[i] = cell
				}
			}
		}
	}
	app.cells, app.next_cells = app.next_cells, app.cells
}

/** Conway's Game of Life
  1. Any live cell with fewer than two live neighbours dies, as if by underpopulation.
  2. Any live cell with two or three live neighbours lives on to the next generation.
  3. Any live cell with more than three live neighbours dies, as if by overpopulation.
  4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
*/
do_conway :: proc(app: ^App, bitmap: ^Bitmap) {
	slice.fill(app.next_cells, 0)
	for cell, i in app.cells {
		num_neighbours: int

		pos_x := i % app.grid_width
		pos_y := i / app.grid_width
		neighbours := [][2]int {
			{pos_x - 1, pos_y - 1},
			{pos_x, pos_y - 1},
			{pos_x + 1, pos_y - 1},
			{pos_x - 1, pos_y},
			{pos_x + 1, pos_y},
			{pos_x - 1, pos_y + 1},
			{pos_x, pos_y + 1},
			{pos_x + 1, pos_y + 1},
		}

		for ni in neighbours {
			x := ni.x
			y := ni.y
			if x < 0 {
				x += app.grid_width
			}
			if x >= app.grid_width {
				x -= app.grid_width
			}
			if y < 0 {
				y += app.grid_width
			}
			if y >= app.grid_width {
				y -= app.grid_width
			}

			ci := (y * app.grid_width) + x
			if app.cells[ci] > 0 {
				num_neighbours += 1
			}
		}

		if cell > 0 && (num_neighbours == 2 || num_neighbours == 3) {
			app.next_cells[i] = cell
		}
		if cell == 0 && num_neighbours == 3 {
			app.next_cells[i] = u8(rand.int_max(len(pallette) - 1) + 1)
		}
	}
	app.cells, app.next_cells = app.next_cells, app.cells
}

app_update_and_draw :: proc(app: ^App, bitmap: ^Bitmap) {
	// test
	//do_xor(app, bitmap)

	// cells
	//do_sand(app, bitmap)
	//do_conway(app, bitmap)
	//draw_cells(app, bitmap)

	// marching squares
	for _, i in app.balls {
		if app.balls[i].x >= app.grid_width || app.balls[i].x <= 0 {
			app.balls[i].z = -app.balls[i].z
		}
		if app.balls[i].y >= app.grid_width || app.balls[i].y <= 0 {
			app.balls[i].w = -app.balls[i].w
		}
		app.balls[i].x += app.balls[i].z
		app.balls[i].y += app.balls[i].w
	}
	for _, i in app.cells {
		pos_x := i % app.grid_width
		pos_y := i / app.grid_width
		cell: int
		for ball in app.balls {
			dist := 1 / math.sqrt(math.pow(f32(ball.x - pos_x), 2) + math.pow(f32(ball.y - pos_y), 2))
			cell += int(dist * 255)
		}
		app.cells[i] = u8(cell / len(app.balls))
	}

	/*left_x := (bitmap.width / 2) - ((app.grid_width / 2) * app.cell_size)
	top_y := (bitmap.height / 2) - ((app.grid_width / 2) * app.cell_size)
	for cell, i in app.cells {
		if cell > 0 {
			pos_x := i % app.grid_width
			pos_y := i / app.grid_width
			draw_rect_filled(
				bitmap,
				left_x + (pos_x * app.cell_size),
				top_y + (pos_y * app.cell_size),
				left_x + (pos_x * app.cell_size) + app.cell_size,
				top_y + (pos_y * app.cell_size) + app.cell_size,
				0xff000000 + u32(cell) << 16 + u32(cell) << 8 + u32(cell),
				0xff000000 + u32(cell) << 16 + u32(cell) << 8 + u32(cell),
			)
		}
	}*/
	draw_marching_squares(app, bitmap)
}