--- /dev/null
+[submodule "assets"]
+ path = assets
+ url = http://git.localhorst.tv/repo/tacos-assets.git
--- /dev/null
+Subproject commit c690bc6e1a92fae19922e48b58c4874e330b490a
--- /dev/null
+#include "assets.hpp"
+
+#include "config.hpp"
+#include "../graphics/shader.hpp"
+
+#include <cstdio>
+#include <memory>
+
+using std::string;
+using std::unique_ptr;
+
+
+namespace {
+
+/// get file's contents as zero terminated string
+unique_ptr<char[]> 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<char[]> 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<char[]> 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<char[]> source(file_string(path.c_str()));
+ return Shader::Fragment(source.get());
+}
+
+}
--- /dev/null
+#ifndef TACOS_APP_ASSETS_HPP_
+#define TACOS_APP_ASSETS_HPP_
+
+#include <string>
+
+
+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
--- /dev/null
+#include "config.hpp"
+
+#include <cstring>
+#include <iostream>
+#include <SDL.h>
+
+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
+}
+
+}
--- /dev/null
+#ifndef TACOS_APP_CONFIG_HPP_
+#define TACOS_APP_CONFIG_HPP_
+
+#include <string>
+
+
+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
#include "init.hpp"
+#include "config.hpp"
#include "error.hpp"
#include <alut.h>
TTF_Quit();
}
+
+Init::Init(const Config &config)
+: sdl()
+, gl(config.double_buffer, config.multi_sample)
+, img()
+, ttf()
+, alut() {
+
+}
+
}
namespace tacos {
+class Config;
+
+
struct InitAlut {
InitAlut();
struct Init {
+ explicit Init(const Config &);
+
InitSDL sdl;
InitGL gl;
InitIMG img;
+++ /dev/null
-#include "window.hpp"
-
-#include "error.hpp"
-
-#include <stdexcept>
-#include <string>
-#include <GL/glew.h>
-
-
-namespace tacos {
-
-Window::Window() {
- window = SDL_CreateWindow(
- "tacos", // title
- SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, // position
- 1440, 900, // size
- SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE // flags
- );
- if (!window) {
- throw SDLError("SDL_CreateWindow");
- }
- context = SDL_GL_CreateContext(window);
- if (!context) {
- SDL_DestroyWindow(window);
- throw SDLError("SDL_GL_CreateContext");
- }
- glewExperimental = GL_TRUE;
- GLenum err = glewInit();
- if (err != GLEW_OK) {
- SDL_GL_DeleteContext(context);
- SDL_DestroyWindow(window);
- std::string msg("glewInit: ");
- msg += reinterpret_cast<const char *>(glewGetErrorString(err));
- throw std::runtime_error(msg);
- }
-}
-
-Window::~Window() noexcept {
- SDL_GL_DeleteContext(context);
- SDL_DestroyWindow(window);
-}
-
-void Window::Flip() noexcept {
- SDL_GL_SwapWindow(window);
-}
-
-
-}
+++ /dev/null
-#ifndef TACOS_APP_WINDOW_HPP_
-#define TACOS_APP_WINDOW_HPP_
-
-#include <SDL.h>
-
-
-namespace tacos {
-
-class Window {
-
-public:
- Window();
- ~Window() noexcept;
-
- Window(const Window &) = delete;
- Window &operator =(const Window &) = delete;
-
- void Flip() noexcept;
-
-private:
- SDL_Window *window;
- SDL_GLContext context;
-
-};
-
-}
-
-#endif
--- /dev/null
+#include "camera.hpp"
+
+#include <glm/gtx/rotate_vector.hpp>
+
+
+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));
+}
+
+}
--- /dev/null
+#ifndef TACOS_GRAPHICS_CAMERA_HPP_
+#define TACOS_GRAPHICS_CAMERA_HPP_
+
+#include <glm/glm.hpp>
+
+
+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
--- /dev/null
+#include "viewport.hpp"
+
+#include <GL/glew.h>
+#include <glm/gtc/matrix_transform.hpp>
+
+
+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);
+}
+
+}
--- /dev/null
+#ifndef TACOS_GRAPHICS_VIEWPORT_HPP_
+#define TACOS_GRAPHICS_VIEWPORT_HPP_
+
+#include <glm/glm.hpp>
+
+
+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
--- /dev/null
+#include "window.hpp"
+
+#include "../app/error.hpp"
+
+#include <stdexcept>
+#include <string>
+#include <GL/glew.h>
+
+
+namespace tacos {
+
+Window::Window(int width, int height) {
+ window = SDL_CreateWindow(
+ "tacos", // title
+ SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, // position
+ width, height, // size
+ SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE // flags
+ );
+ if (!window) {
+ throw SDLError("SDL_CreateWindow");
+ }
+ context = SDL_GL_CreateContext(window);
+ if (!context) {
+ SDL_DestroyWindow(window);
+ throw SDLError("SDL_GL_CreateContext");
+ }
+ glewExperimental = GL_TRUE;
+ GLenum err = glewInit();
+ if (err != GLEW_OK) {
+ SDL_GL_DeleteContext(context);
+ SDL_DestroyWindow(window);
+ std::string msg("glewInit: ");
+ msg += reinterpret_cast<const char *>(glewGetErrorString(err));
+ throw std::runtime_error(msg);
+ }
+}
+
+Window::~Window() noexcept {
+ SDL_GL_DeleteContext(context);
+ SDL_DestroyWindow(window);
+}
+
+void Window::Flip() noexcept {
+ SDL_GL_SwapWindow(window);
+}
+
+
+}
--- /dev/null
+#ifndef TACOS_GRAPHICS_WINDOW_HPP_
+#define TACOS_GRAPHICS_WINDOW_HPP_
+
+#include <SDL.h>
+
+
+namespace tacos {
+
+class Window {
+
+public:
+ Window(int width, int height);
+ ~Window() noexcept;
+
+ Window(const Window &) = delete;
+ Window &operator =(const Window &) = delete;
+
+ void Flip() noexcept;
+
+private:
+ SDL_Window *window;
+ SDL_GLContext context;
+
+};
+
+}
+
+#endif
--- /dev/null
+#ifndef TACOS_RAND_GALOISLFSR_HPP_
+#define TACOS_RAND_GALOISLFSR_HPP_
+
+#include <cstdint>
+#include <limits>
+
+
+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<class T>
+ T operator ()(T &out) noexcept {
+ constexpr int num_bits =
+ std::numeric_limits<T>::digits +
+ std::numeric_limits<T>::is_signed;
+ for (int i = 0; i < num_bits; ++i) {
+ operator ()();
+ }
+ return out = static_cast<T>(state);
+ }
+
+ template<class T>
+ T Next() noexcept {
+ T next;
+ return (*this)(next);
+ }
+
+ float SNorm() noexcept {
+ return float(Next<std::uint32_t>()) * (1.0f / 2147483647.5f) - 1.0f;
+ }
+
+ float UNorm() noexcept {
+ return float(Next<std::uint32_t>()) * (1.0f / 4294967295.0f);
+ }
+
+ template<class Container>
+ typename Container::reference From(Container &c) {
+ return c[Next<typename Container::size_type>() % c.size()];
+ }
+ template<class Container>
+ typename Container::const_reference From(const Container &c) {
+ return c[Next<typename Container::size_type>() % 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
--- /dev/null
+#ifndef TACOS_RAND_OCTAVENOISE_HPP_
+#define TACOS_RAND_OCTAVENOISE_HPP_
+
+#include <glm/glm.hpp>
+
+
+namespace tacos {
+
+template<class Noise>
+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
--- /dev/null
+#ifndef TACOS_RAND_SIMPLEXNOISE_HPP_
+#define TACOS_RAND_SIMPLEXNOISE_HPP_
+
+#include <cstdint>
+#include <glm/glm.hpp>
+
+
+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
--- /dev/null
+#include "GaloisLFSR.hpp"
+#include "SimplexNoise.hpp"
+
+#include <cmath>
+
+
+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];
+}
+
+}
+#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 <iostream>
+#include <GL/glew.h>
+#include <glm/glm.hpp>
+#include <glm/gtc/matrix_transform.hpp>
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;
}
--- /dev/null
+#ifndef TACOS_WORLD_FLOOR_HPP_
+#define TACOS_WORLD_FLOOR_HPP_
+
+#include <vector>
+#include <GL/glew.h>
+#include <glm/glm.hpp>
+
+
+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<float> elevation;
+
+ int tile_width;
+ int tile_depth;
+ int unclean_width;
+ int unclean_depth;
+ int vao_width;
+ int vao_depth;
+ std::vector<GLuint> 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<GLuint> buffers;
+
+ struct Attributes {
+ glm::vec3 position;
+ glm::vec3 normal;
+ };
+
+};
+
+}
+
+#endif
--- /dev/null
+#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<const void *>(offsetof(Attributes, position)));
+ glEnableVertexAttribArray(1);
+ glVertexAttribPointer(1, 3, GL_FLOAT, 0, sizeof(Attributes), reinterpret_cast<const void *>(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<short *>(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<Attributes *>(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);
+}
+
+}