Document and revise Chunk class, and re-implement MapGen-side-lighting.

master
Auri 2020-11-10 17:00:29 -08:00
parent 2c97101c39
commit 7027c687fd
23 changed files with 525 additions and 299 deletions

View File

@ -7,6 +7,7 @@
<inspection_tool class="ClangTidyInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="clangTidyChecks" value="*,-android-*,-bugprone-bool-pointer-implicit-conversion,-cert-env33-c,-cert-dcl50-cpp,-cert-dcl59-cpp,-cppcoreguidelines-no-malloc,-cppcoreguidelines-owning-memory,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-cppcoreguidelines-pro-type-const-cast,-cppcoreguidelines-pro-type-cstyle-cast,-cppcoreguidelines-pro-type-reinterpret-cast,-cppcoreguidelines-pro-type-union-access,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-special-member-functions,-fuchsia-*,-google-*,google-default-arguments,google-explicit-constructor,google-runtime-member-string-references,google-runtime-operator,-hicpp-braces-around-statements,-hicpp-named-parameter,-hicpp-no-array-decay,-hicpp-no-assembler,-hicpp-no-malloc,-hicpp-function-size,-hicpp-special-member-functions,-hicpp-vararg,-llvm-*,-objc-*,-readability-else-after-return,-readability-implicit-bool-conversion,-readability-named-parameter,-readability-simplify-boolean-expr,-readability-braces-around-statements,-readability-identifier-naming,-readability-function-size,-readability-redundant-member-init,-misc-bool-pointer-implicit-conversion,-misc-definitions-in-headers,-misc-unused-alias-decls,-misc-unused-parameters,-misc-unused-using-decls,-modernize-use-using,-modernize-use-default-member-init,-clang-diagnostic-*,-clang-analyzer-*,-cert-msc30-c,-cert-msc50-cpp,-bugprone-integer-division,-modernize-use-auto" />
</inspection_tool>
<inspection_tool class="EndlessLoop" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="LongLine" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="OCDFAInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">

View File

@ -2,5 +2,6 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/subgames/minimal/mods/minetest_polyfill" vcs="Git" />
</component>
</project>

View File

@ -1,3 +1,4 @@
runfile(_PATH .. "none")
runfile(_PATH .. "block")
runfile(_PATH .. "block_foliage")
runfile(_PATH .. "cross_large")

View File

@ -0,0 +1,5 @@
--
-- Basic 'none' model renders nothing.
--
zepha.register_blockmodel("base:none", { parts = {} })

View File

@ -30,12 +30,10 @@ out VS_OUT {
vec3 unpackFloat(float src) { return vec3(fract(src) * 2.0f - 1.0f, fract(src * 256.f) * 2.0f - 1.0f, fract(src * 65536.f) * 2.0f - 1.0f); }
void main() {
// float sunlightIntensity = 1;
// vec3 blockLightColor = (aLight.xyz / MAX_BLOCKLIGHT) * vec3(1 + sunlightIntensity / 4);
// vec3 sunlightColor = clamp(sunlightIntensity * 1.25 * vec3(1, 1, 1) * (aLight.w / 15.0), 0, 1);
// vec3 light = vec3(max(sunlightColor.x, blockLightColor.x), max(sunlightColor.y, blockLightColor.y), max(sunlightColor.z, blockLightColor.z));
vec3 light = vec3(1, 1, 1);
float sunlightIntensity = 1;
vec3 blockLightColor = (aLight.xyz / MAX_BLOCKLIGHT) * vec3(1 + sunlightIntensity / 4);
vec3 sunlightColor = clamp(sunlightIntensity * 1.25 * vec3(1, 1, 1) * (aLight.w / 15.0), 0, 1);
vec3 light = vec3(max(sunlightColor.x, blockLightColor.x), max(sunlightColor.y, blockLightColor.y), max(sunlightColor.z, blockLightColor.z));
vs_out.pos = aPos;
vs_out.normal = unpackFloat(aNormal);

View File

@ -24,6 +24,7 @@ LocalDefinitionAtlas::LocalDefinitionAtlas(TextureAtlas& atlas) {
//Air Node
BlockModel nullModel{};
BlockDef* air = new BlockDef();
air->lightPropagates = true;
air->identifier = "air";
air->index = 1;
air->name = "Air";

View File

@ -22,6 +22,7 @@ ServerDefinitionAtlas::ServerDefinitionAtlas() {
//Air Node
BlockModel nullModel{};
BlockDef* air = new BlockDef();
air->lightPropagates = true;
air->identifier = "air";
air->index = 1;
air->name = "Air";

View File

@ -113,7 +113,7 @@ void LocalLuaParser::loadApi(WorldPtr world, PlayerPtr player) {
// Create sandboxed runfile()
lua["dofile"] = lua["loadfile"] = sol::nil;
lua.set_function("runfile", &LocalLuaParser::runFileSandboxed, this);
lua.set_function("runfile", &LocalLuaParser::runFileSandboxedWithEnv, this);
}
sol::protected_function_result LocalLuaParser::errorCallback(sol::protected_function_result r) const {
@ -177,4 +177,30 @@ sol::protected_function_result LocalLuaParser::runFileSandboxed(const std::strin
throw std::runtime_error("Error opening \"" + file + "\", file not found.");
}
throw std::runtime_error("Error opening \"" + file + "\", mod not found.");
}
sol::protected_function_result LocalLuaParser::runFileSandboxedWithEnv(sol::this_environment cEnv, const std::string& file) {
size_t modname_length = file.find('/');
if (modname_length == std::string::npos)
throw std::runtime_error("Error opening \"" + file + "\", specified file is invalid.");
std::string modname = file.substr(0, modname_length);
for (const LuaMod& mod : handler.cGetMods()) {
if (modname != mod.config.name) continue;
for (const LuaMod::File& f : mod.files) {
if (f.path != file) continue;
sol::environment currentEnv = cEnv;
sol::environment env(lua, sol::create, currentEnv.get<sol::table>("_G"));
env["_PATH"] = f.path.substr(0, f.path.find_last_of('/') + 1);
env["_FILE"] = f.path;
env["_MODNAME"] = mod.config.name;
return lua.safe_script(f.file, env, std::bind(&LocalLuaParser::errorCallback, this, std::placeholders::_2),
"@" + f.path, sol::load_mode::text);
}
throw std::runtime_error("Error opening \"" + file + "\", file not found.");
}
throw std::runtime_error("Error opening \"" + file + "\", mod not found.");
}

View File

@ -11,11 +11,8 @@
#include "lua/LuaKeybindHandler.h"
class Client;
class LocalWorld;
class LocalPlayer;
class LocalSubgame;
class LocalLuaParser : public LuaParser {
@ -34,6 +31,7 @@ class LocalLuaParser : public LuaParser {
virtual sol::protected_function_result errorCallback(sol::protected_function_result r) const override;
sol::protected_function_result runFileSandboxed(const std::string& file);
sol::protected_function_result runFileSandboxedWithEnv(sol::this_environment env, const std::string& file);
LuaKeybindHandler keybinds;
LocalModHandler handler;

View File

@ -21,17 +21,17 @@ namespace {
std::function<void(std::string)> after, const std::string& identifier, const sol::table& data) {
auto modName = env.get<std::string>("_MODNAME");
std::string iden = identifier[0] == ':' ? modName + identifier : identifier;
if (identifier[0] != ':' && strncmp(identifier.data(), modName.data(), modName.length()))
if (iden[0] == '!') iden = iden.substr(1, iden.length() - 1);
else if (iden.length() <= modName.length() || iden.substr(0, modName.length() + 1) != modName + ":")
throw std::runtime_error(identifier + " does not match calling mod name.");
std::string iden = (identifier[0] == ':' ? modName + identifier : identifier);
unsigned int splitters = std::count_if(iden.begin(), iden.end(), [](char c) { return c == ':'; });
if (splitters > 2) throw std::runtime_error("Too many splitters in identifier " + iden + ".");
if (splitters > 2) throw std::runtime_error("Too many splitters in identifier " + identifier + ".");
core[table][iden] = data;
if (after) after(iden);
}
}

View File

@ -351,9 +351,13 @@ namespace RegisterBlock {
auto collisionOpt = blockTable.get<sol::optional<sol::table>>("collision_box");
std::vector<SelectionBox> selectionBoxes{};
try { if (selectionOpt) selectionBoxes = parseBoxes(*selectionOpt); }
catch (const char* error) { throw std::string("selection boxes " + std::string(error)).c_str(); }
if (selectionBoxes.size() == 0) selectionBoxes.emplace_back(glm::vec3{ 0, 0, 0 }, glm::vec3{ 1, 1, 1 });
try {
if (selectionOpt) selectionBoxes = parseBoxes(*selectionOpt);
else selectionBoxes.emplace_back(glm::vec3{ 0, 0, 0 }, glm::vec3{ 1, 1, 1 });
}
catch (const char* error) {
throw std::string("selection boxes " + std::string(error)).c_str();
}
std::vector<SelectionBox> collisionBoxes{};
try { if (collisionOpt) collisionBoxes = parseBoxes(*collisionOpt); }

View File

@ -59,7 +59,7 @@ void ServerClients::createPlayer(std::shared_ptr<ServerClient> client, Dimension
players.push_back(player);
game.s()->getParser().playerConnected(player);
player->setPos({ 0, -37, 0 }, true);
player->setPos({ 0, -32, 0 }, true);
Serializer()
.appendE(NetField::ID).append(player->getId())

View File

@ -8,10 +8,10 @@
#include "game/ServerSubgame.h"
#include "world/dim/chunk/Chunk.h"
ServerGenStream::ServerGenStream(ServerSubgame& game, ServerWorld& world, unsigned int seed) :
ServerGenStream::ServerGenStream(ServerSubgame& game, ServerWorld& world) :
world(world) {
threads.reserve(THREADS);
for (int i = 0; i < THREADS; i++) threads.emplace_back(game, world, seed);
for (int i = 0; i < THREADS; i++) threads.emplace_back(game, world);
}
bool ServerGenStream::queue(unsigned int dimension, glm::ivec3 pos) {
@ -50,7 +50,7 @@ std::unique_ptr<std::vector<ServerGenStream::FinishedJob>> ServerGenStream::upda
return created;
}
ServerGenStream::Thread::Thread(ServerSubgame& game, ServerWorld& world, unsigned int seed) :
ServerGenStream::Thread::Thread(ServerSubgame& game, ServerWorld& world) :
thread(std::bind(&ServerGenStream::Thread::run, this)) {}
void ServerGenStream::Thread::run() {

View File

@ -25,7 +25,7 @@ class ServerGenStream {
std::unique_ptr<MapGen::CreatedSet> created;
};
explicit ServerGenStream(ServerSubgame& game, ServerWorld& world, unsigned int seed);
explicit ServerGenStream(ServerSubgame& game, ServerWorld& world);
~ServerGenStream();
@ -44,7 +44,7 @@ class ServerGenStream {
};
struct Thread {
explicit Thread(ServerSubgame& game, ServerWorld& world, unsigned int seed);
explicit Thread(ServerSubgame& game, ServerWorld& world);
void run();

View File

@ -63,7 +63,7 @@ ServerWorld::ServerWorld(unsigned int seed, SubgamePtr game, ServerClients& clie
}
void ServerWorld::init(const std::string& worldDir) {
genStream = std::make_unique<ServerGenStream>(*game.s(), *this, seed);
genStream = std::make_unique<ServerGenStream>(*game.s(), *this);
packetStream = std::make_unique<ServerPacketStream>(*this);
// fileManip = std::make_shared<FileManipulator>("worlds/" + worldDir + "/");
}

View File

@ -144,27 +144,9 @@ World& DimensionBase::getWorld() {
return world;
}
std::shared_ptr<Chunk> DimensionBase::combinePartials(std::shared_ptr<Chunk> a, std::shared_ptr<Chunk> b) {
std::shared_ptr<Chunk> src;
std::shared_ptr<Chunk> res;
if (a->isGenerated()) {
res = a;
src = b;
}
else {
res = b;
src = a;
}
for (unsigned int i = 0; i < 4096; i++) {
if (src->getBlock(i) > DefinitionAtlas::INVALID) res->setBlock(i, src->getBlock(i));
}
res->setGenerated(src->isGenerated() || res->isGenerated());
res->setPartial(!res->isGenerated());
res->countRenderableBlocks();
return res;
std::shared_ptr<Chunk> DimensionBase::combineChunks(std::shared_ptr<Chunk> a, std::shared_ptr<Chunk> b) {
if (a->isGenerated()) return (a->combineWith(b), a);
else return (b->combineWith(a), b);
}
std::shared_ptr<MapGen> DimensionBase::getGen() {

View File

@ -75,7 +75,7 @@ class DimensionBase : protected Lockable {
// Combine two chunk partials, or a chunk and a chunk partial.
// If both are partials `b` takes preference, if one is a fully generated chunk the partial takes preference.
// TODO: Make this more efficient using proper RIE traversal.
static std::shared_ptr<Chunk> combinePartials(std::shared_ptr<Chunk> a, std::shared_ptr<Chunk> b);
static std::shared_ptr<Chunk> combineChunks(std::shared_ptr<Chunk> a, std::shared_ptr<Chunk> b);
std::shared_ptr<MapGen> mapGen;
SubgamePtr game;

View File

@ -87,7 +87,7 @@ void ServerDimension::blockPlaceOrInteract(const Target& target, PlayerPtr playe
void ServerDimension::setChunk(std::shared_ptr<Chunk> chunk) {
std::shared_ptr<Chunk> existing = getChunk(chunk->getPos());
if (existing != nullptr) chunk = combinePartials(chunk, existing);
if (existing) chunk = combineChunks(chunk, existing);
Dimension::setChunk(chunk);
}

View File

@ -14,29 +14,22 @@
#include "game/atlas/DefinitionAtlas.h"
Chunk::Chunk(const Chunk& o) :
pos(o.pos),
dirty(o.dirty),
blocks(o.blocks),
biomes(o.biomes),
partial(o.partial),
sunLight(o.sunLight),
generated(o.generated),
blockLight(o.blockLight),
shouldRender(o.shouldRender),
pos(o.pos), state(o.state),
blocks(o.blocks), biomes(o.biomes),
sunLight(o.sunLight), blockLight(o.blockLight),
dirty(o.dirty), shouldRender(o.shouldRender),
renderableBlocks(o.renderableBlocks) {}
Chunk::Chunk(glm::ivec3 pos, bool partial) : pos(pos), partial(partial) {}
Chunk::Chunk(glm::ivec3 pos, bool partial) : pos(pos), state(partial ? State::PARTIAL : State::EMPTY) {}
Chunk::Chunk(glm::ivec3 pos, const std::vector<unsigned int>& blocks, const std::vector<unsigned short>& biomes) :
blocks(std::move(blocks)), biomes(std::move(biomes)),
generated(true), pos(pos) {
state(State::GENERATED), pos(pos) {
countRenderableBlocks();
}
bool Chunk::setBlock(unsigned int ind, unsigned int blk) {
auto l = getWriteLock();
if (!RIE::write(ind, blk, blocks, 4096)) return false;
l.unlock();
if (blk == DefinitionAtlas::AIR) {
renderableBlocks = std::max(renderableBlocks - 1, 0);
@ -58,68 +51,75 @@ const std::vector<unsigned short>& Chunk::cGetBiomes() const {
return biomes;
}
std::string Chunk::serialize() {
auto l = getReadLock();
void Chunk::combineWith(std::shared_ptr<Chunk> o) {
// TODO: Leverage the RIE streams to make this more efficient.
std::vector<unsigned short> blockLight = std::vector<unsigned short>(4096);
std::vector<unsigned char> sunLight = std::vector<unsigned char>(2048);
for (unsigned int i = 0; i < 4096; i++) {
if (o->getBlock(i) > DefinitionAtlas::INVALID) setBlock(i, o->getBlock(i));
}
if (state == State::GENERATED || o->isGenerated()) {
state = State::GENERATED;
countRenderableBlocks();
}
else state = State::PARTIAL;
}
std::string Chunk::serialize() {
std::vector<unsigned short> blArray = std::vector<unsigned short>(4096);
std::vector<unsigned char> slArray = std::vector<unsigned char>(2048);
for (unsigned short i = 0; i < 4096; i++) {
blocklight_union bl;
bl.b = this->blockLight[i];
blockLight[i] = bl.sh;
bl.b = blockLight[i];
blArray[i] = bl.sh;
}
for (unsigned short i = 0; i < 2048; i++) {
sunlight_union sl;
sl.s = this->sunLight[i];
sunLight[i] = sl.ch;
sl.s = sunLight[i];
slArray[i] = sl.ch;
}
l.unlock();
Serializer s;
std::string temp = Serializer().append(pos).append(blocks).append(biomes).append(blockLight).append(sunLight).data;
std::string temp = Serializer().append(pos).append(blocks).append(biomes).append(blArray).append(slArray).data;
s.append<std::string>(gzip::compress(temp.data(), temp.size()));
return s.data;
}
void Chunk::deserialize(Deserializer& d) {
auto l = getWriteLock();
std::string gzipped = d.read<std::string>();
if (!gzip::is_compressed(gzipped.data(), gzipped.length()))
throw std::runtime_error("Chunk contains invalid gzipped data.");
std::vector<unsigned char> sunLight{};
std::vector<unsigned short> blockLight{};
std::vector<unsigned char> slArray {};
std::vector<unsigned short> blArray {};
Deserializer(gzip::decompress(gzipped.data(), gzipped.length()))
.read<glm::ivec3>(pos)
.read<std::vector<unsigned int>>(blocks)
.read<std::vector<unsigned short>>(biomes)
.read<std::vector<unsigned short>>(blockLight)
.read<std::vector<unsigned char>>(sunLight);
.read<std::vector<unsigned short>>(blArray)
.read<std::vector<unsigned char>>(slArray);
for (unsigned short i = 0; i < 4096; i++) {
blocklight_union bl;
bl.sh = blockLight[i];
this->blockLight[i] = bl.b;
bl.sh = blArray[i];
blockLight[i] = bl.b;
}
for (unsigned short i = 0; i < 2048; i++) {
sunlight_union sl;
sl.ch = sunLight[i];
this->sunLight[i] = sl.s;
sl.ch = slArray[i];
sunLight[i] = sl.s;
}
l.unlock();
countRenderableBlocks();
}
void Chunk::countRenderableBlocks() {
auto _ = getReadLock();
shouldRender = false;
renderableBlocks = 0;

View File

@ -1,8 +1,3 @@
//
// The Chunk data class that contains the block, biome, and light data.
// Created by aurailus on 14/12/18.
//
#pragma once
#include <mutex>
@ -17,113 +12,366 @@
class Deserializer;
/**
* A single chunk.
* Represents a 16^3 region of blocks in the world.
* Implements Lockable. Must be manually locked when being used across threads.
*/
class Chunk : public Lockable {
public:
public:
friend class MapGen;
struct BlockLight {
// 16 bits - 1 short
unsigned char r: 5;
unsigned char g: 5;
unsigned char b: 5, : 1;
/**
* An enum for indicating the state of a Chunk.
*/
enum class State {
EMPTY, PARTIAL, GENERATED
};
/**
* A struct for storing Block lighting at a position.
* Uses a bitfield, where each light channel is allocated 5 bits.
* Each channel can have an intensity from 0 (off), to 31 (full).
*/
struct BlockLight {
/** The red channel */
unsigned char r: 5;
/** The green channel */
unsigned char g: 5;
/** The blue channel */
unsigned char b: 5,
/** A leftover bit, declared to make the struct use an even 16. */
: 1;
};
/**
* A union for accessing a short as a BlockLight struct, or vice versa.
*/
typedef union {
short sh;
BlockLight b;
} blocklight_union;
/**
* A struct for storing Sunlight at two positions.
* Sunlight intensity ranges from 0 (none) to 15 (full), which can fit in 4 bits,
* so each SunLight struct can store two positions of sunlight.
* Sunlight with a maximum light value of 15 will cascade downwards infinitely, without losing intensity.
*/
struct SunLight {
// 8 bits for two values - 1 char
/** The odd positioned light value */
unsigned char a: 4;
/** The even positioned light value */
unsigned char b: 4;
};
/**
* A union for accessing a char as a Sunlight struct, or vice versa.
*/
typedef union {
char ch;
SunLight s;
} sunlight_union;
/**
* Initialize an empty, ungenerated chunk containing only INVALID.
* Used in Map Generation.
*/
Chunk() = default;
/**
* A simple copy constructor.
*
* @param o - The chunk to copy.
*/
Chunk(const Chunk& o);
/**
* Basic chunk pos constructor, initializes an empty, ungenerated chunk
* that can optionally be identified as a partial.
*
* @param pos - The position of the Chunk in its dimension.
* @param partial - True if the chunk is a MapGen partial.
*/
Chunk(glm::ivec3 pos, bool partial = false);
/**
* Initializes a generated chunk with the blocks and biomes RIE arrays specified.
* Used in Chunk deserialization from the server.
*
* @param pos - The position of the Chunk in its dimension.
* @param blocks - An RIE array of block positions.
* @param biomes - An RIE array of biome positions.
*/
Chunk(glm::ivec3 pos, const std::vector<unsigned int>& blocks, const std::vector<unsigned short>& biomes);
/**
* Get the position of the chunk.
*
* @returns the position of the chunk.
*/
inline glm::ivec3 getPos() const;
inline void setPos(glm::ivec3 pos);
/**
* Set the position of the chunk.
*
* @param newPos - The new position of the chunk.
*/
inline void setPos(glm::ivec3 newPos);
/**
* Get the chunk's dirty state, which is whether it needs to be remeshed or not.
* This value is only set through other classes using setDirty.
*
* @returns if the chunk is dirty.
*/
inline bool isDirty() const;
inline void setDirty(bool dirty);
/**
* Indicate that a chunk needs to be remeshed.
*
* @param isDirty - If the chunk is dirty.
*/
inline void setDirty(bool isDirty);
/**
* Indicates whether or not the chunk should render,
* which will be true if it contains renderable blocks, or false otherwise.
*
* @returns if the chunk should render.
*/
inline bool chunkShouldRender() const;
inline void setShouldRender(bool shouldRender);
/**
* Returns whether or not the chunk is a partial.
* A partial is a chunk that has not been fully generated, often only containing structure data.
* Ungenerated material is filled with INVALID.
*
* @returns if the chunk is a partial.
*/
inline bool isPartial() const;
[[maybe_unused]] inline bool isPartial() const;
inline void setPartial(bool partial);
/**
* Returns whether or not the chunk has been fully generated.
* A fully generated chunk is full of its own materials and structures,
* but it may still be manipulated later by structures generated nearby.
*
* @returns if the chunk has been generated.
*/
inline bool isGenerated() const;
inline void setGenerated(bool generated);
/**
* Gets the block ID at the index specified.
*
* @param ind - The index to get the block at.
* @returns the block ID at the requested index.
*/
inline unsigned int getBlock(unsigned int ind) const;
/**
* Sets the block ID at the index specified.
*
* @param ind - The index to set the block at.
* @param blk - The block ID to set the block to.
* @returns a boolean indicating if the block replaced a *different* block.
*/
bool setBlock(unsigned int ind, unsigned int blk);
/**
* Gets the block ID at the requested local position.
*
* @param pos - The position to get the block at.
* @returns the block ID at the requested position.
*/
inline unsigned int getBlock(const glm::ivec3& pos) const;
/**
* Sets the block ID at the requested local position.
*
* @param pos - The position to set the block at.
* @param blk - The block ID to set the block to.
* @returns a boolean indicating if the block replaced a *different* block.
*/
inline bool setBlock(const glm::ivec3& pos, unsigned int blk);
/**
* Gets the biome ID at the index specified.
*
* @param ind - The index to get the block at.
* @returns the biome ID of the biome at the requested index.
*/
inline unsigned short getBiome(unsigned int ind) const;
/**
* Sets the biome ID at the index specified.
*
* @param ind - The index to set the biome at.
* @param blk - The biome ID to set the biome to.
* @returns a boolean indicating if the biome replaced a *different* biome.
*/
inline bool setBiome(unsigned int ind, unsigned short bio);
/**
* Gets the biome ID at the local position specified.
*
* @param pos - The position to get the block at.
* @returns the biome ID of the biome at the requested index.
*/
inline unsigned short getBiome(const glm::ivec3& pos) const;
/**
* Sets the biome ID at the local position specified.
*
* @param pos - The position to set the biome at.
* @param blk - The biome ID to set the biome to.
* @returns a boolean indicating if the biome replaced a *different* biome.
*/
inline bool setBiome(const glm::ivec3& pos, unsigned short bio);
/**
* Returns a reference to the chunk's raw blocks array.
*
* @returns a const reference to the chunk's internal block RIE array.
*/
const std::vector<unsigned int>& cGetBlocks() const;
/**
* Returns a reference to the chunk's raw biomes array.
*
* @returns a const reference to the chunk's internal biome RIE array.
*/
const std::vector<unsigned short>& cGetBiomes() const;
/**
* Gets the light value at the specified index.
*
* @param ind - The index to get the light values at.
* @returns a four dimensional vector in the format R, G, B, S, with the light values at the specified index.
*/
inline glm::ivec4 getLight(unsigned int ind);
/**
* Sets the light value at the specified index to the vector specified.
*
* @param ind - The index to set the light values at.
* @param light - a four dimensional vector in the format R, G, B, S, with the desired light values.
*/
inline void setLight(unsigned int ind, glm::ivec4 light);
/**
* Gets a single channel's light value at the specified index.
*
* @param ind - The index to get the light value at.
* @param channel - The channel as a char, where 0 = red, 1 = green, 2 = blue, 3 = sunlight.
* @returns the light value of the specified channel and index.
*/
inline unsigned char getLight(unsigned int ind, unsigned char channel);
/**
* Sets a single channel's light value at the specified index.
*
* @param ind - The index to set the light value at.
* @param channel - The channel as a char, where 0 = red, 1 = green, 2 = blue, 3 = sunlight.
* @returns the light value to set.
*/
inline void setLight(unsigned int ind, unsigned char channel, unsigned char light);
/**
* Combines a chunk's blocks with another's, which may be a partial.
* The other chunk's blocks will take priority, but INVALID will be ignored.
* Will update the chunk's state to generated one of the two was already generated.
*
* @param o - The chunk to combine this one with.
*/
void combineWith(std::shared_ptr<Chunk> o);
/**
* Serializes the chunk for sending over the network.
*
* @returns a packet string containing the chunk's data.
*/
std::string serialize();
/**
* Deserialize chunk data into this chunk.
*
* @param d - A deserializer, whose current index is the start of a serialized chunk string.
*/
void deserialize(Deserializer& d);
private:
void countRenderableBlocks();
private:
glm::ivec3 pos{};
bool partial = false;
bool generated = false;
bool dirty = true;
bool shouldRender = true;
std::vector<unsigned int> blocks{ 0, 0 };
std::vector<unsigned short> biomes{ 0, 0 };
std::array<SunLight, 2048> sunLight{};
std::array<BlockLight, 4096> blockLight{};
unsigned short renderableBlocks = 0;
/**
* Gets the sunlight intensity at the specified index.
*
* @param ind - The index to get the sunlight at.
* @returns the sunlight intensity as a char at the specified index.
*/
inline unsigned char getSunlight(unsigned int ind);
/**
* Sets the sunlight intensity at the specified index.
*
* @param ind - The index to set the sunlight at.
* @param val - The value to set the sunlight to, which must range from 0 - 15.
*/
inline void setSunlight(unsigned int ind, unsigned char val);
/**
* Updates the internal Renderable Blocks count, which determines if a chunk should render.
* A renderable block is anything except for AIR and INVALID.
*/
void countRenderableBlocks();
State state = State::EMPTY;
glm::ivec3 pos {};
bool dirty = true;
bool shouldRender = true;
unsigned short renderableBlocks = 0;
std::vector<unsigned int> blocks { 0, 0 };
std::vector<unsigned short> biomes { 0, 0 };
std::array<SunLight, 2048> sunLight {};
std::array<BlockLight, 4096> blockLight {};
};
#include "Chunk.inl"

View File

@ -1,128 +1,99 @@
#include "Chunk.h"
glm::ivec3 Chunk::getPos() const {
auto l = getReadLock();
return pos;
}
void Chunk::setPos(glm::ivec3 pos) {
auto l = getWriteLock();
this->pos = pos;
void Chunk::setPos(glm::ivec3 newPos) {
pos = newPos;
}
bool Chunk::isDirty() const {
auto l = getReadLock();
return dirty;
}
void Chunk::setDirty(bool dirty) {
auto l = getWriteLock();
this->dirty = dirty;
void Chunk::setDirty(bool isDirty) {
dirty = isDirty;
}
bool Chunk::chunkShouldRender() const {
auto l = getReadLock();
return shouldRender;
}
void Chunk::setShouldRender(bool shouldRender) {
auto l = getWriteLock();
this->shouldRender = shouldRender;
[[maybe_unused]] bool Chunk::isPartial() const {
return state == State::PARTIAL;
}
bool Chunk::isPartial() const {
auto l = getReadLock();
return partial;
}
void Chunk::setPartial(bool partial) {
auto l = getWriteLock();
this->partial = partial;
};
bool Chunk::isGenerated() const {
auto l = getReadLock();
return generated;
}
void Chunk::setGenerated(bool generated) {
auto l = getWriteLock();
this->generated = generated;
return state == State::GENERATED;
}
inline unsigned int Chunk::getBlock(unsigned int ind) const {
auto l = getReadLock();
if (ind >= 4096) return 0; // Invalid
return RIE::read<unsigned int>(ind, blocks, 4096);
}
inline unsigned int Chunk::getBlock(const glm::ivec3& pos) const {
if (pos.x > 15 || pos.x < 0 || pos.y > 15 || pos.y < 0 || pos.z > 15 || pos.z < 0) return 0; // Invalid
return getBlock(Space::Block::index(pos));
inline unsigned int Chunk::getBlock(const glm::ivec3& reqPos) const {
if (reqPos.x > 15 || reqPos.x < 0 || reqPos.y > 15 || reqPos.y < 0 || reqPos.z > 15 || reqPos.z < 0) return 0;
return getBlock(Space::Block::index(reqPos));
}
inline bool Chunk::setBlock(const glm::ivec3& pos, unsigned int blk) {
if (pos.x > 15 || pos.x < 0 || pos.y > 15 || pos.y < 0 || pos.z > 15 || pos.z < 0) return false;
return setBlock(Space::Block::index(pos), blk);
inline bool Chunk::setBlock(const glm::ivec3& newPos, unsigned int blk) {
if (newPos.x > 15 || newPos.x < 0 || newPos.y > 15 || newPos.y < 0 || newPos.z > 15 || newPos.z < 0) return false;
return setBlock(Space::Block::index(newPos), blk);
}
inline unsigned short Chunk::getBiome(unsigned int ind) const {
auto l = getReadLock();
if (ind >= 4096) return 0; // Invalid
return RIE::read<unsigned short>(ind, biomes, 4096);
}
inline unsigned short Chunk::getBiome(const glm::ivec3& pos) const {
if (pos.x > 15 || pos.x < 0 || pos.y > 15 || pos.y < 0 || pos.z > 15 || pos.z < 0) return 0; // Invalid
return getBiome(Space::Block::index(pos));
inline unsigned short Chunk::getBiome(const glm::ivec3& reqPos) const {
if (reqPos.x > 15 || reqPos.x < 0 || reqPos.y > 15 || reqPos.y < 0 || reqPos.z > 15 || reqPos.z < 0) return 0;
return getBiome(Space::Block::index(reqPos));
}
inline bool Chunk::setBiome(unsigned int ind, unsigned short bio) {
auto l = getWriteLock();
return RIE::write(ind, bio, biomes, 4096);
}
inline bool Chunk::setBiome(const glm::ivec3& pos, unsigned short bio) {
if (pos.x > 15 || pos.x < 0 || pos.y > 15 || pos.y < 0 || pos.z > 15 || pos.z < 0) return false;
return setBiome(Space::Block::index(pos), bio);
inline bool Chunk::setBiome(const glm::ivec3& newPos, unsigned short bio) {
if (newPos.x > 15 || newPos.x < 0 || newPos.y > 15 || newPos.y < 0 || newPos.z > 15 || newPos.z < 0) return false;
return setBiome(Space::Block::index(newPos), bio);
}
inline glm::ivec4 Chunk::getLight(unsigned int ind) {
auto l = getReadLock();
return { blockLight[ind].r, blockLight[ind].g, blockLight[ind].b, getSunlight(ind) };
}
inline void Chunk::setLight(unsigned int ind, glm::ivec4 light) {
auto l = getWriteLock();
blockLight[ind].r = light.x;
blockLight[ind].g = light.y;
blockLight[ind].b = light.z;
l.unlock();
setSunlight(ind, light.w);
blockLight[ind].r = static_cast<unsigned char>(light.x);
blockLight[ind].g = static_cast<unsigned char>(light.y);
blockLight[ind].b = static_cast<unsigned char>(light.z);
setSunlight(ind, static_cast<unsigned char>(light.w));
}
inline unsigned char Chunk::getLight(unsigned int ind, unsigned char channel) {
auto l = getReadLock();
return channel == 0 ? blockLight[ind].r :
channel == 1 ? blockLight[ind].g :
channel == 2 ? blockLight[ind].b :
(l.unlock(), getSunlight(ind));
channel == 1 ? blockLight[ind].g :
channel == 2 ? blockLight[ind].b :
getSunlight(ind);
}
inline void Chunk::setLight(unsigned int ind, unsigned char channel, unsigned char light) {
auto l = getWriteLock();
channel == 0 ? blockLight[ind].r = light :
channel == 1 ? blockLight[ind].g = light :
channel == 2 ? blockLight[ind].b = light :
(l.unlock(), setSunlight(ind, light), 0);
channel == 1 ? blockLight[ind].g = light :
channel == 2 ? blockLight[ind].b = light :
(setSunlight(ind, light), 0);
}
inline unsigned char Chunk::getSunlight(unsigned int ind) {
auto l = getReadLock();
if (ind % 2 == 0) return sunLight[ind / 2].a;
else return sunLight[ind / 2].b;
}
inline void Chunk::setSunlight(unsigned int ind, unsigned char val) {
auto l = getWriteLock();
if (ind % 2 == 0) sunLight[ind / 2].a = val;
else sunLight[ind / 2].b = val;
}

View File

@ -86,7 +86,7 @@ std::unique_ptr<MapGen::CreatedSet> MapGen::generateArea(unsigned int dim, glm::
// Generate Chunks
glm::ivec3 pos {};
for (pos.x = 0; pos.x < job.size; pos.x++)
for (pos.x = 0; pos.x < job.size; pos.x++) {
for (pos.z = 0; pos.z < job.size; pos.z++) {
std::unique_ptr<ChunkData> densityAbove = nullptr;
for (pos.y = job.size; pos.y >= 0; pos.y--) {
@ -99,13 +99,14 @@ std::unique_ptr<MapGen::CreatedSet> MapGen::generateArea(unsigned int dim, glm::
std::unique_ptr<ChunkData> depth = populateChunkDepth(density, std::move(densityAbove));
generateChunkBlocks(job, pos, biomeMap, *depth);
generateChunkStructures(job, pos, biomeMap, *depth);
generateChunkDecorAndLight(job, pos, biomeMap, *depth);
densityAbove = std::move(density);
}
}
// generateSunlight(chunks, mbPos);
}
propogateSunlightNodes(job);
auto created = std::make_unique<CreatedSet>();
for (const auto& chunk : *job.chunks) {
@ -142,7 +143,7 @@ unsigned int MapGen::getBiomeAt(float temperature, float humidity, float roughne
std::unique_ptr<MapGen::ChunkData> MapGen::populateChunkDensity(MapGen::Job& job, glm::ivec3 localPos) {
auto data = std::make_unique<ChunkData>();
for (int i = 0; i < 4096; i++) {
for (unsigned short i = 0; i < 4096; i++) {
glm::ivec3 indPos = Space::Block::fromIndex(i);
glm::vec3 queryPos = (glm::vec3(localPos) + glm::vec3(indPos) / 16.f) / static_cast<float>(job.size);
(*data)[i] = (job.volume.get(queryPos) + job.heightmap.get({ queryPos.x, 0, queryPos.z })) -
@ -183,8 +184,9 @@ std::unique_ptr<MapGen::ChunkData> MapGen::populateChunkDepth(std::unique_ptr<Ch
return data;
}
void
MapGen::generateChunkBlocks(Job& job, glm::ivec3 localPos, std::vector<unsigned int> biomeMap, ChunkData& depthMap) {
void MapGen::generateChunkBlocks(Job& job, glm::ivec3 localPos,
std::vector<unsigned int> biomeMap, ChunkData& depthMap) {
glm::ivec3 chunkPos = job.pos + localPos;
auto partial = (job.chunks->count(chunkPos) ? job.chunks->at(chunkPos) : nullptr);
@ -202,8 +204,8 @@ MapGen::generateChunkBlocks(Job& job, glm::ivec3 localPos, std::vector<unsigned
for (unsigned short i = 0; i < 4096; i++) {
glm::ivec3 indPos = Space::Block::fromIndex(i);
unsigned int biomeID = biomeMap[(localPos.x * 16 + indPos.x) * (job.size * 16 + 1) +
(localPos.z * 16 + indPos.z)];
unsigned int biomeID = biomeMap[(localPos.x * 16 + indPos.x) *
(job.size * 16 + 1) + (localPos.z * 16 + indPos.z)];
auto& biome = game.getBiomes().biomeFromId(biomeID);
if (partial && i >= partialNextAt) {
@ -214,11 +216,11 @@ MapGen::generateChunkBlocks(Job& job, glm::ivec3 localPos, std::vector<unsigned
float depth = depthMap[i];
unsigned int blockID =
partialBlock != DefinitionAtlas::INVALID ? partialBlock
partialBlock > DefinitionAtlas::INVALID ? partialBlock
: depth <= 1 ? DefinitionAtlas::AIR
: depth <= 2 ? biome.topBlock
: depth <= 4 ? biome.soilBlock
: biome.rockBlock;
: depth <= 4 ? biome.soilBlock
: biome.rockBlock;
if (biomeID != cBiomeID) {
chunk.biomes.emplace_back(i);
@ -234,44 +236,61 @@ MapGen::generateChunkBlocks(Job& job, glm::ivec3 localPos, std::vector<unsigned
}
chunk.countRenderableBlocks();
chunk.generated = true;
}
void MapGen::generateChunkStructures(Job& job, glm::ivec3 localPos, std::vector<unsigned int> biomeMap,
void MapGen::generateChunkDecorAndLight(Job& job, glm::ivec3 localPos, std::vector<unsigned int> biomeMap,
ChunkData& depthMap) {
glm::vec3 posFloat = job.pos + localPos;
std::default_random_engine generator(posFloat.x + posFloat.y / M_PI + posFloat.z / (M_PI * 2));
std::default_random_engine generator(posFloat.x + posFloat.y * M_PI + posFloat.z * (M_PI * 2));
std::uniform_real_distribution<float> distribution(0, 1);
auto& chunk = job.chunks->at(job.pos + localPos);
glm::ivec3 abovePos = job.pos + localPos + glm::ivec3 { 0, 1, 0 };
std::shared_ptr<Chunk> above = (localPos.y != job.size - 1) ?
job.chunks->count(abovePos) ? job.chunks->at(abovePos) : nullptr : nullptr;
for (unsigned short i = 0; i < 256; i++) {
glm::ivec3 indPos = { i / 16, 0, i % 16 };
if (distribution(generator) > 0.97) {
for (indPos.y = 0; indPos.y < 16; indPos.y++) {
bool structure = distribution(generator) > 0.97;
glm::ivec3 indPos = { i / 16, 15, i % 16 };
unsigned char light = above ? above->getLight(Space::Block::index(
indPos), 3) : game.getDefs().blockFromId(chunk->getBlock(indPos)).lightPropagates ? 15 : 0;
for (; indPos.y >= 0 && (light || structure); indPos.y--) {
unsigned short ind = Space::Block::index(indPos);
if (structure && depthMap[ind] >= 1 && depthMap[ind] < 2) {
glm::ivec3 pos = (job.pos + localPos) * 16 + indPos;
pos.y++; // Compensate for the fact that we're finding solid positions.
unsigned short ind = Space::Block::index(indPos);
if (depthMap[ind] > 0 && depthMap[ind] <= 1.1) {
glm::ivec3 pos = (job.pos + localPos) * 16 + indPos;
unsigned int biomeID = biomeMap[(localPos.x * 16 + indPos.x) * (job.size * 16 + 1) +
(localPos.z * 16 + indPos.z)];
auto& biome = game.getBiomes().biomeFromId(biomeID);
auto schematic = biome.schematics.size() > 0 ? biome.schematics[0] : nullptr;
if (schematic) {
if (!schematic->processed) schematic->process(game.getDefs());
for (unsigned int j = 0; j < schematic->length(); j++) {
glm::ivec3 off = schematic->getOffset(j);
setBlock(job, pos + off - schematic->origin, schematic->blocks[j], chunk);
}
unsigned int biomeID = biomeMap[(localPos.x * 16 + indPos.x) * (job.size * 16 + 1) +
(localPos.z * 16 + indPos.z)];
auto& biome = game.getBiomes().biomeFromId(biomeID);
auto schematic = biome.schematics.size() > 0 ? biome.schematics[0] : nullptr;
if (schematic) {
if (!schematic->processed) schematic->process(game.getDefs());
for (unsigned int j = 0; j < schematic->length(); j++) {
glm::ivec3 off = schematic->getOffset(j);
setBlock(job, pos + off - schematic->origin, schematic->blocks[j], chunk);
}
}
}
if (!light) continue;
auto& blockDef = game.getDefs().blockFromId(chunk->getBlock(indPos));
if (!blockDef.lightPropagates) light = 0;
else {
chunk->setLight(ind, 3, light);
job.sunlightQueue.emplace(ind, chunk);
}
}
}
chunk->state = Chunk::State::GENERATED;
}
void MapGen::setBlock(MapGen::Job& job, glm::ivec3 worldPos, unsigned int block, std::shared_ptr<Chunk> hint) {
@ -283,84 +302,38 @@ void MapGen::setBlock(MapGen::Job& job, glm::ivec3 worldPos, unsigned int block,
}
else {
glm::ivec3 chunkPos = Space::Chunk::world::fromBlock(worldPos);
auto chunk = *(*job.chunks->emplace(chunkPos, std::make_shared<Chunk>(chunkPos, true)).first).second;
auto& chunk = *(*job.chunks->emplace(chunkPos, std::make_shared<Chunk>(chunkPos, true)).first).second;
if (chunk.getBlock(ind) <= DefinitionAtlas::AIR) chunk.setBlock(ind, block);
}
}
//void MapGen::generateSunlight(MapGen::chunk_partials_map &chunks, glm::ivec3 mbPos) {
// std::queue<SunlightNode> sunlightQueue;
//
// glm::ivec3 c {};
// for (c.x = 0; c.x < 4; c.x++) {
// for (c.z = 0; c.z < 4; c.z++) {
// c.y = 3;
// Chunk* chunk = chunks[mbPos * 4 + c].second;
//
// glm::ivec3 b {};
// for (b.x = 0; b.x < 16; b.x++) {
// for (b.z = 0; b.z < 16; b.z++) {
// b.y = 15;
//
// while (true) {
// unsigned int ind = Space::Block::index(b);
// if (defs.blockFromId(chunk->getBlock(ind)).lightPropagates) {
// chunk->setLight(ind, 3, 15);
// sunlightQueue.emplace(ind, chunk);
// }
// else {
// c.y = 3;
// chunk = chunks[mbPos * 4 + c].second;
// break;
// }
//
// b.y--;
// if (b.y < 0) {
// b.y = 15;
// c.y = c.y ? c.y - 1 : 3;
// chunk = chunks[mbPos * 4 + c].second;
// if (c.y == 3) break;
// }
// }
// }
// }
// }
// }
//
// propogateSunlightNodes(chunks, sunlightQueue);
//}
//
//bool MapGen::containsWorldPos(Chunk *chunk, glm::ivec3 pos) {
// return chunk && Space::Chunk::world::fromBlock(pos) == chunk->pos;
//}
//
//void MapGen::propogateSunlightNodes(MapGen::chunk_partials_map &chunks, std::queue<SunlightNode> &queue) {
// while (!queue.empty()) {
// SunlightNode& node = queue.front();
//
// unsigned char lightLevel = node.chunk->getLight(node.index, 3);
// glm::ivec3 worldPos = node.chunk->pos * 16 + Space::Block::fromIndex(node.index);
//
// for (const auto& i : Vec::adj) {
// glm::ivec3 check = worldPos + i;
//
// Chunk* chunk;
// if (containsWorldPos(node.chunk, check)) chunk = node.chunk;
// else {
// glm::ivec3 worldPos = Space::Chunk::world::fromBlock(check);
// if (!chunks.count(worldPos)) continue;
// chunk = chunks[worldPos].second;
// if (!chunk) continue;
// }
//
// auto ind = Space::Block::index(check);
// if (defs.blockFromId(chunk->getBlock(ind)).lightPropagates && chunk->getLight(ind, 3) + 2 <= lightLevel) {
// chunk->setLight(ind, 3, lightLevel - static_cast<int>(!(lightLevel == 15 && i.y == -1)));
// queue.emplace(ind, chunk);
// }
// }
//
// queue.pop();
// }
//}
//
void MapGen::propogateSunlightNodes(Job& job) {
while (!job.sunlightQueue.empty()) {
SunlightNode& node = job.sunlightQueue.front();
unsigned char lightLevel = node.chunk->getLight(node.index, 3);
glm::ivec3 worldPos = node.chunk->pos * 16 + Space::Block::fromIndex(node.index);
for (const auto& i : Vec::TO_VEC) {
glm::ivec3 check = worldPos + i;
std::shared_ptr<Chunk> chunk;
if (node.chunk->pos == Space::Chunk::world::fromBlock(check)) chunk = node.chunk;
else {
glm::ivec3 wp = Space::Chunk::world::fromBlock(check);
if (!job.chunks->count(wp)) continue;
chunk = job.chunks->at(wp);
if (!chunk) continue;
}
auto ind = Space::Block::index(check);
if (game.getDefs().blockFromId(chunk->getBlock(ind)).lightPropagates &&
chunk->getLight(ind, 3) + 2 <= lightLevel) {
chunk->setLight(ind, 3, lightLevel - static_cast<int>(!(lightLevel == 15 && i.y == -1)));
job.sunlightQueue.emplace(ind, chunk);
}
}
job.sunlightQueue.pop();
}
}

View File

@ -34,6 +34,17 @@ public:
/** A type alias for the returned list of chunk positions that were generated. */
typedef std::unordered_set<glm::ivec3, Vec::ivec3> CreatedSet;
/**
* A struct representing a single position in a chunk at which sunlight should be updated at.
*/
struct SunlightNode {
SunlightNode(unsigned short index, std::shared_ptr<Chunk> chunk) : index(index), chunk(chunk) {};
unsigned short index;
std::shared_ptr<Chunk> chunk;
};
/**
* A struct containing all the information for a generation job.
* Contains a list of chunks, Noise samples, and the world position of the the job's root.
@ -58,6 +69,7 @@ public:
unsigned int size {};
std::unique_ptr<ChunkMap> chunks = std::make_unique<ChunkMap>();
std::queue<SunlightNode> sunlightQueue {};
NoiseSample volume, heightmap;
NoiseSample temperature, humidity, roughness;
@ -177,6 +189,7 @@ private:
/**
* Generates structures for a Chunk based on data within the generation job and an offset within it.
* Also generates initial light cascade, which will later be refined by propogateSunlightNodes.
* May create chunk partials, inserting them back into the Job for later completion.
*
* @param job - The job to pull the data from.
@ -185,8 +198,8 @@ private:
* @param depthMap - The depth map of the chunk being generated.
*/
void
generateChunkStructures(Job& job, glm::ivec3 localPos, std::vector<unsigned int> biomeMap, ChunkData& depthMap);
void generateChunkDecorAndLight(Job& job, glm::ivec3 localPos,
std::vector<unsigned int> biomeMap, ChunkData& depthMap);
/**
* Sets a block at the position specified into the Job, if the block at said position is not filled by
@ -200,11 +213,14 @@ private:
*/
static void setBlock(Job& job, glm::ivec3 worldPos, unsigned int block, std::shared_ptr<Chunk> hint);
// // Generate sunlight on the mapgen threads to speed up perf
// void generateSunlight(ChunkMap& chunks, glm::ivec3 mbPos);
// static bool containsWorldPos(Chunk *chunk, glm::ivec3 pos);
// void propogateSunlightNodes(ChunkMap& chunks, std::queue<SunlightNode>& queue);
/**
* Calculates and smooths sunlight for an entire Job's chunks.
*
* @param job - The job to act upon.
*/
void propogateSunlightNodes(Job& job);
MapGenProps props;
unsigned int seed = 0;