#include <assert.h>
#include <math.h>
#include <stdint.h>
#include <array>
#include <list>
#include <string>
#include <utility>
#include <vector>
using namespace std;
//////// From Lobster's dev/src/lobster/tools.h ////////
class PCG32 {
// This is apparently better than the Mersenne Twister, and its also smaller/faster!
// Adapted from *Really* minimal PCG32 code / (c) 2014 M.E. O'Neill / pcg-random.org
// Licensed under Apache License 2.0 (NO WARRANTY, etc. see website).
uint64_t state = 0xABADCAFEDEADBEEF;
uint64_t inc = 0xDEADBABEABADD00D;
public:
uint32_t Random() {
uint64_t oldstate = state;
// Advance internal state.
state = oldstate * 6364136223846793005ULL + (inc | 1);
// Calculate output function (XSH RR), uses old state for max ILP.
uint32_t xorshifted = uint32_t(((oldstate >> 18u) ^ oldstate) >> 27u);
uint32_t rot = oldstate >> 59u;
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
}
void ReSeed(uint32_t s) {
state = s;
inc = 0xDEADBABEABADD00D;
}
};
template<typename T> struct RandomNumberGenerator {
T rnd;
void seed(uint32_t s) { rnd.ReSeed(s); }
int operator()(int max) { return rnd.Random() % max; }
int operator()() { return rnd.Random(); }
double rnddouble() { return rnd.Random() * (1.0 / 4294967296.0); }
float rnd_float() { return (float)rnddouble(); } // FIXME: performance?
float rndfloatsigned() { return (float)(rnddouble() * 2 - 1); }
double n2 = 0.0;
bool n2_cached = false;
// Returns gaussian with stddev of 1 and mean of 0.
// Box Muller method.
double rnd_gaussian() {
n2_cached = !n2_cached;
if (n2_cached) {
double x, y, r;
do {
x = 2.0 * rnddouble() - 1;
y = 2.0 * rnddouble() - 1;
r = x * x + y * y;
} while (r == 0.0 || r > 1.0);
double d = sqrt(-2.0 * log(r) / r);
double n1 = x * d;
n2 = y * d;
return n1;
} else {
return n2;
}
}
};
inline int PopCount(uint32_t val) {
#ifdef _MSC_VER
return (int)__popcnt(val);
#else
return __builtin_popcount(val);
#endif
}
inline int PopCount(uint64_t val) {
#ifdef _MSC_VER
#ifdef _WIN64
return (int)__popcnt64(val);
#else
return (int)(__popcnt((uint32_t)val) + __popcnt((uint32_t)(val >> 32)));
#endif
#else
return __builtin_popcountll(val);
#endif
}
//////// From Lobster's dev/src/lobster/geom.h ////////
// vec: supports 1..4 components of any numerical type.
// Compile time unrolled loops for 1..4 components,
#define DOVEC(F) { \
const int i = 0; \
F; \
if constexpr (N > 1) { \
const int i = 1; \
F; \
if constexpr (N > 2) { \
const int i = 2; \
F; \
if constexpr (N > 3) { \
const int i = 3; \
F; \
} \
} \
} \
}
#define DOVECR(F) { vec<T, N> _t; DOVEC(_t.c[i] = F); return _t; }
#define DOVECRI(F) { vec<int, N> _t; DOVEC(_t.c[i] = F); return _t; }
#define DOVECF(I, F) { T _ = I; DOVEC(_ = F); return _; }
#define DOVECB(I, F) { bool _ = I; DOVEC(_ = F); return _; }
union int2float { int i; float f; };
union int2float64 { int64_t i; double f; };
inline void default_debug_value(float &a) { int2float nan; nan.i = 0x7F800001; a = nan.f; }
inline void default_debug_value(double &a) { int2float nan; nan.i = 0x7F800001; a = nan.f; }
inline void default_debug_value(int64_t &a) { a = 0x1BADCAFEABADD00D; }
inline void default_debug_value(int32_t &a) { a = 0x1BADCAFE; }
inline void default_debug_value(uint16_t&a) { a = 0x1BAD; }
inline void default_debug_value(uint8_t &a) { a = 0x1B; }
template<typename T, int C, int R> class matrix;
template<typename T, int N> struct basevec {
};
template<typename T> struct basevec<T, 2> {
union {
T c[2];
struct { T x; T y; };
};
};
template<typename T> struct basevec<T, 3> {
union {
T c[3];
struct { T x; T y; T z; };
};
};
template<typename T> struct basevec<T, 4> {
union {
T c[4];
struct { T x; T y; T z; T w; };
};
};
template<typename T, int N> struct vec : basevec<T, N> {
enum { NUM_ELEMENTS = N };
typedef T CTYPE;
// Clang needs these, but VS is cool without them?
using basevec<T, N>::c;
using basevec<T, N>::x;
using basevec<T, N>::y;
vec() {
#ifndef NDEBUG
DOVEC(default_debug_value(c[i]));
#endif
}
explicit vec(T e) { DOVEC(c[i] = e); }
explicit vec(const T *v) { DOVEC(c[i] = v[i]); }
template<typename U> explicit vec(const vec<U,N> &v) { DOVEC(c[i] = (T)v[i]); }
vec(T _x, T _y, T _z, T _w) { x = _x; y = _y; assert(N == 4);
if constexpr (N > 2) c[2] = _z; else (void)_z;
if constexpr (N > 3) c[3] = _w; else (void)_w; }
vec(T _x, T _y, T _z) { x = _x; y = _y; assert(N == 3);
if constexpr (N > 2) c[2] = _z; else (void)_z; }
vec(T _x, T _y) { x = _x; y = _y; assert(N == 2); }
vec(const pair<T, T> &p) { x = p.first; y = p.second; assert(N == 2); }
const T *data() const { return c; }
const T *begin() const { return c; }
const T *end() const { return c + N; }
T operator[](size_t i) const { return c[i]; }
T &operator[](size_t i) { return c[i]; }
vec(const vec<T,3> &v, T e) { DOVEC(c[i] = i < 3 ? v[i] : e); }
vec(const vec<T,2> &v, T e) { DOVEC(c[i] = i < 2 ? v[i] : e); }
vec<T,3> xyz() const { assert(N == 4); return vec<T,3>(c); }
vec<T,2> xy() const { assert(N >= 3); return vec<T,2>(c); }
pair<T, T> to_pair() const { assert(N == 2); return { x, y }; }
vec operator+(const vec &v) const { DOVECR(c[i] + v[i]); }
vec operator-(const vec &v) const { DOVECR(c[i] - v[i]); }
vec operator*(const vec &v) const { DOVECR(c[i] * v[i]); }
vec operator/(const vec &v) const { DOVECR(c[i] / v[i]); }
vec operator%(const vec &v) const { DOVECR(c[i] % v[i]); }
vec operator+(T e) const { DOVECR(c[i] + e); }
vec operator-(T e) const { DOVECR(c[i] - e); }
vec operator*(T e) const { DOVECR(c[i] * e); }
vec operator/(T e) const { DOVECR(c[i] / e); }
vec operator%(T e) const { DOVECR(c[i] % e); }
vec operator&(T e) const { DOVECR(c[i] & e); }
vec operator|(T e) const { DOVECR(c[i] | e); }
vec operator<<(T e) const { DOVECR(c[i] << e); }
vec operator>>(T e) const { DOVECR(c[i] >> e); }
vec operator-() const { DOVECR(-c[i]); }
vec &operator+=(const vec &v) { DOVEC(c[i] += v[i]); return *this; }
vec &operator-=(const vec &v) { DOVEC(c[i] -= v[i]); return *this; }
vec &operator*=(const vec &v) { DOVEC(c[i] *= v[i]); return *this; }
vec &operator/=(const vec &v) { DOVEC(c[i] /= v[i]); return *this; }
vec &operator+=(T e) { DOVEC(c[i] += e); return *this; }
vec &operator-=(T e) { DOVEC(c[i] -= e); return *this; }
vec &operator*=(T e) { DOVEC(c[i] *= e); return *this; }
vec &operator/=(T e) { DOVEC(c[i] /= e); return *this; }
vec &operator&=(T e) { DOVEC(c[i] &= e); return *this; }
bool operator<=(const vec &v) const {
DOVECB(true, _ && c[i] <= v[i]);
}
bool operator< (const vec &v) const {
DOVECB(true, _ && c[i] < v[i]);
}
bool operator>=(const vec &v) const {
DOVECB(true, _ && c[i] >= v[i]);
}
bool operator> (const vec &v) const {
DOVECB(true, _ && c[i] > v[i]);
}
bool operator==(const vec &v) const {
DOVECB(true, _ && c[i] == v[i]);
}
bool operator!=(const vec &v) const {
DOVECB(false, _ || c[i] != v[i]);
}
bool operator<=(T e) const { DOVECB(true, _ && c[i] <= e); }
bool operator< (T e) const { DOVECB(true, _ && c[i] < e); }
bool operator>=(T e) const { DOVECB(true, _ && c[i] >= e); }
bool operator> (T e) const { DOVECB(true, _ && c[i] > e); }
bool operator==(T e) const { DOVECB(true, _ && c[i] == e); }
bool operator!=(T e) const { DOVECB(false, _ || c[i] != e); }
vec<int, N> lte(T e) const { DOVECRI(c[i] <= e); }
vec<int, N> lt (T e) const { DOVECRI(c[i] < e); }
vec<int, N> gte(T e) const { DOVECRI(c[i] >= e); }
vec<int, N> gt (T e) const { DOVECRI(c[i] > e); }
vec<int, N> eq (T e) const { DOVECRI(c[i] == e); }
vec<int, N> ne (T e) const { DOVECRI(c[i] != e); }
vec iflt(T e, const vec &a, const vec &b) const { DOVECR(c[i] < e ? a[i] : b[i]); }
std::string to_string() const {
string s = "(";
DOVEC(if (i) s += ", "; s += std::to_string(c[i]));
return s + ")";
}
T volume() const { DOVECF(1, _ * c[i]); }
template<typename T2, int C, int R> friend class matrix;
};
typedef vec<int, 2> int2;
template<typename T, int N, typename R> inline vec<int, N> rndivec(RandomNumberGenerator<R> &r,
const vec<int, N> &max) {
DOVECR(r(max[i]));
}
////////////////////////////////////////////////////////
// Copyright 2018 Wouter van Oortmerssen. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Very simple tile based Wave Function Collapse ("Simple Tiled Model") implementation.
// See: https://github.com/mxgmn/WaveFunctionCollapse
// Derives adjacencies from an example rather than explicitly specified neighbors.
// Does not do any symmetries/rotations unless they're in the example.
// Algorithm has a lot of similarities to A* in how its implemented.
// Uses bitmasks to store the set of possible tiles, which currently limits the number of
// unique tiles to 64. This restriction cool be lifted by using std::bitset instead.
// In my testing, generates a 50x50 tile map in <1 msec. 58% of such maps are conflict free.
// At 100x100 that is 3 msec and 34%.
// At 200x200 that is 24 msec and 13%
// At 400x400 that is 205 msec and ~1%
// Algorithm may need to extended to flood more than 2 neighbor levels to make it suitable
// for really gigantic maps.
// inmap & outmap must point to row-major 2D arrays of the given size.
// each in tile char must be in range 0..127, of which max 64 may actually be in use (may be
// sparse).
// Returns false if too many unique tiles in input.
template<typename T> bool WaveFunctionCollapse(const int2 &insize, const char **inmap,
const int2 &outsize, char **outmap,
RandomNumberGenerator<T> &rnd,
int &num_contradictions) {
num_contradictions = 0;
typedef uint64_t bitmask_t;
const auto nbits = sizeof(bitmask_t) * 8;
array<int, 256> tile_lookup;
tile_lookup.fill(-1);
struct Tile { bitmask_t sides[4] = {}; int freq = 0; char tidx = 0; };
vector<Tile> tiles;
int2 neighbors[] = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } };
// Collect unique tiles and their frequency of occurrence.
for (int iny = 0; iny < insize.y; iny++) {
for (int inx = 0; inx < insize.x; inx++) {
auto t = inmap[iny][inx];
if (tile_lookup[t] < 0) {
// We use a bitmask_t mask for valid neighbors.
if (tiles.size() == nbits - 1) return false;
tile_lookup[t] = (int)tiles.size();
tiles.push_back(Tile());
}
auto &tile = tiles[tile_lookup[t]];
tile.freq++;
tile.tidx = t;
}
}
// Construct valid neighbor bitmasks.
auto to_bitmask = [](size_t idx) { return (bitmask_t)1 << idx; };
for (int iny = 0; iny < insize.y; iny++) {
for (int inx = 0; inx < insize.x; inx++) {
auto t = inmap[iny][inx];
auto &tile = tiles[tile_lookup[t]];
int ni = 0;
for (auto n : neighbors) {
auto p = (n + int2(inx, iny) + insize) % insize;
auto tn = inmap[p.y][p.x];
assert(tile_lookup[tn] >= 0);
tile.sides[ni] |= to_bitmask(tile_lookup[tn]);
ni++;
}
}
}
size_t most_common_tile_id = 0;
int most_common_tile_freq = 0;
for (auto &tile : tiles) if (tile.freq > most_common_tile_freq) {
most_common_tile_freq = tile.freq;
most_common_tile_id = &tile - &tiles[0];
}
// Track an open list (much like A*) of next options, sorted by best candidate at the end.
list<pair<int2, int>> open, temp;
// Store a bitmask per output cell of remaining possible choices.
auto max_bitmask = (1 << tiles.size()) - 1;
enum class State : int { NEW, OPEN, CLOSED };
struct Cell {
bitmask_t wf;
int popcnt = 0;
State state = State::NEW;
list<pair<int2, int>>::iterator it;
Cell(bitmask_t wf, int popcnt) : wf(wf), popcnt(popcnt) {}
};
vector<vector<Cell>> cells(outsize.y, vector<Cell>(outsize.x, Cell(max_bitmask,
(int)tiles.size())));
auto start = rndivec<int, 2>(rnd, outsize);
open.push_back({ start, 0 }); // Start.
auto &scell = cells[start.y][start.x];
scell.state = State::OPEN;
scell.it = open.begin();
// Pick tiles until no more possible.
while (!open.empty()) {
// Simply picking the first list item results in the same chance of conflicts as
// random picks over equal options, but it is assumed the latter could generate more
// interesting maps.
int num_candidates = 1;
auto numopts_0 = cells[open.back().first.y][open.back().first.x].popcnt;
for (auto it = ++open.rbegin(); it != open.rend(); ++it)
if (numopts_0 == cells[it->first.y][it->first.x].popcnt &&
open.back().second == it->second)
num_candidates++;
else
break;
auto candidate_i = rnd(num_candidates);
auto candidate_it = --open.end();
for (int i = 0; i < candidate_i; i++) --candidate_it;
auto cur = candidate_it->first;
temp.splice(temp.end(), open, candidate_it);
auto &cell = cells[cur.y][cur.x];
assert(cell.state == State::OPEN);
cell.state = State::CLOSED;
bool contradiction = !cell.popcnt;
if (contradiction) {
num_contradictions++;
// Rather than failing right here, fill in the whole map as best as possible just in
// case a map with bad tile neighbors is still useful to the caller.
// As a heuristic lets just use the most common tile, as that will likely have the
// most neighbor options.
cell.wf = to_bitmask(most_common_tile_id);
cell.popcnt = 1;
}
// From our options, pick one randomly, weighted by frequency of tile occurrence.
// First find total frequency.
int total_freq = 0;
for (size_t i = 0; i < tiles.size(); i++)
if (cell.wf & to_bitmask(i))
total_freq += tiles[i].freq;
auto freqpick = rnd(total_freq);
// Now pick.
size_t picked = 0;
for (size_t i = 0; i < tiles.size(); i++) if (cell.wf & to_bitmask(i)) {
picked = i;
if ((freqpick -= tiles[i].freq) <= 0) break;
}
assert(freqpick <= 0);
// Modify the picked tile.
auto &tile = tiles[picked];
outmap[cur.y][cur.x] = tile.tidx;
cell.wf = to_bitmask(picked); // Exactly one option remains.
cell.popcnt = 1;
// Now lets cycle thru neighbors, reduce their options (and maybe their neighbors options),
// and add them to the open list for next pick.
int ni = 0;
for (auto n : neighbors) {
auto p = (cur + n + outsize) % outsize;
auto &ncell = cells[p.y][p.x];
if (ncell.state != State::CLOSED) {
ncell.wf &= tile.sides[ni]; // Reduce options.
ncell.popcnt = PopCount(ncell.wf);
int totalnnumopts = 0;
if (!contradiction) {
// Hardcoded second level of neighbors of neighbors, to reduce chance of
// contradiction.
// Only do this when our current tile isn't a contradiction, to avoid
// artificially shrinking options.
int nni = 0;
for (auto nn : neighbors) {
auto pnn = (p + nn + outsize) % outsize;
auto &nncell = cells[pnn.y][pnn.x];
if (nncell.state != State::CLOSED) {
// Collect the superset of possible options. If we remove anything but
// these, we are guaranteed the direct neigbor always has a possible
//pick.
bitmask_t superopts = 0;
for (size_t i = 0; i < tiles.size(); i++)
if (ncell.wf & to_bitmask(i))
superopts |= tiles[i].sides[nni];
nncell.wf &= superopts;
nncell.popcnt = PopCount(nncell.wf);
}
totalnnumopts += nncell.popcnt;
nni++;
}
}
if (ncell.state == State::OPEN) {
// Already in the open list, remove it for it to be re-added just in case
// its location is not optimal anymore.
totalnnumopts = min(totalnnumopts, ncell.it->second);
temp.splice(temp.end(), open, ncell.it); // Avoid alloc.
}
// Insert this neighbor, sorted by lowest possibilities.
// Use total possibilities of neighbors as a tie-breaker to avoid causing
// contradictions by needless surrounding of tiles.
list<pair<int2, int>>::iterator dit = open.begin();
for (auto it = open.rbegin(); it != open.rend(); ++it) {
auto onumopts = cells[it->first.y][it->first.x].popcnt;
if (onumopts > ncell.popcnt ||
(onumopts == ncell.popcnt && it->second >= totalnnumopts)) {
dit = it.base();
break;
}
}
if (temp.empty()) temp.push_back({});
open.splice(dit, temp, ncell.it = temp.begin());
*ncell.it = { p, totalnnumopts };
ncell.state = State::OPEN;
}
ni++;
}
}
return true;
}
////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <map>
#include <string>
// Where to write the generated JSON.
#define OUTPUT_FILE "game/dungeons/0_procgen.json"
// Starting positions for map tiles in world space.
#define START_X 75
#define START_Z -1295
// Number of tiles on each axis.
#define MAP_WIDTH 10
#define MAP_HEIGHT 20
// One contradiction is considered manageable for most maps.
#define MAX_CONTRADICTIONS 1
int main(){
std::map<char,std::string> tile_models;
tile_models[0x20] = ""; // Space = empty.
tile_models['+'] = "floor_001.gltf";
tile_models['/'] = "beach_corner_northwest.gltf";
tile_models['-'] = "beach_north.gltf";
tile_models['\\'] = "beach_corner_northeast.gltf";
tile_models[']'] = "beach_east.gltf";
tile_models['J'] = "beach_corner_southeast.gltf";
tile_models['_'] = "beach_south.gltf";
tile_models['L'] = "beach_corner_southwest.gltf";
tile_models['['] = "beach_west.gltf";
tile_models['v'] = "slope_001_north.gltf";
tile_models['^'] = "slope_001_south.gltf";
tile_models['>'] = "slope_001_west.gltf";
tile_models['<'] = "slope_001_east.gltf";
tile_models['H'] = "bridge_010_x.gltf";
tile_models['I'] = "bridge_010_z.gltf";
tile_models['='] = "overpass_010_x.gltf";
// Example data that WFC uses.
auto insize = int2( 24, 11 );
std::vector<char*> inmap = {
(char*)R"(/---^---\ /----------^\ )",
(char*)R"(<+++++++] [+++++++++++>H)",
(char*)R"([+++++++>H<+++++++__++] )",
(char*)R"(<+++++++] [++++++] [+>H)",
(char*)R"(L___v___J [++++++] [+] )",
(char*)R"( I [+++++++\ [+] )",
(char*)R"( /--^-----++++++++] [+] )",
(char*)R"(H<++++++++++++++++>H===H)",
(char*)R"( [++++++++++++++++] [+] )",
(char*)R"( L__v_____________J LvJ )",
(char*)R"( I I )"
};
// Output map.
auto outsize = int2( MAP_WIDTH, MAP_HEIGHT );
std::vector<char*> outmap;
for( int y = 0; y < MAP_HEIGHT; y++ ){
outmap.push_back( (char*)calloc( MAP_WIDTH, 1 ) );
}
// RNG.
RandomNumberGenerator<PCG32> r;
r.seed( 50 );
// Output JSON.
std::string outjson = "{ // Generated by wfcgen\n\t\"partitions\": [\n";
for( int i = 0; i < 10000; i++ ){ // 10000 attempts could take hours in some cases.
// Contradiction counter.
int num_contradictions = 0;
if( !WaveFunctionCollapse(
insize,
(const char**)inmap.data(),
outsize,
outmap.data(),
r,
num_contradictions ) ){
fprintf( stderr, "Too many unique tiles in input\n" );
return 1;
}
if( num_contradictions <= MAX_CONTRADICTIONS ){
// Got a map! Scroll vertically and try to find a near-optimal lower left edge.
// This part is very specific to RSOD's map and should NOT be used in other games.
int offset_y = 0;
for( offset_y = 0; offset_y < MAP_HEIGHT; offset_y++ ){
int test_y = (MAP_HEIGHT - 1 + offset_y) % MAP_HEIGHT;
bool success = true;
for( int x = 1; x <= 7; x++ ){
if( outmap[test_y][x] != '+' ) success = false;
}
if( success ) break;
}
// One more RSOD-specific check: Make sure the map does not have sections that are blocked by empty rows.
bool good_map = true;
for( int y = 1; y < MAP_HEIGHT; y++ ){
for( int x = 0; x < MAP_WIDTH; x++ ){
if( outmap[(y + offset_y) % MAP_HEIGHT][x] != 0x20 )
break;
if( x == MAP_WIDTH - 1 ) good_map = false;
}
if( !good_map ) break;
}
if( good_map ){
// Force map edges to be regular cement floors.
// Top edge.
for( int x = 0; x < MAP_WIDTH; x++ ){
char &c = outmap[offset_y % MAP_HEIGHT][x];
if( c != 0x20 ) c = '+';
}
// Bottom edge.
for( int x = 0; x < MAP_WIDTH; x++ ){
char &c = outmap[(MAP_HEIGHT - 1 + offset_y) % MAP_HEIGHT][x];
if( c != 0x20 ) c = '+';
}
// Left edge.
for( int y = 0; y < MAP_HEIGHT; y++ ){
char &c = outmap[y][0];
if( c != 0x20 ) c = '+';
}
// Right edge.
for( int y = 0; y < MAP_HEIGHT; y++ ){
char &c = outmap[y][MAP_WIDTH - 1];
if( c != 0x20 ) c = '+';
}
// Output the map.
for( int y = 0; y < MAP_HEIGHT; y++ ){
for( int x = 0; x < MAP_WIDTH; x++ ){
char c = outmap[(y + offset_y) % MAP_HEIGHT][x];
putchar( c );
int world_x = x * 50 + START_X, world_z = y * 50 + START_Z;
// Select a model.
auto it = tile_models.find( c );
if( it != tile_models.end()
&& it->second.length() > 0 ){
outjson += std::string( "\t\t{\n" )
+ "\t\t\t\"map\": \"" + it->second + "\",\n"
+ "\t\t\t\"translation\": [ " + std::to_string( world_x ) + ".0, 0.0, " + std::to_string( world_z ) + ".0 ],\n"
+ "\t\t\t\"restitution\": 0.1\n"
+ "\t\t},\n";
}
// Add buildings.
if( c == '+' && r(11) < 4 ){
std::string model =
r(5) < 2 ? (r(2) ? (r(2) ? "bar" : "store") : (r(2) ? "restaurant" : "apartments")) : "apartments";
outjson += std::string( "\t\t{\n" )
+ "\t\t\t\"map\": \"" + model + ".gltf\",\n"
+ "\t\t\t\"translation\": [ " + std::to_string( world_x ) + ".0, 1.0, " + std::to_string( world_z ) + ".0 ],\n"
+ "\t\t\t\"restitution\": 0.1\n"
+ "\t\t},\n";
}
}
putchar( '\n' );
}
printf( "num_contradictions: %d (Attempts made: %d)\n\n", num_contradictions, i + 1 );
outjson += "\t]\n}\n";
FILE *file = fopen( OUTPUT_FILE, "w" );
if( file ){
fprintf( file, "%s", outjson.c_str() );
fclose( file );
printf( "File written: %s\n", OUTPUT_FILE );
}
return 0;
}
}
// Clear the output map and try again.
for( auto &row : outmap ){
for( int x = 0; x < MAP_WIDTH; x++ ){
row[x] = 0;
}
}
}
fprintf( stderr,
"Failed to generate a satisfactory map. Consider changing example data or shrinking the output size." );
return 2;
}