From dfe661278fe5fd69e821d530d50b78082d19ce54 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Mon, 7 Mar 2016 14:20:09 +0100 Subject: [PATCH] basic floor idea --- .gitmodules | 3 + assets | 1 + src/app/assets.cpp | 63 ++++++++++++ src/app/assets.hpp | 27 ++++++ src/app/config.cpp | 61 ++++++++++++ src/app/config.hpp | 28 ++++++ src/app/init.cpp | 11 +++ src/app/init.hpp | 5 + src/graphics/camera.cpp | 25 +++++ src/graphics/camera.hpp | 32 ++++++ src/graphics/viewport.cpp | 30 ++++++ src/graphics/viewport.hpp | 41 ++++++++ src/{app => graphics}/window.cpp | 6 +- src/{app => graphics}/window.hpp | 6 +- src/rand/GaloisLFSR.hpp | 77 +++++++++++++++ src/rand/OctaveNoise.hpp | 33 +++++++ src/rand/SimplexNoise.hpp | 35 +++++++ src/rand/noise.cpp | 161 +++++++++++++++++++++++++++++++ src/tacos.cpp | 88 ++++++++++++++++- src/world/Floor.hpp | 87 +++++++++++++++++ src/world/world.cpp | 143 +++++++++++++++++++++++++++ 21 files changed, 954 insertions(+), 9 deletions(-) create mode 100644 .gitmodules create mode 160000 assets create mode 100644 src/app/assets.cpp create mode 100644 src/app/assets.hpp create mode 100644 src/app/config.cpp create mode 100644 src/app/config.hpp create mode 100644 src/graphics/camera.cpp create mode 100644 src/graphics/camera.hpp create mode 100644 src/graphics/viewport.cpp create mode 100644 src/graphics/viewport.hpp rename src/{app => graphics}/window.cpp (90%) rename src/{app => graphics}/window.hpp (71%) create mode 100644 src/rand/GaloisLFSR.hpp create mode 100644 src/rand/OctaveNoise.hpp create mode 100644 src/rand/SimplexNoise.hpp create mode 100644 src/rand/noise.cpp create mode 100644 src/world/Floor.hpp create mode 100644 src/world/world.cpp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4e1e58a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "assets"] + path = assets + url = http://git.localhorst.tv/repo/tacos-assets.git diff --git a/assets b/assets new file mode 160000 index 0000000..c690bc6 --- /dev/null +++ b/assets @@ -0,0 +1 @@ +Subproject commit c690bc6e1a92fae19922e48b58c4874e330b490a diff --git a/src/app/assets.cpp b/src/app/assets.cpp new file mode 100644 index 0000000..e7773f5 --- /dev/null +++ b/src/app/assets.cpp @@ -0,0 +1,63 @@ +#include "assets.hpp" + +#include "config.hpp" +#include "../graphics/shader.hpp" + +#include +#include + +using std::string; +using std::unique_ptr; + + +namespace { + +/// get file's contents as zero terminated string +unique_ptr file_string(const char *path) { + FILE *file = std::fopen(path, "rb"); + if (!file) { + throw std::runtime_error(string("failed to open file ") + path); + } + if (std::fseek(file, 0, SEEK_END) < 0) { + std::fclose(file); + throw std::runtime_error(string("failed to seek to end of file ") + path); + } + int file_size = std::ftell(file); + if (file_size < 0) { + std::fclose(file); + throw std::runtime_error(string("failed to seek to end of file ") + path); + } + std::rewind(file); + unique_ptr buffer(new char[file_size + 1]); + int read = std::fread(buffer.get(), file_size, 1, file); + if (read != 1) { + std::fclose(file); + throw std::runtime_error(string("failed to read file ") + path); + } + std::fclose(file); + buffer[file_size] = '\0'; + return buffer; +} + +} + +namespace tacos { + +AssetLoader::AssetLoader(const Config &c) +: config(c) { + +} + +Shader AssetLoader::LoadVertexShader(const string &name) const { + string path = config.asset_path + "shader/" + name + ".vertex.glsl"; + unique_ptr source(file_string(path.c_str())); + return Shader::Vertex(source.get()); +} + +Shader AssetLoader::LoadFragmentShader(const string &name) const { + string path = config.asset_path + "shader/" + name + ".fragment.glsl"; + unique_ptr source(file_string(path.c_str())); + return Shader::Fragment(source.get()); +} + +} diff --git a/src/app/assets.hpp b/src/app/assets.hpp new file mode 100644 index 0000000..4c4bcdd --- /dev/null +++ b/src/app/assets.hpp @@ -0,0 +1,27 @@ +#ifndef TACOS_APP_ASSETS_HPP_ +#define TACOS_APP_ASSETS_HPP_ + +#include + + +namespace tacos { + +class Config; +class Shader; + +class AssetLoader { + +public: + explicit AssetLoader(const Config &); + + Shader LoadVertexShader(const std::string &name) const; + Shader LoadFragmentShader(const std::string &name) const; + +private: + const Config &config; + +}; + +} + +#endif diff --git a/src/app/config.cpp b/src/app/config.cpp new file mode 100644 index 0000000..f1e0087 --- /dev/null +++ b/src/app/config.cpp @@ -0,0 +1,61 @@ +#include "config.hpp" + +#include +#include +#include + +using namespace std; + + +namespace tacos { + +Config::Config() { + char *base = SDL_GetBasePath(); + char *pref = SDL_GetPrefPath("localhorst", "tacos"); + + asset_path = string(base) + "assets/"; + config_path = pref; + + SDL_free(base); + SDL_free(pref); +} + +void Config::ReadArgs(int argc, const char *const *argv) { + if (argc < 1) return; + + for (int i = 1; i < argc; ++i) { + const char *arg = argv[i]; + if (!arg || arg[0] == '\0') { + cerr << "empty argument at position " << i << endl; + continue; + } + if (arg[0] != '-' || arg[1] != '-' || arg[2] == '\0') { + cerr << "incomplete argument at position " << i << endl; + continue; + } + const char *name = arg + 2; + if (strcmp(name, "asset-path") == 0) { + ++i; + if (i >= argc || !argv[i]) { + cerr << "missing argument to --asset-path" << endl; + continue; + } + asset_path = argv[i]; + } else if (strcmp(name, "config-path") == 0) { + ++i; + if (i >= argc || !argv[i]) { + cerr << "missing argument to --config-path" << endl; + continue; + } + config_path = argv[i]; + } else { + cerr << "unknown argument " << arg << " at position " << i << endl; + } + } +} + +void Config::ReadFile(const char *path) { + // TODO: implement reading config file +} + +} diff --git a/src/app/config.hpp b/src/app/config.hpp new file mode 100644 index 0000000..1a6e115 --- /dev/null +++ b/src/app/config.hpp @@ -0,0 +1,28 @@ +#ifndef TACOS_APP_CONFIG_HPP_ +#define TACOS_APP_CONFIG_HPP_ + +#include + + +namespace tacos { + +struct Config { + + Config(); + + void ReadArgs(int argc, const char *const *argv); + void ReadFile(const char *path); + + // system settings + std::string asset_path; + std::string config_path; + + // video settings + bool double_buffer = true; + int multi_sample = 0; + +}; + +} + +#endif diff --git a/src/app/init.cpp b/src/app/init.cpp index 146719b..52c86c0 100644 --- a/src/app/init.cpp +++ b/src/app/init.cpp @@ -1,5 +1,6 @@ #include "init.hpp" +#include "config.hpp" #include "error.hpp" #include @@ -85,4 +86,14 @@ InitTTF::~InitTTF() noexcept { TTF_Quit(); } + +Init::Init(const Config &config) +: sdl() +, gl(config.double_buffer, config.multi_sample) +, img() +, ttf() +, alut() { + +} + } diff --git a/src/app/init.hpp b/src/app/init.hpp index 4c82d08..698bbc2 100644 --- a/src/app/init.hpp +++ b/src/app/init.hpp @@ -3,6 +3,9 @@ namespace tacos { +class Config; + + struct InitAlut { InitAlut(); @@ -51,6 +54,8 @@ struct InitTTF { struct Init { + explicit Init(const Config &); + InitSDL sdl; InitGL gl; InitIMG img; diff --git a/src/graphics/camera.cpp b/src/graphics/camera.cpp new file mode 100644 index 0000000..bc43e04 --- /dev/null +++ b/src/graphics/camera.cpp @@ -0,0 +1,25 @@ +#include "camera.hpp" + +#include + + +namespace tacos { + +Camera::Camera(const glm::vec3 &f) +: focus(&f) +, distance(100.0f) +, pitch(1.04719755119659774614f) // π/3 +, yaw(0.0f) { + +} + +void Camera::Focus(const glm::vec3 &f) noexcept { + focus = &f; +} + +glm::mat4 Camera::View() const noexcept { + const glm::vec3 position(*focus - distance * rotateY(rotateX(glm::vec3(0.0f, 0.0f, 1.0f), pitch), yaw)); + return glm::lookAt(position, *focus, glm::vec3(0.0f, 1.0f, 0.0f)); +} + +} diff --git a/src/graphics/camera.hpp b/src/graphics/camera.hpp new file mode 100644 index 0000000..a1660ca --- /dev/null +++ b/src/graphics/camera.hpp @@ -0,0 +1,32 @@ +#ifndef TACOS_GRAPHICS_CAMERA_HPP_ +#define TACOS_GRAPHICS_CAMERA_HPP_ + +#include + + +namespace tacos { + +/// Camera is facing a focus point. Its position is determined by +/// displacing said focus point by a distance in the direction indicated +/// by pitch and yaw. +/// The up direction is fixed to positive Y. +class Camera { + +public: + explicit Camera(const glm::vec3 &focus); + + glm::mat4 View() const noexcept; + + void Focus(const glm::vec3 &) noexcept; + +private: + const glm::vec3 *focus; + float distance; + float pitch; + float yaw; + +}; + +} + +#endif diff --git a/src/graphics/viewport.cpp b/src/graphics/viewport.cpp new file mode 100644 index 0000000..4ee85c8 --- /dev/null +++ b/src/graphics/viewport.cpp @@ -0,0 +1,30 @@ +#include "viewport.hpp" + +#include +#include + + +namespace tacos { + +Viewport::Viewport(int w, int h) +: width(w) +, height(h) +, fov(0.78539816339744830961f) // π/4 +, aspect(float(w) / float(h)) +, near(0.1f) +, far(100.0f) +, perspective(glm::perspective(fov, aspect, near, far)) +, ortho(glm::ortho(0.0f, float(width), float(height), 0.0f, near, far)) { + +} + +void Viewport::Resize(int w, int h) noexcept { + width = w; + height = h; + aspect = float(width) / float(height); + perspective = glm::perspective(fov, aspect, near, far); + ortho = glm::ortho(0.0f, float(width), float(height), 0.0f, near, far); + glViewport(0, 0, width, height); +} + +} diff --git a/src/graphics/viewport.hpp b/src/graphics/viewport.hpp new file mode 100644 index 0000000..cc9a100 --- /dev/null +++ b/src/graphics/viewport.hpp @@ -0,0 +1,41 @@ +#ifndef TACOS_GRAPHICS_VIEWPORT_HPP_ +#define TACOS_GRAPHICS_VIEWPORT_HPP_ + +#include + + +namespace tacos { + +class Viewport { + +public: + Viewport(int width, int height); + + Viewport(const Viewport &) = delete; + Viewport &operator =(const Viewport &) = delete; + + void Resize(int w, int h) noexcept; + + int Width() const noexcept { return width; } + int Height() const noexcept { return height; } + + const glm::mat4 &Perspective() const noexcept { return perspective; } + const glm::mat4 &Ortho() const noexcept { return ortho; } + +private: + int width; + int height; + + float fov; + float aspect; + float near; + float far; + + glm::mat4 perspective; + glm::mat4 ortho; + +}; + +} + +#endif diff --git a/src/app/window.cpp b/src/graphics/window.cpp similarity index 90% rename from src/app/window.cpp rename to src/graphics/window.cpp index ff7a7e1..99f0090 100644 --- a/src/app/window.cpp +++ b/src/graphics/window.cpp @@ -1,6 +1,6 @@ #include "window.hpp" -#include "error.hpp" +#include "../app/error.hpp" #include #include @@ -9,11 +9,11 @@ namespace tacos { -Window::Window() { +Window::Window(int width, int height) { window = SDL_CreateWindow( "tacos", // title SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, // position - 1440, 900, // size + width, height, // size SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE // flags ); if (!window) { diff --git a/src/app/window.hpp b/src/graphics/window.hpp similarity index 71% rename from src/app/window.hpp rename to src/graphics/window.hpp index cc8fc8a..1685cf1 100644 --- a/src/app/window.hpp +++ b/src/graphics/window.hpp @@ -1,5 +1,5 @@ -#ifndef TACOS_APP_WINDOW_HPP_ -#define TACOS_APP_WINDOW_HPP_ +#ifndef TACOS_GRAPHICS_WINDOW_HPP_ +#define TACOS_GRAPHICS_WINDOW_HPP_ #include @@ -9,7 +9,7 @@ namespace tacos { class Window { public: - Window(); + Window(int width, int height); ~Window() noexcept; Window(const Window &) = delete; diff --git a/src/rand/GaloisLFSR.hpp b/src/rand/GaloisLFSR.hpp new file mode 100644 index 0000000..94b12d0 --- /dev/null +++ b/src/rand/GaloisLFSR.hpp @@ -0,0 +1,77 @@ +#ifndef TACOS_RAND_GALOISLFSR_HPP_ +#define TACOS_RAND_GALOISLFSR_HPP_ + +#include +#include + + +namespace tacos { + +class GaloisLFSR { + +public: + // seed should be non-zero + explicit GaloisLFSR(std::uint64_t seed) noexcept + : state(seed) { + if (state == 0) { + state = 1; + } + } + + // get the next bit + bool operator ()() noexcept { + bool result = state & 1; + state >>= 1; + if (result) { + state |= 0x8000000000000000; + state ^= mask; + } else { + state &= 0x7FFFFFFFFFFFFFFF; + } + return result; + } + + template + T operator ()(T &out) noexcept { + constexpr int num_bits = + std::numeric_limits::digits + + std::numeric_limits::is_signed; + for (int i = 0; i < num_bits; ++i) { + operator ()(); + } + return out = static_cast(state); + } + + template + T Next() noexcept { + T next; + return (*this)(next); + } + + float SNorm() noexcept { + return float(Next()) * (1.0f / 2147483647.5f) - 1.0f; + } + + float UNorm() noexcept { + return float(Next()) * (1.0f / 4294967295.0f); + } + + template + typename Container::reference From(Container &c) { + return c[Next() % c.size()]; + } + template + typename Container::const_reference From(const Container &c) { + return c[Next() % c.size()]; + } + +private: + std::uint64_t state; + // bits 64, 63, 61, and 60 set to 1 (counting from 1 lo to hi) + static constexpr std::uint64_t mask = 0xD800000000000000; + +}; + +} + +#endif diff --git a/src/rand/OctaveNoise.hpp b/src/rand/OctaveNoise.hpp new file mode 100644 index 0000000..bcd872e --- /dev/null +++ b/src/rand/OctaveNoise.hpp @@ -0,0 +1,33 @@ +#ifndef TACOS_RAND_OCTAVENOISE_HPP_ +#define TACOS_RAND_OCTAVENOISE_HPP_ + +#include + + +namespace tacos { + +template +float OctaveNoise( + const Noise &noise, + const glm::vec3 &in, + int num, + float persistence, + float frequency = 1.0f, + float amplitude = 1.0f, + float growth = 2.0f +) { + float total = 0.0f; + float max = 0.0f; + for (int i = 0; i < num; ++i) { + total += noise(in * frequency) * amplitude; + max += amplitude; + amplitude *= persistence; + frequency *= growth; + } + + return total / max; +} + +} + +#endif diff --git a/src/rand/SimplexNoise.hpp b/src/rand/SimplexNoise.hpp new file mode 100644 index 0000000..a91e1c4 --- /dev/null +++ b/src/rand/SimplexNoise.hpp @@ -0,0 +1,35 @@ +#ifndef TACOS_RAND_SIMPLEXNOISE_HPP_ +#define TACOS_RAND_SIMPLEXNOISE_HPP_ + +#include +#include + + +namespace tacos { + +class SimplexNoise { + +public: + explicit SimplexNoise(std::uint64_t seed) noexcept; + + float operator ()(const glm::vec3 &) const noexcept; + +private: + int Perm(int idx) const noexcept; + int Perm12(int idx) const noexcept; + const glm::vec3 &Grad(int idx) const noexcept; + +private: + int perm[512]; + int perm12[512]; + glm::vec3 grad[12]; + glm::ivec3 second_ints[8]; + glm::ivec3 third_ints[8]; + glm::vec3 second_floats[8]; + glm::vec3 third_floats[8]; + +}; + +} + +#endif diff --git a/src/rand/noise.cpp b/src/rand/noise.cpp new file mode 100644 index 0000000..7466945 --- /dev/null +++ b/src/rand/noise.cpp @@ -0,0 +1,161 @@ +#include "GaloisLFSR.hpp" +#include "SimplexNoise.hpp" + +#include + + +namespace { + +constexpr float one_third = 1.0f/3.0f; +constexpr float one_sixth = 1.0f/6.0f; + +} + +namespace tacos { + +SimplexNoise::SimplexNoise(std::uint64_t seed) noexcept +: grad({ + { 1.0f, 1.0f, 0.0f }, + { -1.0f, 1.0f, 0.0f }, + { 1.0f, -1.0f, 0.0f }, + { -1.0f, -1.0f, 0.0f }, + { 1.0f, 0.0f, 1.0f }, + { -1.0f, 0.0f, 1.0f }, + { 1.0f, 0.0f, -1.0f }, + { -1.0f, 0.0f, -1.0f }, + { 0.0f, 1.0f, 1.0f }, + { 0.0f, -1.0f, 1.0f }, + { 0.0f, 1.0f, -1.0f }, + { 0.0f, -1.0f, -1.0f }, +}) +, second_ints({ + // x>y x>z y>z + { 0, 0, 1 }, // 0 0 0 ZYX + { 0, 1, 0 }, // 0 0 1 YZX + { 0, 0, 1 }, // 0 1 0 illogical, but ZYX + { 0, 1, 0 }, // 0 1 1 YXZ + { 0, 0, 1 }, // 1 0 0 ZXY + { 1, 0, 0 }, // 1 0 1 illogical, but XYZ + { 1, 0, 0 }, // 1 1 0 XZY + { 1, 0, 0 }, // 1 1 1 XYZ +}) +, third_ints({ + // x>y x>z y>z + { 0, 1, 1 }, // 0 0 0 ZYX + { 0, 1, 1 }, // 0 0 1 YZX + { 0, 1, 1 }, // 0 1 0 illogical, but ZYX + { 1, 1, 0 }, // 0 1 1 YXZ + { 1, 0, 1 }, // 1 0 0 ZXY + { 1, 1, 0 }, // 1 0 1 illogical, but XYZ + { 1, 0, 1 }, // 1 1 0 XZY + { 1, 1, 0 }, // 1 1 1 XYZ +}) +, second_floats({ + // x>y x>z y>z + { 0.0f, 0.0f, 1.0f }, // 0 0 0 ZYX + { 0.0f, 1.0f, 0.0f }, // 0 0 1 YZX + { 0.0f, 0.0f, 1.0f }, // 0 1 0 illogical, but ZYX + { 0.0f, 1.0f, 0.0f }, // 0 1 1 YXZ + { 0.0f, 0.0f, 1.0f }, // 1 0 0 ZXY + { 1.0f, 0.0f, 0.0f }, // 1 0 1 illogical, but XYZ + { 1.0f, 0.0f, 0.0f }, // 1 1 0 XZY + { 1.0f, 0.0f, 0.0f }, // 1 1 1 XYZ +}) +, third_floats({ + // x>y x>z y>z + { 0.0f, 1.0f, 1.0f }, // 0 0 0 ZYX + { 0.0f, 1.0f, 1.0f }, // 0 0 1 YZX + { 0.0f, 1.0f, 1.0f }, // 0 1 0 illogical, but ZYX + { 1.0f, 1.0f, 0.0f }, // 0 1 1 YXZ + { 1.0f, 0.0f, 1.0f }, // 1 0 0 ZXY + { 1.0f, 1.0f, 0.0f }, // 1 0 1 illogical, but XYZ + { 1.0f, 0.0f, 1.0f }, // 1 1 0 XZY + { 1.0f, 1.0f, 0.0f }, // 1 1 1 XYZ +}) { + GaloisLFSR random(seed ^ 0x0123456789ACBDEF); + unsigned char value; + for (size_t i = 0; i < 256; ++i) { + perm[i] = random(value); + perm[i] &= 0xFF; + perm[i + 256] = perm[i]; + perm12[i] = perm[i] % 12; + perm12[i + 256] = perm12[i]; + } +} + + +float SimplexNoise::operator ()(const glm::vec3 &in) const noexcept { + float skew = (in.x + in.y + in.z) * one_third; + + glm::vec3 skewed(glm::floor(in + skew)); + float tr = (skewed.x + skewed.y + skewed.z) * one_sixth; + + glm::vec3 unskewed(skewed - tr); + glm::vec3 relative(in - unskewed); + + bool x_ge_y = relative.x >= relative.y; + bool x_ge_z = relative.x >= relative.z; + bool y_ge_z = relative.y >= relative.z; + unsigned int st = (x_ge_y << 2) | (x_ge_z << 1) | y_ge_z; + + glm::ivec3 second_int(second_ints[st]); + glm::ivec3 third_int(third_ints[st]); + glm::vec3 second_float(second_floats[st]); + glm::vec3 third_float(third_floats[st]); + + glm::vec3 offset[4] = { + in - unskewed, + relative - second_float + one_sixth, + relative - third_float + one_third, + relative - 0.5f, + }; + + int index[3] = { + (int)(skewed.x) & 0xFF, + (int)(skewed.y) & 0xFF, + (int)(skewed.z) & 0xFF, + }; + + float n = 0.0f; + + // 0 + float t = glm::clamp(0.5f - dot(offset[0], offset[0]), 0.0f, 1.0f); + t *= t; + int corner = Perm12(index[0] + Perm(index[1] + Perm(index[2]))); + n += t * t * dot(Grad(corner), offset[0]); + + // 1 + t = glm::clamp(0.5f - dot(offset[1], offset[1]), 0.0f, 1.0f); + t *= t; + corner = Perm12(index[0] + second_int.x + Perm(index[1] + second_int.y + Perm(index[2] + second_int.z))); + n += t * t * dot(Grad(corner), offset[1]); + + // 2 + t = glm::clamp(0.5f - dot(offset[2], offset[2]), 0.0f, 1.0f); + t *= t; + corner = Perm12(index[0] + third_int.x + Perm(index[1] + third_int.y + Perm(index[2] + third_int.z))); + n += t * t * dot(Grad(corner), offset[2]); + + // 3 + t = glm::clamp(0.5f - dot(offset[3], offset[3]), 0.0f, 1.0f); + t *= t; + corner = Perm12(index[0] + 1 + Perm(index[1] + 1 + Perm(index[2] + 1))); + n += t * t * dot(Grad(corner), offset[3]); + + return 32.0f * n; +} + + +int SimplexNoise::Perm(int idx) const noexcept { + return perm[idx]; +} + +int SimplexNoise::Perm12(int idx) const noexcept { + return perm12[idx]; +} + +const glm::vec3 &SimplexNoise::Grad(int idx) const noexcept { + return grad[idx]; +} + +} diff --git a/src/tacos.cpp b/src/tacos.cpp index 7e0976f..580becb 100644 --- a/src/tacos.cpp +++ b/src/tacos.cpp @@ -1,10 +1,92 @@ +#include "app/assets.hpp" +#include "app/config.hpp" #include "app/init.hpp" -#include "app/window.hpp" +#include "graphics/camera.hpp" +#include "graphics/shader.hpp" +#include "graphics/viewport.hpp" +#include "graphics/window.hpp" +#include "rand/SimplexNoise.hpp" +#include "world/Floor.hpp" + +#include +#include +#include +#include using namespace tacos; int main(int argc, char *argv[]) { - Init init; - Window window; + Config config; + config.ReadArgs(argc, argv); + + Init init(config); + AssetLoader asset_loader(config); + Window window(1440, 900); + Viewport viewport(1440, 900); + + constexpr float noise_scale = 1.0f/64.0f; + constexpr float height_scale = 5.0f; + + // === test === + Shader floor_vert(asset_loader.LoadVertexShader("floor")); + Shader floor_frag(asset_loader.LoadFragmentShader("floor")); + Program floor_program; + floor_program.Attach(floor_vert); + floor_program.Attach(floor_frag); + floor_program.Link(); + GLint mv_location = floor_program.UniformLocation("MV"); + GLint mvp_location = floor_program.UniformLocation("MVP"); + + Floor floor(64, 50); + SimplexNoise noise(0); + for (int z = 0; z < floor.Depth(); ++z) { + for (int x = 0; x < floor.Width(); ++x) { + floor.SetElevation(x, z, noise(glm::vec3(x * noise_scale, 0.0f, z * noise_scale)) * height_scale); + } + } + floor.GenerateVertices(); + + glm::vec3 focus(20.0f, 0.0f, 20.0f); + Camera camera(focus); + + glm::mat4 M; + glm::mat4 V; + glm::mat4 MV; + glm::mat4 MVP; + + floor_program.Use(); + + bool running = true; + SDL_Event event; + while (running) { + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + running = false; + break; + case SDL_WINDOWEVENT: + if (event.window.event == SDL_WINDOWEVENT_RESIZED) { + viewport.Resize(event.window.data1, event.window.data2); + } + break; + default: + break; + } + } + V = camera.View(); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + for (int z = 0; z < floor.VAODepth(); ++z) { + for (int x = 0; x < floor.VAOWidth(); ++x) { + M = glm::translate(glm::mat4(1.0f), floor.VAOPosition(x, z)); + MV = camera.View() * M; + MVP = viewport.Perspective() * MV; + floor_program.Uniform(mv_location, MV); + floor_program.Uniform(mvp_location, MVP); + floor.DrawVAO(x, z); + } + } + window.Flip(); + } + return 0; } diff --git a/src/world/Floor.hpp b/src/world/Floor.hpp new file mode 100644 index 0000000..a7046b5 --- /dev/null +++ b/src/world/Floor.hpp @@ -0,0 +1,87 @@ +#ifndef TACOS_WORLD_FLOOR_HPP_ +#define TACOS_WORLD_FLOOR_HPP_ + +#include +#include +#include + + +namespace tacos { + +class Floor { + +public: + /// width and depth in vertices, so will have one less + /// in each dimension in tiles + Floor(int width, int depth); + ~Floor() noexcept; + + Floor(const Floor &) = delete; + Floor &operator =(const Floor &) = delete; + + static constexpr int VAO_DIVISOR = 32; + + int Width() const noexcept { return width; } + int Depth() const noexcept { return depth; } + + int VAOWidth() const noexcept { return vao_width; } + int VAODepth() const noexcept { return vao_depth; } + + glm::vec3 VAOPosition(int vao_x, int vao_z) const noexcept { + return glm::vec3(float(vao_x * VAO_DIVISOR), 0.0f, float(vao_z * VAO_DIVISOR)); + } + void DrawVAO(int vao_x, int vao_z) const noexcept; + + void SetElevation(int x, int z, float e) noexcept { + elevation[z * width + x] = e; + } + float GetElevation(int x, int z) const noexcept { + return elevation[z * width + x]; + } + float ClampedElevation(int x, int z) const noexcept { + return GetElevation(glm::clamp(x, 0, width), glm::clamp(z, 0, depth)); + } + glm::vec3 GetNormal(int x, int z) const noexcept; + + void GenerateVertices(); + +private: + void SetupVAO(int which, GLuint element_buffer, int vertex_count) noexcept; + void FillElementBuffer(GLuint which, int tile_width, int tile_depth); + void FillAttribBuffer(int vao_x, int vao_z); + glm::ivec2 Tiles(int vao_x, int vao_z) const noexcept; + int NumTiles(int vao_x, int vao_z) const noexcept; + int NumVertices(int vao_x, int vao_z) const noexcept; + +private: + int width; + int depth; + std::vector elevation; + + int tile_width; + int tile_depth; + int unclean_width; + int unclean_depth; + int vao_width; + int vao_depth; + std::vector vaos; + // index of general element buffer + int general_elements; + // index of unclean width element buffer, if any + int unclean_width_elements; + // index of unclean depth element buffer, if any + int unclean_depth_elements; + // index of unclean corner element buffer, if any + int unclean_corner_elements; + std::vector buffers; + + struct Attributes { + glm::vec3 position; + glm::vec3 normal; + }; + +}; + +} + +#endif diff --git a/src/world/world.cpp b/src/world/world.cpp new file mode 100644 index 0000000..eba12ea --- /dev/null +++ b/src/world/world.cpp @@ -0,0 +1,143 @@ +#include "Floor.hpp" + + +namespace tacos { + +constexpr int Floor::VAO_DIVISOR; + +Floor::Floor(int w, int d) +: width(w) +, depth(d) +, elevation(w * d) +, tile_width(width - 1) +, tile_depth(depth - 1) +, unclean_width(tile_width % VAO_DIVISOR) +, unclean_depth(tile_depth % VAO_DIVISOR) +, vao_width(tile_width / VAO_DIVISOR + bool(unclean_width)) +, vao_depth(tile_depth / VAO_DIVISOR + bool(unclean_depth)) +, vaos(vao_width * vao_depth) +, general_elements(vao_width * vao_depth) +, unclean_width_elements(general_elements + bool(unclean_width)) +, unclean_depth_elements(unclean_depth ? (unclean_width_elements + 1) : general_elements) +, unclean_corner_elements(std::max(unclean_width_elements, unclean_depth_elements) + (unclean_width && unclean_depth)) +, buffers(unclean_corner_elements + 1) { + glGenVertexArrays(vaos.size(), vaos.data()); + glGenBuffers(buffers.size(), buffers.data()); + int x_end = vao_width - bool(unclean_width); + int z_end = vao_depth - bool(unclean_depth); + for (int z = 0; z < z_end; ++z) { + for (int x = 0; x < x_end; ++x) { + SetupVAO(z * vao_width + x, buffers[general_elements], (VAO_DIVISOR + 1) * (VAO_DIVISOR + 1)); + } + } + // always fill general element buffer (there won't be one needed for maps with x or z less than divisor, + // but that shouldn't happen in practice, only during tests in which case I don't care about the overhead + FillElementBuffer(buffers[general_elements], VAO_DIVISOR, VAO_DIVISOR); + if (unclean_width) { + for (int z = 0; z < z_end; ++z) { + SetupVAO(z * vao_width + x_end, buffers[unclean_width_elements], (unclean_width + 1) * (VAO_DIVISOR + 1)); + } + FillElementBuffer(buffers[unclean_width_elements], unclean_width, VAO_DIVISOR); + } + if (unclean_depth) { + for (int x = 0; x < x_end; ++x) { + SetupVAO(z_end * vao_width + x, buffers[unclean_depth_elements], (VAO_DIVISOR + 1) * (unclean_depth + 1)); + } + FillElementBuffer(buffers[unclean_depth_elements], VAO_DIVISOR, unclean_depth); + } + if (unclean_width && unclean_depth) { + SetupVAO(z_end * vao_width + x_end, buffers[unclean_corner_elements], (unclean_width + 1) * (unclean_depth + 1)); + FillElementBuffer(buffers[unclean_corner_elements], unclean_width, unclean_depth); + } +} + +Floor::~Floor() noexcept { + glDeleteBuffers(buffers.size(), buffers.data()); + glDeleteVertexArrays(vaos.size(), vaos.data()); +} + +void Floor::SetupVAO(int which, GLuint element_buffer, int vertex_count) noexcept { + glBindVertexArray(vaos[which]); + glBindBuffer(GL_ARRAY_BUFFER, buffers[which]); + glBufferData(GL_ARRAY_BUFFER, vertex_count * sizeof(Attributes), nullptr, GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, 0, sizeof(Attributes), reinterpret_cast(offsetof(Attributes, position))); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, 0, sizeof(Attributes), reinterpret_cast(offsetof(Attributes, normal))); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); +} + +void Floor::FillElementBuffer(GLuint which, int tile_width, int tile_depth) { + // unbind VAO so we don't accidentally trash an element buffer binding + glBindVertexArray(0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, which); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, tile_width * tile_depth * sizeof(short) * 6, nullptr, GL_STATIC_DRAW); + // TODO: this can return null on error (out of memory in this case) + // might be worth checking eventually + short *data = reinterpret_cast(glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY)); + for (int z = 0, i = 0; z < tile_depth; ++z) { + for (int x = 0; x < tile_width; ++x, ++i) { + data[i * 6 + 0] = (z + 0) * (tile_width + 1) + (x + 0); + data[i * 6 + 1] = (z + 0) * (tile_width + 1) + (x + 1); + data[i * 6 + 2] = (z + 1) * (tile_width + 1) + (x + 0); + data[i * 6 + 3] = (z + 0) * (tile_width + 1) + (x + 1); + data[i * 6 + 4] = (z + 1) * (tile_width + 1) + (x + 1); + data[i * 6 + 5] = (z + 1) * (tile_width + 1) + (x + 0); + } + } + glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); +} + +void Floor::FillAttribBuffer(int vao_x, int vao_z) { + glBindBuffer(GL_ARRAY_BUFFER, buffers[vao_z * vao_width + vao_x]); + Attributes *data = reinterpret_cast(glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY)); + glm::ivec2 tiles(Tiles(vao_x, vao_z)); + for (int z = 0, abs_z = vao_z * VAO_DIVISOR, i = 0; z < tiles.y + 1; ++z, ++abs_z) { + for (int x = 0, abs_x = vao_x * VAO_DIVISOR; x < tiles.x + 1; ++x, ++abs_x, ++i) { + data[i].position = glm::vec3(x, GetElevation(abs_x, abs_z), z); + data[i].normal = GetNormal(abs_x, abs_z); + } + } + glUnmapBuffer(GL_ARRAY_BUFFER); +} + +glm::vec3 Floor::GetNormal(int x, int z) const noexcept { + // TODO: not sure about the sign here + return normalize(glm::vec3( + ClampedElevation(x - 1, z) - ClampedElevation(x + 1, z), + 2.0f, + ClampedElevation(x, z - 1) - ClampedElevation(x, z + 1) + )); +} + +glm::ivec2 Floor::Tiles(int vao_x, int vao_z) const noexcept { + return glm::ivec2( + (unclean_width && vao_x == vao_width - 1) ? unclean_width : VAO_DIVISOR, + (unclean_depth && vao_z == vao_depth - 1) ? unclean_depth : VAO_DIVISOR); +} + +int Floor::NumTiles(int vao_x, int vao_z) const noexcept { + glm::ivec2 tiles = Tiles(vao_x, vao_z); + return tiles.x * tiles.y; +} + +int Floor::NumVertices(int vao_x, int vao_z) const noexcept { + glm::ivec2 tiles = Tiles(vao_x, vao_z); + return (tiles.x + 1) * (tiles.y + 1); +} + +void Floor::GenerateVertices() { + for (int z = 0; z < vao_depth; ++z) { + for (int x = 0; x < vao_width; ++x) { + FillAttribBuffer(x, z); + } + } +} + +void Floor::DrawVAO(int vao_x, int vao_z) const noexcept { + glBindVertexArray(vaos[vao_z * vao_width + vao_x]); + // TODO: this cries for triangle strips + glDrawElements(GL_TRIANGLES, NumTiles(vao_x, vao_z) * 6, GL_UNSIGNED_SHORT, nullptr); +} + +} -- 2.39.2