-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Implement Wave Function Collapse page w/ backtracking in worker
- Loading branch information
Showing
9 changed files
with
684 additions
and
4 deletions.
There are no files selected for viewing
179 changes: 179 additions & 0 deletions
179
src/pages/wave-function-collapse/carcassonne/definition.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
import image from './image.jpg' | ||
|
||
export async function getImageBitmap(url: string) { | ||
const data = await fetch(url) | ||
const blob = await data.blob() | ||
const bitmap = await createImageBitmap(blob) | ||
return bitmap | ||
} | ||
|
||
export const tileSet = await getImageBitmap(image) | ||
|
||
export const params = { | ||
tile: { | ||
width: 256, // px | ||
height: 256, // px | ||
}, | ||
grid: { | ||
width: 16, // tiles | ||
}, | ||
} | ||
|
||
export const definition = [ | ||
['castle', 'castle', 'road', 'castle'], | ||
['castle', 'castle', 'road', 'castle'], | ||
['castle', 'castle', 'road', 'castle'], | ||
['castle', 'grass', 'grass', 'castle'], | ||
['castle', 'grass', 'grass', 'castle'], | ||
['castle', 'grass', 'grass', 'castle'], | ||
['castle', 'grass', 'grass', 'castle'], | ||
['castle', 'grass', 'grass', 'castle'], | ||
['castle', 'road', 'road', 'castle'], | ||
['castle', 'road', 'road', 'castle'], | ||
['castle', 'road', 'road', 'castle'], | ||
['castle', 'road', 'road', 'castle'], | ||
['castle', 'road', 'road', 'castle'], | ||
['grass', 'castle', 'grass', 'castle'], | ||
['grass', 'castle', 'grass', 'castle'], | ||
['grass', 'castle', 'grass', 'castle'], | ||
['castle', 'grass', 'grass', 'castle'], | ||
['castle', 'grass', 'grass', 'castle'], | ||
['castle', 'grass', 'castle', 'grass'], | ||
['castle', 'grass', 'castle', 'grass'], | ||
['castle', 'grass', 'castle', 'grass'], | ||
['castle', 'grass', 'grass', 'grass'], | ||
['castle', 'grass', 'grass', 'grass'], | ||
['castle', 'grass', 'grass', 'grass'], | ||
['castle', 'grass', 'grass', 'grass'], | ||
['castle', 'grass', 'grass', 'grass'], | ||
['castle', 'grass', 'road', 'road'], | ||
['castle', 'grass', 'road', 'road'], | ||
['castle', 'grass', 'road', 'road'], | ||
['castle', 'road', 'road', 'grass'], | ||
['castle', 'road', 'road', 'grass'], | ||
['castle', 'road', 'road', 'grass'], | ||
['castle', 'road', 'road', 'road'], | ||
['castle', 'road', 'road', 'road'], | ||
['castle', 'road', 'road', 'road'], | ||
['castle', 'road', 'grass', 'road'], | ||
['castle', 'road', 'grass', 'road'], | ||
['castle', 'road', 'grass', 'road'], | ||
['castle', 'road', 'grass', 'road'], | ||
['road', 'grass', 'road', 'grass'], | ||
['road', 'grass', 'road', 'grass'], | ||
['road', 'grass', 'road', 'grass'], | ||
['road', 'grass', 'road', 'grass'], | ||
['road', 'grass', 'road', 'grass'], | ||
['road', 'grass', 'road', 'grass'], | ||
['road', 'grass', 'road', 'grass'], | ||
['road', 'grass', 'road', 'grass'], | ||
['grass', 'grass', 'road', 'road'], | ||
['grass', 'grass', 'road', 'road'], | ||
['grass', 'grass', 'road', 'road'], | ||
['grass', 'grass', 'road', 'road'], | ||
['grass', 'grass', 'road', 'road'], | ||
['grass', 'grass', 'road', 'road'], | ||
['grass', 'grass', 'road', 'road'], | ||
['grass', 'grass', 'road', 'road'], | ||
['grass', 'grass', 'road', 'road'], | ||
['grass', 'road', 'road', 'road'], | ||
['grass', 'road', 'road', 'road'], | ||
['grass', 'road', 'road', 'road'], | ||
['grass', 'road', 'road', 'road'], | ||
['road', 'road', 'road', 'road'], | ||
['grass', 'grass', 'grass', 'grass'], | ||
['grass', 'grass', 'grass', 'grass'], | ||
['grass', 'grass', 'grass', 'grass'], | ||
['grass', 'grass', 'grass', 'grass'], | ||
['grass', 'grass', 'road', 'grass'], | ||
['grass', 'grass', 'road', 'grass'], | ||
['castle', 'castle', 'castle', 'castle'], | ||
['castle', 'castle', 'grass', 'castle'], | ||
['castle', 'castle', 'grass', 'castle'], | ||
['castle', 'castle', 'grass', 'castle'], | ||
['castle', 'castle', 'grass', 'castle'], | ||
['grass', 'grass', 'road', 'road'], | ||
['castle', 'grass', 'road', 'grass'], | ||
['road', 'castle', 'road', 'castle'], | ||
['grass', 'road', 'grass', 'road'], | ||
['road', 'road', 'road', 'road'], | ||
['castle', 'road', 'grass', 'castle'], | ||
['grass', 'castle', 'grass', 'grass'], | ||
['castle', 'castle', 'castle', 'castle'], | ||
['castle', 'castle', 'castle', 'castle'], | ||
['castle', 'castle', 'castle', 'castle'], | ||
['grass', 'road', 'grass', 'road'], | ||
['grass', 'road', 'road', 'road'], | ||
['castle', 'road', 'road', 'castle'], | ||
['castle', 'grass', 'road', 'road'], | ||
['castle', 'grass', 'road', 'castle'], | ||
['castle', 'grass', 'castle', 'castle'], | ||
['castle', 'grass', 'castle', 'castle'], | ||
['road', 'castle', 'road', 'castle'], | ||
['castle', 'road', 'castle', 'grass'], | ||
['road', 'road', 'castle', 'castle'], | ||
['castle', 'castle', 'road', 'castle'], | ||
['castle', 'castle', 'castle', 'castle'], | ||
['road', 'grass', 'castle', 'castle'], | ||
['castle', 'grass', 'grass', 'castle'], | ||
['castle', 'castle', 'castle', 'castle'], | ||
['castle', 'castle', 'road', 'road'], | ||
['castle', 'road', 'castle', 'road'], | ||
['castle', 'grass', 'castle', 'grass'], | ||
['castle', 'castle', 'castle', 'grass'], | ||
['castle', 'castle', 'grass', 'castle'], | ||
['road', 'grass', 'road', 'road'], | ||
['road', 'castle', 'castle', 'grass'], | ||
['castle', 'castle', 'road', 'grass'], | ||
['road', 'castle', 'castle', 'castle'], | ||
['castle', 'grass', 'castle', 'castle'], | ||
['grass', 'castle', 'castle', 'grass'], | ||
['castle', 'road', 'grass', 'grass'], | ||
['road', 'road', 'road', 'road'], | ||
['road', 'castle', 'grass', 'road'], | ||
['castle', 'road', 'castle', 'grass'], | ||
['grass', 'road', 'castle', 'castle'], | ||
['castle', 'castle', 'castle', 'road'], | ||
['grass', 'grass', 'water', 'grass'], | ||
['grass', 'water', 'water', 'grass'], | ||
['grass', 'water', 'water', 'grass'], | ||
['grass', 'water', 'road', 'water'], | ||
['road', 'road', 'water', 'water'], | ||
['water', 'grass', 'water', 'grass'], | ||
['water', 'grass', 'water', 'grass'], | ||
['water', 'grass', 'grass', 'grass'], | ||
['water', 'castle', 'water', 'road'], | ||
['castle', 'water', 'castle', 'water'], | ||
['road', 'water', 'road', 'water'], | ||
['water', 'castle', 'castle', 'water'], | ||
['road', 'castle', 'road', 'castle'], | ||
['grass', 'castle', 'grass', 'castle'], | ||
['castle', 'castle', 'grass', 'castle'], | ||
['castle', 'grass', 'grass', 'castle'], | ||
['castle', 'grass', 'road', 'road'], | ||
['castle', 'road', 'road', 'grass'], | ||
['castle', 'grass', 'grass', 'grass'], | ||
['grass', 'road', 'road', 'road'], | ||
['grass', 'road', 'road', 'road'], | ||
['grass', 'grass', 'road', 'road'], | ||
['grass', 'grass', 'road', 'road'], | ||
['road', 'grass', 'road', 'grass'], | ||
['grass', 'grass', 'road', 'road'], | ||
['road', 'grass', 'road', 'grass'], | ||
['grass', 'grass', 'road', 'grass'], | ||
['grass', 'grass', 'grass', 'grass'], | ||
['castle', 'grass', 'grass', 'grass'], | ||
['castle', 'grass', 'grass', 'castle'], | ||
['castle', 'castle', 'grass', 'castle'], | ||
['castle', 'grass', 'grass', 'castle'], | ||
['grass', 'castle', 'castle', 'castle'], | ||
['castle', 'grass', 'grass', 'castle'], | ||
['castle', 'road', 'road', 'castle'], | ||
['castle', 'road', 'road', 'road'], | ||
['castle', 'castle', 'grass', 'castle'], | ||
['castle', 'road', 'road', 'castle'], | ||
['castle', 'grass', 'road', 'road'], | ||
['castle', 'road', 'road', 'grass'], | ||
['road', 'road', 'road', 'road'], | ||
['grass', 'road', 'road', 'road'], | ||
] |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import styles from './styles.module.css' | ||
import { Head } from "~/components/Head" | ||
import type { RouteMeta } from "~/router" | ||
import type { Incoming, Outgoing } from "./worker" | ||
import Worker from "./worker?worker" | ||
import { useEffect, useRef } from "react" | ||
import * as utils from './utils' | ||
import * as config from './carcassonne/definition' | ||
|
||
export const meta: RouteMeta = { | ||
title: 'Wave Function Collapse', | ||
image: './screen.png' | ||
} | ||
|
||
const tiles = config.definition.map((row, y) => ({ | ||
name: y, | ||
sides: row, | ||
})) | ||
|
||
const equivalents = tiles.map((tile) => tiles.filter((other) => other.sides.every((side, i) => side === tile.sides[i]))) | ||
|
||
|
||
|
||
function drawTile(ctx: CanvasRenderingContext2D, w: number, h: number, index: number, rotate: number, x: number, y: number) { | ||
if (rotate === 1) x += 1 | ||
if (rotate === 2) y += 1, x += 1 | ||
if (rotate === 3) y += 1 | ||
ctx.save() | ||
ctx.translate(x * w, y * h) | ||
ctx.rotate(rotate * Math.PI / 2) | ||
const setY = (index / config.params.grid.width) | 0 | ||
const setX = index % config.params.grid.width | ||
ctx.drawImage( | ||
config.tileSet, | ||
setX * config.params.tile.width, | ||
setY * config.params.tile.height, | ||
config.params.tile.width, | ||
config.params.tile.height, | ||
0, | ||
0, | ||
w, | ||
h | ||
) | ||
ctx.restore() | ||
} | ||
|
||
export default function () { | ||
const ref = useRef<HTMLCanvasElement | null>(null) | ||
|
||
// useEffect(() => { | ||
// const ctx = ref.current?.getContext("2d")! | ||
// if (!ctx) return | ||
|
||
// drawTile(ctx, 100, 100, 3, 0, 2, 1) | ||
// }, []) | ||
|
||
useEffect(() => { | ||
const canvas = ref.current | ||
if (!canvas) return | ||
canvas.width = canvas.clientWidth * window.devicePixelRatio | ||
canvas.height = canvas.clientHeight * window.devicePixelRatio | ||
const ctx = canvas.getContext("2d")! | ||
if (!ctx) return | ||
|
||
const worker = new Worker() | ||
function post<I extends Incoming["type"]>( | ||
type: I, | ||
data: Extract<Incoming, { type: I }>["data"], | ||
transfer?: Transferable[] | ||
) { | ||
worker.postMessage({ type, data }, { transfer }) | ||
} | ||
|
||
const height = 30 | ||
const width = 30 | ||
const drawX = ctx.canvas.width / width | ||
const drawY = ctx.canvas.height / height | ||
let map: Extract<Outgoing, { type: "started" }>["data"]["map"] | ||
let buffer: Extract<Outgoing, { type: "started" }>["data"]["buffer"] | ||
let done = false | ||
let get: (x: number, y: number, t: number) => 0 | 1 | ||
|
||
|
||
|
||
let i = 0 | ||
function loop() { | ||
if (!done) rafId = requestAnimationFrame(loop) | ||
// rafId = requestAnimationFrame(loop) | ||
if (!map || !buffer || !get) return | ||
|
||
i++ | ||
ctx.clearRect(0, 0, width * drawX, height * drawY) | ||
|
||
// const grid = Array.from({ length: height }, () => Array(width).fill(0)) | ||
|
||
for (let y = 0; y < height; y++) { | ||
for (let x = 0; x < width; x++) { | ||
const indices = map.reduce((acc, _, t) => (get(x, y, t) && acc.push(t), acc), [] as number[]) | ||
if (indices.length === 0) continue | ||
const index = indices[i % indices.length] | ||
// const index = indices[0] | ||
// grid[y][x] = indices.length | ||
const tile = map[index] | ||
|
||
// const options = equivalents[tile.name] | ||
// const pick = options[Math.floor(Math.random() * options.length)] | ||
|
||
drawTile(ctx, drawX, drawY, tile.name, tile.rotate, x, y) | ||
} | ||
} | ||
for (const [x, y] of force) { | ||
ctx.rect(x * drawX, y * drawY, drawX, drawY) | ||
ctx.strokeStyle = "red" | ||
ctx.lineWidth = 2 | ||
ctx.stroke() | ||
} | ||
|
||
// console.log("grid") | ||
// console.log(grid.map(row => row.join(" ")).join("\n")) | ||
} | ||
let rafId = requestAnimationFrame(loop) | ||
|
||
const onMessage = (e: MessageEvent<Outgoing>) => { | ||
if (e.data.type === "started") { | ||
map = e.data.data.map | ||
buffer = e.data.data.buffer | ||
get = utils.get.bind(null, width, height, map.length, new DataView(buffer)) | ||
} else if (e.data.type === "done") { | ||
done = true | ||
console.log("done", e.data.data.solved) | ||
} | ||
} | ||
|
||
const seed = (count: number) => { | ||
const forces: Array<[x: number, y: number, t: number]> = [] | ||
for (let i = 0; i < count; i++) { | ||
forces.push([Math.floor(Math.random() * width), Math.floor(Math.random() * height), tiles[Math.floor(Math.random() * tiles.length)].name]) | ||
} | ||
return forces | ||
} | ||
|
||
worker.addEventListener('message', onMessage) | ||
const force = seed(4) | ||
console.log(force) | ||
post("start", { height, width, tiles, force }) | ||
return () => { | ||
worker.terminate() | ||
worker.removeEventListener('message', onMessage) | ||
cancelAnimationFrame(rafId) | ||
} | ||
}, []) | ||
|
||
return ( | ||
<div className={styles.main} > | ||
<Head /> | ||
<canvas width="1000" height="1000" ref={ref}> | ||
Your browser does not support the HTML5 canvas tag. | ||
</canvas> | ||
</div> | ||
) | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
.main { | ||
margin: 0; | ||
background: #051016; | ||
color: white; | ||
touch-action: none; | ||
width: 100vw; | ||
height: 100svh; | ||
padding: 1em; | ||
|
||
canvas { | ||
border: 1px solid white; | ||
aspect-ratio: 1; | ||
width: 100%; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
export const get = (width: number, height: number, length: number, view: DataView, x: number, y: number, t: number) => { | ||
const index = (y * width + x) * length + t | ||
const byte = index >> 3 // index / 8 | ||
const offset = index & ~(byte << 3) // index % 8 | ||
return ((view.getUint8(byte) >> offset) & 1) as 0 | 1 | ||
} | ||
|
||
export const set = (width: number, height: number, length: number, view: DataView, x: number, y: number, t: number, value: boolean) => { | ||
const index = (y * width + x) * length + t | ||
const byte = index >> 3 // index / 8 | ||
const offset = index & ~(byte << 3) // index % 8 | ||
if (value) { | ||
view.setUint8(byte, view.getUint8(byte) | (1 << offset)) | ||
} else { | ||
view.setUint8(byte, view.getUint8(byte) & ~(1 << offset)) | ||
} | ||
} |
Oops, something went wrong.