mse / RSOD (public) (License: CC0 and other licenses) (since 2025-03-01) (hash sha1)
Free software FPS engine

/src/wfcgen.cpp (7fecc4bd6367ad77ebe91494acaabc29c316223d) (22991 bytes) (mode 100644) (type blob)

#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;
}


Mode Type Size Ref File
100644 blob 170 42b08f467f099371d96fc6f35757c4510bfe7987 .gitignore
100644 blob 13711 25dbc408cf6dce13f6aac062d31879240d7f4f5e Makefile
100644 blob 2396 fc392e2bdb9b9ac4abb5fded56a32df18102c407 README.md
040000 tree - 56f2c438287fb1f800a0493d6b0d6907dc648241 base
100644 blob 487269 29cfd3578eb40b1f039e271bcaa81af49d1b7f3c gamecontrollerdb.txt
040000 tree - 99c807d76953d139e1c10f9f5c053455b9a79d94 include
100755 blob 257 5da83586fddf2984c55ed40e03f43c504552e8aa makeanib
100755 blob 677 e3ce6d4069311dcbb89f1d1166a00a2f9d2934b5 package-steam
100644 blob 879 1aa6cc46749b8ad10c792dd501a23d62886ae951 package-steam-build-demo.vdf
100644 blob 887 accdaa60338652380422b5c2caa0651cc8b0ea7e package-steam-build-playtest.vdf
100644 blob 879 0b12a202f0ef30f781c82678a68c9a5093365e7e package-steam-build.vdf
100644 blob 1182 81610ae8ed0d39b7b1412ef22eff86289fe2adb2 rsod.cbp
040000 tree - 43b2388b0f36e437807a966f6cf9a0dfae5fef7a src
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/mse/RSOD

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/mse/RSOD

Clone this repository using git:
git clone git://git.rocketgit.com/user/mse/RSOD

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main