openCL-accelerated python implementation of the Wave Function Collapse procgen algorithm
Implementation of the Wave Function Collapse procedural content generation algorithm, using (py)OpenCL for GPU acceleration.
make sure you have the python packages pyopencl, numpy and pyglet installed.
You can then run a basic example using
python main.py
in the preview window the following keybindings are set:
escape
: closespace
: do one oberservation/propagation cycle and renderr
: cycle until stable, then render againd
: debug view (overlay decimal display of bitmask for each tile)There is also a more interesting sprite-based example that you can run using
python circuit.py [render]
but as you can see I didn't set up the model constraints properly. Maybe you want to fix that?
main.py
can take a few options that are just passed as strings on the command line, in any order.
They might not all be compatible with each other, in any case main.py
is only a
starting point to write your own set up code with a more serious model.
cpu
propagate using a simplistic CPU algorithm.
3d
work in a 3d space (4x4x2 by default), with a very rudimentary preview. more of a proof of concept, but totally workable.
In the 3d preview, the up and down keys can be used to cycle through slices of the Z axis.
silent
don't open a preview or render, just measure the execution time.
render
automatically step execution forward and take save a screenshot to shots/0001.png
etc.
You can use e.g. ffmpeg to turn the png frames into an animation.
gpWFC
is set up to follow a 'mix and match' modular architecture as best as possible.
It is therefore divided into a couple of components that need to be used to run a simulation:
Tile
and SpriteTile
from models.py
):
tile.weight
(float): the relative probability of occurencetile.compatible(other, direction_id)
(bool): constraint informationtile.image
and tile.rotation
for SpriteTile
Model2d
and Model3d
from models.py
):
model.world_shape
(tuple): dimensions of the world (any nr of axes)model.get_neighbours(pos)
(generator): tile adjacency informationmodel.tiles
(list): the tiles to be usedmodel.get_allowed_tiles(bitmask)
(list): a way to resolve the opaque bitmaskRunner
and BacktrackingRunner
from runners.py
):
runner.step()
(string): execute a single observartion/propagation cyclerunner.finish()
(string): run the simulation until it either fails or stabilizesrunner.run()
(generator): iterate over runner.step()
'done'
- fully collapsed'error'
- overconstrained / stuck'continue'
- step successful but uncollapsed tiles remainPreviewWindow*
from previews.py
):
preview.draw_tiles(pos, bits)
: draw the tiles at pos
(tuple)preview.launch()
: enter interactive preview modepreview.render()
: enter non-interactive render loopobservers.py
and propagators.py
):
You can find a straightforward example of the basic setup steps in circuit.py
, it should follow this flow:
There is a terribly broken glsl-render
branch that tries to not ever get the buffer back to CPU memory during propagation,
while still rendering the world in a GLSL shader.
Unfortunately I could never get it to work properly with pyOpenCL to date, and due to some other constraints
I also cannot test or bring the current version back to the best state it was in,
so it will remain in a messy test state for now.
If anyone is brave enough to touch it though, when working, it should give some incredible performance gains as the rendering and memory transfer / gpu blocking are by far the biggest slow-downs at the moment. There is also some hope since a new version of pyOpenCL is apparently on the way.