Created
December 6, 2022 17:36
-
-
Save wernsey/9b0b26fbb465636eb1fb73754e10ac5a to your computer and use it in GitHub Desktop.
Random Dungeon Generator for a Roguelike
This file contains hidden or 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
/** | |
* **Random Dungeon Generator** | |
* | |
* A random dungeon generator for a Roguelike game. | |
* | |
* It is based on the "Rogue" algorithm, as described by Mark Damon Hughes | |
* [Game Design: Article 07: Roguelike Dungeon Generation][algorithm], but with | |
* some modifications. | |
* | |
* The basic algorithm is this: | |
* | |
* 1. Divide the map into a grid of `GRID_W` × `GRID_H` cells. Each cell will contain a room. The cells are | |
* marked as un_d_connected initially. | |
* 2. Choose a random cell as the starting room; set it as the _current room_. | |
* 3. While the _current room_ has un_d_connected neighbour cells, | |
* * mark the _current room_ as _d_connected, | |
* * set its _number_ to the number of rooms added so far, | |
* * mark it as a _path_ room, and | |
* * choose one of those un_d_connected neighbours at random and _d_connect it to the _current room_, | |
* * set the new room to the _current room_. | |
* 4. Iteration stops when the current room reached a dead end. | |
* * The last room you added is labelled the end room. | |
* * You should now have a fairly long path from the start room to the end room. | |
* * The number of rooms added up to now is referred to as the _distance_: | |
* * If the distance is too short, it might be too easy for the adventurer to get from the start | |
* to the end, so the implementation has the option to go back to step 2 | |
* 5. Color the rooms: All the __d_connected_ rooms whose _number_ is less than $\frac{distance}{2}$ is | |
* colored blue, all the other rooms colored red. | |
* * Mark the _d_connection between the last blue room and the first red room as a door. | |
* 6. While there are un_d_connected rooms on the grid: | |
* * choose a random un_d_connected room and _d_connect it to one of its _d_connected neighbours. | |
* * (If a room doesn't have _d_connected neighbours you can just retry with a different room; | |
* you'll _d_connect all of them eventually) | |
* * Mark the room as _d_connected. Give it the same _color_ as the neighbour you're _d_connecting it too. | |
* * The room is also marked as an _extra_ room. | |
* 7. Add some random _d_connections between the rooms | |
* * Choose a room at random and _d_connect it to a random neighbour if they don't have a _d_connection yet. | |
* * If the two rooms have different colors, mark the _d_connection between them as a door... | |
* * ...unless the rooms are start/end rooms - we don't want a door directly to one of these rooms because | |
* it might make the map a bit too easy to solve. | |
* 8. Some of the rooms are marked as _gone_ rooms - they will consist of just corridors, for effect. | |
* * The start room and end room cannot be gone rooms. | |
* * Rooms with only one exit can't be gone rooms either (they would just be corridors leading nowhere). | |
* 9. Each room is given a random position and width/height to occupy within its cell on the grid. | |
* | |
* So when the algorithm is done, you can place the player in the room marked as the start room and place a key | |
* in any of the blue rooms and you'll be guaranteed that the player will be able to find a key to open the | |
* door in one of the accessible rooms. You'll also be guaranteed that the player will have to pass through | |
* a door to reach the end room that will be in a room colored red. | |
* | |
* Of course, the _door_ and the _key_ doesn't have to be an actual door and key. The _door_ could be any obstacle | |
* blocking the hero, like a river of lava, and the _key_ would be some means to overcome that obstacle, like a | |
* lever to lower a d_drawbridge over the lava. | |
* | |
* All rooms marked as _path_ rooms will be guaranteed to lie on a path from the start room to the end room, | |
* although other paths may exist thanks to the random _d_connections. | |
* | |
* The rooms marked as _extra_ rooms may be used for some additional purposes. For example, if an extra room has | |
* only one corridor _d_connecting it to the rest of the maze, then it could be turned into a secret room by hiding | |
* said corridor. | |
* | |
* You could put tougher _boss_ monsters and/or better treasure in rooms colored 1, so that the flow is that the | |
* player starts in the blue area, fights a couple of easier enemies, find the key, go through the door to | |
* the red area, encounter a boss, loot some treasure and reach the exit. | |
* | |
* This online tool [svg-path-editor](https://yqnn.github.io/svg-path-editor/) helped me create the SVG paths. | |
* | |
* https://dysonlogos.blog/2013/12/23/the-key-to-all-this-madness/ | |
* https://watabou.itch.io/one-page-dungeon | |
* | |
* [algorithm]: https://web.archive.org/web/20131025132021/http://kuoi.org/~kamikaze/GameDesign/art07_rogue_dungeon.php | |
*/ | |
#ifndef DUNGEON_H | |
#define DUNGEON_H | |
#define D_GRID_W 5 | |
#define D_GRID_H 3 | |
#define D_MAX_ROOM_SIZE 4 | |
#define D_MIN_ROOM_SIZE 3 | |
#define D_MIN_DISTANCE 7 | |
#define D_SECRET_PROB 50 | |
#define D_MAX_GONE_ROOMS 2 | |
#define D_MIN_GONE_ROOMS 1 | |
#define D_COLOR_BLUE 0 | |
#define D_COLOR_RED 1 | |
#define D_FLAG_START_ROOM 0x01 | |
#define D_FLAG_END_ROOM 0x02 | |
#define D_FLAG_PATH_ROOM 0x04 | |
#define D_FLAG_XTRA_ROOM 0x08 | |
#define D_FLAG_GONE_ROOM 0x10 | |
#define D_FLAG_SECRET_ROOM 0x20 | |
#define D_NORTH 0x01 | |
#define D_SOUTH 0x02 | |
#define D_EAST 0x04 | |
#define D_WEST 0x08 | |
#define D_ORIENT_EW (D_EAST | D_WEST) | |
#define D_ORIENT_NS (D_NORTH | D_SOUTH) | |
#define D_TILE_EMPTY ' ' | |
#define D_TILE_WALL '#' | |
#define D_TILE_FLOOR '.' | |
#define D_TILE_FLOOR_EDGE ',' | |
#define D_TILE_CORRIDOR '`' | |
#define D_TILE_CROSSING '+' | |
#define D_TILE_ENTRANCE '@' | |
#define D_TILE_EXIT '$' | |
#define D_TILE_LOCKED_DOOR 'D' | |
#define D_TILE_THRESHOLD 't' | |
#define D_TILE_SECRET 's' | |
/* Choose a random number in [0,N) */ | |
#ifndef D_RAND | |
# define D_RAND(N) ((rand() >> 8) % (N)) | |
#endif | |
typedef struct { | |
short x, y; | |
} D_Point; | |
typedef struct { | |
char _d_connected; | |
char color; | |
unsigned char flags; | |
char visited; | |
short x, y, w, h; | |
} D_Room; | |
/* Connections between the rooms. An edge p,q means that | |
there's a corridor between rooms[p] and rooms[q] */ | |
typedef struct { | |
short p, q; | |
char door, direction, offset; | |
short index; | |
short x, y, w, h; | |
} D_Edge; | |
typedef struct { | |
/* These fields are used for configuration */ | |
short grid_w, grid_h; | |
short min_room_size, max_room_size; | |
short min_distance; | |
short secret_prob; | |
short min_gone_rooms, max_gone_rooms; | |
/* These fields are computed; don't mess with them: */ | |
short map_w, map_h; | |
D_Room *rooms; | |
short n_rooms; | |
char *map; | |
D_Edge *edges; | |
short n_edges, max_edges; | |
short start_room, end_room; | |
/* Temporary indexes */ | |
short *cells; | |
} D_Dungeon; | |
void d_init(D_Dungeon *M); | |
void d_deinit(D_Dungeon *M); | |
int d_generate(D_Dungeon *D); | |
int d_is_wall(D_Dungeon *M, int x, int y); | |
int d_is_floor(D_Dungeon *M, int x, int y); | |
void d_set_tile(D_Dungeon *M, int x, int y, char tile); | |
char d_get_tile(D_Dungeon *M, int x, int y); | |
D_Room *d_room_at(D_Dungeon *M, int x, int y); | |
D_Edge *d_corridor_at(D_Dungeon *M, int x, int y); | |
D_Room *d_random_room(D_Dungeon *M); | |
int d_random_wall(D_Dungeon *M, D_Room *room, D_Point *p); | |
void d_draw(D_Dungeon *M, FILE *f); | |
#ifdef DUNGEON_IMPLEMENTATION | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <assert.h> | |
void d_init(D_Dungeon *M) { | |
M->grid_w = D_GRID_W; | |
M->grid_h = D_GRID_H; | |
M->min_room_size = D_MIN_ROOM_SIZE; | |
M->max_room_size = D_MAX_ROOM_SIZE; | |
M->min_distance = D_MIN_DISTANCE; | |
M->secret_prob = D_SECRET_PROB; | |
M->min_gone_rooms = D_MIN_GONE_ROOMS; | |
M->max_gone_rooms = D_MAX_GONE_ROOMS; | |
M->rooms = NULL; | |
M->map = NULL; | |
M->edges = NULL; | |
M->cells = NULL; | |
} | |
void d_deinit(D_Dungeon *M) { | |
if(M->rooms) | |
free(M->rooms); | |
M->rooms = NULL; | |
if(M->map) | |
free(M->map); | |
M->map = NULL; | |
if(M->edges) | |
free(M->edges); | |
M->edges = NULL; | |
if(M->cells) | |
free(M->cells); | |
M->cells = NULL; | |
} | |
static void _d_reset_generator(D_Dungeon *M) { | |
int i; | |
memset(M->rooms, 0, M->n_rooms * sizeof *M->rooms); | |
for(i = 0; i < M->map_w * M->map_h; i++) { | |
M->map[i] = ' '; | |
} | |
M->n_edges = 0; | |
memset(M->edges, 0, M->max_edges * sizeof *M->edges); | |
} | |
static int _d_init_generator(D_Dungeon *M) { | |
M->map_w = M->grid_w * (M->max_room_size + 1) + 1; | |
M->map_h = M->grid_h * (M->max_room_size + 1) + 1; | |
M->n_rooms = M->grid_w * M->grid_h; | |
M->rooms = malloc(M->n_rooms * sizeof *M->rooms); | |
if(!M->rooms) { | |
return 0; | |
} | |
M->map = malloc(M->map_w * M->map_h * sizeof *M->map); | |
if(!M->map) { | |
free(M->rooms); | |
return 0; | |
} | |
/* Maximum number of edges in a M*N rooms graph is 2MN-M-N */ | |
M->max_edges = (2 * M->grid_w * M->grid_h) - M->grid_w - M->grid_h; | |
M->edges = malloc(M->max_edges * sizeof *M->edges); | |
if(!M->edges) { | |
free(M->rooms); | |
free(M->map); | |
return 0; | |
} | |
return 1; | |
} | |
static int _d_grid_index(D_Dungeon *M, int row, int col) { | |
if(row < 0 || row >= M->grid_h) return -1; | |
if(col < 0 || col >= M->grid_w) return -1; | |
return row * M->grid_w + col; | |
} | |
static int _d_get_unconnected(D_Dungeon *M) { | |
int i, n = 0; | |
assert(M->cells); | |
for(i = 0; i < M->n_rooms; i++) { | |
if(!M->rooms[i]._d_connected) { | |
M->cells[n++] = i; | |
} | |
} | |
return n; | |
} | |
static int _d_get_unconnected_neighbors(D_Dungeon *M, int gi, int n[4]) { | |
int j = 0, i, count; | |
int row = gi / M->grid_w; | |
int col = gi % M->grid_w; | |
i = _d_grid_index(M, row-1, col); | |
if(i >= 0 && !M->rooms[i]._d_connected) | |
n[j++] = i; | |
i = _d_grid_index(M, row+1, col); | |
if(i >= 0 && !M->rooms[i]._d_connected) | |
n[j++] = i; | |
i = _d_grid_index(M, row, col-1); | |
if(i >= 0 && !M->rooms[i]._d_connected) | |
n[j++] = i; | |
i = _d_grid_index(M, row, col+1); | |
if(i >= 0 && !M->rooms[i]._d_connected) | |
n[j++] = i; | |
count = j; | |
while(j < 4) n[j++] = -1; | |
return count; | |
} | |
static int _d_get_connected_neighbors(D_Dungeon *M, int gi, int n[4]) { | |
int j = 0, i, count; | |
int row = gi / M->grid_w; | |
int col = gi % M->grid_w; | |
i = _d_grid_index(M, row-1, col); | |
if(i >= 0 && M->rooms[i]._d_connected) | |
n[j++] = i; | |
i = _d_grid_index(M, row+1, col); | |
if(i >= 0 && M->rooms[i]._d_connected) | |
n[j++] = i; | |
i = _d_grid_index(M, row, col-1); | |
if(i >= 0 && M->rooms[i]._d_connected) | |
n[j++] = i; | |
i = _d_grid_index(M, row, col+1); | |
if(i >= 0 && M->rooms[i]._d_connected) | |
n[j++] = i; | |
count = j; | |
while(j < 4) n[j++] = -1; | |
return count; | |
} | |
static D_Edge *_d_get_edge(D_Dungeon *M, int p, int q) { | |
int i; | |
for(i = 0; i < M->n_edges; i++) { | |
D_Edge *e = &M->edges[i]; | |
if((e->p == p && e->q == q) || (e->p == q && e->q == p)) | |
return e; | |
} | |
return NULL; | |
} | |
static D_Edge *_d_connect(D_Dungeon *M, int from, int to) { | |
D_Edge *e; | |
assert(!_d_get_edge(M, from, to)); | |
assert(M->n_edges < M->max_edges); | |
e = &M->edges[M->n_edges]; | |
e->index = M->n_edges; | |
e->p = from; | |
e->q = to; | |
M->n_edges++; | |
return e; | |
} | |
static int _d_count_exits(D_Dungeon *M, int room) { | |
int i, c = 0; | |
for(i = 0; i < M->n_edges; i++) { | |
if(M->edges[i].p == room || M->edges[i].q == room) | |
c++; | |
} | |
return c; | |
} | |
static int _d_layout(D_Dungeon *M) { | |
int count; | |
int neighbours[4]; | |
int curr, next, split, c, attempts = 0; | |
D_Edge *edge; | |
if(!_d_init_generator(M)) | |
return 0; | |
M->cells = calloc(M->n_rooms, sizeof *M->cells); | |
if(!M->cells) | |
return 0; | |
do { | |
_d_reset_generator(M); | |
count = 0; | |
curr = D_RAND(M->n_rooms); | |
M->start_room = curr; | |
for(;;) { | |
M->cells[count++] = curr; | |
M->rooms[curr]._d_connected = 1; | |
M->rooms[curr].flags |= D_FLAG_PATH_ROOM; | |
c = _d_get_unconnected_neighbors(M, curr, neighbours); | |
if(!c) { | |
M->end_room = curr; | |
break; | |
} | |
next = neighbours[D_RAND(c)]; | |
_d_connect(M, curr, next); | |
curr = next; | |
} | |
} while (count < M->min_distance && ++attempts < 10); | |
/* Color the first half of the rooms BLUE and the | |
second half of the rooms RED. We put a locked door | |
between the BLUE and RED rooms. */ | |
split = count / 2; | |
curr = M->cells[split]; | |
next = M->cells[split+1]; | |
assert(curr >= 0 && next >= 0); | |
edge = _d_get_edge(M, curr, next); | |
assert(edge != NULL); | |
edge->door = 1; | |
for(c = 0; c < count; c++) { | |
int i = M->cells[c]; | |
D_Room *r = &M->rooms[i]; | |
if(c <= split) | |
r->color = D_COLOR_BLUE; | |
else | |
r->color = D_COLOR_RED; | |
} | |
#if 1 | |
/* Connect all the un_d_connected rooms... */ | |
attempts = 0; | |
for(;;) { | |
c = _d_get_unconnected(M); | |
if(!c) break; | |
curr = M->cells[ D_RAND(c) ]; | |
c = _d_get_connected_neighbors(M, curr, neighbours); | |
if(!c) continue; | |
next = neighbours[ D_RAND(c) ]; | |
_d_connect(M, curr, next); | |
M->rooms[curr]._d_connected = 1; | |
/* Give it the same color */ | |
M->rooms[curr].color = M->rooms[next].color; | |
/* Mark it as an "Extra D_Room". You might want to | |
use the knowledge to use the knowledge later to | |
hide a treasure there, or make it a secret room */ | |
M->rooms[curr].flags |= D_FLAG_XTRA_ROOM; | |
} | |
#endif | |
#if 1 | |
/* Count the number of blue rooms */ | |
for(curr = 0, c = 0; curr < M->n_rooms; curr++) { | |
if(M->rooms[curr].color == D_COLOR_BLUE) | |
c++; | |
} | |
/* If there are more Red rooms (`n_rooms - c`) than blue rooms (`c`), | |
swap everything around */ | |
if(M->n_rooms - c > c) { | |
int t = M->start_room; | |
M->start_room = M->end_room; | |
M->end_room = t; | |
for(curr = 0, c = 0; curr < M->n_rooms; curr++) { | |
if(M->rooms[curr].color == D_COLOR_BLUE) | |
M->rooms[curr].color = D_COLOR_RED; | |
else | |
M->rooms[curr].color = D_COLOR_BLUE; | |
} | |
} | |
#endif | |
M->rooms[M->start_room].flags |= D_FLAG_START_ROOM; | |
M->rooms[M->end_room].flags |= D_FLAG_END_ROOM; | |
#if 1 | |
count = D_RAND((M->grid_w + M->grid_h + 1)/2) + 1; | |
while(count > 0) { | |
int row, col, door; | |
curr = D_RAND(M->n_rooms); | |
row = curr / M->grid_w; | |
col = curr % M->grid_w; | |
if(++attempts > M->n_rooms) { | |
/* Too many attempts - bail out | |
Since I made it possible to add a door | |
between rooms with different colors, I | |
don't think this would be a problem */ | |
break; | |
} | |
/* Find candidate neighbours */ | |
c = 0; | |
next = _d_grid_index(M, row-1, col); | |
if(next >= 0 && !_d_get_edge(M, curr, next)) | |
neighbours[c++] = next; | |
next = _d_grid_index(M, row+1, col); | |
if(next >= 0 && !_d_get_edge(M, curr, next)) | |
neighbours[c++] = next; | |
next = _d_grid_index(M, row, col-1); | |
if(next >= 0 && !_d_get_edge(M, curr, next)) | |
neighbours[c++] = next; | |
next = _d_grid_index(M, row, col+1); | |
if(next >= 0 && !_d_get_edge(M, curr, next)) | |
neighbours[c++] = next; | |
if(c == 0) { | |
continue; | |
} | |
next = neighbours[ D_RAND(c) ]; | |
/* If the two rooms have different colors, put a locked door between them */ | |
door = M->rooms[curr].color != M->rooms[next].color; | |
if(door) { | |
if(M->rooms[curr].flags & (D_FLAG_START_ROOM | D_FLAG_END_ROOM) | |
|| M->rooms[next].flags & (D_FLAG_START_ROOM | D_FLAG_END_ROOM)) { | |
/* don't put the door in the start/end room - it might make the | |
map too easy (as I found through experimentation) */ | |
continue; | |
} | |
} | |
edge = _d_connect(M, curr, next); | |
edge->door = door; | |
count--; | |
} | |
#endif | |
#if 1 | |
/* Make gone rooms */ | |
count = D_RAND(M->max_gone_rooms - M->min_gone_rooms + 1) + M->min_gone_rooms; | |
attempts = 0; | |
while(count > 0) { | |
if(++attempts > 10) break; | |
curr = D_RAND(M->n_rooms); | |
if(_d_count_exits(M, curr) < 2) | |
continue; | |
if(M->rooms[curr].flags & (D_FLAG_GONE_ROOM | D_FLAG_START_ROOM | D_FLAG_END_ROOM)) | |
continue; | |
M->rooms[curr].flags |= D_FLAG_GONE_ROOM; | |
count--; | |
} | |
#endif | |
for(curr = 0; curr < M->n_rooms; curr++) { | |
D_Room *room = &M->rooms[curr]; | |
int row = curr / M->grid_w; | |
int col = curr % M->grid_w; | |
if(M->rooms[curr].flags & D_FLAG_GONE_ROOM) { | |
room->w = 1; | |
room->h = 1; | |
room->x = col * (M->max_room_size + 1) + 1 + M->max_room_size/2; | |
room->y = row * (M->max_room_size + 1) + 1 + M->max_room_size/2; | |
} else { | |
room->w = D_RAND(M->max_room_size - M->min_room_size + 1) + M->min_room_size; | |
room->h = D_RAND(M->max_room_size - M->min_room_size + 1) + M->min_room_size; | |
room->x = col * (M->max_room_size + 1) + 1 + (M->max_room_size - room->w + 1)/2; | |
room->y = row * (M->max_room_size + 1) + 1 + (M->max_room_size - room->h + 1)/2; | |
} | |
} | |
/* Shift the edges up/down or left/right a bit */ | |
for(curr = 0; curr < M->n_edges; curr++) { | |
M->edges[curr].offset = D_RAND(3)-1; | |
} | |
free(M->cells); | |
M->cells = NULL; | |
return 1; | |
} | |
int d_is_wall(D_Dungeon *M, int x, int y) { | |
char t; | |
if(x < 0 || x >= M->map_w || y < 0 || y >= M->map_h) | |
return 1; | |
t = M->map[ y * M->map_w + x ]; | |
return t == D_TILE_EMPTY || t == D_TILE_WALL; | |
} | |
int d_is_floor(D_Dungeon *M, int x, int y) { | |
char t; | |
if(x < 0 || x >= M->map_w || y < 0 || y >= M->map_h) | |
return 0; | |
t = M->map[ y * M->map_w + x ]; | |
return t == D_TILE_FLOOR_EDGE || t == D_TILE_FLOOR; | |
} | |
static int _d_is_tile(D_Dungeon *M, int x, int y, char tile) { | |
if(x < 0 || x >= M->map_w || y < 0 || y >= M->map_h) | |
return 0; | |
return (M->map[ y * M->map_w + x ] == tile); | |
} | |
void d_set_tile(D_Dungeon *M, int x, int y, char tile) { | |
if(x < 0 || x >= M->map_w || y < 0 || y >= M->map_h) | |
return; | |
M->map[ y * M->map_w + x ] = tile; | |
} | |
char d_get_tile(D_Dungeon *M, int x, int y) { | |
if(x < 0 || x >= M->map_w || y < 0 || y >= M->map_h) | |
return D_TILE_EMPTY; | |
return M->map[ y * M->map_w + x ]; | |
} | |
static int _d_has_neighbour(D_Dungeon *M, int x, int y, char tile) { | |
return (_d_is_tile(M, x - 1, y, tile) || | |
_d_is_tile(M, x + 1, y, tile) || | |
_d_is_tile(M, x, y - 1, tile) || | |
_d_is_tile(M, x, y + 1, tile)); | |
} | |
int d_random_wall(D_Dungeon *M, D_Room *room, D_Point *p) { | |
int attempts = 0, x, y; | |
do { | |
switch(D_RAND(4)) { | |
case 0: | |
x = room->x - 1; | |
y = room->y + D_RAND(room->h); | |
if(d_is_floor(M, x + 1, y) && d_is_wall(M, x - 1, y) && d_is_wall(M, x, y - 1) && d_is_wall(M, x, y + 1)) { | |
p->x = x; p->y = y; | |
return 1; | |
} | |
break; | |
case 1: | |
x = room->x + room->w; | |
y = room->y + D_RAND(room->h); | |
if(d_is_floor(M, x - 1, y) && d_is_wall(M, x + 1, y) && d_is_wall(M, x, y - 1) && d_is_wall(M, x, y + 1)) { | |
p->x = x; p->y = y; | |
return 1; | |
} | |
break; | |
case 2: | |
x = room->x + D_RAND(room->w); | |
y = room->y - 1; | |
if(d_is_floor(M, x, y + 1) && d_is_wall(M, x, y - 1) && d_is_wall(M, x - 1, y) && d_is_wall(M, x + 1, y)) { | |
p->x = x; p->y = y; | |
return 1; | |
} | |
break; | |
case 3: | |
x = room->x + D_RAND(room->w); | |
y = room->y + room->h; | |
if(d_is_floor(M, x, y - 1) && d_is_wall(M, x, y + 1) && d_is_wall(M, x - 1, y) && d_is_wall(M, x + 1, y)) { | |
p->x = x; p->y = y; | |
return 1; | |
} | |
break; | |
} | |
} while(++attempts < 8); | |
return 0; | |
} | |
static int _d_edge_direction(int p, int q) { | |
if(q < p) { | |
if(q == p - 1) { | |
return D_WEST; | |
} else { | |
return D_NORTH; | |
} | |
} else { | |
if(q == p + 1) { | |
return D_EAST; | |
} else { | |
return D_SOUTH; | |
} | |
} | |
} | |
static const char *_d_dir_name(int dir) { | |
switch(dir) { | |
case D_NORTH: return "North"; | |
case D_SOUTH: return "South"; | |
case D_EAST: return "East"; | |
case D_WEST: return "West"; | |
} | |
return ""; | |
} | |
static int _d_traverse_room(D_Dungeon *M, int idx, int prev) { | |
int i, e = 0, exits[4]; | |
D_Edge *edges[4]; | |
D_Point exit_points[4]; | |
D_Room *room = &M->rooms[idx]; | |
if(room->visited) return 0; | |
room->visited = 1; | |
printf("Room %d:\n", idx); | |
for(i = 0; i < M->n_edges; i++) { | |
if(M->edges[i].p == idx) { | |
assert(e < 4); | |
exits[e] = M->edges[i].q; | |
edges[e] = &M->edges[i]; | |
e++; | |
} else if(M->edges[i].q == idx) { | |
assert(e < 4); | |
exits[e] = M->edges[i].p; | |
edges[e] = &M->edges[i]; | |
e++; | |
} | |
} | |
for(i = 0; i < e; i++) { | |
int dir = _d_edge_direction(idx, exits[i]); | |
printf(" * %s %d %c\n", _d_dir_name(dir), exits[i], exits[i]==prev?'*':' '); | |
switch(dir) { | |
case D_NORTH: | |
exit_points[i].x = edges[i]->x; | |
exit_points[i].y = edges[i]->y + edges[i]->h - 1; | |
break; | |
case D_SOUTH: | |
exit_points[i].x = edges[i]->x; | |
exit_points[i].y = edges[i]->y; | |
break; | |
case D_WEST: | |
exit_points[i].x = edges[i]->x + edges[i]->w - 1; | |
exit_points[i].y = edges[i]->y; | |
break; | |
case D_EAST: | |
exit_points[i].x = edges[i]->x; | |
exit_points[i].y = edges[i]->y; | |
break; | |
} | |
} | |
for(i = 0; i < e; i++) { | |
int s; | |
if(exits[i] == prev) continue; | |
s = _d_traverse_room(M, exits[i], idx); | |
if(s == 1 && !(room->flags & D_FLAG_GONE_ROOM) && (M->rooms[ exits[i] ].flags & D_FLAG_XTRA_ROOM)) { | |
if(D_RAND(100) > M->secret_prob) continue; | |
M->rooms[ exits[i] ].flags |= D_FLAG_SECRET_ROOM; | |
d_set_tile(M, exit_points[i].x, exit_points[i].y, D_TILE_SECRET); | |
} | |
} | |
return e; | |
} | |
static void _d_postprocess(D_Dungeon *M) { | |
int i, j, start, placed; | |
D_Room *room; | |
D_Edge *edge; | |
D_Point point; | |
for(j = 0; j < M->grid_h; j++) { | |
for(i = 0; i < M->grid_w; i++) { | |
D_Room *room = &M->rooms[_d_grid_index(M, j, i)]; | |
int p, q; | |
if(!room->_d_connected || (room->flags & D_FLAG_GONE_ROOM)) { | |
d_set_tile(M, room->x, room->y, D_TILE_CROSSING); | |
continue; | |
} | |
for(p = 0; p < room->w; p++) { | |
for(q = 0; q < room->h; q++) { | |
d_set_tile(M, room->x + p, room->y + q, D_TILE_FLOOR); | |
} | |
} | |
} | |
} | |
/* Draw all the edges, computing their bounding boxes in the process */ | |
for(i = 1; i < M->n_rooms; i++) { | |
int row = i / M->grid_w; | |
int col = i % M->grid_w; | |
int x = col * (M->max_room_size + 1) + 1 + (M->max_room_size)/2; | |
int y = row * (M->max_room_size + 1) + 1 + (M->max_room_size)/2; | |
if(col > 0) { | |
int left = i - 1; | |
int maxx, minx, tx = x; | |
edge = _d_get_edge(M, i, left); | |
if(edge) { | |
edge->direction = D_ORIENT_EW; | |
if((M->rooms[i].flags & D_FLAG_GONE_ROOM) || (M->rooms[left].flags & D_FLAG_GONE_ROOM)) | |
edge->y = y; | |
else | |
edge->y = y + edge->offset; | |
start = edge->y * M->map_w + x; | |
edge->h = 1; | |
maxx = x - (M->max_room_size + 1); | |
minx = x; | |
for(j = 0; j <= M->max_room_size + 1; j++, tx--) { | |
if(M->map[ start - j ] != D_TILE_EMPTY) | |
continue; | |
if(tx < minx) | |
minx = tx; | |
if(tx > maxx) | |
maxx = tx; | |
if(edge->door && j == M->max_room_size/2 + 1) | |
M->map[ start - j ] = D_TILE_LOCKED_DOOR; | |
else | |
M->map[ start - j ] = D_TILE_CORRIDOR; | |
} | |
edge->x = minx; | |
edge->w = maxx - minx + 1; | |
} | |
} | |
if(row > 0) { | |
int up = i - M->grid_w; | |
int maxy, miny, ty = y; | |
edge = _d_get_edge(M, i, up); | |
if(edge) { | |
edge->direction = D_ORIENT_NS; | |
if((M->rooms[i].flags & D_FLAG_GONE_ROOM) || (M->rooms[up].flags & D_FLAG_GONE_ROOM)) | |
edge->x = x; | |
else | |
edge->x = x + edge->offset; | |
start = y * M->map_w + edge->x; | |
edge->w = 1; | |
maxy = y - (M->max_room_size + 1); | |
miny = y; | |
for(j = 0; j <= M->max_room_size; j++, ty--) { | |
if(M->map[start - j * M->map_w] != D_TILE_EMPTY) | |
continue; | |
if(ty < miny) | |
miny = ty; | |
if(ty > maxy) | |
maxy = ty; | |
if(edge->door && j == M->max_room_size/2 + 1) | |
M->map[ start - j * M->map_w ] = D_TILE_LOCKED_DOOR; | |
else | |
M->map[ start - j * M->map_w ] = D_TILE_CORRIDOR; | |
} | |
edge->y = miny; | |
edge->h = maxy - miny + 1; | |
} | |
} | |
} | |
for(j = 0; j < M->map_h; j++) { | |
for(i = 0; i < M->map_w; i++) { | |
if(d_is_wall(M, i, j) && ( | |
!d_is_wall(M, i - 1, j) || | |
!d_is_wall(M, i + 1, j) || | |
!d_is_wall(M, i, j - 1) || | |
!d_is_wall(M, i, j + 1) || | |
/* Diagonal checks only makes it look nice in ASCII: */ | |
!d_is_wall(M, i - 1, j - 1) || | |
!d_is_wall(M, i + 1, j - 1) || | |
!d_is_wall(M, i - 1, j + 1) || | |
!d_is_wall(M, i + 1, j + 1) | |
)) { | |
d_set_tile(M, i, j, D_TILE_WALL); | |
} | |
if(_d_is_tile(M, i, j, D_TILE_CORRIDOR) && _d_has_neighbour(M, i, j, D_TILE_FLOOR)) { | |
D_Edge * e = d_corridor_at(M, i, j); | |
if(!e->door) | |
d_set_tile(M, i, j, D_TILE_THRESHOLD); | |
} | |
} | |
} | |
/* Place the dungeon entrance and exit */ | |
room = &M->rooms[M->start_room]; | |
placed = d_random_wall(M, room, &point); | |
if(placed) { | |
d_set_tile(M, point.x, point.y, D_TILE_ENTRANCE); | |
} else { | |
/* Ugly, but better than nothing */ | |
d_set_tile(M, room->x + room->w/2, room->y + room->h/2, D_TILE_ENTRANCE); | |
} | |
room = &M->rooms[M->end_room]; | |
placed = d_random_wall(M, room, &point); | |
if(placed) { | |
d_set_tile(M, point.x, point.y, D_TILE_EXIT); | |
} else { | |
d_set_tile(M, room->x + room->w/2, room->y + room->h/2, D_TILE_EXIT); | |
} | |
for(j = 0; j < M->map_h; j++) { | |
for(i = 0; i < M->map_w; i++) { | |
if(_d_is_tile(M, i, j, D_TILE_FLOOR) && _d_has_neighbour(M, i, j, D_TILE_WALL) && | |
!(_d_has_neighbour(M, i, j, D_TILE_THRESHOLD) | |
|| _d_has_neighbour(M, i, j, D_TILE_CORRIDOR) | |
|| _d_has_neighbour(M, i, j, D_TILE_LOCKED_DOOR) | |
|| _d_has_neighbour(M, i, j, D_TILE_ENTRANCE) | |
|| _d_has_neighbour(M, i, j, D_TILE_EXIT) | |
|| _d_has_neighbour(M, i, j, D_TILE_SECRET))) | |
M->map[ j * M->map_w + i ] = D_TILE_FLOOR_EDGE; | |
} | |
} | |
_d_traverse_room(M, M->start_room, -1); | |
} | |
int d_generate(D_Dungeon *D) { | |
if(!_d_layout(D)) | |
return 0; | |
_d_postprocess(D); | |
return 1; | |
} | |
D_Room *d_room_at(D_Dungeon *M, int x, int y) { | |
int col = (x - 1) / (M->max_room_size + 1); | |
int row = (y - 1) / (M->max_room_size + 1); | |
int i = _d_grid_index(M, row, col); | |
D_Room *r = &M->rooms[i]; | |
if(x >= r->x && x < r->x + r->w && y >= r->y && y < r->y + r->h) | |
return r; | |
return NULL; | |
} | |
D_Edge *d_corridor_at(D_Dungeon *M, int x, int y) { | |
int i; | |
for(i = 0; i < M->n_edges; i++) { | |
D_Edge *e = &M->edges[i]; | |
if(x >= e->x && x < e->x + e->w && y >= e->y && y < e->y + e->h) | |
return e; | |
} | |
return NULL; | |
} | |
D_Room *d_random_room(D_Dungeon *M) { | |
D_Room *r; | |
do { | |
r = &M->rooms[D_RAND(M->n_rooms)]; | |
} while(r->flags & D_FLAG_GONE_ROOM); | |
return r; | |
} | |
void d_draw(D_Dungeon *M, FILE *f) { | |
int i, j; | |
for(j = 0; j < M->map_h; j++) { | |
for(i = 0; i < M->map_w; i++) { | |
putc(d_get_tile(M, i, j), f); | |
} | |
putc('\n', f); | |
} | |
} | |
#endif /* DUNGEON_IMPLEMENTATION */ | |
#endif /* DUNGEON_H */ | |
This file contains hidden or 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
#include <stdio.h> | |
#include <stdlib.h> | |
#include <time.h> | |
#include <assert.h> | |
#define DUNGEON_IMPLEMENTATION | |
#include "dungeon.c" | |
#define POISSON_IMPLEMENTATION | |
#include "poisson.c" | |
static float frand() { | |
return ((float)rand())/RAND_MAX; | |
} | |
static int wall_constraint(struct poisson *P, float x, float y) { | |
D_Dungeon *M = P->data; | |
float fx, fy; | |
int mx, my; | |
(void)P; | |
x /= 10.0; | |
y /= 10.0; | |
mx = (int)x; | |
my = (int)y; | |
fx = x - mx; | |
fy = y - my; | |
if(!d_is_wall(M, mx, my)) return 1; | |
if(fx < 0.3 && !d_is_wall(M, mx - 1, my)) return 1; | |
if(fx > 0.7 && !d_is_wall(M, mx + 1, my)) return 1; | |
if(fy < 0.3 && !d_is_wall(M, mx, my - 1)) return 1; | |
if(fy > 0.7 && !d_is_wall(M, mx, my + 1)) return 1; | |
return 0; | |
} | |
static void drawSvg(D_Dungeon *M, FILE *f) { | |
int i, j; | |
struct poisson P; | |
D_Room *r; | |
poisson_init(&P); | |
P.r = 2.75; | |
P.w = (M->map_w+1) * 10; | |
P.h = (M->map_h+1) * 10; | |
P.x0.y = (M->rooms[0].y + M->rooms[0].h/2) * 10; | |
P.x0.x = (M->rooms[0].x + M->rooms[0].w/2) * 10; | |
P.constraint = wall_constraint; | |
P.data = M; | |
if(!poisson_plot(&P)) { | |
fprintf(stderr, "Couldn't do Poisson Disk thing :(\n"); | |
/* return; */ | |
} | |
fprintf(f, "<svg viewBox=\"0 0 %d %d\" xmlns=\"http://www.w3.org/2000/svg\">\n", M->map_w * 10, M->map_h * 10); | |
fprintf(f, " <defs>\n"); | |
fprintf(f, " <symbol id=\"floor\" width=\"12\" height=\"12\" viewBox=\"-1 -1 12 12\">\n"); | |
fprintf(f, " <rect x=\"-0.2\" y=\"-0.2\" width=\"10.4\" height=\"10.4\" fill=\"#FFF\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, " <symbol id=\"wall-l\" width=\"12\" height=\"12\" viewBox=\"-1 -1 12 12\">\n"); | |
fprintf(f, " <path d=\"M 0 0 C 0.2 2.5, -0.2 7.5, 0 10\" stroke=\"black\" fill=\"none\" stroke-width=\"0.5\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, " <symbol id=\"wall-r\" width=\"12\" height=\"12\" viewBox=\"-1 -1 12 12\">\n"); | |
fprintf(f, " <path d=\"M 10 0 C 10.2 2.5, 9.8 7.5, 10 10\" stroke=\"black\" fill=\"none\" stroke-width=\"0.5\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, " <symbol id=\"wall-t\" width=\"12\" height=\"12\" viewBox=\"-1 -1 12 12\">\n"); | |
fprintf(f, " <path d=\"M 0 0 C 2.5 0.2, 7.5 -0.2, 10 0\" stroke=\"black\" fill=\"none\" stroke-width=\"0.5\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, " <symbol id=\"wall-b\" width=\"12\" height=\"12\" viewBox=\"-1 -1 12 12\">\n"); | |
fprintf(f, " <path d=\"M 0 10 C 2.5 10.2, 7.5 9.8, 10 10\" stroke=\"black\" fill=\"none\" stroke-width=\"0.5\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, " <symbol id=\"floor-r1\" width=\"12\" height=\"12\" viewBox=\"-1 -1 12 12\">\n"); | |
fprintf(f, " <path d=\"M 10 0 C 10.2 2.5, 9.8 7.5, 10 10\" stroke=\"black\" fill=\"none\" stroke-width=\"0.125\" stroke-dasharray=\"0.3 1.5 1 2.5 0.4 2\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, " <symbol id=\"floor-r2\" width=\"12\" height=\"12\" viewBox=\"-1 -1 12 12\">\n"); | |
fprintf(f, " <path d=\"M 10 0 C 10.2 2.5, 9.8 7.5, 10 10\" stroke=\"black\" fill=\"none\" stroke-width=\"0.125\" stroke-dasharray=\"0.7 2.5 0.7 1.5 0.5 2\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, " <symbol id=\"floor-r3\" width=\"12\" height=\"12\" viewBox=\"-1 -1 12 12\">\n"); | |
fprintf(f, " <path d=\"M 10 0 C 10.2 2.5, 9.8 7.5, 10 10\" stroke=\"black\" fill=\"none\" stroke-width=\"0.125\" stroke-dasharray=\"0.7 1.5 0.7 1 0.5 2\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, " <symbol id=\"floor-b1\" width=\"12\" height=\"12\" viewBox=\"-1 -1 12 12\">\n"); | |
fprintf(f, " <path d=\"M 0 10 C 2.5 10.2, 7.5 9.8, 10 10\" stroke=\"black\" fill=\"none\" stroke-width=\"0.125\" stroke-dasharray=\"0.3 1.5 1 2.5 0.4 2\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, " <symbol id=\"floor-b2\" width=\"12\" height=\"12\" viewBox=\"-1 -1 12 12\">\n"); | |
fprintf(f, " <path d=\"M 0 10 C 2.5 10.2, 7.5 9.8, 10 10\" stroke=\"black\" fill=\"none\" stroke-width=\"0.125\" stroke-dasharray=\"0.7 2.5 0.7 1.5 0.5 2\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, " <symbol id=\"floor-b3\" width=\"12\" height=\"12\" viewBox=\"-1 -1 12 12\">\n"); | |
fprintf(f, " <path d=\"M 0 10 C 2.5 10.2, 7.5 9.8, 10 10\" stroke=\"black\" fill=\"none\" stroke-width=\"0.125\" stroke-dasharray=\"0.7 1.5 0.7 1 0.5 2\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, " <symbol id=\"blobs\" width=\"6\" height=\"6\" viewBox=\"-3 -3 6 6\" x=\"-3\" y=\"-3\">\n"); | |
fprintf(f, " <path d=\"M -0.01 -2.88 C -0.86 -2.92 -1.02 -2.89 -2.22 -2.13 C -2.58 -1.96 -2.83 -1.13 -2.89 -0.08 C -3 0.63 -2.61 1.18 -2.2 2.01 C -1.66 2.77 -0.59 2.73 -0.01 2.73 C 0.69 2.8 1.79 2.73 2.17 2.47 C 2.84 2.24 2.86 0.82 2.85 -0.05 C 2.83 -0.68 2.78 -2.05 2.29 -2.1 C 1.6 -2.31 1.21 -2.92 0 -2.89\"\n"); | |
fprintf(f, " fill=\"#EEE\" stroke=\"none\"/>\n"); | |
fprintf(f, " </symbol>\n\n"); | |
fprintf(f, "\n"); | |
fprintf(f, " <symbol id=\"strokes\" width=\"4\" height=\"4\" viewBox=\"-2 -2 4 4\" x=\"-2\" y=\"-2\">\n"); | |
fprintf(f, " <path d=\"M-1.5,-1.5 C -1.75,-0.5 -1.25,0.5 -1.5,1.5 M0,-1.75 C -0.2,-0.5 0.2,0.5 0,1.5 M1.5,-1.5 C 1.75,-0.5 1.25,0.5 1.5,1.5\"\n"); | |
fprintf(f, " fill=\"none\" stroke=\"black\" stroke-width=\"0.25\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol>\n\n"); | |
fprintf(f, "\n"); | |
fprintf(f, " <symbol id=\"stone1\" width=\"2\" height=\"2\" viewBox=\"-1 -1 2 2\" x=\"-1\" y=\"-1\">\n"); | |
fprintf(f, " <path d=\"M -0.66 -0.7 Q -0.96 -0.45 -0.88 0.17 Q -0.9 0.4 -0.5 0.63 Q 0.065 0.87 0.44 0.76 C 0.9 0.56 1 -0.78 0.59 -0.8 C 0.15 -0.87 -0.54 -1.01 -0.66 -0.7 Z\" \n"); | |
fprintf(f, " fill=\"white\" stroke=\"black\" stroke-width=\"0.25\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol> \n"); | |
fprintf(f, "\n"); | |
fprintf(f, " <symbol id=\"stone2\" width=\"2\" height=\"2\" viewBox=\"-1 -1 2 2\" x=\"-1\" y=\"-1\">\n"); | |
fprintf(f, " <path d=\"M -0.34 -0.79 Q -0.69 -0.53 -0.73 -0.29 Q -0.73 -0.05 -0.47 0.14 Q -0.33 0.37 0.05 0.33 C 0.53 -0.05 0.62 -0.41 0.16 -0.65 C 0.15 -0.87 -0.23 -0.92 -0.34 -0.79 Z M 0.61 0.22 C 0.44 0.12 0.22 0.28 0.24 0.56 C 0.29 0.88 0.56 0.84 0.69 0.69 C 0.7333 0.5667 0.7767 0.4433 0.61 0.22\" \n"); | |
fprintf(f, " fill=\"white\" stroke=\"black\" stroke-width=\"0.25\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, "\n"); | |
fprintf(f, " <symbol id=\"stone3\" width=\"2\" height=\"2\" viewBox=\"-1 -1 2 2\" x=\"-1\" y=\"-1\">\n"); | |
fprintf(f, " <path d=\"M -0.57 -0.41 Q -0.89 -0.15 -0.88 0.17 Q -0.77 0.57 -0.27 0.47 Q 0.28 0.58 0.46 0.37 C 0.62 0.23 0.73 -0.1 0.46 -0.41 C 0.28 -0.64 -0.15 -0.85 -0.56 -0.41\" \n"); | |
fprintf(f, " fill=\"white\" stroke=\"black\" stroke-width=\"0.25\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, "\n"); | |
fprintf(f, " <symbol id=\"door-floor\" width=\"12\" height=\"12\" viewBox=\"-1 -1 12 12\">\n"); | |
fprintf(f, " <path d=\"M 0 0 L 3 0 L 3 1.3 L 7 1.3 L 7 0.01 L 10 0 L 10 10 L 7 10 L 7 8.7 L 3 8.7 L 3 10 L 0 10 Z\" \n"); | |
fprintf(f, " fill=\"white\" stroke=\"none\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, "\n"); | |
fprintf(f, " <symbol id=\"doorway-floor\" width=\"12\" height=\"12\" viewBox=\"-1 -1 12 12\">\n"); | |
fprintf(f, " <path d=\"M 2 0 L 2 2 L 0 2 L 0 10 L 10 10 L 10 2 L 8 2 L 8 0 Z \" \n"); | |
fprintf(f, " fill=\"white\" stroke=\"none\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, " <symbol id=\"doorway\" width=\"12\" height=\"12\" viewBox=\"-1 -1 12 12\">\n"); | |
fprintf(f, " <path d=\"M 0 0 C 0.46 0.07 0.77 -0.07 2 0 C 2.07 0.27 1.92 0.43 2 2 C 1.43 1.92 1.21 2.03 0 2 C 0.07 2.9 -0.14 4.05 0 10 M 10 10 C 9.83 3.45 10.1 2.91 10 2 C 8.64 1.87 8.51 2.03 8 2 C 8.12 0.62 7.93 0.33 8 0 C 9.21 -0.07 9.31 0.07 10 0 \" \n"); | |
fprintf(f, " fill=\"none\" stroke=\"black\" stroke-width=\"0.5\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, " <symbol id=\"secret-door\" width=\"12\" height=\"12\" viewBox=\"-1 -1 12 12\">\n"); | |
fprintf(f, " <path d=\"M 0 0 C 0.46 0.07 0.77 -0.07 2 0 C 2.07 0.27 1.92 0.43 2 2 C 1.43 1.92 1.21 2.03 0 2 C 0.07 2.9 -0.14 4.05 0 10 M 10 10 C 9.83 3.45 10.1 2.91 10 2 C 8.64 1.87 8.51 2.03 8 2 C 8.12 0.62 7.93 0.33 8 0 C 9.21 -0.07 9.31 0.07 10 0 M 2 1 C 5 -2 5 4 8 1\" \n"); | |
fprintf(f, " fill=\"none\" stroke=\"black\" stroke-width=\"0.5\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, "\n"); | |
fprintf(f, " <symbol id=\"door\" width=\"12\" height=\"12\" viewBox=\"-1 -1 12 12\">\n"); | |
fprintf(f, " <path d=\"M 0 0 C 2.1 0.17 2.53 0.12 2.94 0.03 C 2.99 0.51 3.02 0.87 2.99 1.36 C 4.16 1.3 6.05 1.35 7 1.39 C 7.03 0.84 7.01 0.42 6.96 0.04 C 8.22 -0.08 8.95 0.12 10 0 M 10 10 C 7.94 9.96 7.31 9.96 6.93 9.98 C 6.85 9.36 6.83 8.95 6.9 8.51 C 5.04 8.48 4.29 8.53 3.01 8.58 C 3.04 8.93 3.12 9.47 3.09 10.04 C 2.76 10.08 1.26 9.82 0 10 M 3.59 1.35 C 3.58 5.91 3.67 7.09 3.66 8.51 M 6.52 8.46 C 6.51 6.54 6.33 3.25 6.31 1.38 M 3.64 5.09 C 5.1 5.07 5.36 5.12 6.44 5.06\" \n"); | |
fprintf(f, " fill=\"none\" stroke=\"black\" stroke-width=\"0.5\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol> \n"); | |
fprintf(f, "\n"); | |
fprintf(f, " <symbol id=\"stairs\" width=\"12\" height=\"12\" viewBox=\"-6 -6 12 12\">\n"); | |
fprintf(f, " <path d=\"M -3.86 -3.8 C -1.31 -3.77 1.61 -3.67 4.23 -3.7 M -1.67 3.19 C -0.45 3.16 0.75 3.19 1.68 3.29 M -3.16 -1.39 C -0.9433 -1.4667 1.0133 -1.4733 3.35 -1.51 M -2.43 0.97 C -0.97 0.86 1.05 0.89 2.45 0.91\" \n"); | |
fprintf(f, " fill=\"none\" stroke=\"black\" stroke-width=\"0.5\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, "\n"); | |
fprintf(f, " <symbol id=\"cobwebs\" width=\"12\" height=\"12\" viewBox=\"-6 -6 12 12\">\n"); | |
fprintf(f, " <path d=\"M -5 -5 M -5 -5 M -5 -5 C -4.51 -3.32 -3.86 -1.71 -3.37 -0.02 M -5 -5 C -4.01 -3.48 -2.8 -2.01 -1.95 -0.71 M -5 -5 C -3.43 -4.04 -2 -2.8 -0.77 -1.97 M -5 -5 C -3.35 -4.62 -1.78 -4.05 -0.21 -3.61 M -4.98 -0.56 C -4.74 -1.19 -4.33 -1.57 -3.75 -1.19 C -3.6 -1.75 -3.37 -1.9 -2.76 -1.85 C -2.79 -2.55 -2.42 -2.69 -1.94 -2.85 C -2 -3.4 -1.72 -3.76 -1.03 -3.86 C -1.5 -4.44 -1.38 -4.78 -0.92 -4.99 M -5.04 -2.65 C -4.85 -3.12 -4.62 -3.12 -4.29 -2.83 C -4.22 -3.16 -4.08 -3.4 -3.69 -3.14 C -3.63 -3.6 -3.49 -3.75 -3.14 -3.76 C -3.3 -4.11 -3.19 -4.34 -2.85 -4.4 C -3.07 -4.72 -3.05 -4.9 -2.67 -5.01\" \n"); | |
fprintf(f, " fill=\"none\" stroke=\"black\" stroke-width=\"0.25\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, "\n"); | |
fprintf(f, " <symbol id=\"statue\" width=\"12\" height=\"12\" viewBox=\"-1 -1 12 12\">\n"); | |
fprintf(f, " <path d=\"M 1.58 4.88 C 1.39 3.5 3.32 1.79 5.08 2.06 C 6.56 2.23 7.33 3.04 7.7 4.69 C 7.58 8.01 5.18 7.56 4.9 7.64 C 3.9 7.53 2.22 7.34 1.95 5\" \n"); | |
fprintf(f, " fill=\"none\" stroke=\"black\" stroke-width=\"0.5\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " <path d=\"M 5.01 2 c -0.55 0.38 -0.62 1.84 -0.87 1.83 C 3.19 3.88 1.83 3.76 1.73 3.93 C 1.69 4.36 3.69 5.04 3.48 5.24 C 3.61 5.49 2.87 7.18 3.09 7.37 C 3.67 7.55 4.64 5.92 4.88 5.94 C 5.09 5.7 6.56 7.31 6.8 7.2 C 6.99 6.95 6.14 5.28 6.38 5.2 C 6.66 4.79 7.33 4.5 7.61 3.9 C 7.46 3.82 5.72 4.09 5.68 3.86 C 5.39 3.5 5.26 1.81 5.02 2\" \n"); | |
fprintf(f, " stroke=\"none\" fill=\"black\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, "\n"); | |
fprintf(f, " <symbol id=\"crack-t\" width=\"12\" height=\"12\" viewBox=\"-1 -1 12 12\">\n"); | |
fprintf(f, " <path d=\"M 4.19 0 C 2.91 1.02 2.79 0.98 2.2 1.49 C 3.22 1.93 3.67 2.25 4.48 2.83 C 4.1 3.17 2.56 3.98 2.6 4.1 C 3.51 6.25 4.04 7.15 4.13 7.04 C 3.84 5.84 3.42 4.98 3.19 4.17 C 4.13 3.65 4.85 3.12 5.26 2.9 C 4.84 2.37 4.35 1.85 3.71 1.36 C 3.87 1.15 5.49 0.46 6.19 0\" \n"); | |
fprintf(f, " fill=\"black\" stroke=\"none\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, "\n"); | |
fprintf(f, " <symbol id=\"pillar\" width=\"10\" height=\"10\" viewBox=\"-3 -3 10 10\" x=\"-3\" y=\"-3\">\n"); | |
fprintf(f, " <path d=\"M 0.08 -2.44 C -1.5 -2.35 -1.98 -1.48 -2.19 -0.03 C -2.05 1.32 -1.28 2.02 0.04 2.18 C 1.2 2.07 2.31 1 2.3 0 C 2.16 -1.22 1.65 -2.14 -0.05 -2.3\" \n"); | |
fprintf(f, " fill=\"#EEE\" stroke=\"black\" stroke-width=\"0.5\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol>\n"); | |
fprintf(f, " </defs>\n\n"); | |
for(i = 0; i < P.n_points; i++) { | |
float sx = frand()*0.1 + 0.9; | |
float sy = frand()*0.1 + 0.9; | |
fprintf(f, " <use href=\"#blobs\" transform=\"translate(%.2f %.2f) rotate(%d) scale(%.2f %.2f)\"/>\n", P.points[i].x, P.points[i].y, ((i*60) + D_RAND(30) - 15)%360, sx, sy); | |
} | |
for(i = 0; i < P.n_points; i++) { | |
float sx = frand()*0.1 + 0.9; | |
float sy = frand()*0.1 + 0.9; | |
fprintf(f, " <use href=\"#strokes\" transform=\"translate(%.2f %.2f) rotate(%d) scale(%.2f %.2f)\"/>\n", P.points[i].x, P.points[i].y, ((i*60) + D_RAND(30) - 15)%360, sx, sy); | |
} | |
poisson_done(&P); | |
for(j = 0; j < M->map_h; j++) { | |
for(i = 0; i < M->map_w; i++) { | |
char tile = d_get_tile(M, i,j); | |
if(tile == 'D') { | |
D_Edge *edge = d_corridor_at(M, i, j); | |
assert(edge); | |
if(edge->direction == D_ORIENT_EW) { | |
fprintf(f, " <use href=\"#door-floor\" transform=\"translate(%d %d)\"/>\n", i * 10, j * 10); | |
} else { | |
fprintf(f, " <use href=\"#door-floor\" transform=\"translate(%d %d) rotate(90 6 6)\"/>\n", i * 10, j * 10); | |
} | |
} else if(tile == D_TILE_SECRET){ | |
int rot = 0, t; | |
D_Edge *edge = d_corridor_at(M, i, j); | |
assert(edge); | |
if(edge->direction == D_ORIENT_EW) { | |
if(edge->w == 1) { | |
D_Room *r = d_room_at(M, i+1, j); | |
assert(r); | |
if(r->flags & D_FLAG_SECRET_ROOM) | |
rot = -90; | |
else | |
rot = 90; | |
} else if((t = d_get_tile(M, i+1, j)) == D_TILE_FLOOR || t == D_TILE_CROSSING) | |
rot = 90; | |
else if((t = d_get_tile(M, i-1, j)) == D_TILE_FLOOR || t == D_TILE_CROSSING) | |
rot = -90; | |
} else { | |
if(edge->h == 1) { | |
D_Room *r = d_room_at(M, i, j + 1); | |
assert(r); | |
if(r->flags & D_FLAG_SECRET_ROOM) | |
rot = 0; | |
else | |
rot = 180; | |
} else if((t = d_get_tile(M, i, j - 1)) == D_TILE_FLOOR || t == D_TILE_CROSSING) | |
rot = 0; | |
else if((t = d_get_tile(M, i, j + 1)) == D_TILE_FLOOR || t == D_TILE_CROSSING) | |
rot = 180; | |
} | |
fprintf(f, " <use href=\"#doorway-floor\" transform=\"translate(%d %d) rotate(%d 6 6)\"/>\n", i * 10, j * 10, rot); | |
} else if(tile == 't') { | |
int rot = 0; | |
if(d_get_tile(M, i, j + 1) == D_TILE_FLOOR) | |
rot = 180; | |
if(d_get_tile(M, i+1, j) == D_TILE_FLOOR) | |
rot = 90; | |
if(d_get_tile(M, i-1, j) == D_TILE_FLOOR) | |
rot = -90; | |
fprintf(f, " <use href=\"#doorway-floor\" transform=\"translate(%d %d) rotate(%d 6 6)\"/>\n", i * 10, j * 10, rot); | |
} else if(!d_is_wall(M, i,j)) { | |
fprintf(f, " <use href=\"#floor\" transform=\"translate(%d %d)\"/>\n", i * 10, j * 10); | |
} | |
} | |
} | |
for(j = 0; j < M->map_h; j++) { | |
for(i = 0; i <M->map_w; i++) { | |
char tile = d_get_tile(M, i,j); | |
if(d_is_wall(M, i,j)) | |
continue; | |
fprintf(f, " <g transform=\"translate(%d %d)\">\n", i * 10, j * 10); | |
if(tile == '@') { | |
int rot = 0, t; | |
if((t = d_get_tile(M, i, j + 1)) == D_TILE_FLOOR || t == D_TILE_FLOOR_EDGE) { | |
if((t = d_get_tile(M, i+1, j)) == D_TILE_FLOOR || t == D_TILE_FLOOR_EDGE) { | |
/* The entrance is a hole in the ceiling */ | |
fprintf(f, " <use href=\"#wall-l\"/><use href=\"#wall-r\"/><use href=\"#wall-t\"/>\n"); | |
rot = 0; | |
} else { | |
fprintf(f, " <use href=\"#wall-l\"/><use href=\"#wall-r\"/>\n"); | |
rot = 0; | |
} | |
} else if((t = d_get_tile(M, i, j - 1)) == D_TILE_FLOOR || t == D_TILE_FLOOR_EDGE) { | |
fprintf(f, " <use href=\"#wall-l\"/><use href=\"#wall-r\"/>\n"); | |
rot = 180; | |
} else if((t = d_get_tile(M, i + 1, j)) == D_TILE_FLOOR || t == D_TILE_FLOOR_EDGE) { | |
fprintf(f, " <use href=\"#wall-t\"/><use href=\"#wall-b\"/>\n"); | |
rot = -90; | |
} else if((t = d_get_tile(M, i - 1, j)) == D_TILE_FLOOR || t == D_TILE_FLOOR_EDGE) { | |
fprintf(f, " <use href=\"#wall-t\"/><use href=\"#wall-b\"/>\n"); | |
rot = 90; | |
} | |
fprintf(f, " <use href=\"#stairs\" transform=\"rotate(%d 6 6)\"/>\n", rot); | |
} else if(tile == '$') { | |
int rot = 0, t; | |
if((t = d_get_tile(M, i, j + 1)) == D_TILE_FLOOR || t == D_TILE_FLOOR_EDGE) { | |
if((t = d_get_tile(M, i+1, j)) == D_TILE_FLOOR || t == D_TILE_FLOOR_EDGE) { | |
/* The exit is just a hole in the floor */ | |
fprintf(f, " <use href=\"#wall-l\"/><use href=\"#wall-r\"/><use href=\"#wall-b\"/>\n"); | |
rot = 0; | |
} else { | |
fprintf(f, " <use href=\"#wall-l\"/><use href=\"#wall-r\"/>\n"); | |
rot = 180; | |
} | |
} else if((t = d_get_tile(M, i, j - 1)) == D_TILE_FLOOR || t == D_TILE_FLOOR_EDGE) { | |
fprintf(f, " <use href=\"#wall-l\"/><use href=\"#wall-r\"/>\n"); | |
rot = 0; | |
} else if((t = d_get_tile(M, i + 1, j)) == D_TILE_FLOOR || t == D_TILE_FLOOR_EDGE) { | |
fprintf(f, " <use href=\"#wall-t\"/><use href=\"#wall-b\"/>\n"); | |
rot = 90; | |
} else if((t = d_get_tile(M, i - 1, j)) == D_TILE_FLOOR || t == D_TILE_FLOOR_EDGE) { | |
fprintf(f, " <use href=\"#wall-t\"/><use href=\"#wall-b\"/>\n"); | |
rot = -90; | |
} | |
fprintf(f, " <use href=\"#stairs\" transform=\"rotate(%d 6 6)\"/>\n", rot); | |
} else if(tile == 'S') { | |
fprintf(f, " <use href=\"#statue\" transform=\"rotate(%d 6 6)\"/>\n", D_RAND(360)); | |
} else if(tile == D_TILE_SECRET) { | |
int rot = 0, t; | |
D_Edge *edge = d_corridor_at(M, i, j); | |
assert(edge); | |
if(edge->direction == D_ORIENT_EW) { | |
if(edge->w == 1) { | |
D_Room *r = d_room_at(M, i+1, j); | |
assert(r); | |
if(r->flags & D_FLAG_SECRET_ROOM) | |
rot = -90; | |
else | |
rot = 90; | |
} else if((t = d_get_tile(M, i+1, j)) == D_TILE_FLOOR || t == D_TILE_CROSSING) | |
rot = 90; | |
else if((t = d_get_tile(M, i-1, j)) == D_TILE_FLOOR || t == D_TILE_CROSSING) | |
rot = -90; | |
} else { | |
if(edge->h == 1) { | |
D_Room *r = d_room_at(M, i, j + 1); | |
assert(r); | |
if(r->flags & D_FLAG_SECRET_ROOM) | |
rot = 0; | |
else | |
rot = 180; | |
} else if((t = d_get_tile(M, i, j - 1)) == D_TILE_FLOOR || t == D_TILE_CROSSING) | |
rot = 0; | |
else if((t = d_get_tile(M, i, j + 1)) == D_TILE_FLOOR || t == D_TILE_CROSSING) | |
rot = 180; | |
} | |
fprintf(f, " <use href=\"#secret-door\" transform=\"rotate(%d 6 6)\"/>\n", rot); | |
} else if(tile == 't') { | |
int rot = 0; | |
if(d_get_tile(M, i, j + 1) == D_TILE_FLOOR) | |
rot = 180; | |
if(d_get_tile(M, i + 1, j) == D_TILE_FLOOR) | |
rot = 90; | |
if(d_get_tile(M, i - 1, j) == D_TILE_FLOOR) | |
rot = -90; | |
fprintf(f, " <use href=\"#doorway\" transform=\"rotate(%d 6 6)\"/>\n", rot); | |
} else if(tile == 'D') { | |
D_Edge *edge = d_corridor_at(M, i, j); | |
assert(edge); | |
if(edge->direction == D_ORIENT_EW) { | |
fprintf(f, " <use href=\"#door\"/>\n"); | |
} else { | |
fprintf(f, " <use href=\"#door\" transform=\"rotate(90 6 6)\"/>\n"); | |
} | |
} else { | |
int r = D_RAND(100); | |
if(d_is_wall(M, i - 1, j)) | |
fprintf(f, " <use href=\"#wall-l\"/>\n"); | |
if(d_is_wall(M, i + 1, j)) | |
fprintf(f, " <use href=\"#wall-r\"/>\n"); | |
if(d_is_wall(M, i, j - 1)) | |
fprintf(f, " <use href=\"#wall-t\"/>\n"); | |
if(d_is_wall(M, i, j + 1)) | |
fprintf(f, " <use href=\"#wall-b\"/>\n"); | |
if(r < 4) { | |
float sx = frand()*0.1 + 0.9; | |
float sy = frand()*0.1 + 0.9; | |
fprintf(f, " <use href=\"#stone1\" transform=\"translate(%.2f %.2f) rotate(%d) scale(%.2f %.2f)\"/>\n", frand()*7.0+2.0, frand()*7.0+2.0, D_RAND(360) - 180, sx, sy); | |
} else if(r < 8) { | |
float sx = frand()*0.1 + 0.9; | |
float sy = frand()*0.1 + 0.9; | |
fprintf(f, " <use href=\"#stone2\" transform=\"translate(%.2f %.2f) rotate(%d) scale(%.2f %.2f)\"/>\n", frand()*7.0+2.0, frand()*7.0+2.0, D_RAND(360) - 180, sx, sy); | |
} else if(r < 12) { | |
float sx = frand()*0.1 + 0.9; | |
float sy = frand()*0.1 + 0.9; | |
fprintf(f, " <use href=\"#stone3\" transform=\"translate(%.2f %.2f) rotate(%d) scale(%.2f %.2f)\"/>\n", frand()*7.0+2.0, frand()*7.0+2.0, D_RAND(360) - 180, sx, sy); | |
} | |
} | |
if(!d_is_wall(M, i + 1, j)) | |
fprintf(f, " <use href=\"#floor-r%c\"/>\n",(i+j)%3 + '1'); | |
if(!d_is_wall(M, i, j + 1)) | |
fprintf(f, " <use href=\"#floor-b%c\"/>\n",(i+j)%3 + '1'); | |
fprintf(f, " </g>\n"); | |
} | |
} | |
/* Random cobwebs */ | |
for(i = 0; i < 3; i++) { | |
r = d_random_room(M); | |
switch(D_RAND(4)) { | |
case 0: | |
if(d_get_tile(M, r->x, r->y) == D_TILE_FLOOR_EDGE) { | |
fprintf(f, " <use href=\"#cobwebs\" transform=\"translate(%d %d)\"/>\n", r->x * 10, r->y * 10); | |
d_set_tile(M, r->x, r->y, 'X'); | |
} else | |
i--; | |
break; | |
case 1: | |
if(d_get_tile(M, r->x + r->w - 1, r->y) == D_TILE_FLOOR_EDGE) { | |
fprintf(f, " <use href=\"#cobwebs\" transform=\"translate(%d %d) rotate(90 6 6)\"/>\n", (r->x + r->w - 1) * 10, r->y * 10); | |
d_set_tile(M, r->x + r->w - 1, r->y, 'X'); | |
} else | |
i--; | |
break; | |
case 2: | |
if(d_get_tile(M, r->x + r->w - 1, r->y + r->h - 1) == D_TILE_FLOOR_EDGE) { | |
fprintf(f, " <use href=\"#cobwebs\" transform=\"translate(%d %d) rotate(180 6 6)\"/>\n", (r->x + r->w - 1) * 10, (r->y + r->h - 1) * 10); | |
d_set_tile(M, r->x + r->w - 1, r->y + r->h - 1, 'X'); | |
} else | |
i--; | |
break; | |
case 3: | |
if(d_get_tile(M, r->x, r->y + r->h - 1) == D_TILE_FLOOR_EDGE) { | |
fprintf(f, " <use href=\"#cobwebs\" transform=\"translate(%d %d) rotate(-90 6 6)\"/>\n", r->x * 10, (r->y + r->h - 1) * 10); | |
d_set_tile(M, r->x, r->y + r->h - 1, 'X'); | |
} | |
else | |
i--; | |
break; | |
} | |
} | |
/* Random cracks */ | |
for(i = 0; i < 3; i++) { | |
r = d_random_room(M); | |
switch(D_RAND(4)) { | |
case 0: | |
j = r->x + D_RAND(r->w); | |
if(d_get_tile(M, j, r->y) == D_TILE_FLOOR_EDGE) { | |
fprintf(f, " <use href=\"#crack-t\" transform=\"translate(%d %d)\"/>\n", j * 10, r->y * 10); | |
d_set_tile(M, j, r->y, 'X'); | |
} else | |
i--; | |
break; | |
case 1: | |
j = r->y + D_RAND(r->h); | |
if(d_get_tile(M, r->x + r->w - 1, j) == D_TILE_FLOOR_EDGE) { | |
fprintf(f, " <use href=\"#crack-t\" transform=\"translate(%d %d) rotate(90 6 6)\"/>\n", (r->x + r->w - 1) * 10, j * 10); | |
d_set_tile(M, r->x + r->w - 1, j, 'X'); | |
} else | |
i--; | |
break; | |
case 2: | |
j = r->x + D_RAND(r->w); | |
if(d_get_tile(M, j, r->y + r->h - 1) == D_TILE_FLOOR_EDGE) { | |
fprintf(f, " <use href=\"#crack-t\" transform=\"translate(%d %d) rotate(180 6 6)\"/>\n", j * 10, (r->y + r->h - 1) * 10); | |
d_set_tile(M, j, r->y + r->h - 1, 'X'); | |
} else | |
i--; | |
break; | |
case 3: | |
j = r->y + D_RAND(r->h); | |
if(d_get_tile(M, r->x, j) == D_TILE_FLOOR_EDGE) { | |
fprintf(f, " <use href=\"#crack-t\" transform=\"translate(%d %d) rotate(-90 6 6)\"/>\n", r->x * 10, j * 10); | |
d_set_tile(M, r->x, j, 'X'); | |
} | |
else | |
i--; | |
break; | |
} | |
} | |
/* Room with pillars */ | |
do { | |
r = d_random_room(M); | |
} while(r->flags & (D_FLAG_START_ROOM | D_FLAG_END_ROOM | D_FLAG_GONE_ROOM) || (r->w * r->h < 8)); | |
for(i = 1; i < r->w; i++) | |
for(j = 1; j < r->h; j++) { | |
fprintf(f, " <use href=\"#pillar\" transform=\"translate(%d %d) rotate(%d)\"/>\n", (r->x + i) * 10 + 1, (r->y + j) * 10 + 1, D_RAND(360)); | |
} | |
fprintf(f, "</svg>\n"); | |
} | |
int main(int argc, char *argv[]) { | |
FILE *f; | |
int seed; | |
D_Dungeon M; | |
if(argc > 1) | |
seed = atoi(argv[1]); | |
else { | |
seed = time(NULL); | |
} | |
srand(seed); | |
printf("Seed: %d\n", seed); | |
d_init(&M); | |
M.grid_w = 4; | |
M.grid_h = 4; | |
d_generate(&M); | |
d_draw(&M, stdout); | |
f = fopen("map.svg","w"); | |
if(f) { | |
drawSvg(&M, f); | |
fclose(f); | |
} else { | |
fprintf(stderr, "Unable to save SVG"); | |
} | |
printf("Seed: %d", seed); | |
d_deinit(&M); | |
return 0; | |
} |
This file contains hidden or 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
/* | |
* Implementation of Poisson Disk Sampling for a series of 2D points. | |
* | |
* I used this article as reference [Poisson Disk Sampling in Processing][pdsp] | |
* | |
* [pdsp]: https://sighack.com/post/poisson-disk-sampling-bridsons-algorithm | |
*/ | |
#ifndef POISSON_H | |
#define POISSON_H | |
struct point { | |
float x, y; | |
}; | |
struct poisson; | |
typedef int (*poisson_constraint)(struct poisson *P, float x, float y); | |
struct poisson { | |
int k; | |
float r; | |
int w, h; | |
struct point x0; | |
short n_points; | |
struct point *points; | |
void *data; | |
poisson_constraint constraint; | |
}; | |
void poisson_init(struct poisson *P); | |
int poisson_plot(struct poisson *P); | |
void poisson_done(struct poisson *P); | |
#if defined(POISSON_TEST) && !defined(POISSON_IMPLEMENTATION) | |
# define POISSON_IMPLEMENTATION | |
#endif | |
#ifdef POISSON_IMPLEMENTATION | |
#include <stdlib.h> | |
#include <math.h> | |
#include <time.h> | |
#include <assert.h> | |
#ifndef P_W0 | |
# define P_W0 100 | |
#endif | |
#ifndef P_H0 | |
# define P_H0 100 | |
#endif | |
#ifndef P_R0 | |
# define P_R0 5.0 | |
#endif | |
#ifndef P_K0 | |
# define P_K0 30 | |
#endif | |
#define N 2 | |
void poisson_init(struct poisson *P) { | |
/* These are set to sensible defaults, | |
but you're allowed to change them: */ | |
P->k = P_K0; | |
P->r = P_R0; | |
P->w = P_W0; | |
P->h = P_H0; | |
P->x0.x = rand() % P->w; | |
P->x0.y = rand() % P->h; | |
P->constraint = NULL; | |
P->data = 0; | |
/* Don't change these: */ | |
P->n_points = 0; | |
P->points = NULL; | |
} | |
void poisson_done(struct poisson *P) { | |
if(P->points) | |
free(P->points); | |
P->points = NULL; | |
P->n_points = 0; | |
} | |
/* Used internally */ | |
struct generator { | |
struct poisson *P; | |
short *grid; | |
short grid_size, grid_w, grid_h; | |
short *active_list; | |
short n_active; | |
float cell_size; | |
}; | |
static short _p_insert_point(struct generator *G, float x, float y) { | |
short index, gx, gy; | |
gx = floor(x/G->cell_size); | |
gy = floor(y/G->cell_size); | |
index = gy * G->grid_w + gx; | |
assert(index < G->grid_size); | |
G->grid[index] = G->P->n_points; | |
assert(G->P->n_points < G->grid_size); | |
index = G->P->n_points++; | |
G->P->points[index].x = x; | |
G->P->points[index].y = y; | |
return index; | |
} | |
static float _p_frand() { | |
return ((float)rand())/RAND_MAX; | |
} | |
#define MAX(a,b) ((a) > (b)? (a) : (b)) | |
#define MIN(a,b) ((a) < (b)? (a) : (b)) | |
static float dist(float x0, float y0, float x1, float y1) { | |
float dx = x1 - x0, dy = y1 - y0; | |
return sqrt(dx * dx + dy * dy); | |
} | |
static int _p_is_valid_point(struct generator *G, float px, float py) { | |
short gx, gy, i0, i1, j0, j1, i, j; | |
struct poisson *P = G->P; | |
if(px < 0 || px >= P->w || py < 0 || py >= P->h) return 0; | |
if(P->constraint) | |
if(!P->constraint(P, px, py)) | |
return 0; | |
gx = floor(px / G->cell_size); | |
gy = floor(py / G->cell_size); | |
/* Note to self: I did see some cases where the constraint that no two | |
* points should be closer than R is being violated, and I suspect that | |
* what's happening is that they are just placed so that there ends up | |
* being a empty grid cell between them. This implies some misunderstanding | |
* on my part, but I've decided I can live with it. If you can't live with | |
* it, changing the +/-1s below to +/-2 works around the issue. | |
*/ | |
i0 = MAX(gx - 1, 0); | |
i1 = MIN(gx + 1, G->grid_w - 1); | |
j0 = MAX(gy - 1, 0); | |
j1 = MIN(gy + 1, G->grid_h - 1); | |
for(i = i0; i <= i1; i++) | |
for(j = j0; j <= j1; j++) { | |
short t = G->grid[j * G->grid_w + i]; | |
if(t >= 0) { | |
struct point *q = &G->P->points[t]; | |
if(dist(px, py, q->x, q->y) < P->r) | |
return 0; | |
} | |
} | |
return 1; | |
} | |
int poisson_plot(struct poisson *P) { | |
short i, k; | |
struct generator G; | |
G.P = P; | |
/* Note to self: I found that if you use `floor()` as in the [pdsp][] | |
article, then some of the points end up violating the contstraints */ | |
G.cell_size = ceil((float)P->r / sqrt(N)); | |
G.grid_w = ceil((float)P->w / G.cell_size) + 1; | |
G.grid_h = ceil((float)P->h / G.cell_size) + 1; | |
G.grid_size = G.grid_w * G.grid_h; | |
P->n_points = 0; | |
G.grid = malloc(G.grid_size * sizeof *G.grid); | |
if(!G.grid) { | |
fprintf(stderr, "no memory\n"); | |
return 0; | |
} | |
for(i = 0; i < G.grid_size; i++) | |
G.grid[i] = -1; | |
G.active_list = malloc(G.grid_size * sizeof *G.active_list); | |
if(!G.active_list) { | |
free(G.grid); | |
fprintf(stderr, "no memory\n"); | |
return 0; | |
} | |
G.n_active = 0; | |
P->points = malloc(G.grid_size * sizeof *P->points); | |
if(!P->points) { | |
free(G.grid); | |
free(G.active_list); | |
fprintf(stderr, "no memory\n"); | |
return 0; | |
} | |
i = _p_insert_point(&G, P->x0.x, P->x0.y); | |
G.active_list[G.n_active++] = i; | |
while(G.n_active > 0) { | |
short ax = rand() % G.n_active; | |
short a = G.active_list[ax]; | |
struct point *p = &P->points[a]; | |
for(k = 0; k < P->k; k++) { | |
float th = ((float)(rand() % 360)) * M_PI / 180; | |
float rad = (_p_frand() * 2.0 + 1.0) * P->r; | |
float nx = p->x + rad * cos(th); | |
float ny = p->y + rad * sin(th); | |
if(!_p_is_valid_point(&G, nx, ny)) | |
continue; | |
i = _p_insert_point(&G, nx, ny); | |
assert(G.n_active < G.grid_size); | |
G.active_list[G.n_active++] = i; | |
break; | |
} | |
if(k == P->k) { | |
/* No point found - remove the point from the | |
active list by replacing it with the last one in the | |
list, and decrementing the counter */ | |
G.active_list[ax] = G.active_list[--G.n_active]; | |
} | |
} | |
free(G.grid); | |
free(G.active_list); | |
return 1; | |
} | |
#endif /* POISSON_IMPLEMENTATION */ | |
#ifdef POISSON_TEST | |
#include <stdio.h> | |
static int circle_constraint(struct poisson *P, float x, float y) { | |
return dist(P->x0.x, P->x0.y, x, y) < 20; | |
} | |
int main(int argc, char* argv[]) { | |
FILE *f = stdout; | |
int i; | |
struct poisson P; | |
(void)argc; (void)argv; | |
srand(time(NULL)); | |
poisson_init(&P); | |
P.r = 3.0; | |
P.x0.x = 50; | |
P.x0.y = 60; | |
P.constraint = circle_constraint; | |
if(!poisson_plot(&P)) { | |
return 1; | |
} | |
fprintf(f, "<svg viewBox=\"0 0 %d %d\" xmlns=\"http://www.w3.org/2000/svg\">\n", P.w, P.h); | |
#if 0 | |
fprintf(f, " <symbol id=\"strokes\" width=\"30\" height=\"30\" viewBox=\"-15 -15 30 30\" x=\"-15\" y=\"-15\">\n"); | |
fprintf(f, " <circle cx=\"0\" cy=\"0\" r=\"%g\" fill=\"red\" opacity=\"0.1\"/>\n", P_R0); | |
fprintf(f, " <circle cx=\"0\" cy=\"0\" r=\"1\" fill=\"black\"/>\n"); | |
fprintf(f, " </symbol>\n\n"); | |
#else | |
fprintf(f, " <symbol id=\"strokes\" width=\"4\" height=\"4\" viewBox=\"-2 -2 4 4\" x=\"-2\" y=\"-2\">\n"); | |
fprintf(f, " <path d=\"M-1.5,-1.5 v3 M0,-1.75 v3.5 M1.5,-1.5 v3\" fill=\"none\" stroke-width=\"0.3\" stroke=\"gray\" stroke-linecap=\"round\"/>\n"); | |
fprintf(f, " </symbol>\n\n"); | |
#endif | |
for(i = 0; i < P.n_points; i++) { | |
float sx = _p_frand()*0.1 + 0.9; | |
float sy = _p_frand()*0.1 + 0.9; | |
fprintf(f, " <use href=\"#strokes\" transform=\"translate(%.2f %.2f) rotate(%d) scale(%.2f %.2f)\"/>\n", P.points[i].x, P.points[i].y, rand()%360 - 180, sx, sy); | |
} | |
fprintf(f, "</svg>\n"); | |
poisson_done(&P); | |
return 0; | |
} | |
#endif /* POISSON_TEST */ | |
#endif /* POISSON_H */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment