diff --git a/bin/data/shaders/terrain.frag.glsl b/bin/data/shaders/terrain.frag.glsl index dce42753..21a7e5d7 100644 --- a/bin/data/shaders/terrain.frag.glsl +++ b/bin/data/shaders/terrain.frag.glsl @@ -10,7 +10,7 @@ layout (location = 0) out vec4 outFragColor; void fog( inout vec3 i ) { vec3 color = vec3( 0, 0, 0 ); - float inner = 8, outer = 64; + float inner = 8, outer = 32; float distance = length(-inPositionEye); float factor = (distance - inner) / (outer - inner); factor = clamp( factor, 0.0, 1.0 ); @@ -46,7 +46,7 @@ void phong( inout vec3 i ) { void main() { outFragColor = texture(samplerColor, inUv); -// phong(outFragColor.rgb); + phong(outFragColor.rgb); fog(outFragColor.rgb); // outFragColor.rgb = mix( outFragColor.rgb, inColor.rgb, inColor.a ); outFragColor.rgb = outFragColor.rgb * inColor.rgb; diff --git a/ext/world/terrain/generator.cpp b/ext/world/terrain/generator.cpp index 65e1d48d..659d056a 100644 --- a/ext/world/terrain/generator.cpp +++ b/ext/world/terrain/generator.cpp @@ -84,7 +84,7 @@ void ext::TerrainGenerator::generate( ext::Region& region ){ } */ std::string base = region.getParent().getComponent()["_config"]["hash"].asString(); - std::string filename = "./data/save/regions/" + base + "/" + std::to_string(location.x) + "." + std::to_string(location.y) + "." + std::to_string(location.z) + ".json"; + std::string filename = "./data/save/" + base + "/regions/" + std::to_string(location.x) + "." + std::to_string(location.y) + "." + std::to_string(location.z) + ".json"; // old region, load save if ( uf::string::exists( filename ) ) { uf::Serializer save; save.readFromFile(filename); @@ -123,6 +123,22 @@ void ext::TerrainGenerator::generate( ext::Region& region ){ } // load failed / new region } + + // maze + /* + { + ext::Maze& maze = this->m_terrain->getComponent(); + int ROW = this->m_location.z + (maze.WIDTH / 2) - 1; + int COL = this->m_location.x + (maze.LENGTH / 2) - 1; + auto value = maze( ROW, COL, 0 ); + bool east = value & ext::Maze::EAST; // ROW + 1 < maze.WIDTH ? maze( ROW + 1, COL, 1 ) : 0; + bool west = value & ext::Maze::WEST; // ROW - 1 > 0 ? maze( ROW - 1, COL, 1 ) : 0; + bool north = value & ext::Maze::NORTH; // COL + 1 < maze.LENGTH ? maze( ROW, COL + 1, 1 ) : 0; + bool south = value & ext::Maze::SOUTH; // COL - 1 > 0 ? maze( ROW, COL - 1, 1 ) : 0; + std::cout << ROW << ", " << COL << " = " << value << "\t" << east << " " << west << " " << north << " " << south << std::endl; + } + */ + if ( this->m_voxels.id.rle.empty() ) { double maxValue = 0.0; double base = 0; @@ -131,9 +147,9 @@ void ext::TerrainGenerator::generate( ext::Region& region ){ ext::TerrainVoxel::light_t raw_lighting[this->m_size.x][this->m_size.y][this->m_size.z]; double raw_noise[this->m_size.x][this->m_size.y][this->m_size.z]; - for ( int y = 0; y < this->m_size.y; ++y ) - for ( int z = 0; z < this->m_size.z; ++z ) - for ( int x = 0; x < this->m_size.x; ++x ) { + for ( uint y = 0; y < this->m_size.y; ++y ) + for ( uint z = 0; z < this->m_size.z; ++z ) + for ( uint x = 0; x < this->m_size.x; ++x ) { pod::Vector3d position = { transform.position.x - half_x, transform.position.y - half_y, transform.position.z - half_z }; @@ -145,14 +161,13 @@ void ext::TerrainGenerator::generate( ext::Region& region ){ } - for ( int y = 0; y < this->m_size.y; ++y ) - for ( int z = 0; z < this->m_size.z; ++z ) - for ( int x = 0; x < this->m_size.x; ++x ) + for ( uint y = 0; y < this->m_size.y; ++y ) + for ( uint z = 0; z < this->m_size.z; ++z ) + for ( uint x = 0; x < this->m_size.x; ++x ) base += raw_noise[x][y][z] / maxValue; base /= this->m_size.x * this->m_size.y * this->m_size.z; - uint partitions = this->m_modulus; struct { ext::TerrainVoxel::uid_t floor = ext::TerrainVoxelFloor().uid(); @@ -165,18 +180,32 @@ void ext::TerrainGenerator::generate( ext::Region& region ){ this->m_voxels.id.raw.reserve( this->m_size.x * this->m_size.y * this->m_size.z ); this->m_voxels.lighting.raw.reserve( this->m_size.x * this->m_size.y * this->m_size.z ); - - std::size_t i = 0; + ext::Maze& maze = this->m_terrain->getComponent(); + int LABYRINTH = ext::Maze::FLOOR | ext::Maze::CEIL; + { + int ROW = this->m_location.z + (maze.WIDTH / 2) - 1; + int COL = this->m_location.x + (maze.LENGTH / 2) - 1; + if ( ROW >= 0 && ROW < maze.WIDTH && COL >= 0 && COL < maze.LENGTH ) + LABYRINTH = maze( ROW, COL, 0 ); + } + + std::size_t i = 0; for ( uint y = 0; y < this->m_size.y; ++y ) { for ( uint z = 0; z < this->m_size.z; ++z ) { for ( uint x = 0; x < this->m_size.x; ++x ) { ext::TerrainVoxel::uid_t voxel = 0; - /* - if ( y <= 4 ) voxel = atlas.floor; - */ double e = raw_noise[x][y][z] / maxValue; + // generate walls if wall exists in maze + if ( LABYRINTH & ext::Maze::EAST ) if ( x == 0 ) voxel = atlas.wall; + if ( LABYRINTH & ext::Maze::WEST ) if ( x == this->m_size.x - 1 ) voxel = atlas.wall; + if ( LABYRINTH & ext::Maze::NORTH ) if ( z == 0 ) voxel = atlas.wall; + if ( LABYRINTH & ext::Maze::SOUTH ) if ( z == this->m_size.z - 1 ) voxel = atlas.wall; + if ( LABYRINTH & ext::Maze::FLOOR ) if ( y == 0 ) voxel = atlas.floor; + if ( LABYRINTH & ext::Maze::CEIL ) if ( y == this->m_size.y - 1 ) voxel = atlas.ceiling; + + /* // if ( (x) % (this->m_size.x / partitions) == 0 ) voxel = atlas.wall; // if ( (z) % (this->m_size.z / partitions) == 0 ) voxel = atlas.wall; if ( (x + 1) % (this->m_size.x / partitions) == 0 ) voxel = atlas.wall; @@ -195,7 +224,7 @@ void ext::TerrainGenerator::generate( ext::Region& region ){ } else { // if ( x == half_x && y == 4 && z == half_z ) voxel = atlas.lava; } - + */ auto light = ext::TerrainVoxel::atlas(voxel).light(); if ( light < 0x1111 ) light = 0x1111; @@ -226,7 +255,7 @@ void ext::TerrainGenerator::writeToFile() { uf::Serializer serializer; std::string base = this->m_terrain ? this->m_terrain->getComponent()["_config"]["hash"].asString() : "UNKNOWN"; - std::string filename = "./data/save/regions/" + base + "/" + std::to_string(this->m_location.x) + "." + std::to_string(this->m_location.y) + "." + std::to_string(this->m_location.z) + ".json"; + std::string filename = "./data/save/" + base + "/regions/" + std::to_string(this->m_location.x) + "." + std::to_string(this->m_location.y) + "." + std::to_string(this->m_location.z) + ".json"; { // encode as base64, json safe serializer["voxels"]["id"]["base64"] = uf::base64::encode( this->m_voxels.id.rle ); @@ -347,7 +376,7 @@ ext::TerrainVoxel::light_t ext::TerrainGenerator::getLight( int x, int y, int z ext::Region* region = terrain.at( location ); if ( !region ) { // std::cout << "Out of bounds Region("<< location.x << ", " << location.y << ", " << location.z <<") @ getLight( " << x << ", " << y << ", " << z << " ) " << std::endl; - return 0x0000; + return 0xFFFF; } return region->getComponent().getLight( x, y, z ); } diff --git a/ext/world/terrain/maze.cpp b/ext/world/terrain/maze.cpp new file mode 100644 index 00000000..9cbf675c --- /dev/null +++ b/ext/world/terrain/maze.cpp @@ -0,0 +1,436 @@ +#include "Maze.h" +#include +#include +#include +#include +#include +#include +#include +#include + +void ext::Maze::initialize(int columns, int rows, int floors, double horizontal_bias, double vertical_bias) { + /* The Maze class contructor + * Input: + * int columns - Set the number of columns in the maze. Correspond to the X axis (length). + * int rows - Set the number of rows in the maze. Correspond to the Y axis (width). + * int floors - Set the number of floors in the maze. Correspond to the Z axis (height). + * + * All three must be larger than 1. + * + * double horizontal_bias - Sets the likelihood of a passage between rooms on the same row. + * double vertical_bias - Sets the likelihood of a passage between rooms in the same column. + * + * Both must be between 0 and 1 exclusive. + * Higher number gives higher likelihood of a passage. + * + * Output: + * Maze object + */ + if (rows < 1 || columns < 1 || floors < 1){ + std::cout << "A maze must have dimensions greater than zero.\n"; + throw std::invalid_argument("A maze must have dimensions greater than zero.\n"); + } + LENGTH = columns; + WIDTH = rows; + HEIGHT = floors; + + if (horizontal_bias <= 0 || horizontal_bias >= 1 || vertical_bias <= 0 || vertical_bias >= 1){ + std::cout << "Biases must be between 0 and 1 exclusive.\n"; + throw std::invalid_argument("Biases must be between 0 and 1 exclusive.\n"); + } + EAST_WALL_THRESHOLD = horizontal_bias; + SOUTH_WALL_THRESHOLD = vertical_bias; + + //Creating a vector of all rooms. + cells.assign(LENGTH*WIDTH*HEIGHT, -1); + + // seed + seed = time(NULL); + + // Give each room a different set number (ascending). + // (Which is used during maze generation) + std::iota(std::begin(cells), std::end(cells), 0); +}; + +int ext::Maze::get(int i, int col, int floor){ + // Gets the value stored at row i, column j, level k, in cells; + return cells.at(col + LENGTH*i + LENGTH*WIDTH*floor); +} + +void ext::Maze::set(int row, int col, int floor, int val){ + // Set the value of cell at (row, col, floor) to val + cells[col + LENGTH*row + LENGTH*WIDTH*floor] = val; + return; +} + +void ext::Maze::build(){ + /* This function generates a maze. + * + * It uses sets to keep track of areas created by removing walls. + * Initially each room has it's own set, known by it's room value. + * The room sets grows as walls are removed and sets joined. + * To keep track, each room value is mapped to itself, or the room value it has joined. + * The mapping and sets are reset at each floor. + * + * Summary: + * It goes through each cell, removing the walls randomly. + * (First WEST to EAST, then NORTH to SOUTH, then DOWN to UP. + * Just as one write in English, putting each new page on top of the other) + * If a wall is between two rooms in the same set, it is not removed. + * + * Each room value denote which set it belongs to + * + * Each wall removed is stored in as a passage between the two rooms. + * (The rooms position is stored in ext::Maze::passages) + * + * If the current room value is not mapped, then map it to itself. + * Set current room value to it's mapped value. + * + * Add the room to the set given by room value + * + * If a EASTERN wall is removed, then: + * - The set of the connected room is added to the set of this room. + * - The set of the connected room is deleted. + * - Map the connected room value to the current room value. + * - Change the connected room value to the current room value. + * - Add connected room to set. + * + * If a SOUTHERN wall is removed, then: + * - Change the connected room value to the current room value. + * - Add the connected room to set. + * + * After all the rooms of the current floor has been visited: + * - Go through each set, pick a random room, and make a passage up. + * - Clear the sets, and mapping. + * + * On the last floor, each set on each row must have a passage south. + * + * On the last row on the last floor, passages between unconnected sets are removed. + * + * Finally each room value is set according to which walls remain. (Including floor and ceiling) + */ + + + // The room sets + std::map> room_set; + + // The mapping of connected rooms + std::map merged_room_sets; + + + int room_value; + int east_room_value; + int south_room_value; + + // To get different mazes each time + srand(seed); + + for (int floor = 0; floor < HEIGHT - 1; floor++){ // Go through each floor but the last. + + for (int row = 0; row < WIDTH; row++){ // Go through each row + + for(int col = 0; col < LENGTH; col++){ // Go through each element in row (Each column) + + room_value = get(row, col, floor); + + // If this room value is mapped to another, get that value: else map value to itself + if(merged_room_sets.find(room_value) != merged_room_sets.end()){ + room_value = merged_room_sets[room_value]; + } else { + merged_room_sets[room_value] = room_value; + } + + // If not the last cell in row, store eastern room value + if (col next_room_position_set = room_set[get(row, col+1, floor)]; + + // Add set of eastern room to this set. + room_set[room_value].insert(room_set[room_value].end(), next_room_position_set.begin(), next_room_position_set.end() ); + + // Remove eastern room set + room_set.erase(get(row, col+1, floor)); + + // Map eastern room value to this room value. + merged_room_sets[get(row,col+1,floor)] = room_value; + + // Set eastern room value to this + set(row, col+1, floor, room_value); + + // Add the new horizontal passage created. + passages.push_back(std::make_tuple(row, col, floor, row, col+1, floor)); + + } // Else the eastern wall remain + + // Don't remove southern boundary wall. + if(row == WIDTH - 1){} + + // If current and southern room is not in same set, then maybe remove wall + else if(rand() < SOUTH_WALL_THRESHOLD * RAND_MAX && room_value != south_room_value){ + + // Set southern room value to this one + set(row+1, col, floor, room_value); + + // Add the newly created vertical passage. + passages.push_back(std::make_tuple(row, col, floor, row+1, col, floor)); + } //Else wall down remain + + } // All rooms in this row has been visited + + } // All rows on this floor has been visited + + // Clear room set mapping + merged_room_sets.clear(); + + // Go through all sets this floor + for(auto entry : room_set) + { + auto group = entry.second; + int x, y, z; + + // Picking out a random room in each group + // Room position saved in matrix notation (i, j, k = y, x, z) + std::tie(y, x, z) = group.at(rand() % group.size()); + + // Only one passage up for each group. To avoid graph cycles + passages.push_back(std::make_tuple(y, x, z, y, x, z+1)); + } + // Passages up have been set. Clearing room groups for next floor. + room_set.clear(); + + }// Finished with all but last floor + + + int floor = HEIGHT - 1; + + std::set can_go_south; + + //deal with last floor, but leave the last row + for(int row = 0; row < WIDTH - 1; row++){ + + for(int col = 0; col +#include + +namespace ext { + class Maze { + protected: + double EAST_WALL_THRESHOLD; + double SOUTH_WALL_THRESHOLD; + std::vector< std::tuple > passages; + typedef std::tuple pos; + + int get(int, int, int); + void set(int, int, int, int); + void calculate(); + public: + int LENGTH; + int WIDTH; + int HEIGHT; + size_t seed; + + static const uint8_t FLOOR = 1; // 00 00 00 01; + static const uint8_t EAST = 2; // 00 00 00 10; + static const uint8_t NORTH = 4; // 00 00 01 00; + static const uint8_t WEST = 8; // 00 00 10 00; + static const uint8_t SOUTH = 16; // 00 01 00 00; + static const uint8_t CEIL = 32; // 00 10 00 00; + + std::vector cells; + + int& operator()(int row, int col, int floor){ + return cells.at(col + LENGTH*row + LENGTH*WIDTH*floor); + } + void initialize(int, int, int, double, double); + void build(); + void print(); + }; +} \ No newline at end of file diff --git a/ext/world/terrain/terrain.cpp b/ext/world/terrain/terrain.cpp index 38689b62..9a92e53a 100644 --- a/ext/world/terrain/terrain.cpp +++ b/ext/world/terrain/terrain.cpp @@ -52,28 +52,17 @@ void ext::Terrain::initialize() { ext::Object::initialize(); uf::Serializer& metadata = this->getComponent(); + std::size_t seed; + if ( metadata["terrain"]["seed"].isUInt64() ) { + seed = metadata["terrain"]["seed"].asUInt64(); + } else if ( metadata["terrain"]["seed"].isString() ) { + seed = std::hash{}( metadata["terrain"]["seed"].asString() ); + } { - std::size_t seed; - if ( metadata["terrain"]["seed"].isUInt64() ) { - seed = metadata["terrain"]["seed"].asUInt64(); - } else if ( metadata["terrain"]["seed"].isString() ) { - seed = std::hash{}( metadata["terrain"]["seed"].asString() ); - } std::cout << "Seed: " << seed << std::endl; ext::TerrainGenerator::noise.seed( seed ); } - /* Test */ -/* - { - std::string seed = "170271159441424384 170271159441424384"; - std::size_t hash = std::hash{}(seed); - uf::PerlinNoise& noise = ext::TerrainGenerator::noise; - noise.seed(hash); - double y = noise.sample(pod::Vector3d{ 0.01, 0.01, 0.01 }); - std::cout << seed << ": " << y << std::endl; - } -*/ if ( metadata["terrain"]["raytrace"].asBool() ) { this->addComponent(); @@ -90,11 +79,26 @@ void ext::Terrain::initialize() { metadata["_config"]["hash"] = uf::string::sha256( input ); try { - std::string save = "./data/save/regions/" + metadata["_config"]["hash"].asString(); + std::string save = "./data/save/" + metadata["_config"]["hash"].asString() + "/"; int status = mkdir(save.c_str()); } catch ( ... ) { } + try { + std::string save = "./data/save/" + metadata["_config"]["hash"].asString() + "/regions/"; + int status = mkdir(save.c_str()); + } catch ( ... ) { + + } + } + + // setup maze + { + ext::Maze& maze = this->getComponent(); + maze.initialize(16, 16, 1, 0.5, 0.5); + maze.seed = seed; + maze.build(); + maze.print(); } if ( this->hasComponent() ) { @@ -325,7 +329,7 @@ void ext::Terrain::tick() { } /* Sort by closest to farthest */ { const ext::World& world = this->getRootParent(); - const ext::Player& player = world.getController(); + const ext::Object& player = world.getController(); const pod::Vector3& position = player.getComponent>().position; std::sort( regions.begin(), regions.end(), [&]( const uf::Entity* l, const uf::Entity* r ){ if ( !l ) return false; if ( !r ) return true; @@ -359,7 +363,7 @@ void ext::Terrain::tick() { } /* Sort by closest to farthest */ { const ext::World& world = this->getRootParent(); - const ext::Player& player = world.getController(); + const ext::Object& player = world.getController(); const pod::Vector3& position = player.getComponent>().position; std::sort( regions.begin(), regions.end(), [&]( const uf::Entity* l, const uf::Entity* r ){ if ( !l ) return false; if ( !r ) return true; @@ -447,7 +451,7 @@ void ext::Terrain::tick() { pod::Thread& mainThread = uf::thread::has("Main") ? uf::thread::get("Main") : uf::thread::create( "Main", false, true ); if ( metadata["terrain"]["state"] == "open" && - ( (generationTimer.running() && generationTimer.elapsed().asDouble() > 3) || !generationTimer.running() ) + ( (generationTimer.running() && generationTimer.elapsed().asDouble() > 1) || !generationTimer.running() ) ) { metadata["terrain"]["state"] = "resolving:" + metadata["terrain"]["state"].asString(); if ( !generationTimer.running() ) generationTimer.start(); else generationTimer.reset(); @@ -489,6 +493,7 @@ void ext::Terrain::tick() { bool resolved = !metadata["terrain"]["modified"].asBool(); if ( !locations.empty() ) { + std::cout << "GENERATING" << std::endl; this->generate(); for ( pod::Vector3i& location : locations ) this->degenerate(location); resolved = false; diff --git a/ext/world/terrain/terrain.h b/ext/world/terrain/terrain.h index 0f5f19f6..bc02880c 100644 --- a/ext/world/terrain/terrain.h +++ b/ext/world/terrain/terrain.h @@ -8,7 +8,9 @@ #include #include "../../object/object.h" + #include "region.h" +#include "maze.h" namespace ext { class EXT_API Terrain : public ext::Object { diff --git a/ext/world/terrain/voxel.cpp b/ext/world/terrain/voxel.cpp index c45dc590..e516be99 100644 --- a/ext/world/terrain/voxel.cpp +++ b/ext/world/terrain/voxel.cpp @@ -476,7 +476,7 @@ ext::TerrainVoxelWall::TerrainVoxelWall() : ext::TerrainVoxel( 2, true, true, {1 ext::TerrainVoxelCeiling::TerrainVoxelCeiling() : ext::TerrainVoxel( 3, true, true, {2, 1}, 0x0000 ) {} ext::TerrainVoxelPillar::TerrainVoxelPillar() : ext::TerrainVoxel( 4, true, true, {3, 0}, 0x0000 ) {} ext::TerrainVoxelStair::TerrainVoxelStair() : ext::TerrainVoxel( 5, true, true, {2, 0}, 0x0000 ) {} -ext::TerrainVoxelLava::TerrainVoxelLava() : ext::TerrainVoxel( 6, true, true, {0, 2}, 0x66FF ) {} +ext::TerrainVoxelLava::TerrainVoxelLava() : ext::TerrainVoxel( 6, true, true, {0, 2}, 0xFFFF ) {} // const std::vector& ext::TerrainVoxel::atlas() {