]> git.localhorst.tv Git - blank.git/commitdiff
some code reorganization
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Wed, 10 Jun 2015 14:57:25 +0000 (16:57 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Wed, 10 Jun 2015 14:57:25 +0000 (16:57 +0200)
87 files changed:
Makefile
TODO
src/app.cpp [deleted file]
src/app.hpp [deleted file]
src/app/Application.cpp [new file with mode: 0644]
src/app/Application.hpp [new file with mode: 0644]
src/app/FPSController.hpp [new file with mode: 0644]
src/app/IntervalTimer.hpp [new file with mode: 0644]
src/app/RandomWalk.hpp [new file with mode: 0644]
src/app/Runtime.cpp [new file with mode: 0644]
src/app/Runtime.hpp [new file with mode: 0644]
src/app/controller.cpp [new file with mode: 0644]
src/app/init.cpp [new file with mode: 0644]
src/app/init.hpp [new file with mode: 0644]
src/blank.cpp [new file with mode: 0644]
src/block.cpp [deleted file]
src/block.hpp [deleted file]
src/camera.cpp [deleted file]
src/camera.hpp [deleted file]
src/chunk.cpp [deleted file]
src/chunk.hpp [deleted file]
src/controller.cpp [deleted file]
src/controller.hpp [deleted file]
src/entity.cpp [deleted file]
src/entity.hpp [deleted file]
src/generator.cpp [deleted file]
src/generator.hpp [deleted file]
src/geometry.cpp [deleted file]
src/geometry.hpp [deleted file]
src/graphics/BlockLighting.hpp [new file with mode: 0644]
src/graphics/Camera.cpp [new file with mode: 0644]
src/graphics/Camera.hpp [new file with mode: 0644]
src/graphics/DirectionalLighting.hpp [new file with mode: 0644]
src/graphics/Program.hpp [new file with mode: 0644]
src/graphics/Shader.hpp [new file with mode: 0644]
src/graphics/shader.cpp [new file with mode: 0644]
src/hud.cpp [deleted file]
src/hud.hpp [deleted file]
src/init.cpp [deleted file]
src/init.hpp [deleted file]
src/interface.cpp [deleted file]
src/interface.hpp [deleted file]
src/main.cpp [deleted file]
src/model.cpp [deleted file]
src/model.hpp [deleted file]
src/model/BlockModel.hpp [new file with mode: 0644]
src/model/Model.hpp [new file with mode: 0644]
src/model/OutlineModel.hpp [new file with mode: 0644]
src/model/Shape.hpp [new file with mode: 0644]
src/model/geometry.cpp [new file with mode: 0644]
src/model/geometry.hpp [new file with mode: 0644]
src/model/model.cpp [new file with mode: 0644]
src/model/shape.cpp [new file with mode: 0644]
src/model/shapes.hpp [new file with mode: 0644]
src/noise.cpp [deleted file]
src/noise.hpp [deleted file]
src/rand/GaloisLFSR.hpp [new file with mode: 0644]
src/rand/OctaveNoise.hpp [new file with mode: 0644]
src/rand/SimplexNoise.hpp [new file with mode: 0644]
src/rand/WorleyNoise.hpp [new file with mode: 0644]
src/rand/noise.cpp [new file with mode: 0644]
src/runtime.cpp [deleted file]
src/runtime.hpp [deleted file]
src/shader.cpp [deleted file]
src/shader.hpp [deleted file]
src/shape.cpp [deleted file]
src/shape.hpp [deleted file]
src/timer.hpp [deleted file]
src/ui/HUD.hpp [new file with mode: 0644]
src/ui/Interface.hpp [new file with mode: 0644]
src/ui/ui.cpp [new file with mode: 0644]
src/world.cpp [deleted file]
src/world.hpp [deleted file]
src/world/Block.hpp [new file with mode: 0644]
src/world/BlockLookup.hpp [new file with mode: 0644]
src/world/BlockType.hpp [new file with mode: 0644]
src/world/BlockTypeRegistry.hpp [new file with mode: 0644]
src/world/Chunk.hpp [new file with mode: 0644]
src/world/ChunkLoader.hpp [new file with mode: 0644]
src/world/Entity.cpp [new file with mode: 0644]
src/world/Entity.hpp [new file with mode: 0644]
src/world/Generator.cpp [new file with mode: 0644]
src/world/Generator.hpp [new file with mode: 0644]
src/world/World.cpp [new file with mode: 0644]
src/world/World.hpp [new file with mode: 0644]
src/world/block.cpp [new file with mode: 0644]
src/world/chunk.cpp [new file with mode: 0644]

index 36c7048c7ec60202f260e3ecab1b594c4a6debcd..d063a9a75befe1793abccf50fcac8bcd010476c2 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -23,7 +23,9 @@ PROFILE_DIR := build/profile
 RELEASE_DIR := build/release
 DIR := $(RELEASE_DIR) $(DEBUG_DIR) $(PROFILE_DIR) build
 
-SRC := $(wildcard $(SOURCE_DIR)/*.cpp)
+LIB_SRC := $(wildcard $(SOURCE_DIR)/*/*.cpp)
+BIN_SRC := $(wildcard $(SOURCE_DIR)/*.cpp)
+SRC := $(LIB_SRC) $(BIN_SRC)
 RELEASE_OBJ := $(patsubst $(SOURCE_DIR)/%.cpp, $(RELEASE_DIR)/%.o, $(SRC))
 DEBUG_OBJ := $(patsubst $(SOURCE_DIR)/%.cpp, $(DEBUG_DIR)/%.o, $(SRC))
 PROFILE_OBJ := $(patsubst $(SOURCE_DIR)/%.cpp, $(PROFILE_DIR)/%.o, $(SRC))
@@ -84,14 +86,17 @@ $(PROFILE_BIN): $(PROFILE_OBJ)
        @$(LDXX) -o $@ $(CXXFLAGS) $(LDXXFLAGS) $(PROFILE_FLAGS) $^
 
 $(RELEASE_DIR)/%.o: $(SOURCE_DIR)/%.cpp | $(RELEASE_DIR)
+       @mkdir -p "$(@D)"
        @echo compile: $@
        @$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $(RELEASE_FLAGS) -o $@ -MMD -MP -MF"$(@:.o=.d)" -MT"$@" $<
 
 $(DEBUG_DIR)/%.o: $(SOURCE_DIR)/%.cpp | $(DEBUG_DIR)
+       @mkdir -p "$(@D)"
        @echo compile: $@
        @$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $(DEBUG_FLAGS) -o $@ -MMD -MP -MF"$(@:.o=.d)" -MT"$@" $<
 
 $(PROFILE_DIR)/%.o: $(SOURCE_DIR)/%.cpp | $(PROFILE_DIR)
+       @mkdir -p "$(@D)"
        @echo compile: $@
        @$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $(PROFILE_FLAGS) -o $@ -MMD -MP -MF"$(@:.o=.d)" -MT"$@" $<
 
diff --git a/TODO b/TODO
index 926a35516e4ed01b3d455ce7be77d163affd099e..6234fd0464048c6a29ac103210758c7377f0c6de 100644 (file)
--- a/TODO
+++ b/TODO
@@ -43,12 +43,20 @@ entity ai
        this could be solved by using a pre-interpolated light texture and
        mapping light levels to coordinates on that
 
+       there seems to be a bug with adding/removing obstacles...again
+
        also: how could block light affect entity lighting?
+       maybe get the interpolated light level at the entity's center and use
+       that as the light power for the directional lighting shader and use a
+       direction that's fixed relative to the camera?
 
 gravity
 
        maybe like light levels? should also store a direction with it in
        that case. also, global gravity may be a world option.
+       no, per-block gravity vector is most probably too expensive.
+       better have the chunks store a few point masses (maybe blocks that
+       emit gravitation?) and calculate from that
 
 block attributes
 
@@ -61,6 +69,9 @@ chunk traversal
        maybe the chunk loader should keep an index of interesting, if not
        all chunks by position, possibly base-relative
 
+       profiling indicates that this is not neccessary atm. maybe it will
+       when some more action is in the world
+
 transparency (blocks and entities)
 
        transparent blocks because awesome
diff --git a/src/app.cpp b/src/app.cpp
deleted file mode 100644 (file)
index 57651e3..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-#include "app.hpp"
-
-#include <iostream>
-#include <stdexcept>
-
-
-namespace blank {
-
-Application::Application(const Config &config)
-: init_sdl()
-, init_img()
-, init_gl(config.doublebuf, config.multisampling)
-, window()
-, ctx(window.CreateContext())
-, init_glew()
-, chunk_prog()
-, entity_prog()
-, cam()
-, world(config.world)
-, interface(config.interface, world)
-, test_controller(MakeTestEntity(world))
-, running(false) {
-       if (config.vsync) {
-               GLContext::EnableVSync();
-       }
-
-       glClearColor(0.0, 0.0, 0.0, 1.0);
-}
-
-Entity &Application::MakeTestEntity(World &world) {
-       Entity &e = world.AddEntity();
-       e.Position({ 0.0f, 0.0f, 0.0f });
-       e.SetShape(world.BlockTypes()[1].shape, { 1.0f, 1.0f, 0.0f });
-       e.AngularVelocity(glm::quat(glm::vec3{ 0.00001f, 0.000006f, 0.000013f }));
-       return e;
-}
-
-
-void Application::RunN(size_t n) {
-       Uint32 last = SDL_GetTicks();
-       for (size_t i = 0; i < n; ++i) {
-               Uint32 now = SDL_GetTicks();
-               int delta = now - last;
-               Loop(delta);
-               last = now;
-       }
-}
-
-void Application::RunT(size_t t) {
-       Uint32 last = SDL_GetTicks();
-       Uint32 finish = last + t;
-       while (last < finish) {
-               Uint32 now = SDL_GetTicks();
-               int delta = now - last;
-               Loop(delta);
-               last = now;
-       }
-}
-
-void Application::RunS(size_t n, size_t t) {
-       for (size_t i = 0; i < n; ++i) {
-               Loop(t);
-       }
-}
-
-
-void Application::Run() {
-       running = true;
-       Uint32 last = SDL_GetTicks();
-       window.GrabMouse();
-       while (running) {
-               Uint32 now = SDL_GetTicks();
-               int delta = now - last;
-               Loop(delta);
-               last = now;
-       }
-}
-
-void Application::Loop(int dt) {
-       HandleEvents();
-       Update(dt);
-       Render();
-}
-
-
-void Application::HandleEvents() {
-       SDL_Event event;
-       while (SDL_PollEvent(&event)) {
-               switch (event.type) {
-                       case SDL_KEYDOWN:
-                               interface.HandlePress(event.key);
-                               break;
-                       case SDL_KEYUP:
-                               interface.HandleRelease(event.key);
-                               break;
-                       case SDL_MOUSEBUTTONDOWN:
-                               interface.HandlePress(event.button);
-                               break;
-                       case SDL_MOUSEBUTTONUP:
-                               interface.HandleRelease(event.button);
-                               break;
-                       case SDL_MOUSEMOTION:
-                               interface.Handle(event.motion);
-                               break;
-                       case SDL_MOUSEWHEEL:
-                               interface.Handle(event.wheel);
-                               break;
-                       case SDL_QUIT:
-                               running = false;
-                               break;
-                       case SDL_WINDOWEVENT:
-                               switch (event.window.event) {
-                                       case SDL_WINDOWEVENT_FOCUS_GAINED:
-                                               window.GrabMouse();
-                                               break;
-                                       case SDL_WINDOWEVENT_FOCUS_LOST:
-                                               window.ReleaseMouse();
-                                               break;
-                                       case SDL_WINDOWEVENT_RESIZED:
-                                               cam.Viewport(event.window.data1, event.window.data2);
-                                               interface.Handle(event.window);
-                                               break;
-                                       default:
-                                               interface.Handle(event.window);
-                                               break;
-                               }
-                               break;
-                       default:
-                               break;
-               }
-       }
-}
-
-void Application::Update(int dt) {
-       interface.Update(dt);
-       test_controller.Update(dt);
-       world.Update(dt);
-}
-
-void Application::Render() {
-       GLContext::Clear();
-
-       chunk_prog.SetProjection(cam.Projection());
-       entity_prog.SetProjection(cam.Projection());
-
-       world.Render(chunk_prog, entity_prog);
-
-       interface.Render(entity_prog);
-
-       window.Flip();
-}
-
-}
diff --git a/src/app.hpp b/src/app.hpp
deleted file mode 100644 (file)
index e6226dc..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-#ifndef BLANK_APP_HPP_
-#define BLANK_APP_HPP_
-
-#include "camera.hpp"
-#include "controller.hpp"
-#include "init.hpp"
-#include "interface.hpp"
-#include "shader.hpp"
-#include "world.hpp"
-
-
-namespace blank {
-
-class Application {
-
-public:
-       struct Config {
-               bool vsync = true;
-               bool doublebuf = true;
-               int multisampling = 1;
-
-               Interface::Config interface = Interface::Config();
-               World::Config world = World::Config();
-       };
-
-       explicit Application(const Config &);
-
-       Application(const Application &) = delete;
-       Application &operator =(const Application &) = delete;
-
-       /// run until user quits
-       void Run();
-       void Loop(int dt);
-
-       /// run for n frames
-       void RunN(size_t n);
-       /// run for t milliseconds
-       void RunT(size_t t);
-       /// run for n frames, assuming t milliseconds for each
-       void RunS(size_t n, size_t t);
-
-       void HandleEvents();
-       void Update(int dt);
-       void Render();
-
-       static Entity &MakeTestEntity(World &);
-
-private:
-       InitSDL init_sdl;
-       InitIMG init_img;
-       InitGL init_gl;
-       Window window;
-       GLContext ctx;
-       InitGLEW init_glew;
-       BlockLighting chunk_prog;
-       DirectionalLighting entity_prog;
-
-       Camera cam;
-       World world;
-       Interface interface;
-
-       RandomWalk test_controller;
-
-       bool running;
-
-};
-
-}
-
-#endif
diff --git a/src/app/Application.cpp b/src/app/Application.cpp
new file mode 100644 (file)
index 0000000..67438db
--- /dev/null
@@ -0,0 +1,156 @@
+#include "Application.hpp"
+
+#include "../world/BlockType.hpp"
+#include "../world/Entity.hpp"
+
+#include <iostream>
+#include <stdexcept>
+
+
+namespace blank {
+
+Application::Application(const Config &config)
+: init_sdl()
+, init_img()
+, init_gl(config.doublebuf, config.multisampling)
+, window()
+, ctx(window.CreateContext())
+, init_glew()
+, chunk_prog()
+, entity_prog()
+, cam()
+, world(config.world)
+, interface(config.interface, world)
+, test_controller(MakeTestEntity(world))
+, running(false) {
+       if (config.vsync) {
+               GLContext::EnableVSync();
+       }
+
+       glClearColor(0.0, 0.0, 0.0, 1.0);
+}
+
+Entity &Application::MakeTestEntity(World &world) {
+       Entity &e = world.AddEntity();
+       e.Position({ 0.0f, 0.0f, 0.0f });
+       e.SetShape(world.BlockTypes()[1].shape, { 1.0f, 1.0f, 0.0f });
+       e.AngularVelocity(glm::quat(glm::vec3{ 0.00001f, 0.000006f, 0.000013f }));
+       return e;
+}
+
+
+void Application::RunN(size_t n) {
+       Uint32 last = SDL_GetTicks();
+       for (size_t i = 0; i < n; ++i) {
+               Uint32 now = SDL_GetTicks();
+               int delta = now - last;
+               Loop(delta);
+               last = now;
+       }
+}
+
+void Application::RunT(size_t t) {
+       Uint32 last = SDL_GetTicks();
+       Uint32 finish = last + t;
+       while (last < finish) {
+               Uint32 now = SDL_GetTicks();
+               int delta = now - last;
+               Loop(delta);
+               last = now;
+       }
+}
+
+void Application::RunS(size_t n, size_t t) {
+       for (size_t i = 0; i < n; ++i) {
+               Loop(t);
+       }
+}
+
+
+void Application::Run() {
+       running = true;
+       Uint32 last = SDL_GetTicks();
+       window.GrabMouse();
+       while (running) {
+               Uint32 now = SDL_GetTicks();
+               int delta = now - last;
+               Loop(delta);
+               last = now;
+       }
+}
+
+void Application::Loop(int dt) {
+       HandleEvents();
+       Update(dt);
+       Render();
+}
+
+
+void Application::HandleEvents() {
+       SDL_Event event;
+       while (SDL_PollEvent(&event)) {
+               switch (event.type) {
+                       case SDL_KEYDOWN:
+                               interface.HandlePress(event.key);
+                               break;
+                       case SDL_KEYUP:
+                               interface.HandleRelease(event.key);
+                               break;
+                       case SDL_MOUSEBUTTONDOWN:
+                               interface.HandlePress(event.button);
+                               break;
+                       case SDL_MOUSEBUTTONUP:
+                               interface.HandleRelease(event.button);
+                               break;
+                       case SDL_MOUSEMOTION:
+                               interface.Handle(event.motion);
+                               break;
+                       case SDL_MOUSEWHEEL:
+                               interface.Handle(event.wheel);
+                               break;
+                       case SDL_QUIT:
+                               running = false;
+                               break;
+                       case SDL_WINDOWEVENT:
+                               switch (event.window.event) {
+                                       case SDL_WINDOWEVENT_FOCUS_GAINED:
+                                               window.GrabMouse();
+                                               break;
+                                       case SDL_WINDOWEVENT_FOCUS_LOST:
+                                               window.ReleaseMouse();
+                                               break;
+                                       case SDL_WINDOWEVENT_RESIZED:
+                                               cam.Viewport(event.window.data1, event.window.data2);
+                                               interface.Handle(event.window);
+                                               break;
+                                       default:
+                                               interface.Handle(event.window);
+                                               break;
+                               }
+                               break;
+                       default:
+                               break;
+               }
+       }
+}
+
+void Application::Update(int dt) {
+       interface.Update(dt);
+       test_controller.Update(dt);
+       world.Update(dt);
+}
+
+void Application::Render() {
+       GLContext::Clear();
+
+       chunk_prog.SetProjection(cam.Projection());
+       entity_prog.SetProjection(cam.Projection());
+
+       world.Render(chunk_prog, entity_prog);
+
+       interface.Render(entity_prog);
+
+       window.Flip();
+}
+
+}
diff --git a/src/app/Application.hpp b/src/app/Application.hpp
new file mode 100644 (file)
index 0000000..c8236ff
--- /dev/null
@@ -0,0 +1,71 @@
+#ifndef BLANK_APP_APPLICATION_HPP_
+#define BLANK_APP_APPLICATION_HPP_
+
+#include "init.hpp"
+#include "RandomWalk.hpp"
+#include "../graphics/BlockLighting.hpp"
+#include "../graphics/Camera.hpp"
+#include "../graphics/DirectionalLighting.hpp"
+#include "../ui/Interface.hpp"
+#include "../world/World.hpp"
+
+
+namespace blank {
+
+class Application {
+
+public:
+       struct Config {
+               bool vsync = true;
+               bool doublebuf = true;
+               int multisampling = 1;
+
+               Interface::Config interface = Interface::Config();
+               World::Config world = World::Config();
+       };
+
+       explicit Application(const Config &);
+
+       Application(const Application &) = delete;
+       Application &operator =(const Application &) = delete;
+
+       /// run until user quits
+       void Run();
+       void Loop(int dt);
+
+       /// run for n frames
+       void RunN(size_t n);
+       /// run for t milliseconds
+       void RunT(size_t t);
+       /// run for n frames, assuming t milliseconds for each
+       void RunS(size_t n, size_t t);
+
+       void HandleEvents();
+       void Update(int dt);
+       void Render();
+
+       static Entity &MakeTestEntity(World &);
+
+private:
+       InitSDL init_sdl;
+       InitIMG init_img;
+       InitGL init_gl;
+       Window window;
+       GLContext ctx;
+       InitGLEW init_glew;
+       BlockLighting chunk_prog;
+       DirectionalLighting entity_prog;
+
+       Camera cam;
+       World world;
+       Interface interface;
+
+       RandomWalk test_controller;
+
+       bool running;
+
+};
+
+}
+
+#endif
diff --git a/src/app/FPSController.hpp b/src/app/FPSController.hpp
new file mode 100644 (file)
index 0000000..88dca64
--- /dev/null
@@ -0,0 +1,44 @@
+#ifndef BLANK_APP_FPSCONTROLLER_HPP_
+#define BLANK_APP_FPSCONTROLLER_HPP_
+
+#include "../model/geometry.hpp"
+#include "../world/Entity.hpp"
+
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class FPSController {
+
+public:
+       explicit FPSController(Entity &) noexcept;
+
+       Ray Aim() const noexcept { return entity.Aim(entity.ChunkCoords()); }
+
+       const glm::vec3 &Velocity() const noexcept { return velocity; }
+       void Velocity(const glm::vec3 &vel) noexcept { velocity = vel; }
+
+       // all angles in radians (full circle = 2Ï€)
+       float Pitch() const noexcept { return pitch; }
+       void Pitch(float p) noexcept;
+       void RotatePitch(float delta) noexcept;
+       float Yaw() const noexcept { return yaw; }
+       void Yaw(float y) noexcept;
+       void RotateYaw(float delta) noexcept;
+
+       void Update(int dt) noexcept;
+
+private:
+       Entity &entity;
+
+       glm::vec3 velocity;
+
+       float pitch;
+       float yaw;
+
+};
+
+}
+
+#endif
diff --git a/src/app/IntervalTimer.hpp b/src/app/IntervalTimer.hpp
new file mode 100644 (file)
index 0000000..e5d0248
--- /dev/null
@@ -0,0 +1,49 @@
+#ifndef BLANK_APP_INTERVALTIMER_HPP
+#define BLANK_APP_INTERVALTIMER_HPP
+
+
+namespace blank {
+
+class IntervalTimer {
+
+public:
+       explicit IntervalTimer(int interval_ms) noexcept
+       : intv(interval_ms) { }
+
+       void Start() noexcept {
+               speed = 1;
+       }
+       void Stop() noexcept {
+               value = 0;
+               speed = 0;
+       }
+
+       bool Running() const noexcept {
+               return speed != 0;
+       }
+       bool Hit() const noexcept {
+               return Running() && value % intv < last_dt;
+       }
+       int Elapsed() const noexcept {
+               return value;
+       }
+       int Iteration() const noexcept {
+               return value / intv;
+       }
+
+       void Update(int dt) noexcept {
+               value += dt * speed;
+               last_dt = dt;
+       }
+
+private:
+       int intv;
+       int value = 0;
+       int speed = 0;
+       int last_dt = 0;
+
+};
+
+}
+
+#endif
diff --git a/src/app/RandomWalk.hpp b/src/app/RandomWalk.hpp
new file mode 100644 (file)
index 0000000..5660ab3
--- /dev/null
@@ -0,0 +1,27 @@
+#ifndef BLANK_APP_RANDOMWALK_HPP_
+#define BLANK_APP_RANDOMWALK_HPP_
+
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class Entity;
+
+class RandomWalk {
+
+public:
+       explicit RandomWalk(Entity &) noexcept;
+
+       void Update(int dt) noexcept;
+
+private:
+       Entity &entity;
+
+       int time_left;
+
+};
+
+}
+
+#endif
diff --git a/src/app/Runtime.cpp b/src/app/Runtime.cpp
new file mode 100644 (file)
index 0000000..8ddab26
--- /dev/null
@@ -0,0 +1,164 @@
+#include "Runtime.hpp"
+
+#include <cctype>
+#include <cstdlib>
+#include <iostream>
+
+using namespace std;
+
+
+namespace blank {
+
+Runtime::Runtime() noexcept
+: name("blank")
+, mode(NORMAL)
+, n(0)
+, t(0)
+, config() {
+
+}
+
+
+void Runtime::ReadArgs(int argc, const char *const *argv) {
+       if (argc <= 0) return;
+       name = argv[0];
+
+       bool options = true;
+       bool error = false;
+
+       for (int i = 1; i < argc; ++i) {
+               const char *arg = argv[i];
+               if (!arg || arg[0] == '\0') {
+                       cerr << "warning: found empty argument at position " << i << endl;
+                       continue;
+               }
+               if (options && arg[0] == '-') {
+                       if (arg[1] == '\0') {
+                               cerr << "warning: incomplete option list at position " << i << endl;
+                       } else if (arg[1] == '-') {
+                               if (arg[2] == '\0') {
+                                       // stopper
+                                       options = false;
+                               } else {
+                                       // long option
+                                       if (strcmp(arg + 2, "no-vsync") == 0) {
+                                               config.vsync = false;
+                                       } else if (strcmp(arg + 2, "no-keyboard") == 0) {
+                                               config.interface.keyboard_disabled = true;
+                                       } else if (strcmp(arg + 2, "no-mouse") == 0) {
+                                               config.interface.mouse_disabled = true;
+                                       } else if (strcmp(arg + 2, "no-hud") == 0) {
+                                               config.interface.visual_disabled = true;
+                                       } else {
+                                               cerr << "unknown option " << arg << endl;
+                                               error = true;
+                                       }
+                               }
+                       } else {
+                               // short options
+                               for (int j = 1; arg[j] != '\0'; ++j) {
+                                       switch (arg[j]) {
+                                               case 'd':
+                                                       config.doublebuf = false;
+                                                       break;
+                                               case 'm':
+                                                       ++i;
+                                                       if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
+                                                               cerr << "missing argument to -m" << endl;
+                                                               error = true;
+                                                       } else {
+                                                               config.multisampling = strtoul(argv[i], nullptr, 10);
+                                                       }
+                                                       break;
+                                               case 'n':
+                                                       ++i;
+                                                       if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
+                                                               cerr << "missing argument to -n" << endl;
+                                                               error = true;
+                                                       } else {
+                                                               n = strtoul(argv[i], nullptr, 10);
+                                                       }
+                                                       break;
+                                               case 's':
+                                                       ++i;
+                                                       if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
+                                                               cerr << "missing argument to -s" << endl;
+                                                               error = true;
+                                                       } else {
+                                                               config.world.gen.solid_seed = strtoul(argv[i], nullptr, 10);
+                                                               config.world.gen.type_seed = config.world.gen.solid_seed;
+                                                       }
+                                                       break;
+                                               case 't':
+                                                       ++i;
+                                                       if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
+                                                               cerr << "missing argument to -t" << endl;
+                                                               error = true;
+                                                       } else {
+                                                               t = strtoul(argv[i], nullptr, 10);
+                                                       }
+                                                       break;
+                                               case '-':
+                                                       // stopper
+                                                       options = false;
+                                                       break;
+                                               default:
+                                                       cerr << "unknown option " << arg[j] << endl;
+                                                       error = true;
+                                                       break;
+                                       }
+                               }
+                       }
+               } else if (isdigit(arg[0])) {
+                       // positional number interpreted as -n
+                       n = strtoul(arg, nullptr, 10);
+               } else {
+                       cerr << "unable to interpret argument "
+                               << i << " (" << arg << ")" << endl;
+                       error = true;
+               }
+       }
+
+       if (error) {
+               mode = ERROR;
+               return;
+       }
+
+       if (n > 0) {
+               if (t > 0) {
+                       mode = FIXED_FRAME_LIMIT;
+               } else {
+                       mode = FRAME_LIMIT;
+               }
+       } else if (t > 0) {
+               mode = TIME_LIMIT;
+       } else {
+               mode = NORMAL;
+       }
+}
+
+int Runtime::Execute() {
+       if (mode == ERROR) {
+               return 1;
+       }
+
+       Application app(config);
+       switch (mode) {
+               default:
+               case NORMAL:
+                       app.Run();
+                       break;
+               case FRAME_LIMIT:
+                       app.RunN(n);
+                       break;
+               case TIME_LIMIT:
+                       app.RunT(t);
+                       break;
+               case FIXED_FRAME_LIMIT:
+                       app.RunS(n, t);
+                       break;
+       }
+       return 0;
+}
+
+}
diff --git a/src/app/Runtime.hpp b/src/app/Runtime.hpp
new file mode 100644 (file)
index 0000000..2fdf2e5
--- /dev/null
@@ -0,0 +1,39 @@
+#ifndef BLANK_RUNTIME_HPP_
+#define BLANK_RUNTIME_HPP_
+
+#include "Application.hpp"
+
+#include <cstddef>
+
+
+namespace blank {
+
+class Runtime {
+
+public:
+       enum Mode {
+               NORMAL,
+               FRAME_LIMIT,
+               TIME_LIMIT,
+               FIXED_FRAME_LIMIT,
+               ERROR,
+       };
+
+       Runtime() noexcept;
+
+       void ReadArgs(int argc, const char *const *argv);
+
+       int Execute();
+
+private:
+       const char *name;
+       Mode mode;
+       std::size_t n;
+       std::size_t t;
+       Application::Config config;
+
+};
+
+}
+
+#endif
diff --git a/src/app/controller.cpp b/src/app/controller.cpp
new file mode 100644 (file)
index 0000000..9cf2d6c
--- /dev/null
@@ -0,0 +1,100 @@
+#include "FPSController.hpp"
+#include "RandomWalk.hpp"
+
+#include <glm/gtx/euler_angles.hpp>
+#include <glm/gtx/rotate_vector.hpp>
+
+
+namespace blank {
+
+FPSController::FPSController(Entity &entity) noexcept
+: entity(entity)
+, pitch(0)
+, yaw(0) {
+
+}
+
+
+void FPSController::Pitch(float p) noexcept {
+       pitch = p;
+       if (pitch > PI / 2) {
+               pitch = PI / 2;
+       } else if (pitch < -PI / 2) {
+               pitch = -PI / 2;
+       }
+}
+
+void FPSController::RotatePitch(float delta) noexcept {
+       Pitch(pitch + delta);
+}
+
+void FPSController::Yaw(float y) noexcept {
+       yaw = y;
+       if (yaw > PI) {
+               yaw -= PI * 2;
+       } else if (yaw < -PI) {
+               yaw += PI * 2;
+       }
+}
+
+void FPSController::RotateYaw(float delta) noexcept {
+       Yaw(yaw + delta);
+}
+
+
+void FPSController::Update(int dt) noexcept {
+       entity.Rotation(glm::eulerAngleYX(yaw, pitch));
+       entity.Velocity(glm::rotateY(velocity, yaw));
+}
+
+
+RandomWalk::RandomWalk(Entity &e) noexcept
+: entity(e)
+, time_left(0) {
+
+}
+
+
+void RandomWalk::Update(int dt) noexcept {
+       time_left -= dt;
+       if (time_left > 0) return;
+       time_left += 2500 + (rand() % 5000);
+
+       constexpr float move_vel = 0.0005f;
+
+       glm::vec3 new_vel = entity.Velocity();
+
+       switch (rand() % 9) {
+               case 0:
+                       new_vel.x = -move_vel;
+                       break;
+               case 1:
+                       new_vel.x = 0.0f;
+                       break;
+               case 2:
+                       new_vel.x = move_vel;
+                       break;
+               case 3:
+                       new_vel.y = -move_vel;
+                       break;
+               case 4:
+                       new_vel.y = 0.0f;
+                       break;
+               case 5:
+                       new_vel.y = move_vel;
+                       break;
+               case 6:
+                       new_vel.z = -move_vel;
+                       break;
+               case 7:
+                       new_vel.z = 0.0f;
+                       break;
+               case 8:
+                       new_vel.z = move_vel;
+                       break;
+       }
+
+       entity.Velocity(new_vel);
+}
+
+}
diff --git a/src/app/init.cpp b/src/app/init.cpp
new file mode 100644 (file)
index 0000000..a56674c
--- /dev/null
@@ -0,0 +1,185 @@
+#include "init.hpp"
+
+#include <algorithm>
+#include <SDL.h>
+#include <SDL_image.h>
+#include <stdexcept>
+#include <string>
+#include <GL/glew.h>
+
+
+namespace {
+
+void sdl_error(std::string msg) {
+       const char *error = SDL_GetError();
+       if (*error != '\0') {
+               msg += ": ";
+               msg += error;
+               SDL_ClearError();
+       }
+       throw std::runtime_error(msg);
+}
+
+}
+
+namespace blank {
+
+InitSDL::InitSDL() {
+       if (SDL_Init(SDL_INIT_VIDEO) != 0) {
+               sdl_error("SDL_Init(SDL_INIT_VIDEO)");
+       }
+}
+
+InitSDL::~InitSDL() {
+       SDL_Quit();
+}
+
+
+InitIMG::InitIMG() {
+       if (IMG_Init(IMG_INIT_PNG) == 0) {
+               sdl_error("IMG_Init(IMG_INIT_PNG)");
+       }
+}
+
+InitIMG::~InitIMG() {
+       IMG_Quit();
+}
+
+
+InitGL::InitGL(bool double_buffer, int sample_size) {
+       if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3) != 0) {
+               sdl_error("SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3)");
+       }
+       if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3) != 0) {
+               sdl_error("SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3)");
+       }
+       if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE) != 0) {
+               sdl_error("SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE)");
+       }
+
+       if (double_buffer) {
+               if (SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1) != 0) {
+                       sdl_error("SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1)");
+               }
+       }
+
+       if (sample_size > 1) {
+               if (SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1) != 0) {
+                       sdl_error("SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS)");
+               }
+               if (SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, sample_size) != 0) {
+                       sdl_error("SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES)");
+               }
+       }
+}
+
+
+Window::Window()
+: handle(SDL_CreateWindow(
+       "blank",
+       SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
+       960, 600,
+       SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE
+)) {
+       if (!handle) {
+               sdl_error("SDL_CreateWindow");
+       }
+}
+
+Window::~Window() {
+       SDL_DestroyWindow(handle);
+}
+
+void Window::GrabInput() {
+       SDL_SetWindowGrab(handle, SDL_TRUE);
+}
+
+void Window::ReleaseInput() {
+       SDL_SetWindowGrab(handle, SDL_FALSE);
+}
+
+void Window::GrabMouse() {
+       if (SDL_SetRelativeMouseMode(SDL_TRUE) != 0) {
+               sdl_error("SDL_SetRelativeMouseMode");
+       }
+}
+
+void Window::ReleaseMouse() {
+       if (SDL_SetRelativeMouseMode(SDL_FALSE) != 0) {
+               sdl_error("SDL_SetRelativeMouseMode");
+       }
+}
+
+GLContext Window::CreateContext() {
+       return GLContext(handle);
+}
+
+void Window::Flip() {
+       SDL_GL_SwapWindow(handle);
+}
+
+
+GLContext::GLContext(SDL_Window *win)
+: handle(SDL_GL_CreateContext(win)) {
+       if (!handle) {
+               sdl_error("SDL_GL_CreateContext");
+       }
+}
+
+GLContext::~GLContext() {
+       if (handle) {
+               SDL_GL_DeleteContext(handle);
+       }
+}
+
+
+GLContext::GLContext(GLContext &&other)
+: handle(other.handle) {
+       other.handle = nullptr;
+}
+
+GLContext &GLContext::operator =(GLContext &&other) {
+       std::swap(handle, other.handle);
+       return *this;
+}
+
+void GLContext::EnableVSync() {
+       if (SDL_GL_SetSwapInterval(1) != 0) {
+               sdl_error("SDL_GL_SetSwapInterval");
+       }
+}
+
+void GLContext::EnableDepthTest() noexcept {
+       glEnable(GL_DEPTH_TEST);
+       glDepthFunc(GL_LESS);
+}
+
+void GLContext::EnableBackfaceCulling() noexcept {
+       glEnable(GL_CULL_FACE);
+}
+
+void GLContext::Clear() noexcept {
+       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+}
+
+void GLContext::ClearDepthBuffer() noexcept {
+       glClear(GL_DEPTH_BUFFER_BIT);
+}
+
+
+InitGLEW::InitGLEW() {
+       glewExperimental = GL_TRUE;
+       GLenum glew_err = glewInit();
+       if (glew_err != GLEW_OK) {
+               std::string msg("glewInit: ");
+               const GLubyte *errBegin = glewGetErrorString(glew_err);
+               const GLubyte *errEnd = errBegin;
+               while (*errEnd != '\0') {
+                       ++errEnd;
+               }
+               msg.append(errBegin, errEnd);
+               throw std::runtime_error(msg);
+       }
+}
+
+}
diff --git a/src/app/init.hpp b/src/app/init.hpp
new file mode 100644 (file)
index 0000000..98a30ed
--- /dev/null
@@ -0,0 +1,109 @@
+#ifndef BLANK_APP_INIT_HPP_
+#define BLANK_APP_INIT_HPP_
+
+#include <SDL.h>
+
+
+namespace blank {
+
+class GLContext;
+
+
+class InitSDL {
+
+public:
+       InitSDL();
+       ~InitSDL();
+
+       InitSDL(const InitSDL &) = delete;
+       InitSDL &operator =(const InitSDL &) = delete;
+
+};
+
+
+class InitIMG {
+
+public:
+       InitIMG();
+       ~InitIMG();
+
+       InitIMG(const InitIMG &) = delete;
+       InitIMG &operator =(const InitIMG &) = delete;
+
+};
+
+
+class InitGL {
+
+public:
+       explicit InitGL(bool double_buffer = true, int sample_size = 1);
+
+       InitGL(const InitGL &) = delete;
+       InitGL &operator =(const InitGL &) = delete;
+
+};
+
+
+class Window {
+
+public:
+       Window();
+       ~Window();
+
+       Window(const Window &) = delete;
+       Window &operator =(const Window &) = delete;
+
+       void GrabInput();
+       void ReleaseInput();
+
+       void GrabMouse();
+       void ReleaseMouse();
+
+       GLContext CreateContext();
+
+       void Flip();
+
+private:
+       SDL_Window *handle;
+
+};
+
+
+class GLContext {
+
+public:
+       explicit GLContext(SDL_Window *);
+       ~GLContext();
+
+       GLContext(GLContext &&);
+       GLContext &operator =(GLContext &&);
+
+       GLContext(const GLContext &) = delete;
+       GLContext &operator =(const GLContext &) = delete;
+
+       static void EnableVSync();
+       static void EnableDepthTest() noexcept;
+       static void EnableBackfaceCulling() noexcept;
+
+       static void Clear() noexcept;
+       static void ClearDepthBuffer() noexcept;
+
+private:
+       SDL_GLContext handle;
+
+};
+
+
+class InitGLEW {
+
+public:
+       InitGLEW();
+
+       InitGLEW(const InitGLEW &) = delete;
+       InitGLEW &operator =(const InitGLEW &) = delete;
+
+};
+
+}
+
+#endif
diff --git a/src/blank.cpp b/src/blank.cpp
new file mode 100644 (file)
index 0000000..d11ee9b
--- /dev/null
@@ -0,0 +1,9 @@
+#include "app/Runtime.hpp"
+
+using namespace blank;
+
+int main(int argc, char *argv[]) {
+       Runtime rt;
+       rt.ReadArgs(argc, argv);
+       return rt.Execute();
+}
diff --git a/src/block.cpp b/src/block.cpp
deleted file mode 100644 (file)
index ab090da..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-#include "block.hpp"
-
-#include "geometry.hpp"
-
-#include <glm/gtx/euler_angles.hpp>
-#include <glm/gtx/transform.hpp>
-
-
-namespace blank {
-
-const NullShape BlockType::DEFAULT_SHAPE;
-
-BlockType::BlockType(bool v, const glm::vec3 &col, const Shape *s) noexcept
-: shape(s)
-, color(col)
-, outline_color(-1, -1, -1)
-, id(0)
-, luminosity(0)
-, visible(v)
-, block_light(false)
-, fill({ false, false, false, false, false, false }) {
-
-}
-
-void BlockType::FillModel(
-       Model::Buffer &buf,
-       const glm::mat4 &transform,
-       Model::Index idx_offset
-) const noexcept {
-       shape->Vertices(buf.vertices, buf.normals, buf.indices, transform, idx_offset);
-       buf.colors.insert(buf.colors.end(), shape->VertexCount(), color);
-}
-
-void BlockType::FillBlockModel(
-       BlockModel::Buffer &buf,
-       const glm::mat4 &transform,
-       BlockModel::Index idx_offset
-) const noexcept {
-       shape->Vertices(buf.vertices, buf.indices, transform, idx_offset);
-       buf.colors.insert(buf.colors.end(), shape->VertexCount(), color);
-}
-
-void BlockType::FillOutlineModel(
-       OutlineModel &model,
-       const glm::vec3 &pos_offset,
-       OutlineModel::Index idx_offset
-) const noexcept {
-       shape->Outline(model.vertices, model.indices, pos_offset, idx_offset);
-       model.colors.insert(model.colors.end(), shape->OutlineCount(), outline_color);
-}
-
-
-BlockTypeRegistry::BlockTypeRegistry() {
-       Add(BlockType());
-}
-
-Block::Type BlockTypeRegistry::Add(const BlockType &t) {
-       int id = types.size();
-       types.push_back(t);
-       types.back().id = id;
-       return id;
-}
-
-
-const glm::mat4 Block::orient2transform[ORIENT_COUNT] = {
-       {  1,  0,  0,  0,  0,  1,  0,  0,  0,  0,  1,  0,  0,  0,  0,  1, }, // face: up,    turn: none
-       {  0,  0, -1,  0,  0,  1,  0,  0,  1,  0,  0,  0,  0,  0,  0,  1, }, // face: up,    turn: left
-       { -1,  0,  0,  0,  0,  1,  0,  0,  0,  0, -1,  0,  0,  0,  0,  1, }, // face: up,    turn: around
-       {  0,  0,  1,  0,  0,  1,  0,  0, -1,  0,  0,  0,  0,  0,  0,  1, }, // face: up,    turn: right
-       {  1,  0,  0,  0,  0, -1,  0,  0,  0,  0, -1,  0,  0,  0,  0,  1, }, // face: down,  turn: none
-       {  0,  0, -1,  0,  0, -1,  0,  0, -1,  0,  0,  0,  0,  0,  0,  1, }, // face: down,  turn: left
-       { -1,  0,  0,  0,  0, -1,  0,  0,  0,  0,  1,  0,  0,  0,  0,  1, }, // face: down,  turn: around
-       {  0,  0,  1,  0,  0, -1,  0,  0,  1,  0,  0,  0,  0,  0,  0,  1, }, // face: down,  turn: right
-       {  0, -1,  0,  0,  1,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  1, }, // face: right, turn: none
-       {  0, -1,  0,  0,  0,  0, -1,  0,  1,  0,  0,  0,  0,  0,  0,  1, }, // face: right, turn: left
-       {  0, -1,  0,  0, -1,  0,  0,  0,  0,  0, -1,  0,  0,  0,  0,  1, }, // face: right, turn: around
-       {  0, -1,  0,  0,  0,  0,  1,  0, -1,  0,  0,  0,  0,  0,  0,  1, }, // face: right, turn: right
-       {  0,  1,  0,  0, -1,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  1, }, // face: left,  turn: none
-       {  0,  1,  0,  0,  0,  0,  1,  0,  1,  0,  0,  0,  0,  0,  0,  1, }, // face: left,  turn: left
-       {  0,  1,  0,  0,  1,  0,  0,  0,  0,  0, -1,  0,  0,  0,  0,  1, }, // face: left,  turn: around
-       {  0,  1,  0,  0,  0,  0, -1,  0, -1,  0,  0,  0,  0,  0,  0,  1, }, // face: left,  turn: right
-       {  1,  0,  0,  0,  0,  0,  1,  0,  0, -1,  0,  0,  0,  0,  0,  1, }, // face: front, turn: none
-       {  0,  0, -1,  0,  1,  0,  0,  0,  0, -1,  0,  0,  0,  0,  0,  1, }, // face: front, turn: left
-       { -1,  0,  0,  0,  0,  0, -1,  0,  0, -1,  0,  0,  0,  0,  0,  1, }, // face: front, turn: around
-       {  0,  0,  1,  0, -1,  0,  0,  0,  0, -1,  0,  0,  0,  0,  0,  1, }, // face: front, turn: right
-       {  1,  0,  0,  0,  0,  0, -1,  0,  0,  1,  0,  0,  0,  0,  0,  1, }, // face: back,  turn: none
-       {  0,  0, -1,  0, -1,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  1, }, // face: back,  turn: left
-       { -1,  0,  0,  0,  0,  0,  1,  0,  0,  1,  0,  0,  0,  0,  0,  1, }, // face: back,  turn: around
-       {  0,  0,  1,  0,  1,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  1, }, // face: back,  turn: right
-};
-
-const glm::tvec3<int> Block::face2normal[FACE_COUNT] = {
-       {  0,  1,  0 },
-       {  0, -1,  0 },
-       {  1,  0,  0 },
-       { -1,  0,  0 },
-       {  0,  0,  1 },
-       {  0,  0, -1 },
-};
-
-const Block::Face Block::orient2face[ORIENT_COUNT][FACE_COUNT] = {
-       { FACE_UP,    FACE_DOWN,  FACE_RIGHT, FACE_LEFT,  FACE_FRONT, FACE_BACK,  }, // face: up,    turn: none
-       { FACE_UP,    FACE_DOWN,  FACE_FRONT, FACE_BACK,  FACE_LEFT,  FACE_RIGHT, }, // face: up,    turn: left
-       { FACE_UP,    FACE_DOWN,  FACE_LEFT,  FACE_RIGHT, FACE_BACK,  FACE_FRONT, }, // face: up,    turn: around
-       { FACE_UP,    FACE_DOWN,  FACE_BACK,  FACE_FRONT, FACE_RIGHT, FACE_LEFT,  }, // face: up,    turn: right
-       { FACE_DOWN,  FACE_UP,    FACE_RIGHT, FACE_LEFT,  FACE_BACK,  FACE_FRONT, }, // face: down,  turn: none
-       { FACE_DOWN,  FACE_UP,    FACE_BACK,  FACE_FRONT, FACE_LEFT,  FACE_RIGHT, }, // face: down,  turn: left
-       { FACE_DOWN,  FACE_UP,    FACE_LEFT,  FACE_RIGHT, FACE_FRONT, FACE_BACK,  }, // face: down,  turn: around
-       { FACE_DOWN,  FACE_UP,    FACE_FRONT, FACE_BACK,  FACE_RIGHT, FACE_LEFT,  }, // face: down,  turn: right
-       { FACE_LEFT,  FACE_RIGHT, FACE_UP,    FACE_DOWN,  FACE_FRONT, FACE_BACK,  }, // face: right, turn: none
-       { FACE_LEFT,  FACE_RIGHT, FACE_FRONT, FACE_BACK,  FACE_DOWN,  FACE_UP,    }, // face: right, turn: left
-       { FACE_LEFT,  FACE_RIGHT, FACE_DOWN,  FACE_UP,    FACE_BACK,  FACE_FRONT, }, // face: right, turn: around
-       { FACE_LEFT,  FACE_RIGHT, FACE_BACK,  FACE_FRONT, FACE_UP,    FACE_DOWN,  }, // face: right, turn: right
-       { FACE_RIGHT, FACE_LEFT,  FACE_DOWN,  FACE_UP,    FACE_FRONT, FACE_BACK,  }, // face: left,  turn: none
-       { FACE_RIGHT, FACE_LEFT,  FACE_FRONT, FACE_BACK,  FACE_UP,    FACE_DOWN,  }, // face: left,  turn: left
-       { FACE_RIGHT, FACE_LEFT,  FACE_UP,    FACE_DOWN,  FACE_BACK,  FACE_FRONT, }, // face: left,  turn: around
-       { FACE_RIGHT, FACE_LEFT,  FACE_BACK,  FACE_FRONT, FACE_DOWN,  FACE_UP,    }, // face: left,  turn: right
-       { FACE_BACK,  FACE_FRONT, FACE_RIGHT, FACE_LEFT,  FACE_UP,    FACE_DOWN,  }, // face: front, turn: none
-       { FACE_BACK,  FACE_FRONT, FACE_UP,    FACE_DOWN,  FACE_LEFT,  FACE_RIGHT, }, // face: front, turn: left
-       { FACE_BACK,  FACE_FRONT, FACE_LEFT,  FACE_RIGHT, FACE_DOWN,  FACE_UP,    }, // face: front, turn: around
-       { FACE_BACK,  FACE_FRONT, FACE_DOWN,  FACE_UP,    FACE_RIGHT, FACE_LEFT,  }, // face: front, turn: right
-       { FACE_FRONT, FACE_BACK,  FACE_RIGHT, FACE_LEFT,  FACE_DOWN,  FACE_UP,    }, // face: back,  turn: none
-       { FACE_FRONT, FACE_BACK,  FACE_DOWN,  FACE_UP,    FACE_LEFT,  FACE_RIGHT, }, // face: back,  turn: left
-       { FACE_FRONT, FACE_BACK,  FACE_LEFT,  FACE_RIGHT, FACE_UP,    FACE_DOWN,  }, // face: back,  turn: around
-       { FACE_FRONT, FACE_BACK,  FACE_UP,    FACE_DOWN,  FACE_RIGHT, FACE_LEFT,  }, // face: back,  turn: right
-};
-
-}
diff --git a/src/block.hpp b/src/block.hpp
deleted file mode 100644 (file)
index 84efa48..0000000
+++ /dev/null
@@ -1,217 +0,0 @@
-#ifndef BLANK_BLOCK_HPP_
-#define BLANK_BLOCK_HPP_
-
-#include "geometry.hpp"
-#include "model.hpp"
-#include "shape.hpp"
-
-#include <vector>
-#include <glm/glm.hpp>
-
-
-namespace blank {
-
-/// single 1x1x1 cube
-struct Block {
-
-       using Type = unsigned short;
-       using Pos = glm::vec3;
-
-       enum Face {
-               FACE_UP,
-               FACE_DOWN,
-               FACE_RIGHT,
-               FACE_LEFT,
-               FACE_FRONT,
-               FACE_BACK,
-               FACE_COUNT,
-       };
-       enum Turn {
-               TURN_NONE,
-               TURN_LEFT,
-               TURN_AROUND,
-               TURN_RIGHT,
-               TURN_COUNT,
-       };
-
-       static constexpr int ORIENT_COUNT = FACE_COUNT * TURN_COUNT;
-
-       Type type;
-       unsigned char orient;
-
-       constexpr explicit Block(Type type = 0, Face face = FACE_UP, Turn turn = TURN_NONE) noexcept
-       : type(type), orient(face * TURN_COUNT + turn) { }
-
-       const glm::mat4 &Transform() const noexcept { return orient2transform[orient]; }
-
-       Face GetFace() const noexcept { return Face(orient / TURN_COUNT); }
-       void SetFace(Face face) noexcept { orient = face * TURN_COUNT + GetTurn(); }
-       Turn GetTurn() const noexcept { return Turn(orient % TURN_COUNT); }
-       void SetTurn(Turn turn) noexcept { orient = GetFace() * TURN_COUNT + turn; }
-
-       Face OrientedFace(Face f) const noexcept { return orient2face[orient][f]; }
-
-       static Face Opposite(Face f) noexcept {
-               return Face(f ^ 1);
-       }
-
-       static int Axis(Face f) noexcept {
-               switch (f) {
-                       case FACE_UP:
-                       case FACE_DOWN:
-                               return 1;
-                       default:
-                       case FACE_RIGHT:
-                       case FACE_LEFT:
-                               return 0;
-                       case FACE_FRONT:
-                       case FACE_BACK:
-                               return 2;
-               }
-       }
-
-       static glm::tvec3<int> FaceNormal(Face face) noexcept {
-               return face2normal[face];
-       }
-
-       static Face NormalFace(const glm::vec3 &norm) noexcept {
-               const glm::vec3 anorm(abs(norm));
-               if (anorm.x > anorm.y) {
-                       if (anorm.x > anorm.z) {
-                               return norm.x > 0.0f ? FACE_RIGHT : FACE_LEFT;
-                       } else {
-                               return norm.z > 0.0f ? FACE_FRONT : FACE_BACK;
-                       }
-               } else {
-                       if (anorm.y > anorm.z) {
-                               return norm.y > 0.0f ? FACE_UP : FACE_DOWN;
-                       } else {
-                               return norm.z > 0.0f ? FACE_FRONT : FACE_BACK;
-                       }
-               }
-       }
-
-       struct FaceSet {
-
-               explicit FaceSet(unsigned char v = 0)
-               : value(v) { }
-
-               bool IsSet(Face f) const {
-                       return value & Mask(f);
-               }
-               void Set(Face f) {
-                       value |= Mask(f);
-               }
-               void Unset(Face f) {
-                       value |= ~Mask(f);
-               }
-
-               void Clear() {
-                       value = 0;
-               }
-               void Fill() {
-                       value = Mask(FACE_COUNT) - 1;
-               }
-
-               bool Empty() const {
-                       return value == 0;
-               }
-               bool All() const {
-                       return value == Mask(FACE_COUNT) - 1;
-               }
-
-               unsigned char Mask(Face f) const {
-                       return 1 << f;
-               }
-
-               unsigned char value;
-
-       };
-
-private:
-       static const glm::tvec3<int> face2normal[6];
-       static const glm::mat4 orient2transform[ORIENT_COUNT];
-       static const Face orient2face[ORIENT_COUNT][FACE_COUNT];
-
-};
-
-
-/// attributes of a type of block
-struct BlockType {
-
-       const Shape *shape;
-       glm::vec3 color;
-       glm::vec3 outline_color;
-
-       Block::Type id;
-
-       int luminosity;
-
-       bool visible;
-       bool block_light;
-
-       struct Faces {
-               bool face[Block::FACE_COUNT];
-               Faces &operator =(const Faces &other) noexcept {
-                       for (int i = 0; i < Block::FACE_COUNT; ++i) {
-                               face[i] = other.face[i];
-                       }
-                       return *this;
-               }
-               bool operator [](Block::Face f) const noexcept {
-                       return face[f];
-               }
-       } fill;
-
-       explicit BlockType(
-               bool v = false,
-               const glm::vec3 &color = { 1, 1, 1 },
-               const Shape *shape = &DEFAULT_SHAPE
-       ) noexcept;
-
-       static const NullShape DEFAULT_SHAPE;
-
-       bool FaceFilled(const Block &block, Block::Face face) const noexcept {
-               return fill[block.OrientedFace(face)];
-       }
-
-       void FillModel(
-               Model::Buffer &m,
-               const glm::mat4 &transform = glm::mat4(1.0f),
-               Model::Index idx_offset = 0
-       ) const noexcept;
-       void FillBlockModel(
-               BlockModel::Buffer &m,
-               const glm::mat4 &transform = glm::mat4(1.0f),
-               BlockModel::Index idx_offset = 0
-       ) const noexcept;
-       void FillOutlineModel(
-               OutlineModel &m,
-               const glm::vec3 &pos_offset = { 0, 0, 0 },
-               OutlineModel::Index idx_offset = 0
-       ) const noexcept;
-
-};
-
-
-class BlockTypeRegistry {
-
-public:
-       BlockTypeRegistry();
-
-public:
-       Block::Type Add(const BlockType &);
-
-       size_t Size() const noexcept { return types.size(); }
-
-       BlockType &operator [](Block::Type id) { return types[id]; }
-       const BlockType &Get(Block::Type id) const { return types[id]; }
-
-private:
-       std::vector<BlockType> types;
-
-};
-
-}
-
-#endif
diff --git a/src/camera.cpp b/src/camera.cpp
deleted file mode 100644 (file)
index f4e3171..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-#include "camera.hpp"
-
-#include "geometry.hpp"
-
-#include <GL/glew.h>
-#include <glm/gtc/matrix_transform.hpp>
-
-
-namespace blank {
-
-Camera::Camera() noexcept
-: fov(PI_0p25)
-, aspect(1.0f)
-, near_clip(0.1f)
-, far_clip(256.0f)
-, projection(glm::perspective(fov, aspect, near_clip, far_clip)) {
-
-}
-
-
-void Camera::Viewport(int width, int height) noexcept {
-       Viewport(0, 0, width, height);
-}
-
-void Camera::Viewport(int x, int y, int width, int height) noexcept {
-       glViewport(x, y, width, height);
-       Aspect(width, height);
-}
-
-void Camera::FOV(float f) noexcept {
-       fov = f;
-       UpdateProjection();
-}
-
-void Camera::Aspect(float r) noexcept {
-       aspect = r;
-       UpdateProjection();
-}
-
-void Camera::Aspect(float w, float h) noexcept {
-       Aspect(w / h);
-}
-
-void Camera::Clip(float near, float far) noexcept {
-       near_clip = near;
-       far_clip = far;
-       UpdateProjection();
-}
-
-
-void Camera::UpdateProjection() noexcept {
-       projection = glm::perspective(fov, aspect, near_clip, far_clip);
-}
-
-}
diff --git a/src/camera.hpp b/src/camera.hpp
deleted file mode 100644 (file)
index 717da24..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-#ifndef BLANK_CAMERA_HPP_
-#define BLANK_CAMERA_HPP_
-
-#include <glm/glm.hpp>
-
-
-namespace blank {
-
-class Camera {
-
-public:
-       Camera() noexcept;
-
-       void Viewport(int width, int height) noexcept;
-       void Viewport(int x, int y, int width, int height) noexcept;
-
-       /// FOV in radians
-       void FOV(float f) noexcept;
-       void Aspect(float r) noexcept;
-       void Aspect(float w, float h) noexcept;
-       void Clip(float near, float far) noexcept;
-
-       const glm::mat4 &Projection() noexcept { return projection; }
-
-private:
-       void UpdateProjection() noexcept;
-
-private:
-       float fov;
-       float aspect;
-       float near_clip;
-       float far_clip;
-
-       glm::mat4 projection;
-
-};
-
-}
-
-#endif
diff --git a/src/chunk.cpp b/src/chunk.cpp
deleted file mode 100644 (file)
index 3824a47..0000000
+++ /dev/null
@@ -1,841 +0,0 @@
-#include "chunk.hpp"
-
-#include "generator.hpp"
-
-#include <algorithm>
-#include <limits>
-#include <queue>
-
-
-namespace blank {
-
-constexpr int Chunk::width;
-constexpr int Chunk::height;
-constexpr int Chunk::depth;
-constexpr int Chunk::size;
-
-
-Chunk::Chunk(const BlockTypeRegistry &types) noexcept
-: types(&types)
-, neighbor{0}
-, blocks{}
-, light{0}
-, model()
-, position(0, 0, 0)
-, dirty(false) {
-
-}
-
-Chunk::Chunk(Chunk &&other) noexcept
-: types(other.types)
-, model(std::move(other.model))
-, position(other.position)
-, dirty(other.dirty) {
-       std::copy(other.neighbor, other.neighbor + sizeof(neighbor), neighbor);
-       std::copy(other.blocks, other.blocks + sizeof(blocks), blocks);
-       std::copy(other.light, other.light + sizeof(light), light);
-}
-
-Chunk &Chunk::operator =(Chunk &&other) noexcept {
-       types = other.types;
-       std::copy(other.neighbor, other.neighbor + sizeof(neighbor), neighbor);
-       std::copy(other.blocks, other.blocks + sizeof(blocks), blocks);
-       std::copy(other.light, other.light + sizeof(light), light);
-       model = std::move(other.model);
-       position = other.position;
-       dirty = other.dirty;
-       return *this;
-}
-
-
-namespace {
-
-struct SetNode {
-
-       Chunk *chunk;
-       Chunk::Pos pos;
-
-       SetNode(Chunk *chunk, Chunk::Pos pos)
-       : chunk(chunk), pos(pos) { }
-
-       int Get() const noexcept { return chunk->GetLight(pos); }
-       void Set(int level) noexcept { chunk->SetLight(pos, level); }
-
-       bool HasNext(Block::Face face) noexcept {
-               const BlockLookup next(chunk, pos, face);
-               return next && !next.GetType().block_light;
-       }
-       SetNode GetNext(Block::Face face) noexcept {
-               const BlockLookup next(chunk, pos, face);
-               return SetNode(&next.GetChunk(), next.GetBlockPos());
-       }
-
-};
-
-struct UnsetNode
-: public SetNode {
-
-       int level;
-
-       UnsetNode(Chunk *chunk, Chunk::Pos pos)
-       : SetNode(chunk, pos), level(Get()) { }
-
-       UnsetNode(const SetNode &set)
-       : SetNode(set), level(Get()) { }
-
-
-       bool HasNext(Block::Face face) noexcept {
-               const BlockLookup next(chunk, pos, face);
-               return next;
-       }
-       UnsetNode GetNext(Block::Face face) noexcept { return UnsetNode(SetNode::GetNext(face)); }
-
-};
-
-std::queue<SetNode> light_queue;
-std::queue<UnsetNode> dark_queue;
-
-void work_light() noexcept {
-       while (!light_queue.empty()) {
-               SetNode node = light_queue.front();
-               light_queue.pop();
-
-               int level = node.Get() - 1;
-               for (int face = 0; face < Block::FACE_COUNT; ++face) {
-                       if (node.HasNext(Block::Face(face))) {
-                               SetNode other = node.GetNext(Block::Face(face));
-                               if (other.Get() < level) {
-                                       other.Set(level);
-                                       light_queue.emplace(other);
-                               }
-                       }
-               }
-       }
-}
-
-void work_dark() noexcept {
-       while (!dark_queue.empty()) {
-               UnsetNode node = dark_queue.front();
-               dark_queue.pop();
-
-               for (int face = 0; face < Block::FACE_COUNT; ++face) {
-                       if (node.HasNext(Block::Face(face))) {
-                               UnsetNode other = node.GetNext(Block::Face(face));
-                               // TODO: if there a light source here with the same level this will err
-                               if (other.Get() != 0 && other.Get() < node.level) {
-                                       other.Set(0);
-                                       dark_queue.emplace(other);
-                               } else {
-                                       light_queue.emplace(other);
-                               }
-                       }
-               }
-       }
-}
-
-}
-
-void Chunk::SetBlock(int index, const Block &block) noexcept {
-       const BlockType &old_type = Type(blocks[index]);
-       const BlockType &new_type = Type(block);
-
-       blocks[index] = block;
-
-       if (&old_type == &new_type) return;
-
-       if (new_type.luminosity > old_type.luminosity) {
-               // light added
-               SetLight(index, new_type.luminosity);
-               light_queue.emplace(this, ToPos(index));
-               work_light();
-       } else if (new_type.luminosity < old_type.luminosity) {
-               // light removed
-               dark_queue.emplace(this, ToPos(index));
-               SetLight(index, 0);
-               work_dark();
-               SetLight(index, new_type.luminosity);
-               light_queue.emplace(this, ToPos(index));
-               work_light();
-       } else if (new_type.block_light && !old_type.block_light) {
-               // obstacle added
-               if (GetLight(index) > 0) {
-                       dark_queue.emplace(this, ToPos(index));
-                       SetLight(index, 0);
-                       work_dark();
-                       work_light();
-               }
-       } else if (!new_type.block_light && old_type.block_light) {
-               // obstacle removed
-               int level = 0;
-               for (int face = 0; face < Block::FACE_COUNT; ++face) {
-                       BlockLookup next_block(this, ToPos(index), Block::Face(face));
-                       if (next_block) {
-                               level = std::min(level, next_block.GetLight());
-                       }
-               }
-               if (level > 1) {
-                       SetLight(index, level - 1);
-                       light_queue.emplace(this, ToPos(index));
-                       work_light();
-               }
-       }
-}
-
-void Chunk::SetNeighbor(Chunk &other) noexcept {
-       if (other.position == position + Pos(-1, 0, 0)) {
-               if (neighbor[Block::FACE_LEFT] != &other) {
-                       neighbor[Block::FACE_LEFT] = &other;
-                       other.neighbor[Block::FACE_RIGHT] = this;
-                       for (int z = 0; z < depth; ++z) {
-                               for (int y = 0; y < height; ++y) {
-                                       Pos my_pos(0, y, z);
-                                       Pos other_pos(width - 1, y, z);
-                                       if (GetLight(my_pos) > 0) {
-                                               light_queue.emplace(this, my_pos);
-                                       }
-                                       if (other.GetLight(other_pos) > 0) {
-                                               light_queue.emplace(&other, other_pos);
-                                       }
-                               }
-                       }
-                       work_light();
-               }
-       } else if (other.position == position + Pos(1, 0, 0)) {
-               if (neighbor[Block::FACE_RIGHT] != &other) {
-                       neighbor[Block::FACE_RIGHT] = &other;
-                       other.neighbor[Block::FACE_LEFT] = this;
-                       for (int z = 0; z < depth; ++z) {
-                               for (int y = 0; y < height; ++y) {
-                                       Pos my_pos(width - 1, y, z);
-                                       Pos other_pos(0, y, z);
-                                       if (GetLight(my_pos) > 0) {
-                                               light_queue.emplace(this, my_pos);
-                                       }
-                                       if (other.GetLight(other_pos) > 0) {
-                                               light_queue.emplace(&other, other_pos);
-                                       }
-                               }
-                       }
-                       work_light();
-               }
-       } else if (other.position == position + Pos(0, -1, 0)) {
-               if (neighbor[Block::FACE_DOWN] != &other) {
-                       neighbor[Block::FACE_DOWN] = &other;
-                       other.neighbor[Block::FACE_UP] = this;
-                       for (int z = 0; z < depth; ++z) {
-                               for (int x = 0; x < width; ++x) {
-                                       Pos my_pos(x, 0, z);
-                                       Pos other_pos(x, height - 1, z);
-                                       if (GetLight(my_pos) > 0) {
-                                               light_queue.emplace(this, my_pos);
-                                       }
-                                       if (other.GetLight(other_pos) > 0) {
-                                               light_queue.emplace(&other, other_pos);
-                                       }
-                               }
-                       }
-                       work_light();
-               }
-       } else if (other.position == position + Pos(0, 1, 0)) {
-               if (neighbor[Block::FACE_UP] != &other) {
-                       neighbor[Block::FACE_UP] = &other;
-                       other.neighbor[Block::FACE_DOWN] = this;
-                       for (int z = 0; z < depth; ++z) {
-                               for (int x = 0; x < width; ++x) {
-                                       Pos my_pos(x, height - 1, z);
-                                       Pos other_pos(x, 0, z);
-                                       if (GetLight(my_pos) > 0) {
-                                               light_queue.emplace(this, my_pos);
-                                       }
-                                       if (other.GetLight(other_pos) > 0) {
-                                               light_queue.emplace(&other, other_pos);
-                                       }
-                               }
-                       }
-                       work_light();
-               }
-       } else if (other.position == position + Pos(0, 0, -1)) {
-               if (neighbor[Block::FACE_BACK] != &other) {
-                       neighbor[Block::FACE_BACK] = &other;
-                       other.neighbor[Block::FACE_FRONT] = this;
-                       for (int y = 0; y < height; ++y) {
-                               for (int x = 0; x < width; ++x) {
-                                       Pos my_pos(x, y, 0);
-                                       Pos other_pos(x, y, depth - 1);
-                                       if (GetLight(my_pos) > 0) {
-                                               light_queue.emplace(this, my_pos);
-                                       }
-                                       if (other.GetLight(other_pos) > 0) {
-                                               light_queue.emplace(&other, other_pos);
-                                       }
-                               }
-                       }
-                       work_light();
-               }
-       } else if (other.position == position + Pos(0, 0, 1)) {
-               if (neighbor[Block::FACE_FRONT] != &other) {
-                       neighbor[Block::FACE_FRONT] = &other;
-                       other.neighbor[Block::FACE_BACK] = this;
-                       for (int y = 0; y < height; ++y) {
-                               for (int x = 0; x < width; ++x) {
-                                       Pos my_pos(x, y, depth - 1);
-                                       Pos other_pos(x, y, 0);
-                                       if (GetLight(my_pos) > 0) {
-                                               light_queue.emplace(this, my_pos);
-                                       }
-                                       if (other.GetLight(other_pos) > 0) {
-                                               light_queue.emplace(&other, other_pos);
-                                       }
-                               }
-                       }
-                       work_light();
-               }
-       }
-}
-
-void Chunk::ClearNeighbors() noexcept {
-       for (int i = 0; i < Block::FACE_COUNT; ++i) {
-               neighbor[i] = nullptr;
-       }
-}
-
-void Chunk::Unlink() noexcept {
-       for (int face = 0; face < Block::FACE_COUNT; ++face) {
-               if (neighbor[face]) {
-                       neighbor[face]->neighbor[Block::Opposite(Block::Face(face))] = nullptr;
-               }
-       }
-}
-
-void Chunk::Relink() noexcept {
-       for (int face = 0; face < Block::FACE_COUNT; ++face) {
-               if (neighbor[face]) {
-                       neighbor[face]->neighbor[Block::Opposite(Block::Face(face))] = this;
-               }
-       }
-}
-
-
-void Chunk::SetLight(int index, int level) noexcept {
-       if (light[index] != level) {
-               light[index] = level;
-               Invalidate();
-       }
-}
-
-int Chunk::GetLight(int index) const noexcept {
-       return light[index];
-}
-
-float Chunk::GetVertexLight(const Pos &pos, const BlockModel::Position &vtx, const Model::Normal &norm) const noexcept {
-       int index = ToIndex(pos);
-       float light = GetLight(index);
-
-       Block::Face direct_face(Block::NormalFace(norm));
-       // tis okay
-       BlockLookup direct(const_cast<Chunk *>(this), pos, Block::NormalFace(norm));
-       if (direct) {
-               float direct_light = direct.GetLight();
-               if (direct_light > light) {
-                       light = direct_light;
-               }
-       } else {
-               return light;
-       }
-
-       if (Type(BlockAt(index)).luminosity > 0 || direct.GetType().block_light) {
-               return light;
-       }
-
-       Block::Face edge[2];
-       switch (Block::Axis(direct_face)) {
-               case 0: // X
-                       edge[0] = (vtx.y - pos.y) > 0.5f ? Block::FACE_UP : Block::FACE_DOWN;
-                       edge[1] = (vtx.z - pos.z) > 0.5f ? Block::FACE_FRONT : Block::FACE_BACK;
-                       break;
-               case 1: // Y
-                       edge[0] = (vtx.z - pos.z) > 0.5f ? Block::FACE_FRONT : Block::FACE_BACK;
-                       edge[1] = (vtx.x - pos.x) > 0.5f ? Block::FACE_RIGHT : Block::FACE_LEFT;
-                       break;
-               case 2: // Z
-                       edge[0] = (vtx.x - pos.x) > 0.5f ? Block::FACE_RIGHT : Block::FACE_LEFT;
-                       edge[1] = (vtx.y - pos.y) > 0.5f ? Block::FACE_UP : Block::FACE_DOWN;
-                       break;
-       }
-
-       int num = 1;
-       int occlusion = 0;
-
-       BlockLookup next[2] = {
-               direct.Next(edge[0]),
-               direct.Next(edge[1]),
-       };
-
-       if (next[0]) {
-               if (next[0].GetType().block_light) {
-                       ++occlusion;
-               } else {
-                       light += next[0].GetLight();
-                       ++num;
-               }
-       }
-       if (next[1]) {
-               if (next[1].GetType().block_light) {
-                       ++occlusion;
-               } else {
-                       light += next[1].GetLight();
-                       ++num;
-               }
-       }
-       if (occlusion < 2) {
-               if (next[0]) {
-                       BlockLookup corner = next[0].Next(edge[1]);
-                       if (corner) {
-                               if (corner.GetType().block_light) {
-                                       ++occlusion;
-                               } else {
-                                       light += corner.GetLight();
-                                       ++num;
-                               }
-                       }
-               } else if (next[1]) {
-                       BlockLookup corner = next[1].Next(edge[0]);
-                       if (corner) {
-                               if (corner.GetType().block_light) {
-                                       ++occlusion;
-                               } else {
-                                       light += corner.GetLight();
-                                       ++num;
-                               }
-                       }
-               }
-       } else {
-               ++occlusion;
-       }
-
-       return (light / num) - (occlusion * 0.8f);
-}
-
-
-bool Chunk::IsSurface(const Pos &pos) const noexcept {
-       const Block &block = BlockAt(pos);
-       if (!Type(block).visible) {
-               return false;
-       }
-       for (int face = 0; face < Block::FACE_COUNT; ++face) {
-               BlockLookup next = BlockLookup(const_cast<Chunk *>(this), pos, Block::Face(face));
-               if (!next || !next.GetType().visible) {
-                       return true;
-               }
-       }
-       return false;
-}
-
-
-void Chunk::Draw() noexcept {
-       if (dirty) {
-               Update();
-       }
-       model.Draw();
-}
-
-
-bool Chunk::Intersection(
-       const Ray &ray,
-       const glm::mat4 &M,
-       int &blkid,
-       float &dist,
-       glm::vec3 &normal
-) const noexcept {
-       // TODO: should be possible to heavily optimize this
-       int idx = 0;
-       blkid = -1;
-       dist = std::numeric_limits<float>::infinity();
-       for (int z = 0; z < depth; ++z) {
-               for (int y = 0; y < height; ++y) {
-                       for (int x = 0; x < width; ++x, ++idx) {
-                               const BlockType &type = Type(idx);
-                               if (!type.visible) {
-                                       continue;
-                               }
-                               float cur_dist;
-                               glm::vec3 cur_norm;
-                               if (type.shape->Intersects(ray, M * ToTransform(Pos(x, y, z), idx), cur_dist, cur_norm)) {
-                                       if (cur_dist < dist) {
-                                               blkid = idx;
-                                               dist = cur_dist;
-                                               normal = cur_norm;
-                                       }
-                               }
-                       }
-               }
-       }
-
-       if (blkid < 0) {
-               return false;
-       } else {
-               normal = glm::vec3(BlockAt(blkid).Transform() * glm::vec4(normal, 0.0f));
-               return true;
-       }
-}
-
-
-namespace {
-
-BlockModel::Buffer buf;
-
-}
-
-void Chunk::CheckUpdate() noexcept {
-       if (dirty) {
-               Update();
-       }
-}
-
-void Chunk::Update() noexcept {
-       int vtx_count = 0, idx_count = 0;
-       for (const auto &block : blocks) {
-               const Shape *shape = Type(block).shape;
-               vtx_count += shape->VertexCount();
-               idx_count += shape->VertexIndexCount();
-       }
-       buf.Clear();
-       buf.Reserve(vtx_count, idx_count);
-
-       int idx = 0;
-       BlockModel::Index vtx_counter = 0;
-       for (size_t z = 0; z < depth; ++z) {
-               for (size_t y = 0; y < height; ++y) {
-                       for (size_t x = 0; x < width; ++x, ++idx) {
-                               const BlockType &type = Type(BlockAt(idx));
-                               const Pos pos(x, y, z);
-
-                               if (!type.visible || Obstructed(pos).All()) continue;
-
-                               type.FillBlockModel(buf, ToTransform(pos, idx), vtx_counter);
-                               size_t vtx_begin = vtx_counter;
-                               vtx_counter += type.shape->VertexCount();
-
-                               for (size_t vtx = vtx_begin; vtx < vtx_counter; ++vtx) {
-                                       buf.lights.emplace_back(GetVertexLight(
-                                               pos,
-                                               buf.vertices[vtx],
-                                               type.shape->VertexNormal(vtx - vtx_begin, BlockAt(idx).Transform())
-                                       ));
-                               }
-                       }
-               }
-       }
-
-       model.Update(buf);
-       dirty = false;
-}
-
-Block::FaceSet Chunk::Obstructed(const Pos &pos) const noexcept {
-       Block::FaceSet result;
-
-       for (int f = 0; f < Block::FACE_COUNT; ++f) {
-               Block::Face face = Block::Face(f);
-               BlockLookup next(const_cast<Chunk *>(this), pos, face);
-               if (next && next.GetType().FaceFilled(next.GetBlock(), Block::Opposite(face))) {
-                       result.Set(face);
-               }
-       }
-
-       return result;
-}
-
-glm::mat4 Chunk::ToTransform(const Pos &pos, int idx) const noexcept {
-       return glm::translate(ToCoords(pos)) * BlockAt(idx).Transform();
-}
-
-
-BlockLookup::BlockLookup(Chunk *c, const Chunk::Pos &p) noexcept
-: chunk(c), pos(p) {
-       while (pos.x >= Chunk::width) {
-               if (chunk->HasNeighbor(Block::FACE_RIGHT)) {
-                       chunk = &chunk->GetNeighbor(Block::FACE_RIGHT);
-                       pos.x -= Chunk::width;
-               } else {
-                       chunk = nullptr;
-                       return;
-               }
-       }
-       while (pos.x < 0) {
-               if (chunk->HasNeighbor(Block::FACE_LEFT)) {
-                       chunk = &chunk->GetNeighbor(Block::FACE_LEFT);
-                       pos.x += Chunk::width;
-               } else {
-                       chunk = nullptr;
-                       return;
-               }
-       }
-       while (pos.y >= Chunk::height) {
-               if (chunk->HasNeighbor(Block::FACE_UP)) {
-                       chunk = &chunk->GetNeighbor(Block::FACE_UP);
-                       pos.y -= Chunk::height;
-               } else {
-                       chunk = nullptr;
-                       return;
-               }
-       }
-       while (pos.y < 0) {
-               if (chunk->HasNeighbor(Block::FACE_DOWN)) {
-                       chunk = &chunk->GetNeighbor(Block::FACE_DOWN);
-                       pos.y += Chunk::height;
-               } else {
-                       chunk = nullptr;
-                       return;
-               }
-       }
-       while (pos.z >= Chunk::depth) {
-               if (chunk->HasNeighbor(Block::FACE_FRONT)) {
-                       chunk = &chunk->GetNeighbor(Block::FACE_FRONT);
-                       pos.z -= Chunk::depth;
-               } else {
-                       chunk = nullptr;
-                       return;
-               }
-       }
-       while (pos.z < 0) {
-               if (chunk->HasNeighbor(Block::FACE_BACK)) {
-                       chunk = &chunk->GetNeighbor(Block::FACE_BACK);
-                       pos.z += Chunk::depth;
-               } else {
-                       chunk = nullptr;
-                       return;
-               }
-       }
-}
-
-BlockLookup::BlockLookup(Chunk *c, const Chunk::Pos &p, Block::Face face) noexcept
-: chunk(c), pos(p) {
-       pos += Block::FaceNormal(face);
-       if (!Chunk::InBounds(pos)) {
-               pos -= Block::FaceNormal(face) * Chunk::Extent();
-               chunk = &chunk->GetNeighbor(face);
-       }
-}
-
-
-ChunkLoader::ChunkLoader(const Config &config, const BlockTypeRegistry &reg, const Generator &gen) noexcept
-: base(0, 0, 0)
-, reg(reg)
-, gen(gen)
-, loaded()
-, to_generate()
-, to_free()
-, load_dist(config.load_dist)
-, unload_dist(config.unload_dist) {
-
-}
-
-namespace {
-
-struct ChunkLess {
-
-       explicit ChunkLess(const Chunk::Pos &base) noexcept
-       : base(base) { }
-
-       bool operator ()(const Chunk::Pos &a, const Chunk::Pos &b) const noexcept {
-               Chunk::Pos da(base - a);
-               Chunk::Pos db(base - b);
-               return
-                       da.x * da.x + da.y * da.y + da.z * da.z <
-                       db.x * db.x + db.y * db.y + db.z * db.z;
-       }
-
-       Chunk::Pos base;
-
-};
-
-}
-
-void ChunkLoader::Generate(const Chunk::Pos &from, const Chunk::Pos &to) {
-       for (int z = from.z; z < to.z; ++z) {
-               for (int y = from.y; y < to.y; ++y) {
-                       for (int x = from.x; x < to.x; ++x) {
-                               Chunk::Pos pos(x, y, z);
-                               if (Known(pos)) {
-                                       continue;
-                               } else if (pos == base) {
-                                       Generate(pos);
-
-                               //      light testing
-                               //      for (int i = 0; i < 16; ++i) {
-                               //              for (int j = 0; j < 16; ++j) {
-                               //                      loaded.back().SetBlock(Chunk::Pos{  i, j,  0 }, Block(1));
-                               //                      loaded.back().SetBlock(Chunk::Pos{  i, j, 15 }, Block(1));
-                               //                      loaded.back().SetBlock(Chunk::Pos{  0, j,  i }, Block(1));
-                               //                      loaded.back().SetBlock(Chunk::Pos{ 15, j,  i }, Block(1));
-                               //              }
-                               //      }
-                               //      loaded.back().SetBlock(Chunk::Pos{  1,  0,  1 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{ 14,  0,  1 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  1,  0, 14 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{ 14,  0, 14 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  1, 15,  1 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{ 14, 15,  1 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  1, 15, 14 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{ 14, 15, 14 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  7,  7,  0 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  8,  7,  0 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  7,  8,  0 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  8,  8,  0 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  7,  7, 15 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  8,  7, 15 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  7,  8, 15 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  8,  8, 15 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  0,  7,  7 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  0,  7,  8 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  0,  8,  7 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{  0,  8,  8 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{ 15,  7,  7 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{ 15,  7,  8 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{ 15,  8,  7 }, Block(13));
-                               //      loaded.back().SetBlock(Chunk::Pos{ 15,  8,  8 }, Block(13));
-                               //      loaded.back().Invalidate();
-                               //      loaded.back().CheckUpdate();
-
-                               //      orientation testing
-                               //      for (int i = 0; i < Block::FACE_COUNT; ++i) {
-                               //              for (int j = 0; j < Block::TURN_COUNT; ++j) {
-                               //                      loaded.back().BlockAt(512 * j + 2 * i) = Block(3 * (j + 1), Block::Face(i), Block::Turn(j));
-                               //              }
-                               //      }
-                               //      loaded.back().Invalidate();
-                               //      loaded.back().CheckUpdate();
-                               } else {
-                                       to_generate.emplace_back(pos);
-                               }
-                       }
-               }
-       }
-       to_generate.sort(ChunkLess(base));
-}
-
-Chunk &ChunkLoader::Generate(const Chunk::Pos &pos) {
-       loaded.emplace_back(reg);
-       Chunk &chunk = loaded.back();
-       chunk.Position(pos);
-       gen(chunk);
-       Insert(chunk);
-       return chunk;
-}
-
-void ChunkLoader::Insert(Chunk &chunk) noexcept {
-       for (Chunk &other : loaded) {
-               chunk.SetNeighbor(other);
-       }
-}
-
-void ChunkLoader::Remove(Chunk &chunk) noexcept {
-       chunk.Unlink();
-}
-
-Chunk *ChunkLoader::Loaded(const Chunk::Pos &pos) noexcept {
-       for (Chunk &chunk : loaded) {
-               if (chunk.Position() == pos) {
-                       return &chunk;
-               }
-       }
-       return nullptr;
-}
-
-bool ChunkLoader::Queued(const Chunk::Pos &pos) noexcept {
-       for (const Chunk::Pos &chunk : to_generate) {
-               if (chunk == pos) {
-                       return true;
-               }
-       }
-       return nullptr;
-}
-
-bool ChunkLoader::Known(const Chunk::Pos &pos) noexcept {
-       if (Loaded(pos)) return true;
-       return Queued(pos);
-}
-
-Chunk &ChunkLoader::ForceLoad(const Chunk::Pos &pos) {
-       Chunk *chunk = Loaded(pos);
-       if (chunk) {
-               return *chunk;
-       }
-
-       for (auto iter(to_generate.begin()), end(to_generate.end()); iter != end; ++iter) {
-               if (*iter == pos) {
-                       to_generate.erase(iter);
-                       break;
-               }
-       }
-
-       return Generate(pos);
-}
-
-void ChunkLoader::Rebase(const Chunk::Pos &new_base) {
-       if (new_base == base) {
-               return;
-       }
-       base = new_base;
-
-       // unload far away chunks
-       for (auto iter(loaded.begin()), end(loaded.end()); iter != end;) {
-               if (std::abs(base.x - iter->Position().x) > unload_dist
-                               || std::abs(base.y - iter->Position().y) > unload_dist
-                               || std::abs(base.z - iter->Position().z) > unload_dist) {
-                       auto saved = iter;
-                       Remove(*saved);
-                       ++iter;
-                       to_free.splice(to_free.end(), loaded, saved);
-               } else {
-                       ++iter;
-               }
-       }
-       // abort far away queued chunks
-       for (auto iter(to_generate.begin()), end(to_generate.end()); iter != end;) {
-               if (std::abs(base.x - iter->x) > unload_dist
-                               || std::abs(base.y - iter->y) > unload_dist
-                               || std::abs(base.z - iter->z) > unload_dist) {
-                       iter = to_generate.erase(iter);
-               } else {
-                       ++iter;
-               }
-       }
-       // add missing new chunks
-       GenerateSurrounding(base);
-}
-
-void ChunkLoader::GenerateSurrounding(const Chunk::Pos &pos) {
-       const Chunk::Pos offset(load_dist, load_dist, load_dist);
-       Generate(pos - offset, pos + offset);
-}
-
-void ChunkLoader::Update() {
-       if (to_generate.empty()) {
-               return;
-       }
-
-       Chunk::Pos pos(to_generate.front());
-       to_generate.pop_front();
-
-       for (auto iter(to_free.begin()), end(to_free.end()); iter != end; ++iter) {
-               if (iter->Position() == pos) {
-                       iter->Relink();
-                       loaded.splice(loaded.end(), to_free, iter);
-                       return;
-               }
-       }
-
-       if (to_free.empty()) {
-               loaded.emplace_back(reg);
-       } else {
-               to_free.front().ClearNeighbors();
-               loaded.splice(loaded.end(), to_free, to_free.begin());
-       }
-       Chunk &chunk = loaded.back();
-       chunk.Position(pos);
-       gen(chunk);
-       Insert(chunk);
-}
-
-}
diff --git a/src/chunk.hpp b/src/chunk.hpp
deleted file mode 100644 (file)
index d86f417..0000000
+++ /dev/null
@@ -1,236 +0,0 @@
-#ifndef BLANK_CHUNK_HPP_
-#define BLANK_CHUNK_HPP_
-
-#include "block.hpp"
-#include "geometry.hpp"
-#include "model.hpp"
-
-#include <list>
-#include <vector>
-#include <glm/glm.hpp>
-#include <glm/gtx/transform.hpp>
-
-
-namespace blank {
-
-/// cube of size 16 (256 tiles, 4096 blocks)
-class Chunk {
-
-public:
-       using Pos = glm::tvec3<int>;
-
-public:
-       explicit Chunk(const BlockTypeRegistry &) noexcept;
-
-       Chunk(Chunk &&) noexcept;
-       Chunk &operator =(Chunk &&) noexcept;
-
-       static constexpr int width = 16;
-       static constexpr int height = 16;
-       static constexpr int depth = 16;
-       static Pos Extent() noexcept { return { width, height, depth }; }
-       static constexpr int size = width * height * depth;
-
-       static AABB Bounds() noexcept { return AABB{ { 0, 0, 0 }, Extent() }; }
-
-       static constexpr bool InBounds(const Block::Pos &pos) noexcept {
-               return
-                       pos.x >= 0 && pos.x < width &&
-                       pos.y >= 0 && pos.y < height &&
-                       pos.z >= 0 && pos.z < depth;
-       }
-       static constexpr bool InBounds(const Pos &pos) noexcept {
-               return
-                       pos.x >= 0 && pos.x < width &&
-                       pos.y >= 0 && pos.y < height &&
-                       pos.z >= 0 && pos.z < depth;
-       }
-       static constexpr int ToIndex(const Pos &pos) noexcept {
-               return pos.x + pos.y * width + pos.z * width * height;
-       }
-       static constexpr bool InBounds(int idx) noexcept {
-               return idx >= 0 && idx < size;
-       }
-       static Block::Pos ToCoords(int idx) noexcept {
-               return Block::Pos(
-                       0.5f + (idx % width),
-                       0.5f + ((idx / width) % height),
-                       0.5f + (idx / (width * height))
-               );
-       }
-       static Block::Pos ToCoords(const Pos &pos) noexcept {
-               return Block::Pos(pos) + 0.5f;
-       }
-       static Pos ToPos(int idx) noexcept {
-               return Pos(
-                       (idx % width),
-                       ((idx / width) % height),
-                       (idx / (width * height))
-               );
-       }
-       glm::mat4 ToTransform(const Pos &pos, int idx) const noexcept;
-
-       static constexpr bool IsBorder(int idx) noexcept {
-               return
-                       idx < width * height ||                    // low Z plane
-                       idx % width == 0 ||                          // low X plane
-                       (idx / (width * height)) == depth - 1 || // high Z plane
-                       idx % width == width - 1 ||                // high X plane
-                       (idx / width) % height == 0 ||             // low Y plane
-                       (idx / width) % height == height - 1;    // high Y plane
-       }
-
-       bool IsSurface(int index) const noexcept { return IsSurface(ToPos(index)); }
-       bool IsSurface(const Block::Pos &pos) const noexcept { return IsSurface(Pos(pos)); }
-       bool IsSurface(const Pos &pos) const noexcept;
-
-       void SetNeighbor(Chunk &) noexcept;
-       bool HasNeighbor(Block::Face f) const noexcept { return neighbor[f]; }
-       Chunk &GetNeighbor(Block::Face f) noexcept { return *neighbor[f]; }
-       const Chunk &GetNeighbor(Block::Face f) const noexcept { return *neighbor[f]; }
-       void ClearNeighbors() noexcept;
-       void Unlink() noexcept;
-       void Relink() noexcept;
-
-       // check which faces of a block at given index are obstructed (and therefore invisible)
-       Block::FaceSet Obstructed(const Pos &) const noexcept;
-
-       void Invalidate() noexcept { dirty = true; }
-
-       void SetBlock(int index, const Block &) noexcept;
-       void SetBlock(const Block::Pos &pos, const Block &block) noexcept { SetBlock(ToIndex(pos), block); }
-       void SetBlock(const Pos &pos, const Block &block) noexcept { SetBlock(ToIndex(pos), block); }
-
-       const Block &BlockAt(int index) const noexcept { return blocks[index]; }
-       const Block &BlockAt(const Block::Pos &pos) const noexcept { return BlockAt(ToIndex(pos)); }
-       const Block &BlockAt(const Pos &pos) const noexcept { return BlockAt(ToIndex(pos)); }
-
-       const BlockType &Type(const Block &b) const noexcept { return types->Get(b.type); }
-       const BlockType &Type(int index) const noexcept { return Type(BlockAt(index)); }
-
-       void SetLight(int index, int level) noexcept;
-       void SetLight(const Pos &pos, int level) noexcept { SetLight(ToIndex(pos), level); }
-       void SetLight(const Block::Pos &pos, int level) noexcept { SetLight(ToIndex(pos), level); }
-
-       int GetLight(int index) const noexcept;
-       int GetLight(const Pos &pos) const noexcept { return GetLight(ToIndex(pos)); }
-       int GetLight(const Block::Pos &pos) const noexcept { return GetLight(ToIndex(pos)); }
-
-       float GetVertexLight(const Pos &, const BlockModel::Position &, const Model::Normal &) const noexcept;
-
-       bool Intersection(
-               const Ray &ray,
-               const glm::mat4 &M,
-               float &dist
-       ) const noexcept {
-               return blank::Intersection(ray, Bounds(), M, &dist);
-       }
-
-       bool Intersection(
-               const Ray &,
-               const glm::mat4 &M,
-               int &blkid,
-               float &dist,
-               glm::vec3 &normal) const noexcept;
-
-       void Position(const Pos &pos) noexcept { position = pos; }
-       const Pos &Position() const noexcept { return position; }
-       glm::mat4 Transform(const Pos &offset) const noexcept {
-               return glm::translate((position - offset) * Extent());
-       }
-
-       void CheckUpdate() noexcept;
-       void Draw() noexcept;
-
-private:
-       void Update() noexcept;
-
-private:
-       const BlockTypeRegistry *types;
-       Chunk *neighbor[Block::FACE_COUNT];
-       Block blocks[16 * 16 * 16];
-       unsigned char light[16 * 16 * 16];
-       BlockModel model;
-       Pos position;
-       bool dirty;
-
-};
-
-
-class BlockLookup {
-
-public:
-       // resolve chunk/position from oob coordinates
-       BlockLookup(Chunk *c, const Chunk::Pos &p) noexcept;
-
-       // resolve chunk/position from ib coordinates and direction
-       BlockLookup(Chunk *c, const Chunk::Pos &p, Block::Face dir) noexcept;
-
-       // check if lookup was successful
-       operator bool() const { return chunk; }
-
-       // only valid if lookup was successful
-       Chunk &GetChunk() const noexcept { return *chunk; }
-       const Chunk::Pos &GetBlockPos() const noexcept { return pos; }
-       const Block &GetBlock() const noexcept { return GetChunk().BlockAt(GetBlockPos()); }
-       const BlockType &GetType() const noexcept { return GetChunk().Type(GetBlock()); }
-       int GetLight() const noexcept { return GetChunk().GetLight(GetBlockPos()); }
-
-       // traverse in given direction
-       BlockLookup Next(Block::Face f) const { return BlockLookup(chunk, pos, f); }
-
-private:
-       Chunk *chunk;
-       Chunk::Pos pos;
-
-};
-
-
-class Generator;
-
-class ChunkLoader {
-
-public:
-       struct Config {
-               int load_dist = 6;
-               int unload_dist = 8;
-       };
-
-       ChunkLoader(const Config &, const BlockTypeRegistry &, const Generator &) noexcept;
-
-       void Generate(const Chunk::Pos &from, const Chunk::Pos &to);
-       void GenerateSurrounding(const Chunk::Pos &);
-
-       std::list<Chunk> &Loaded() noexcept { return loaded; }
-
-       Chunk *Loaded(const Chunk::Pos &) noexcept;
-       bool Queued(const Chunk::Pos &) noexcept;
-       bool Known(const Chunk::Pos &) noexcept;
-       Chunk &ForceLoad(const Chunk::Pos &);
-
-       void Rebase(const Chunk::Pos &);
-       void Update();
-
-private:
-       Chunk &Generate(const Chunk::Pos &pos);
-       void Insert(Chunk &) noexcept;
-       void Remove(Chunk &) noexcept;
-
-private:
-       Chunk::Pos base;
-
-       const BlockTypeRegistry &reg;
-       const Generator &gen;
-
-       std::list<Chunk> loaded;
-       std::list<Chunk::Pos> to_generate;
-       std::list<Chunk> to_free;
-
-       int load_dist;
-       int unload_dist;
-
-};
-
-}
-
-#endif
diff --git a/src/controller.cpp b/src/controller.cpp
deleted file mode 100644 (file)
index e86551f..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-#include "controller.hpp"
-
-#include <glm/gtx/euler_angles.hpp>
-#include <glm/gtx/rotate_vector.hpp>
-
-
-namespace blank {
-
-FPSController::FPSController(Entity &entity) noexcept
-: entity(entity)
-, pitch(0)
-, yaw(0) {
-
-}
-
-
-void FPSController::Pitch(float p) noexcept {
-       pitch = p;
-       if (pitch > PI / 2) {
-               pitch = PI / 2;
-       } else if (pitch < -PI / 2) {
-               pitch = -PI / 2;
-       }
-}
-
-void FPSController::RotatePitch(float delta) noexcept {
-       Pitch(pitch + delta);
-}
-
-void FPSController::Yaw(float y) noexcept {
-       yaw = y;
-       if (yaw > PI) {
-               yaw -= PI * 2;
-       } else if (yaw < -PI) {
-               yaw += PI * 2;
-       }
-}
-
-void FPSController::RotateYaw(float delta) noexcept {
-       Yaw(yaw + delta);
-}
-
-
-void FPSController::Update(int dt) noexcept {
-       entity.Rotation(glm::eulerAngleYX(yaw, pitch));
-       entity.Velocity(glm::rotateY(velocity, yaw));
-}
-
-
-RandomWalk::RandomWalk(Entity &e) noexcept
-: entity(e)
-, time_left(0) {
-
-}
-
-
-void RandomWalk::Update(int dt) noexcept {
-       time_left -= dt;
-       if (time_left > 0) return;
-       time_left += 2500 + (rand() % 5000);
-
-       constexpr float move_vel = 0.0005f;
-
-       glm::vec3 new_vel = entity.Velocity();
-
-       switch (rand() % 9) {
-               case 0:
-                       new_vel.x = -move_vel;
-                       break;
-               case 1:
-                       new_vel.x = 0.0f;
-                       break;
-               case 2:
-                       new_vel.x = move_vel;
-                       break;
-               case 3:
-                       new_vel.y = -move_vel;
-                       break;
-               case 4:
-                       new_vel.y = 0.0f;
-                       break;
-               case 5:
-                       new_vel.y = move_vel;
-                       break;
-               case 6:
-                       new_vel.z = -move_vel;
-                       break;
-               case 7:
-                       new_vel.z = 0.0f;
-                       break;
-               case 8:
-                       new_vel.z = move_vel;
-                       break;
-       }
-
-       entity.Velocity(new_vel);
-}
-
-}
diff --git a/src/controller.hpp b/src/controller.hpp
deleted file mode 100644 (file)
index f674e23..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-#ifndef BLANK_CONTROLLER_HPP_
-#define BLANK_CONTROLLER_HPP_
-
-#include "entity.hpp"
-#include "geometry.hpp"
-
-#include <glm/glm.hpp>
-
-
-namespace blank {
-
-class FPSController {
-
-public:
-       explicit FPSController(Entity &) noexcept;
-
-       Ray Aim() const noexcept { return entity.Aim(entity.ChunkCoords()); }
-
-       const glm::vec3 &Velocity() const noexcept { return velocity; }
-       void Velocity(const glm::vec3 &vel) noexcept { velocity = vel; }
-
-       // all angles in radians (full circle = 2Ï€)
-       float Pitch() const noexcept { return pitch; }
-       void Pitch(float p) noexcept;
-       void RotatePitch(float delta) noexcept;
-       float Yaw() const noexcept { return yaw; }
-       void Yaw(float y) noexcept;
-       void RotateYaw(float delta) noexcept;
-
-       void Update(int dt) noexcept;
-
-private:
-       Entity &entity;
-
-       glm::vec3 velocity;
-
-       float pitch;
-       float yaw;
-
-};
-
-
-class RandomWalk {
-
-public:
-       explicit RandomWalk(Entity &) noexcept;
-
-       void Update(int dt) noexcept;
-
-private:
-       Entity &entity;
-
-       int time_left;
-
-};
-
-}
-
-#endif
diff --git a/src/entity.cpp b/src/entity.cpp
deleted file mode 100644 (file)
index 275c846..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-#include "entity.hpp"
-
-#include "geometry.hpp"
-#include "shape.hpp"
-
-#include <cmath>
-#include <glm/gtx/transform.hpp>
-
-namespace {
-
-blank::Model::Buffer model_buffer;
-
-}
-
-namespace blank {
-
-Entity::Entity() noexcept
-: shape(nullptr)
-, model()
-, velocity(0, 0, 0)
-, position(0, 0, 0)
-, chunk(0, 0, 0)
-, angular_velocity(1.0f, 0.0f, 0.0f, 0.0f)
-, rotation(1.0f) {
-
-}
-
-
-void Entity::SetShape(const Shape *s, const glm::vec3 &color) {
-       shape = s;
-       model_buffer.Clear();
-       shape->Vertices(model_buffer.vertices, model_buffer.normals, model_buffer.indices);
-       model_buffer.colors.resize(shape->VertexCount(), color);
-       model.Update(model_buffer);
-}
-
-void Entity::SetShapeless() noexcept {
-       shape = nullptr;
-}
-
-
-void Entity::Velocity(const glm::vec3 &vel) noexcept {
-       velocity = vel;
-}
-
-void Entity::Position(const Block::Pos &pos) noexcept {
-       position = pos;
-       while (position.x >= Chunk::width) {
-               position.x -= Chunk::width;
-               ++chunk.x;
-       }
-       while (position.x < 0) {
-               position.x += Chunk::width;
-               --chunk.x;
-       }
-       while (position.y >= Chunk::height) {
-               position.y -= Chunk::height;
-               ++chunk.y;
-       }
-       while (position.y < 0) {
-               position.y += Chunk::height;
-               --chunk.y;
-       }
-       while (position.z >= Chunk::depth) {
-               position.z -= Chunk::depth;
-               ++chunk.z;
-       }
-       while (position.z < 0) {
-               position.z += Chunk::depth;
-               --chunk.z;
-       }
-}
-
-void Entity::Move(const glm::vec3 &delta) noexcept {
-       Position(position + delta);
-}
-
-void Entity::AngularVelocity(const glm::quat &v) noexcept {
-       angular_velocity = v;
-}
-
-void Entity::Rotation(const glm::mat4 &rot) noexcept {
-       rotation = rot;
-}
-
-void Entity::Rotate(const glm::quat &delta) noexcept {
-       Rotation(rotation * glm::mat4_cast(delta));
-}
-
-glm::mat4 Entity::Transform(const Chunk::Pos &chunk_offset) const noexcept {
-       const glm::vec3 chunk_pos = (chunk - chunk_offset) * Chunk::Extent();
-       return glm::translate(position + chunk_pos) * rotation;
-}
-
-Ray Entity::Aim(const Chunk::Pos &chunk_offset) const noexcept {
-       glm::mat4 transform = Transform(chunk_offset);
-       glm::vec4 from = transform * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
-       from /= from.w;
-       glm::vec4 to = transform * glm::vec4(0.0f, 0.0f, -1.0f, 1.0f);
-       to /= to.w;
-       return Ray{ glm::vec3(from), glm::normalize(glm::vec3(to - from)) };
-}
-
-void Entity::Update(int dt) noexcept {
-       Move(velocity * float(dt));
-       Rotate(angular_velocity * float(dt));
-}
-
-
-void Entity::Draw() noexcept {
-       model.Draw();
-}
-
-}
diff --git a/src/entity.hpp b/src/entity.hpp
deleted file mode 100644 (file)
index 20c7630..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-#ifndef BLANK_ENTITY_HPP_
-#define BLANK_ENTITY_HPP_
-
-#include "block.hpp"
-#include "chunk.hpp"
-#include "model.hpp"
-
-#include <glm/glm.hpp>
-#include <glm/gtc/quaternion.hpp>
-
-
-namespace blank {
-
-class Ray;
-class Shape;
-
-class Entity {
-
-public:
-       Entity() noexcept;
-
-       bool HasShape() const noexcept { return shape; }
-       const Shape *GetShape() const noexcept { return shape; }
-       void SetShape(const Shape *, const glm::vec3 &color);
-       void SetShapeless() noexcept;
-
-       const glm::vec3 &Velocity() const noexcept { return velocity; }
-       void Velocity(const glm::vec3 &) noexcept;
-
-       const Block::Pos &Position() const noexcept { return position; }
-       void Position(const Block::Pos &) noexcept;
-       void Move(const glm::vec3 &delta) noexcept;
-
-       const Chunk::Pos ChunkCoords() const noexcept { return chunk; }
-
-       const glm::quat &AngularVelocity() const noexcept { return angular_velocity; }
-       void AngularVelocity(const glm::quat &) noexcept;
-
-       const glm::mat4 &Rotation() const noexcept { return rotation; }
-       void Rotation(const glm::mat4 &) noexcept;
-       void Rotate(const glm::quat &delta) noexcept;
-
-       glm::mat4 Transform(const Chunk::Pos &chunk_offset) const noexcept;
-       Ray Aim(const Chunk::Pos &chunk_offset) const noexcept;
-
-       void Update(int dt) noexcept;
-
-       void Draw() noexcept;
-
-private:
-       const Shape *shape;
-       Model model;
-
-       glm::vec3 velocity;
-       Block::Pos position;
-       Chunk::Pos chunk;
-
-       glm::quat angular_velocity;
-       glm::mat4 rotation;
-
-};
-
-}
-
-#endif
diff --git a/src/generator.cpp b/src/generator.cpp
deleted file mode 100644 (file)
index 3d61b75..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-#include "generator.hpp"
-
-#include <glm/glm.hpp>
-
-
-namespace blank {
-
-Generator::Generator(const Config &config) noexcept
-: solidNoise(config.solid_seed)
-, typeNoise(config.type_seed)
-, stretch(1.0f/config.stretch)
-, solid_threshold(config.solid_threshold)
-, space(0)
-, light(0)
-, solids() {
-
-}
-
-
-void Generator::operator ()(Chunk &chunk) const noexcept {
-       Chunk::Pos pos(chunk.Position());
-       glm::vec3 coords(pos * Chunk::Extent());
-       for (int z = 0; z < Chunk::depth; ++z) {
-               for (int y = 0; y < Chunk::height; ++y) {
-                       for (int x = 0; x < Chunk::width; ++x) {
-                               Block::Pos block_pos(x, y, z);
-                               glm::vec3 gen_pos = (coords + block_pos) * stretch;
-                               float val = OctaveNoise(solidNoise, coords + block_pos, 3, 0.5f, stretch, 2.0f);
-                               if (val > solid_threshold) {
-                                       int type_val = int((typeNoise(gen_pos) + 1.0f) * solids.size()) % solids.size();
-                                       chunk.SetBlock(block_pos, Block(solids[type_val]));
-                               } else {
-                                       chunk.SetBlock(block_pos, Block(space));
-                               }
-                       }
-               }
-       }
-       unsigned int random = 263167 * pos.x + 2097593 * pos.y + 426389 * pos.z;
-       for (int index = 0; index < Chunk::size; ++index) {
-               if (chunk.IsSurface(index)) {
-                       random = random * 666649 + 7778777;
-                       if ((random % 32) == 0) {
-                               chunk.SetBlock(index, Block(light));
-                       }
-               }
-       }
-       chunk.Invalidate();
-       chunk.CheckUpdate();
-}
-
-}
diff --git a/src/generator.hpp b/src/generator.hpp
deleted file mode 100644 (file)
index dedfb72..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-#ifndef BLANK_GENERATOR_HPP_
-#define BLANK_GENERATOR_HPP_
-
-#include "block.hpp"
-#include "chunk.hpp"
-#include "noise.hpp"
-
-#include <vector>
-
-
-namespace blank {
-
-class Generator {
-
-public:
-       struct Config {
-               unsigned int solid_seed = 0;
-               unsigned int type_seed = 0;
-               float stretch = 64.0f;
-               float solid_threshold = 0.5f;
-       };
-
-       explicit Generator(const Config &) noexcept;
-
-       void operator ()(Chunk &) const noexcept;
-
-       void Space(Block::Type t) noexcept { space = t; }
-       void Light(Block::Type t) noexcept { light = t; }
-       void Solids(const std::vector<Block::Type> &s) { solids = s; }
-
-private:
-       SimplexNoise solidNoise;
-       WorleyNoise typeNoise;
-
-       float stretch;
-       float solid_threshold;
-
-       Block::Type space;
-       Block::Type light;
-       std::vector<Block::Type> solids;
-
-};
-
-}
-
-#endif
diff --git a/src/geometry.cpp b/src/geometry.cpp
deleted file mode 100644 (file)
index 416d930..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-#include "geometry.hpp"
-
-#include <limits>
-
-
-namespace blank {
-
-bool Intersection(
-       const Ray &ray,
-       const AABB &aabb,
-       const glm::mat4 &M,
-       float *dist,
-       glm::vec3 *normal
-) noexcept {
-       float t_min = 0.0f;
-       float t_max = std::numeric_limits<float>::infinity();
-       const glm::vec3 aabb_pos(M[3].x, M[3].y, M[3].z);
-       const glm::vec3 delta = aabb_pos - ray.orig;
-
-       glm::vec3 t1(t_min, t_min, t_min), t2(t_max, t_max, t_max);
-
-       for (int i = 0; i < 3; ++i) {
-               const glm::vec3 axis(M[i].x, M[i].y, M[i].z);
-               const float e = glm::dot(axis, delta);
-               const float f = glm::dot(axis, ray.dir);
-
-               if (std::abs(f) > std::numeric_limits<float>::epsilon()) {
-                       t1[i] = (e + aabb.min[i]) / f;
-                       t2[i] = (e + aabb.max[i]) / f;
-
-                       t_min = std::max(t_min, std::min(t1[i], t2[i]));
-                       t_max = std::min(t_max, std::max(t1[i], t2[i]));
-
-                       if (t_max < t_min) {
-                               return false;
-                       }
-               } else {
-                       if (aabb.min[i] - e < 0.0f || -aabb.max[i] - e > 0.0f) {
-                               return false;
-                       }
-               }
-       }
-
-       glm::vec3 min_all(min(t1, t2));
-
-       if (dist) {
-               *dist = t_min;
-       }
-       if (normal) {
-               glm::vec4 norm(0.0f);
-               if (min_all.x > min_all.y) {
-                       if (min_all.x > min_all.z) {
-                               norm.x = t2.x < t1.x ? 1 : -1;
-                       } else {
-                               norm.z = t2.z < t1.z ? 1 : -1;
-                       }
-               } else if (min_all.y > min_all.z) {
-                       norm.y = t2.y < t1.y ? 1 : -1;
-               } else {
-                       norm.z = t2.z < t1.z ? 1 : -1;
-               }
-               norm = M * norm;
-               *normal = glm::vec3(norm);
-       }
-       return true;
-}
-
-bool CullTest(const AABB &box, const glm::mat4 &MVP) noexcept {
-       // transform corners into clip space
-       glm::vec4 corners[8] = {
-               { box.min.x, box.min.y, box.min.z, 1.0f },
-               { box.min.x, box.min.y, box.max.z, 1.0f },
-               { box.min.x, box.max.y, box.min.z, 1.0f },
-               { box.min.x, box.max.y, box.max.z, 1.0f },
-               { box.max.x, box.min.y, box.min.z, 1.0f },
-               { box.max.x, box.min.y, box.max.z, 1.0f },
-               { box.max.x, box.max.y, box.min.z, 1.0f },
-               { box.max.x, box.max.y, box.max.z, 1.0f },
-       };
-       for (glm::vec4 &corner : corners) {
-               corner = MVP * corner;
-               corner /= corner.w;
-       }
-
-       int hits[6] = { 0, 0, 0, 0, 0, 0 };
-
-       // check how many corners lie outside
-       for (const glm::vec4 &corner : corners) {
-               if (corner.x >  1.0f) ++hits[0];
-               if (corner.x < -1.0f) ++hits[1];
-               if (corner.y >  1.0f) ++hits[2];
-               if (corner.y < -1.0f) ++hits[3];
-               if (corner.z >  1.0f) ++hits[4];
-               if (corner.z < -1.0f) ++hits[5];
-       }
-
-       // if all corners are outside any given clip plane, the test is true
-       for (int hit : hits) {
-               if (hit == 8) return true;
-       }
-
-       // otherwise the box might still get culled completely, but can't say for sure ;)
-       return false;
-}
-
-}
diff --git a/src/geometry.hpp b/src/geometry.hpp
deleted file mode 100644 (file)
index df75f22..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-#ifndef BLANK_GEOMETRY_H_
-#define BLANK_GEOMETRY_H_
-
-#include <algorithm>
-#include <glm/glm.hpp>
-
-
-namespace blank {
-
-constexpr float PI = 3.141592653589793238462643383279502884;
-constexpr float PI_0p25 = PI * 0.25f;
-constexpr float PI_0p5 = PI * 0.5f;
-constexpr float PI_1p5 = PI * 1.5f;
-constexpr float PI_2p0 = PI * 2.0f;
-
-struct AABB {
-       glm::vec3 min;
-       glm::vec3 max;
-
-       void Adjust() noexcept {
-               if (max.x < min.x) std::swap(max.x, min.x);
-               if (max.y < min.y) std::swap(max.y, min.y);
-               if (max.z < min.z) std::swap(max.z, min.z);
-       }
-};
-
-struct Ray {
-       glm::vec3 orig;
-       glm::vec3 dir;
-};
-
-bool Intersection(
-       const Ray &,
-       const AABB &,
-       const glm::mat4 &M,
-       float *dist = nullptr,
-       glm::vec3 *normal = nullptr) noexcept;
-
-bool CullTest(const AABB &box, const glm::mat4 &MVP) noexcept;
-
-}
-
-#endif
diff --git a/src/graphics/BlockLighting.hpp b/src/graphics/BlockLighting.hpp
new file mode 100644 (file)
index 0000000..8f7f01d
--- /dev/null
@@ -0,0 +1,50 @@
+#ifndef BLANK_GRAPHICS_BLOCKLIGHTING_HPP_
+#define BLANK_GRAPHICS_BLOCKLIGHTING_HPP_
+
+#include "Program.hpp"
+
+#include <GL/glew.h>
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class BlockLighting {
+
+public:
+       BlockLighting();
+
+       void Activate() noexcept;
+
+       void SetFogDensity(float) noexcept;
+
+       void SetM(const glm::mat4 &m) noexcept;
+       void SetProjection(const glm::mat4 &p) noexcept;
+       void SetView(const glm::mat4 &v) noexcept;
+       void SetVP(const glm::mat4 &v, const glm::mat4 &p) noexcept;
+       void SetMVP(const glm::mat4 &m, const glm::mat4 &v, const glm::mat4 &p) noexcept;
+
+       const glm::mat4 &Projection() const noexcept { return projection; }
+       const glm::mat4 &View() const noexcept { return view; }
+       const glm::mat4 &GetVP() const noexcept { return vp; }
+
+private:
+       Program program;
+
+       float fog_density;
+
+       glm::mat4 projection;
+       glm::mat4 view;
+       glm::mat4 vp;
+
+       GLuint mv_handle;
+       GLuint mvp_handle;
+       GLuint light_direction_handle;
+       GLuint light_color_handle;
+       GLuint fog_density_handle;
+
+};
+
+}
+
+#endif
diff --git a/src/graphics/Camera.cpp b/src/graphics/Camera.cpp
new file mode 100644 (file)
index 0000000..5f61d26
--- /dev/null
@@ -0,0 +1,55 @@
+#include "Camera.hpp"
+
+#include "../model/geometry.hpp"
+
+#include <GL/glew.h>
+#include <glm/gtc/matrix_transform.hpp>
+
+
+namespace blank {
+
+Camera::Camera() noexcept
+: fov(PI_0p25)
+, aspect(1.0f)
+, near_clip(0.1f)
+, far_clip(256.0f)
+, projection(glm::perspective(fov, aspect, near_clip, far_clip)) {
+
+}
+
+
+void Camera::Viewport(int width, int height) noexcept {
+       Viewport(0, 0, width, height);
+}
+
+void Camera::Viewport(int x, int y, int width, int height) noexcept {
+       glViewport(x, y, width, height);
+       Aspect(width, height);
+}
+
+void Camera::FOV(float f) noexcept {
+       fov = f;
+       UpdateProjection();
+}
+
+void Camera::Aspect(float r) noexcept {
+       aspect = r;
+       UpdateProjection();
+}
+
+void Camera::Aspect(float w, float h) noexcept {
+       Aspect(w / h);
+}
+
+void Camera::Clip(float near, float far) noexcept {
+       near_clip = near;
+       far_clip = far;
+       UpdateProjection();
+}
+
+
+void Camera::UpdateProjection() noexcept {
+       projection = glm::perspective(fov, aspect, near_clip, far_clip);
+}
+
+}
diff --git a/src/graphics/Camera.hpp b/src/graphics/Camera.hpp
new file mode 100644 (file)
index 0000000..2fabfc6
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef BLANK_GRAPHICS_CAMERA_HPP_
+#define BLANK_GRAPHICS_CAMERA_HPP_
+
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class Camera {
+
+public:
+       Camera() noexcept;
+
+       void Viewport(int width, int height) noexcept;
+       void Viewport(int x, int y, int width, int height) noexcept;
+
+       /// FOV in radians
+       void FOV(float f) noexcept;
+       void Aspect(float r) noexcept;
+       void Aspect(float w, float h) noexcept;
+       void Clip(float near, float far) noexcept;
+
+       const glm::mat4 &Projection() noexcept { return projection; }
+
+private:
+       void UpdateProjection() noexcept;
+
+private:
+       float fov;
+       float aspect;
+       float near_clip;
+       float far_clip;
+
+       glm::mat4 projection;
+
+};
+
+}
+
+#endif
diff --git a/src/graphics/DirectionalLighting.hpp b/src/graphics/DirectionalLighting.hpp
new file mode 100644 (file)
index 0000000..df89053
--- /dev/null
@@ -0,0 +1,56 @@
+#ifndef BLANK_GRAPHICS_DIRECTIONALLIGHTING_HPP_
+#define BLANK_GRAPHICS_DIRECTIONALLIGHTING_HPP_
+
+#include "Program.hpp"
+
+#include <GL/glew.h>
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class DirectionalLighting {
+
+public:
+       DirectionalLighting();
+
+       void Activate() noexcept;
+
+       void SetLightDirection(const glm::vec3 &) noexcept;
+
+       void SetFogDensity(float) noexcept;
+
+       void SetM(const glm::mat4 &m) noexcept;
+       void SetProjection(const glm::mat4 &p) noexcept;
+       void SetView(const glm::mat4 &v) noexcept;
+       void SetVP(const glm::mat4 &v, const glm::mat4 &p) noexcept;
+       void SetMVP(const glm::mat4 &m, const glm::mat4 &v, const glm::mat4 &p) noexcept;
+
+       const glm::mat4 &Projection() const noexcept { return projection; }
+       const glm::mat4 &View() const noexcept { return view; }
+       const glm::mat4 &GetVP() const noexcept { return vp; }
+
+private:
+       Program program;
+
+       glm::vec3 light_direction;
+       glm::vec3 light_color;
+
+       float fog_density;
+
+       glm::mat4 projection;
+       glm::mat4 view;
+       glm::mat4 vp;
+
+       GLuint m_handle;
+       GLuint mv_handle;
+       GLuint mvp_handle;
+       GLuint light_direction_handle;
+       GLuint light_color_handle;
+       GLuint fog_density_handle;
+
+};
+
+}
+
+#endif
diff --git a/src/graphics/Program.hpp b/src/graphics/Program.hpp
new file mode 100644 (file)
index 0000000..a67a0b3
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef BLANK_GRAPHICS_PROGRAM_HPP_
+#define BLANK_GRAPHICS_PROGRAM_HPP_
+
+#include <iosfwd>
+#include <list>
+#include <GL/glew.h>
+
+
+namespace blank {
+
+class Shader;
+
+class Program {
+
+public:
+       Program();
+       ~Program();
+
+       Program(const Program &) = delete;
+       Program &operator =(const Program &) = delete;
+
+       const Shader &LoadShader(GLenum type, const GLchar *src);
+       void Attach(Shader &) noexcept;
+       void Link() noexcept;
+       bool Linked() const noexcept;
+       void Log(std::ostream &) const;
+
+       GLint AttributeLocation(const GLchar *name) const noexcept;
+       GLint UniformLocation(const GLchar *name) const noexcept;
+
+       void Use() const noexcept { glUseProgram(handle); }
+
+private:
+       GLuint handle;
+       std::list<Shader> shaders;
+
+};
+
+}
+
+#endif
diff --git a/src/graphics/Shader.hpp b/src/graphics/Shader.hpp
new file mode 100644 (file)
index 0000000..2fab0c7
--- /dev/null
@@ -0,0 +1,36 @@
+#ifndef BLANK_GRAPHICS_SHADER_HPP_
+#define BLANK_GRAPHICS_SHADER_HPP_
+
+#include <iosfwd>
+#include <GL/glew.h>
+
+
+namespace blank {
+
+class Shader {
+
+public:
+       explicit Shader(GLenum type);
+       ~Shader();
+
+       Shader(Shader &&) noexcept;
+       Shader &operator =(Shader &&) noexcept;
+
+       Shader(const Shader &) = delete;
+       Shader &operator =(const Shader &) = delete;
+
+       void Source(const GLchar *src) noexcept;
+       void Compile() noexcept;
+       bool Compiled() const noexcept;
+       void Log(std::ostream &) const;
+
+       void AttachToProgram(GLuint id) const noexcept;
+
+private:
+       GLuint handle;
+
+};
+
+}
+
+#endif
diff --git a/src/graphics/shader.cpp b/src/graphics/shader.cpp
new file mode 100644 (file)
index 0000000..45634a9
--- /dev/null
@@ -0,0 +1,358 @@
+#include "BlockLighting.hpp"
+#include "DirectionalLighting.hpp"
+#include "Program.hpp"
+#include "Shader.hpp"
+
+#include "../app/init.hpp"
+
+#include <algorithm>
+#include <iostream>
+#include <memory>
+#include <ostream>
+#include <stdexcept>
+#include <string>
+
+
+namespace {
+
+void gl_error(std::string msg) {
+       const GLubyte *errBegin = gluErrorString(glGetError());
+       if (errBegin && *errBegin != '\0') {
+               const GLubyte *errEnd = errBegin;
+               while (*errEnd != '\0') {
+                       ++errEnd;
+               }
+               msg += ": ";
+               msg.append(errBegin, errEnd);
+       }
+       throw std::runtime_error(msg);
+}
+
+}
+
+namespace blank {
+
+Shader::Shader(GLenum type)
+: handle(glCreateShader(type)) {
+       if (handle == 0) {
+               gl_error("glCreateShader");
+       }
+}
+
+Shader::~Shader() {
+       if (handle != 0) {
+               glDeleteShader(handle);
+       }
+}
+
+Shader::Shader(Shader &&other) noexcept
+: handle(other.handle) {
+       other.handle = 0;
+}
+
+Shader &Shader::operator =(Shader &&other) noexcept {
+       std::swap(handle, other.handle);
+       return *this;
+}
+
+
+void Shader::Source(const GLchar *src) noexcept {
+       const GLchar* src_arr[] = { src };
+       glShaderSource(handle, 1, src_arr, nullptr);
+}
+
+void Shader::Compile() noexcept {
+       glCompileShader(handle);
+}
+
+bool Shader::Compiled() const noexcept {
+       GLint compiled = GL_FALSE;
+       glGetShaderiv(handle, GL_COMPILE_STATUS, &compiled);
+       return compiled == GL_TRUE;
+}
+
+void Shader::Log(std::ostream &out) const {
+       int log_len = 0, max_len = 0;
+       glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &max_len);
+       std::unique_ptr<char[]> log(new char[max_len]);
+       glGetShaderInfoLog(handle, max_len, &log_len, log.get());
+       out.write(log.get(), log_len);
+}
+
+
+void Shader::AttachToProgram(GLuint id) const noexcept {
+       glAttachShader(id, handle);
+}
+
+
+Program::Program()
+: handle(glCreateProgram()) {
+       if (handle == 0) {
+               gl_error("glCreateProgram");
+       }
+}
+
+Program::~Program() {
+       if (handle != 0) {
+               glDeleteProgram(handle);
+       }
+}
+
+
+const Shader &Program::LoadShader(GLenum type, const GLchar *src) {
+       shaders.emplace_back(type);
+       Shader &shader = shaders.back();
+       shader.Source(src);
+       shader.Compile();
+       if (!shader.Compiled()) {
+               shader.Log(std::cerr);
+               throw std::runtime_error("compile shader");
+       }
+       Attach(shader);
+       return shader;
+}
+
+void Program::Attach(Shader &shader) noexcept {
+       shader.AttachToProgram(handle);
+}
+
+void Program::Link() noexcept {
+       glLinkProgram(handle);
+}
+
+bool Program::Linked() const noexcept {
+       GLint linked = GL_FALSE;
+       glGetProgramiv(handle, GL_LINK_STATUS, &linked);
+       return linked == GL_TRUE;
+}
+
+void Program::Log(std::ostream &out) const {
+       int log_len = 0, max_len = 0;
+       glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &max_len);
+       std::unique_ptr<char[]> log(new char[max_len]);
+       glGetProgramInfoLog(handle, max_len, &log_len, log.get());
+       out.write(log.get(), log_len);
+}
+
+
+GLint Program::AttributeLocation(const GLchar *name) const noexcept {
+       return glGetAttribLocation(handle, name);
+}
+
+GLint Program::UniformLocation(const GLchar *name) const noexcept {
+       return glGetUniformLocation(handle, name);
+}
+
+
+DirectionalLighting::DirectionalLighting()
+: program()
+, light_direction(1.0f, 3.0f, 2.0f)
+, light_color(0.9f, 0.9f, 0.9f)
+, vp(1.0f)
+, m_handle(0)
+, mv_handle(0)
+, mvp_handle(0)
+, light_direction_handle(0)
+, light_color_handle(0)
+, fog_density_handle(0) {
+       program.LoadShader(
+               GL_VERTEX_SHADER,
+               "#version 330 core\n"
+               "layout(location = 0) in vec3 vtx_position;\n"
+               "layout(location = 1) in vec3 vtx_color;\n"
+               "layout(location = 2) in vec3 vtx_normal;\n"
+               "uniform mat4 M;\n"
+               "uniform mat4 MV;\n"
+               "uniform mat4 MVP;\n"
+               "out vec3 frag_color;\n"
+               "out vec3 vtx_viewspace;\n"
+               "out vec3 normal;\n"
+               "void main() {\n"
+                       "gl_Position = MVP * vec4(vtx_position, 1);\n"
+                       "vtx_viewspace = (MV * vec4(vtx_position, 1)).xyz;\n"
+                       "normal = (M * vec4(vtx_normal, 0)).xyz;\n"
+                       "frag_color = vtx_color;\n"
+               "}\n"
+       );
+       program.LoadShader(
+               GL_FRAGMENT_SHADER,
+               "#version 330 core\n"
+               "in vec3 frag_color;\n"
+               "in vec3 vtx_viewspace;\n"
+               "in vec3 normal;\n"
+               "uniform vec3 light_direction;\n"
+               "uniform vec3 light_color;\n"
+               "uniform float fog_density;\n"
+               "out vec3 color;\n"
+               "void main() {\n"
+                       "vec3 ambient = vec3(0.1, 0.1, 0.1) * frag_color;\n"
+                       // this should be the same as the clear color, otherwise looks really weird
+                       "vec3 fog_color = vec3(0, 0, 0);\n"
+                       "float e = 2.718281828;\n"
+                       "vec3 n = normalize(normal);\n"
+                       "vec3 l = normalize(light_direction);\n"
+                       "float cos_theta = clamp(dot(n, l), 0, 1);\n"
+                       "vec3 reflect_color = ambient + frag_color * light_color * cos_theta;\n"
+                       "float value = pow(e, -pow(fog_density * length(vtx_viewspace), 5));"
+                       "color = mix(fog_color, reflect_color, value);\n"
+               "}\n"
+       );
+       program.Link();
+       if (!program.Linked()) {
+               program.Log(std::cerr);
+               throw std::runtime_error("link program");
+       }
+
+       m_handle = program.UniformLocation("M");
+       mv_handle = program.UniformLocation("MV");
+       mvp_handle = program.UniformLocation("MVP");
+       light_direction_handle = program.UniformLocation("light_direction");
+       light_color_handle = program.UniformLocation("light_color");
+       fog_density_handle = program.UniformLocation("fog_density");
+}
+
+
+void DirectionalLighting::Activate() noexcept {
+       GLContext::EnableDepthTest();
+       GLContext::EnableBackfaceCulling();
+       program.Use();
+
+       glUniform3f(light_direction_handle, light_direction.x, light_direction.y, light_direction.z);
+       glUniform3f(light_color_handle, light_color.x, light_color.y, light_color.z);
+}
+
+void DirectionalLighting::SetM(const glm::mat4 &m) noexcept {
+       glm::mat4 mv(view * m);
+       glm::mat4 mvp(vp * m);
+       glUniformMatrix4fv(m_handle, 1, GL_FALSE, &m[0][0]);
+       glUniformMatrix4fv(mv_handle, 1, GL_FALSE, &mv[0][0]);
+       glUniformMatrix4fv(mvp_handle, 1, GL_FALSE, &mvp[0][0]);
+}
+
+void DirectionalLighting::SetLightDirection(const glm::vec3 &dir) noexcept {
+       light_direction = -dir;
+       glUniform3f(light_direction_handle, light_direction.x, light_direction.y, light_direction.z);
+}
+
+void DirectionalLighting::SetFogDensity(float f) noexcept {
+       fog_density = f;
+       glUniform1f(fog_density_handle, fog_density);
+}
+
+void DirectionalLighting::SetProjection(const glm::mat4 &p) noexcept {
+       projection = p;
+       vp = p * view;
+}
+
+void DirectionalLighting::SetView(const glm::mat4 &v) noexcept {
+       view = v;
+       vp = projection * v;
+}
+
+void DirectionalLighting::SetVP(const glm::mat4 &v, const glm::mat4 &p) noexcept {
+       projection = p;
+       view = v;
+       vp = p * v;
+}
+
+void DirectionalLighting::SetMVP(const glm::mat4 &m, const glm::mat4 &v, const glm::mat4 &p) noexcept {
+       SetVP(v, p);
+       SetM(m);
+}
+
+
+BlockLighting::BlockLighting()
+: program()
+, vp(1.0f)
+, mv_handle(0)
+, mvp_handle(0)
+, fog_density_handle(0) {
+       program.LoadShader(
+               GL_VERTEX_SHADER,
+               "#version 330 core\n"
+               "layout(location = 0) in vec3 vtx_position;\n"
+               "layout(location = 1) in vec3 vtx_color;\n"
+               "layout(location = 2) in float vtx_light;\n"
+               "uniform mat4 MV;\n"
+               "uniform mat4 MVP;\n"
+               "out vec3 frag_color;\n"
+               "out vec3 vtx_viewspace;\n"
+               "out float frag_light;\n"
+               "void main() {\n"
+                       "gl_Position = MVP * vec4(vtx_position, 1);\n"
+                       "frag_color = vtx_color;\n"
+                       "vtx_viewspace = (MV * vec4(vtx_position, 1)).xyz;\n"
+                       "frag_light = vtx_light;\n"
+               "}\n"
+       );
+       program.LoadShader(
+               GL_FRAGMENT_SHADER,
+               "#version 330 core\n"
+               "in vec3 frag_color;\n"
+               "in vec3 vtx_viewspace;\n"
+               "in float frag_light;\n"
+               "uniform float fog_density;\n"
+               "out vec3 color;\n"
+               "void main() {\n"
+                       "vec3 ambient = vec3(0.1, 0.1, 0.1) * frag_color;\n"
+                       "float light_power = clamp(pow(0.8, 15 - frag_light), 0, 1);\n"
+                       "vec3 fog_color = vec3(0, 0, 0);\n"
+                       "float e = 2.718281828;\n"
+                       //"vec3 reflect_color = ambient + frag_color * light_power;\n"
+                       "vec3 reflect_color = frag_color * light_power;\n"
+                       "float value = pow(e, -pow(fog_density * length(vtx_viewspace), 5));"
+                       "color = mix(fog_color, reflect_color, value);\n"
+               "}\n"
+       );
+       program.Link();
+       if (!program.Linked()) {
+               program.Log(std::cerr);
+               throw std::runtime_error("link program");
+       }
+
+       mv_handle = program.UniformLocation("MV");
+       mvp_handle = program.UniformLocation("MVP");
+       fog_density_handle = program.UniformLocation("fog_density");
+}
+
+
+void BlockLighting::Activate() noexcept {
+       GLContext::EnableDepthTest();
+       GLContext::EnableBackfaceCulling();
+       program.Use();
+}
+
+void BlockLighting::SetM(const glm::mat4 &m) noexcept {
+       glm::mat4 mv(view * m);
+       glm::mat4 mvp(vp * m);
+       glUniformMatrix4fv(mv_handle, 1, GL_FALSE, &mv[0][0]);
+       glUniformMatrix4fv(mvp_handle, 1, GL_FALSE, &mvp[0][0]);
+}
+
+void BlockLighting::SetFogDensity(float f) noexcept {
+       fog_density = f;
+       glUniform1f(fog_density_handle, fog_density);
+}
+
+void BlockLighting::SetProjection(const glm::mat4 &p) noexcept {
+       projection = p;
+       vp = p * view;
+}
+
+void BlockLighting::SetView(const glm::mat4 &v) noexcept {
+       view = v;
+       vp = projection * v;
+}
+
+void BlockLighting::SetVP(const glm::mat4 &v, const glm::mat4 &p) noexcept {
+       projection = p;
+       view = v;
+       vp = p * v;
+}
+
+void BlockLighting::SetMVP(const glm::mat4 &m, const glm::mat4 &v, const glm::mat4 &p) noexcept {
+       SetVP(v, p);
+       SetM(m);
+}
+
+}
diff --git a/src/hud.cpp b/src/hud.cpp
deleted file mode 100644 (file)
index 0f496c6..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-#include "hud.hpp"
-
-#include "block.hpp"
-#include "init.hpp"
-#include "shader.hpp"
-#include "shape.hpp"
-
-#include <glm/gtc/matrix_transform.hpp>
-
-
-namespace blank {
-
-HUD::HUD(const BlockTypeRegistry &types)
-: types(types)
-, block()
-, block_buf()
-, block_transform(1.0f)
-, block_visible(false)
-, crosshair()
-, crosshair_transform(1.0f)
-, near(100.0f)
-, far(-100.0f)
-, projection(glm::ortho(0.0f, 1.0f, 1.0f, 0.0f, near, far))
-, view(glm::translate(glm::mat4(1.0f), glm::vec3(-0.5f, -0.5f, 0))) {
-       block_transform = glm::translate(block_transform, glm::vec3(50.0f, 50.0f, 0.0f));
-       block_transform = glm::scale(block_transform, glm::vec3(50.0f));
-       block_transform = glm::rotate(block_transform, 3.5f, glm::vec3(1.0f, 0.0f, 0.0f));
-       block_transform = glm::rotate(block_transform, 0.35f, glm::vec3(0.0f, 1.0f, 0.0f));
-
-       crosshair.vertices = std::vector<glm::vec3>({
-               { -10.0f,   0.0f, 0.0f }, { 10.0f,  0.0f, 0.0f },
-               {   0.0f, -10.0f, 0.0f }, {  0.0f, 10.0f, 0.0f },
-       });
-       crosshair.indices = std::vector<OutlineModel::Index>({
-               0, 1, 2, 3
-       });
-       crosshair.colors.resize(4, { 10.0f, 10.0f, 10.0f });
-       crosshair.Invalidate();
-}
-
-
-void HUD::Viewport(float width, float height) noexcept {
-       Viewport(0, 0, width, height);
-}
-
-void HUD::Viewport(float x, float y, float width, float height) noexcept {
-       projection = glm::ortho(x, width, height, y, near, far);
-       crosshair_transform = glm::translate(glm::mat4(1.0f), glm::vec3(width * 0.5f, height * 0.5f, 0.0f));
-}
-
-
-void HUD::Display(const Block &b) {
-       const BlockType &type = types.Get(b.type);
-
-       block_buf.Clear();
-       type.FillModel(block_buf, b.Transform());
-       block.Update(block_buf);
-       block_visible = type.visible;
-}
-
-
-void HUD::Render(DirectionalLighting &program) noexcept {
-       if (block_visible) {
-               program.SetLightDirection({ 1.0f, 3.0f, 5.0f });
-               // disable distance fog
-               program.SetFogDensity(0.0f);
-               GLContext::ClearDepthBuffer();
-               program.SetMVP(block_transform, view, projection);
-               block.Draw();
-               program.SetM(crosshair_transform);
-               crosshair.Draw();
-       }
-}
-
-}
diff --git a/src/hud.hpp b/src/hud.hpp
deleted file mode 100644 (file)
index 984a24d..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-#ifndef BLANK_HUD_H_
-#define BLANK_HUD_H_
-
-#include "model.hpp"
-#include "world.hpp"
-
-#include <glm/glm.hpp>
-
-
-namespace blank {
-
-class BlockTypeRegistry;
-class DirectionalLighting;
-
-class HUD {
-
-public:
-       explicit HUD(const BlockTypeRegistry &);
-
-       HUD(const HUD &) = delete;
-       HUD &operator =(const HUD &) = delete;
-
-       void Viewport(float width, float height) noexcept;
-       void Viewport(float x, float y, float width, float height) noexcept;
-
-       void Display(const Block &);
-
-       void Render(DirectionalLighting &) noexcept;
-
-private:
-       const BlockTypeRegistry &types;
-
-       Model block;
-       Model::Buffer block_buf;
-       glm::mat4 block_transform;
-       bool block_visible;
-
-       OutlineModel crosshair;
-       glm::mat4 crosshair_transform;
-
-       float near, far;
-       glm::mat4 projection;
-       glm::mat4 view;
-
-};
-
-}
-
-#endif
diff --git a/src/init.cpp b/src/init.cpp
deleted file mode 100644 (file)
index a56674c..0000000
+++ /dev/null
@@ -1,185 +0,0 @@
-#include "init.hpp"
-
-#include <algorithm>
-#include <SDL.h>
-#include <SDL_image.h>
-#include <stdexcept>
-#include <string>
-#include <GL/glew.h>
-
-
-namespace {
-
-void sdl_error(std::string msg) {
-       const char *error = SDL_GetError();
-       if (*error != '\0') {
-               msg += ": ";
-               msg += error;
-               SDL_ClearError();
-       }
-       throw std::runtime_error(msg);
-}
-
-}
-
-namespace blank {
-
-InitSDL::InitSDL() {
-       if (SDL_Init(SDL_INIT_VIDEO) != 0) {
-               sdl_error("SDL_Init(SDL_INIT_VIDEO)");
-       }
-}
-
-InitSDL::~InitSDL() {
-       SDL_Quit();
-}
-
-
-InitIMG::InitIMG() {
-       if (IMG_Init(IMG_INIT_PNG) == 0) {
-               sdl_error("IMG_Init(IMG_INIT_PNG)");
-       }
-}
-
-InitIMG::~InitIMG() {
-       IMG_Quit();
-}
-
-
-InitGL::InitGL(bool double_buffer, int sample_size) {
-       if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3) != 0) {
-               sdl_error("SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3)");
-       }
-       if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3) != 0) {
-               sdl_error("SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3)");
-       }
-       if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE) != 0) {
-               sdl_error("SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE)");
-       }
-
-       if (double_buffer) {
-               if (SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1) != 0) {
-                       sdl_error("SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1)");
-               }
-       }
-
-       if (sample_size > 1) {
-               if (SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1) != 0) {
-                       sdl_error("SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS)");
-               }
-               if (SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, sample_size) != 0) {
-                       sdl_error("SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES)");
-               }
-       }
-}
-
-
-Window::Window()
-: handle(SDL_CreateWindow(
-       "blank",
-       SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
-       960, 600,
-       SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE
-)) {
-       if (!handle) {
-               sdl_error("SDL_CreateWindow");
-       }
-}
-
-Window::~Window() {
-       SDL_DestroyWindow(handle);
-}
-
-void Window::GrabInput() {
-       SDL_SetWindowGrab(handle, SDL_TRUE);
-}
-
-void Window::ReleaseInput() {
-       SDL_SetWindowGrab(handle, SDL_FALSE);
-}
-
-void Window::GrabMouse() {
-       if (SDL_SetRelativeMouseMode(SDL_TRUE) != 0) {
-               sdl_error("SDL_SetRelativeMouseMode");
-       }
-}
-
-void Window::ReleaseMouse() {
-       if (SDL_SetRelativeMouseMode(SDL_FALSE) != 0) {
-               sdl_error("SDL_SetRelativeMouseMode");
-       }
-}
-
-GLContext Window::CreateContext() {
-       return GLContext(handle);
-}
-
-void Window::Flip() {
-       SDL_GL_SwapWindow(handle);
-}
-
-
-GLContext::GLContext(SDL_Window *win)
-: handle(SDL_GL_CreateContext(win)) {
-       if (!handle) {
-               sdl_error("SDL_GL_CreateContext");
-       }
-}
-
-GLContext::~GLContext() {
-       if (handle) {
-               SDL_GL_DeleteContext(handle);
-       }
-}
-
-
-GLContext::GLContext(GLContext &&other)
-: handle(other.handle) {
-       other.handle = nullptr;
-}
-
-GLContext &GLContext::operator =(GLContext &&other) {
-       std::swap(handle, other.handle);
-       return *this;
-}
-
-void GLContext::EnableVSync() {
-       if (SDL_GL_SetSwapInterval(1) != 0) {
-               sdl_error("SDL_GL_SetSwapInterval");
-       }
-}
-
-void GLContext::EnableDepthTest() noexcept {
-       glEnable(GL_DEPTH_TEST);
-       glDepthFunc(GL_LESS);
-}
-
-void GLContext::EnableBackfaceCulling() noexcept {
-       glEnable(GL_CULL_FACE);
-}
-
-void GLContext::Clear() noexcept {
-       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-}
-
-void GLContext::ClearDepthBuffer() noexcept {
-       glClear(GL_DEPTH_BUFFER_BIT);
-}
-
-
-InitGLEW::InitGLEW() {
-       glewExperimental = GL_TRUE;
-       GLenum glew_err = glewInit();
-       if (glew_err != GLEW_OK) {
-               std::string msg("glewInit: ");
-               const GLubyte *errBegin = glewGetErrorString(glew_err);
-               const GLubyte *errEnd = errBegin;
-               while (*errEnd != '\0') {
-                       ++errEnd;
-               }
-               msg.append(errBegin, errEnd);
-               throw std::runtime_error(msg);
-       }
-}
-
-}
diff --git a/src/init.hpp b/src/init.hpp
deleted file mode 100644 (file)
index 30da3be..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-#ifndef BLANK_INIT_HPP_
-#define BLANK_INIT_HPP_
-
-#include <SDL.h>
-
-
-namespace blank {
-
-class GLContext;
-
-
-class InitSDL {
-
-public:
-       InitSDL();
-       ~InitSDL();
-
-       InitSDL(const InitSDL &) = delete;
-       InitSDL &operator =(const InitSDL &) = delete;
-
-};
-
-
-class InitIMG {
-
-public:
-       InitIMG();
-       ~InitIMG();
-
-       InitIMG(const InitIMG &) = delete;
-       InitIMG &operator =(const InitIMG &) = delete;
-
-};
-
-
-class InitGL {
-
-public:
-       explicit InitGL(bool double_buffer = true, int sample_size = 1);
-
-       InitGL(const InitGL &) = delete;
-       InitGL &operator =(const InitGL &) = delete;
-
-};
-
-
-class Window {
-
-public:
-       Window();
-       ~Window();
-
-       Window(const Window &) = delete;
-       Window &operator =(const Window &) = delete;
-
-       void GrabInput();
-       void ReleaseInput();
-
-       void GrabMouse();
-       void ReleaseMouse();
-
-       GLContext CreateContext();
-
-       void Flip();
-
-private:
-       SDL_Window *handle;
-
-};
-
-
-class GLContext {
-
-public:
-       explicit GLContext(SDL_Window *);
-       ~GLContext();
-
-       GLContext(GLContext &&);
-       GLContext &operator =(GLContext &&);
-
-       GLContext(const GLContext &) = delete;
-       GLContext &operator =(const GLContext &) = delete;
-
-       static void EnableVSync();
-       static void EnableDepthTest() noexcept;
-       static void EnableBackfaceCulling() noexcept;
-
-       static void Clear() noexcept;
-       static void ClearDepthBuffer() noexcept;
-
-private:
-       SDL_GLContext handle;
-
-};
-
-
-class InitGLEW {
-
-public:
-       InitGLEW();
-
-       InitGLEW(const InitGLEW &) = delete;
-       InitGLEW &operator =(const InitGLEW &) = delete;
-
-};
-
-}
-
-#endif
diff --git a/src/interface.cpp b/src/interface.cpp
deleted file mode 100644 (file)
index a5dcc0e..0000000
+++ /dev/null
@@ -1,315 +0,0 @@
-#include "interface.hpp"
-
-#include "world.hpp"
-
-#include <iostream>
-#include <glm/gtc/matrix_transform.hpp>
-#include <glm/gtx/io.hpp>
-
-
-namespace blank {
-
-Interface::Interface(const Config &config, World &world)
-: world(world)
-, ctrl(world.Player())
-, hud(world.BlockTypes())
-, aim{{ 0, 0, 0 }, { 0, 0, -1 }}
-, aim_chunk(nullptr)
-, aim_block(0)
-, aim_normal()
-, outline()
-, outline_transform(1.0f)
-, config(config)
-, place_timer(256)
-, remove_timer(256)
-, remove(0)
-, selection(1)
-, fwd(0)
-, rev(0) {
-       hud.Viewport(960, 600);
-       hud.Display(selection);
-}
-
-
-void Interface::HandlePress(const SDL_KeyboardEvent &event) {
-       if (config.keyboard_disabled) return;
-
-       switch (event.keysym.sym) {
-               case SDLK_w:
-                       rev.z = 1;
-                       break;
-               case SDLK_s:
-                       fwd.z = 1;
-                       break;
-               case SDLK_a:
-                       rev.x = 1;
-                       break;
-               case SDLK_d:
-                       fwd.x = 1;
-                       break;
-               case SDLK_SPACE:
-                       fwd.y = 1;
-                       break;
-               case SDLK_LSHIFT:
-                       rev.y = 1;
-                       break;
-
-               case SDLK_q:
-                       FaceBlock();
-                       break;
-               case SDLK_e:
-                       TurnBlock();
-                       break;
-
-               case SDLK_b:
-                       PrintBlockInfo();
-                       break;
-               case SDLK_c:
-                       PrintChunkInfo();
-                       break;
-               case SDLK_l:
-                       PrintLightInfo();
-                       break;
-               case SDLK_p:
-                       PrintSelectionInfo();
-                       break;
-       }
-}
-
-void Interface::HandleRelease(const SDL_KeyboardEvent &event) {
-       if (config.keyboard_disabled) return;
-
-       switch (event.keysym.sym) {
-               case SDLK_w:
-                       rev.z = 0;
-                       break;
-               case SDLK_s:
-                       fwd.z = 0;
-                       break;
-               case SDLK_a:
-                       rev.x = 0;
-                       break;
-               case SDLK_d:
-                       fwd.x = 0;
-                       break;
-               case SDLK_SPACE:
-                       fwd.y = 0;
-                       break;
-               case SDLK_LSHIFT:
-                       rev.y = 0;
-                       break;
-       }
-}
-
-void Interface::FaceBlock() {
-       selection.SetFace(Block::Face((selection.GetFace() + 1) % Block::FACE_COUNT));
-       hud.Display(selection);
-}
-
-void Interface::TurnBlock() {
-       selection.SetTurn(Block::Turn((selection.GetTurn() + 1) % Block::TURN_COUNT));
-       hud.Display(selection);
-}
-
-void Interface::PrintBlockInfo() {
-       std::cout << std::endl;
-       if (!aim_chunk) {
-               std::cout << "not looking at any block" << std::endl;
-               Ray aim = ctrl.Aim();
-               std::cout << "aim ray: " << aim.orig << ", " << aim.dir << std::endl;
-               return;
-       }
-       std::cout << "looking at block " << aim_block
-               << " " << Chunk::ToCoords(aim_block)
-               << " of chunk " << aim_chunk->Position()
-               << std::endl;
-       Print(aim_chunk->BlockAt(aim_block));
-}
-
-void Interface::PrintChunkInfo() {
-       std::cout << std::endl;
-       if (!aim_chunk) {
-               std::cout << "not looking at any block" << std::endl;
-               return;
-       }
-       std::cout << "looking at chunk "
-               << aim_chunk->Position()
-               << std::endl;
-
-       std::cout << "  neighbors:" << std::endl;
-       if (aim_chunk->HasNeighbor(Block::FACE_LEFT)) {
-               std::cout << " left  " << aim_chunk->GetNeighbor(Block::FACE_LEFT).Position() << std::endl;
-       }
-       if (aim_chunk->HasNeighbor(Block::FACE_RIGHT)) {
-               std::cout << " right " << aim_chunk->GetNeighbor(Block::FACE_RIGHT).Position() << std::endl;
-       }
-       if (aim_chunk->HasNeighbor(Block::FACE_UP)) {
-               std::cout << " up    " << aim_chunk->GetNeighbor(Block::FACE_UP).Position() << std::endl;
-       }
-       if (aim_chunk->HasNeighbor(Block::FACE_DOWN)) {
-               std::cout << " down  " << aim_chunk->GetNeighbor(Block::FACE_DOWN).Position() << std::endl;
-       }
-       if (aim_chunk->HasNeighbor(Block::FACE_FRONT)) {
-               std::cout << " front " << aim_chunk->GetNeighbor(Block::FACE_FRONT).Position() << std::endl;
-       }
-       if (aim_chunk->HasNeighbor(Block::FACE_BACK)) {
-               std::cout << " back  " << aim_chunk->GetNeighbor(Block::FACE_BACK).Position() << std::endl;
-       }
-       std::cout << std::endl;
-}
-
-void Interface::PrintLightInfo() {
-       std::cout
-               << "light level " << world.PlayerChunk().GetLight(world.Player().Position())
-               << " at position " << world.Player().Position()
-               << std::endl;
-}
-
-void Interface::PrintSelectionInfo() {
-       std::cout << std::endl;
-       Print(selection);
-}
-
-void Interface::Print(const Block &block) {
-       std::cout << "type: " << block.type
-               << ", face: " << block.GetFace()
-               << ", turn: " << block.GetTurn()
-               << std::endl;
-}
-
-
-void Interface::Handle(const SDL_MouseMotionEvent &event) {
-       if (config.mouse_disabled) return;
-       ctrl.RotateYaw(event.xrel * config.yaw_sensitivity);
-       ctrl.RotatePitch(event.yrel * config.pitch_sensitivity);
-}
-
-void Interface::HandlePress(const SDL_MouseButtonEvent &event) {
-       if (config.mouse_disabled) return;
-
-       if (event.button == 1) {
-               RemoveBlock();
-               remove_timer.Start();
-       } else if (event.button == 2) {
-               PickBlock();
-       } else if (event.button == 3) {
-               PlaceBlock();
-               place_timer.Start();
-       }
-}
-
-void Interface::HandleRelease(const SDL_MouseButtonEvent &event) {
-       if (config.mouse_disabled) return;
-
-       if (event.button == 1) {
-               remove_timer.Stop();
-       } else if (event.button == 3) {
-               place_timer.Stop();
-       }
-}
-
-void Interface::PickBlock() {
-       if (!aim_chunk) return;
-       selection = aim_chunk->BlockAt(aim_block);
-       hud.Display(selection);
-}
-
-void Interface::PlaceBlock() {
-       if (!aim_chunk) return;
-       Chunk *mod_chunk = aim_chunk;
-       glm::vec3 next_pos = Chunk::ToCoords(aim_block) + aim_normal;
-       if (!Chunk::InBounds(next_pos)) {
-               mod_chunk = &world.Next(*aim_chunk, aim_normal);
-               next_pos -= aim_normal * glm::vec3(Chunk::Extent());
-       }
-       mod_chunk->SetBlock(next_pos, selection);
-       mod_chunk->Invalidate();
-}
-
-void Interface::RemoveBlock() noexcept {
-       if (!aim_chunk) return;
-       aim_chunk->SetBlock(aim_block, remove);
-       aim_chunk->Invalidate();
-}
-
-
-void Interface::Handle(const SDL_MouseWheelEvent &event) {
-       if (config.mouse_disabled) return;
-
-       if (event.y < 0) {
-               SelectNext();
-       } else if (event.y > 0) {
-               SelectPrevious();
-       }
-}
-
-void Interface::SelectNext() {
-       ++selection.type;
-       if (size_t(selection.type) >= world.BlockTypes().Size()) {
-               selection.type = 1;
-       }
-       hud.Display(selection);
-}
-
-void Interface::SelectPrevious() {
-       --selection.type;
-       if (selection.type <= 0) {
-               selection.type = world.BlockTypes().Size() - 1;
-       }
-       hud.Display(selection);
-}
-
-void Interface::Handle(const SDL_WindowEvent &event) noexcept {
-       if (event.event == SDL_WINDOWEVENT_RESIZED) {
-               hud.Viewport(event.data1, event.data2);
-       }
-}
-
-
-void Interface::Update(int dt) {
-       ctrl.Velocity(glm::vec3(fwd - rev) * config.move_velocity);
-       ctrl.Update(dt);
-
-       place_timer.Update(dt);
-       remove_timer.Update(dt);
-
-       aim = ctrl.Aim();
-       CheckAim();
-
-       if (remove_timer.Hit()) {
-               RemoveBlock();
-               CheckAim();
-       }
-
-       if (place_timer.Hit()) {
-               PlaceBlock();
-               CheckAim();
-       }
-}
-
-void Interface::CheckAim() {
-       float dist;
-       if (world.Intersection(aim, glm::mat4(1.0f), &aim_chunk, &aim_block, &dist, &aim_normal)) {
-               outline.Clear();
-               aim_chunk->Type(aim_chunk->BlockAt(aim_block)).FillOutlineModel(outline);
-               outline_transform = glm::scale(glm::vec3(1.0002f));
-               outline_transform *= aim_chunk->Transform(world.Player().ChunkCoords());
-               outline_transform *= aim_chunk->ToTransform(Chunk::ToPos(aim_block), aim_block);
-       } else {
-               aim_chunk = nullptr;
-       }
-}
-
-
-void Interface::Render(DirectionalLighting &program) noexcept {
-       if (config.visual_disabled) return;
-
-       if (aim_chunk) {
-               program.SetM(outline_transform);
-               outline.Draw();
-       }
-
-       hud.Render(program);
-}
-
-}
diff --git a/src/interface.hpp b/src/interface.hpp
deleted file mode 100644 (file)
index 9f43f4f..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-#ifndef BLANK_INTERFACE_HPP_
-#define BLANK_INTERFACE_HPP_
-
-#include "block.hpp"
-#include "controller.hpp"
-#include "geometry.hpp"
-#include "hud.hpp"
-#include "model.hpp"
-#include "shader.hpp"
-#include "timer.hpp"
-
-#include <SDL.h>
-#include <glm/glm.hpp>
-
-
-namespace blank {
-
-class Chunk;
-class World;
-
-class Interface {
-
-public:
-       struct Config {
-               float move_velocity = 0.005f;
-               float pitch_sensitivity = -0.0025f;
-               float yaw_sensitivity = -0.001f;
-
-               bool keyboard_disabled = false;
-               bool mouse_disabled = false;
-               bool visual_disabled = false;
-       };
-
-       Interface(const Config &, World &);
-
-       void HandlePress(const SDL_KeyboardEvent &);
-       void HandleRelease(const SDL_KeyboardEvent &);
-       void Handle(const SDL_MouseMotionEvent &);
-       void HandlePress(const SDL_MouseButtonEvent &);
-       void HandleRelease(const SDL_MouseButtonEvent &);
-       void Handle(const SDL_MouseWheelEvent &);
-       void Handle(const SDL_WindowEvent &) noexcept;
-
-       void FaceBlock();
-       void TurnBlock();
-
-       void PickBlock();
-       void PlaceBlock();
-       void RemoveBlock() noexcept;
-
-       void PrintBlockInfo();
-       void PrintChunkInfo();
-       void PrintLightInfo();
-       void PrintSelectionInfo();
-       void Print(const Block &);
-
-       void SelectNext();
-       void SelectPrevious();
-
-       void Update(int dt);
-
-       void Render(DirectionalLighting &) noexcept;
-
-private:
-       void CheckAim();
-
-private:
-       World &world;
-       FPSController ctrl;
-       HUD hud;
-
-       Ray aim;
-       Chunk *aim_chunk;
-       int aim_block;
-       glm::vec3 aim_normal;
-
-       OutlineModel outline;
-       glm::mat4 outline_transform;
-
-       Config config;
-
-       IntervalTimer place_timer;
-       IntervalTimer remove_timer;
-
-       Block remove;
-       Block selection;
-
-       glm::tvec3<int> fwd, rev;
-
-};
-
-}
-
-#endif
diff --git a/src/main.cpp b/src/main.cpp
deleted file mode 100644 (file)
index 3cf7bf9..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-#include "runtime.hpp"
-
-using namespace blank;
-
-int main(int argc, char *argv[]) {
-       Runtime rt;
-       rt.ReadArgs(argc, argv);
-       return rt.Execute();
-}
diff --git a/src/model.cpp b/src/model.cpp
deleted file mode 100644 (file)
index 4e75674..0000000
+++ /dev/null
@@ -1,288 +0,0 @@
-#include "model.hpp"
-
-#include <iostream>
-
-
-namespace blank {
-
-Model::Model() noexcept
-: va(0)
-, handle{}
-, count(0) {
-       glGenVertexArrays(1, &va);
-       glGenBuffers(ATTRIB_COUNT, handle);
-}
-
-Model::~Model() noexcept {
-       glDeleteBuffers(ATTRIB_COUNT, handle);
-       glDeleteVertexArrays(1, &va);
-}
-
-Model::Model(Model &&other) noexcept
-: va(other.va)
-, count(other.count) {
-       other.va = 0;
-       for (int i = 0; i < ATTRIB_COUNT; ++i) {
-               handle[i] = other.handle[i];
-               other.handle[i] = 0;
-       }
-}
-
-Model &Model::operator =(Model &&other) noexcept {
-       std::swap(va, other.va);
-       for (int i = 0; i < ATTRIB_COUNT; ++i) {
-               std::swap(handle[i], other.handle[i]);
-       }
-       count = other.count;
-       return *this;
-}
-
-void Model::Update(const Buffer &buf) noexcept {
-       glBindVertexArray(va);
-       glBindBuffer(GL_ARRAY_BUFFER, handle[ATTRIB_VERTEX]);
-       glBufferData(GL_ARRAY_BUFFER, buf.vertices.size() * sizeof(glm::vec3), buf.vertices.data(), GL_STATIC_DRAW);
-       glEnableVertexAttribArray(ATTRIB_VERTEX);
-       glVertexAttribPointer(
-               ATTRIB_VERTEX, // location (for shader)
-               3,             // size
-               GL_FLOAT,      // type
-               GL_FALSE,      // normalized
-               0,             // stride
-               nullptr        // offset
-       );
-
-#ifndef NDEBUG
-       if (buf.colors.size() < buf.vertices.size()) {
-               std::cerr << "Model: not enough colors!" << std::endl;
-       }
-#endif
-       glBindBuffer(GL_ARRAY_BUFFER, handle[ATTRIB_COLOR]);
-       glBufferData(GL_ARRAY_BUFFER, buf.colors.size() * sizeof(glm::vec3), buf.colors.data(), GL_STATIC_DRAW);
-       glEnableVertexAttribArray(ATTRIB_COLOR);
-       glVertexAttribPointer(
-               ATTRIB_COLOR,  // location (for shader)
-               3,             // size
-               GL_FLOAT,      // type
-               GL_FALSE,      // normalized
-               0,             // stride
-               nullptr        // offset
-       );
-
-#ifndef NDEBUG
-       if (buf.normals.size() < buf.vertices.size()) {
-               std::cerr << "Model: not enough normals!" << std::endl;
-       }
-#endif
-       glBindBuffer(GL_ARRAY_BUFFER, handle[ATTRIB_NORMAL]);
-       glBufferData(GL_ARRAY_BUFFER, buf.normals.size() * sizeof(glm::vec3), buf.normals.data(), GL_STATIC_DRAW);
-       glEnableVertexAttribArray(ATTRIB_NORMAL);
-       glVertexAttribPointer(
-               ATTRIB_NORMAL, // location (for shader)
-               3,             // size
-               GL_FLOAT,      // type
-               GL_FALSE,      // normalized
-               0,             // stride
-               nullptr        // offset
-       );
-
-       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle[ATTRIB_INDEX]);
-       glBufferData(GL_ELEMENT_ARRAY_BUFFER, buf.indices.size() * sizeof(Index), buf.indices.data(), GL_STATIC_DRAW);
-       count = buf.indices.size();
-}
-
-
-void Model::Draw() const noexcept {
-       glBindVertexArray(va);
-       glDrawElements(
-               GL_TRIANGLES,    // how
-               count,           // count
-               GL_UNSIGNED_INT, // type
-               nullptr          // offset
-       );
-}
-
-
-BlockModel::BlockModel() noexcept
-: va(0)
-, handle{}
-, count(0) {
-       glGenVertexArrays(1, &va);
-       glGenBuffers(ATTRIB_COUNT, handle);
-}
-
-BlockModel::~BlockModel() noexcept {
-       glDeleteBuffers(ATTRIB_COUNT, handle);
-       glDeleteVertexArrays(1, &va);
-}
-
-BlockModel::BlockModel(BlockModel &&other) noexcept
-: va(other.va)
-, count(other.count) {
-       other.va = 0;
-       for (int i = 0; i < ATTRIB_COUNT; ++i) {
-               handle[i] = other.handle[i];
-               other.handle[i] = 0;
-       }
-}
-
-BlockModel &BlockModel::operator =(BlockModel &&other) noexcept {
-       std::swap(va, other.va);
-       for (int i = 0; i < ATTRIB_COUNT; ++i) {
-               std::swap(handle[i], other.handle[i]);
-       }
-       count = other.count;
-       return *this;
-}
-
-void BlockModel::Update(const Buffer &buf) noexcept {
-       glBindVertexArray(va);
-       glBindBuffer(GL_ARRAY_BUFFER, handle[ATTRIB_VERTEX]);
-       glBufferData(GL_ARRAY_BUFFER, buf.vertices.size() * sizeof(glm::vec3), buf.vertices.data(), GL_STATIC_DRAW);
-       glEnableVertexAttribArray(ATTRIB_VERTEX);
-       glVertexAttribPointer(
-               ATTRIB_VERTEX, // location (for shader)
-               3,             // size
-               GL_FLOAT,      // type
-               GL_FALSE,      // normalized
-               0,             // stride
-               nullptr        // offset
-       );
-
-#ifndef NDEBUG
-       if (buf.colors.size() < buf.vertices.size()) {
-               std::cerr << "BlockModel: not enough colors!" << std::endl;
-       }
-#endif
-       glBindBuffer(GL_ARRAY_BUFFER, handle[ATTRIB_COLOR]);
-       glBufferData(GL_ARRAY_BUFFER, buf.colors.size() * sizeof(glm::vec3), buf.colors.data(), GL_STATIC_DRAW);
-       glEnableVertexAttribArray(ATTRIB_COLOR);
-       glVertexAttribPointer(
-               ATTRIB_COLOR,  // location (for shader)
-               3,             // size
-               GL_FLOAT,      // type
-               GL_FALSE,      // normalized
-               0,             // stride
-               nullptr        // offset
-       );
-
-#ifndef NDEBUG
-       if (buf.lights.size() < buf.vertices.size()) {
-               std::cerr << "BlockModel: not enough lights!" << std::endl;
-       }
-#endif
-       glBindBuffer(GL_ARRAY_BUFFER, handle[ATTRIB_LIGHT]);
-       glBufferData(GL_ARRAY_BUFFER, buf.lights.size() * sizeof(float), buf.lights.data(), GL_STATIC_DRAW);
-       glEnableVertexAttribArray(ATTRIB_LIGHT);
-       glVertexAttribPointer(
-               ATTRIB_LIGHT, // location (for shader)
-               1,            // size
-               GL_FLOAT,     // type
-               GL_FALSE,     // normalized
-               0,            // stride
-               nullptr       // offset
-       );
-
-       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle[ATTRIB_INDEX]);
-       glBufferData(GL_ELEMENT_ARRAY_BUFFER, buf.indices.size() * sizeof(Index), buf.indices.data(), GL_STATIC_DRAW);
-       count = buf.indices.size();
-}
-
-
-void BlockModel::Draw() const noexcept {
-       glBindVertexArray(va);
-       glDrawElements(
-               GL_TRIANGLES,    // how
-               count,           // count
-               GL_UNSIGNED_INT, // type
-               nullptr          // offset
-       );
-}
-
-OutlineModel::OutlineModel() noexcept
-: vertices()
-, colors()
-, indices()
-, va(0)
-, handle{}
-, dirty(false) {
-       glGenVertexArrays(1, &va);
-       glGenBuffers(ATTRIB_COUNT, handle);
-}
-
-OutlineModel::~OutlineModel() noexcept {
-       glDeleteBuffers(ATTRIB_COUNT, handle);
-       glDeleteVertexArrays(1, &va);
-}
-
-
-void OutlineModel::Clear() noexcept {
-       vertices.clear();
-       colors.clear();
-       indices.clear();
-       Invalidate();
-}
-
-void OutlineModel::Reserve(int v, int i) {
-       vertices.reserve(v);
-       colors.reserve(v);
-       indices.reserve(i);
-}
-
-
-void OutlineModel::Update() noexcept {
-       glBindBuffer(GL_ARRAY_BUFFER, handle[ATTRIB_VERTEX]);
-       glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(glm::vec3), vertices.data(), GL_STATIC_DRAW);
-       glEnableVertexAttribArray(ATTRIB_VERTEX);
-       glVertexAttribPointer(
-               ATTRIB_VERTEX, // location (for shader)
-               3,             // size
-               GL_FLOAT,      // type
-               GL_FALSE,      // normalized
-               0,             // stride
-               nullptr        // offset
-       );
-
-#ifndef NDEBUG
-       if (colors.size() < vertices.size()) {
-               std::cerr << "OutlineModel: not enough colors!" << std::endl;
-               colors.resize(vertices.size(), { 1, 0, 1 });
-       }
-#endif
-       glBindBuffer(GL_ARRAY_BUFFER, handle[ATTRIB_COLOR]);
-       glBufferData(GL_ARRAY_BUFFER, colors.size() * sizeof(glm::vec3), colors.data(), GL_STATIC_DRAW);
-       glEnableVertexAttribArray(ATTRIB_COLOR);
-       glVertexAttribPointer(
-               ATTRIB_COLOR,  // location (for shader)
-               3,             // size
-               GL_FLOAT,      // type
-               GL_FALSE,      // normalized
-               0,             // stride
-               nullptr        // offset
-       );
-
-       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle[ATTRIB_INDEX]);
-       glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(Index), indices.data(), GL_STATIC_DRAW);
-
-       dirty = false;
-}
-
-
-void OutlineModel::Draw() noexcept {
-       glBindVertexArray(va);
-
-       if (dirty) {
-               Update();
-       }
-
-       glEnable(GL_LINE_SMOOTH);
-       glLineWidth(2.0f);
-
-       glDrawElements(
-               GL_LINES,          // how
-               indices.size(),    // count
-               GL_UNSIGNED_SHORT, // type
-               nullptr            // offset
-       );
-}
-
-}
diff --git a/src/model.hpp b/src/model.hpp
deleted file mode 100644 (file)
index 096e1cf..0000000
+++ /dev/null
@@ -1,194 +0,0 @@
-#ifndef BLANK_MODEL_HPP_
-#define BLANK_MODEL_HPP_
-
-#include <vector>
-#include <GL/glew.h>
-#include <glm/glm.hpp>
-
-
-namespace blank {
-
-class Model {
-
-public:
-       using Position = glm::vec3;
-       using Color = glm::vec3;
-       using Normal = glm::vec3;
-       using Index = unsigned int;
-
-       using Positions = std::vector<Position>;
-       using Colors = std::vector<Color>;
-       using Normals = std::vector<Normal>;
-       using Indices = std::vector<Index>;
-
-public:
-       struct Buffer {
-
-               Positions vertices;
-               Colors colors;
-               Normals normals;
-               Indices indices;
-
-               void Clear() noexcept {
-                       vertices.clear();
-                       colors.clear();
-                       normals.clear();
-                       indices.clear();
-               }
-
-               void Reserve(size_t p, size_t i) {
-                       vertices.reserve(p);
-                       colors.reserve(p);
-                       normals.reserve(p);
-                       indices.reserve(i);
-               }
-
-       };
-
-public:
-       Model() noexcept;
-       ~Model() noexcept;
-
-       Model(const Model &) = delete;
-       Model &operator =(const Model &) = delete;
-
-       Model(Model &&) noexcept;
-       Model &operator =(Model &&) noexcept;
-
-       void Update(const Buffer &) noexcept;
-
-       void Draw() const noexcept;
-
-private:
-       enum Attribute {
-               ATTRIB_VERTEX,
-               ATTRIB_COLOR,
-               ATTRIB_NORMAL,
-               ATTRIB_INDEX,
-               ATTRIB_COUNT,
-       };
-
-       GLuint va;
-       GLuint handle[ATTRIB_COUNT];
-       size_t count;
-
-};
-
-
-class BlockModel {
-
-public:
-       using Position = glm::vec3;
-       using Color = glm::vec3;
-       using Light = float;
-       using Index = unsigned int;
-
-       using Positions = std::vector<Position>;
-       using Colors = std::vector<Color>;
-       using Lights = std::vector<Light>;
-       using Indices = std::vector<Index>;
-
-public:
-       struct Buffer {
-
-               Positions vertices;
-               Colors colors;
-               Lights lights;
-               Indices indices;
-
-               void Clear() noexcept {
-                       vertices.clear();
-                       colors.clear();
-                       lights.clear();
-                       indices.clear();
-               }
-
-               void Reserve(size_t p, size_t i) {
-                       vertices.reserve(p);
-                       colors.reserve(p);
-                       lights.reserve(p);
-                       indices.reserve(i);
-               }
-
-       };
-
-public:
-       BlockModel() noexcept;
-       ~BlockModel() noexcept;
-
-       BlockModel(const BlockModel &) = delete;
-       BlockModel &operator =(const Model &) = delete;
-
-       BlockModel(BlockModel &&) noexcept;
-       BlockModel &operator =(BlockModel &&) noexcept;
-
-       void Update(const Buffer &) noexcept;
-
-       void Draw() const noexcept;
-
-private:
-       enum Attribute {
-               ATTRIB_VERTEX,
-               ATTRIB_COLOR,
-               ATTRIB_LIGHT,
-               ATTRIB_INDEX,
-               ATTRIB_COUNT,
-       };
-
-       GLuint va;
-       GLuint handle[ATTRIB_COUNT];
-       size_t count;
-
-};
-
-
-class OutlineModel {
-
-public:
-       using Position = glm::vec3;
-       using Color = glm::vec3;
-       using Index = unsigned short;
-
-       using Positions = std::vector<Position>;
-       using Colors = std::vector<Color>;
-       using Indices = std::vector<Index>;
-
-public:
-       Positions vertices;
-       Colors colors;
-       Indices indices;
-
-public:
-       OutlineModel() noexcept;
-       ~OutlineModel() noexcept;
-
-       OutlineModel(const OutlineModel &) = delete;
-       OutlineModel &operator =(const OutlineModel &) = delete;
-
-       void Invalidate() noexcept { dirty = true; }
-
-       void Clear() noexcept;
-       void Reserve(int vtx_count, int idx_count);
-
-       void Draw() noexcept;
-
-private:
-       void Update() noexcept;
-
-private:
-       enum Attribute {
-               ATTRIB_VERTEX,
-               ATTRIB_COLOR,
-               ATTRIB_INDEX,
-               ATTRIB_COUNT,
-       };
-
-       GLuint va;
-       GLuint handle[ATTRIB_COUNT];
-       bool dirty;
-
-};
-
-}
-
-#endif
diff --git a/src/model/BlockModel.hpp b/src/model/BlockModel.hpp
new file mode 100644 (file)
index 0000000..d1a37c9
--- /dev/null
@@ -0,0 +1,79 @@
+#ifndef BLANK_MODEL_BLOCKMODEL_HPP_
+#define BLANK_MODEL_BLOCKMODEL_HPP_
+
+#include <vector>
+#include <GL/glew.h>
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class BlockModel {
+
+public:
+       using Position = glm::vec3;
+       using Color = glm::vec3;
+       using Light = float;
+       using Index = unsigned int;
+
+       using Positions = std::vector<Position>;
+       using Colors = std::vector<Color>;
+       using Lights = std::vector<Light>;
+       using Indices = std::vector<Index>;
+
+public:
+       struct Buffer {
+
+               Positions vertices;
+               Colors colors;
+               Lights lights;
+               Indices indices;
+
+               void Clear() noexcept {
+                       vertices.clear();
+                       colors.clear();
+                       lights.clear();
+                       indices.clear();
+               }
+
+               void Reserve(size_t p, size_t i) {
+                       vertices.reserve(p);
+                       colors.reserve(p);
+                       lights.reserve(p);
+                       indices.reserve(i);
+               }
+
+       };
+
+public:
+       BlockModel() noexcept;
+       ~BlockModel() noexcept;
+
+       BlockModel(const BlockModel &) = delete;
+       BlockModel &operator =(const BlockModel &) = delete;
+
+       BlockModel(BlockModel &&) noexcept;
+       BlockModel &operator =(BlockModel &&) noexcept;
+
+       void Update(const Buffer &) noexcept;
+
+       void Draw() const noexcept;
+
+private:
+       enum Attribute {
+               ATTRIB_VERTEX,
+               ATTRIB_COLOR,
+               ATTRIB_LIGHT,
+               ATTRIB_INDEX,
+               ATTRIB_COUNT,
+       };
+
+       GLuint va;
+       GLuint handle[ATTRIB_COUNT];
+       size_t count;
+
+};
+
+}
+
+#endif
diff --git a/src/model/Model.hpp b/src/model/Model.hpp
new file mode 100644 (file)
index 0000000..95ad086
--- /dev/null
@@ -0,0 +1,79 @@
+#ifndef BLANK_MODEL_MODEL_HPP_
+#define BLANK_MODEL_MODEL_HPP_
+
+#include <vector>
+#include <GL/glew.h>
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class Model {
+
+public:
+       using Position = glm::vec3;
+       using Color = glm::vec3;
+       using Normal = glm::vec3;
+       using Index = unsigned int;
+
+       using Positions = std::vector<Position>;
+       using Colors = std::vector<Color>;
+       using Normals = std::vector<Normal>;
+       using Indices = std::vector<Index>;
+
+public:
+       struct Buffer {
+
+               Positions vertices;
+               Colors colors;
+               Normals normals;
+               Indices indices;
+
+               void Clear() noexcept {
+                       vertices.clear();
+                       colors.clear();
+                       normals.clear();
+                       indices.clear();
+               }
+
+               void Reserve(size_t p, size_t i) {
+                       vertices.reserve(p);
+                       colors.reserve(p);
+                       normals.reserve(p);
+                       indices.reserve(i);
+               }
+
+       };
+
+public:
+       Model() noexcept;
+       ~Model() noexcept;
+
+       Model(const Model &) = delete;
+       Model &operator =(const Model &) = delete;
+
+       Model(Model &&) noexcept;
+       Model &operator =(Model &&) noexcept;
+
+       void Update(const Buffer &) noexcept;
+
+       void Draw() const noexcept;
+
+private:
+       enum Attribute {
+               ATTRIB_VERTEX,
+               ATTRIB_COLOR,
+               ATTRIB_NORMAL,
+               ATTRIB_INDEX,
+               ATTRIB_COUNT,
+       };
+
+       GLuint va;
+       GLuint handle[ATTRIB_COUNT];
+       size_t count;
+
+};
+
+}
+
+#endif
diff --git a/src/model/OutlineModel.hpp b/src/model/OutlineModel.hpp
new file mode 100644 (file)
index 0000000..308ab51
--- /dev/null
@@ -0,0 +1,60 @@
+#ifndef BLANK_MODEL_OUTLINEMODEL_HPP_
+#define BLANK_MODEL_OUTLINEMODEL_HPP_
+
+#include <vector>
+#include <GL/glew.h>
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class OutlineModel {
+
+public:
+       using Position = glm::vec3;
+       using Color = glm::vec3;
+       using Index = unsigned short;
+
+       using Positions = std::vector<Position>;
+       using Colors = std::vector<Color>;
+       using Indices = std::vector<Index>;
+
+public:
+       Positions vertices;
+       Colors colors;
+       Indices indices;
+
+public:
+       OutlineModel() noexcept;
+       ~OutlineModel() noexcept;
+
+       OutlineModel(const OutlineModel &) = delete;
+       OutlineModel &operator =(const OutlineModel &) = delete;
+
+       void Invalidate() noexcept { dirty = true; }
+
+       void Clear() noexcept;
+       void Reserve(int vtx_count, int idx_count);
+
+       void Draw() noexcept;
+
+private:
+       void Update() noexcept;
+
+private:
+       enum Attribute {
+               ATTRIB_VERTEX,
+               ATTRIB_COLOR,
+               ATTRIB_INDEX,
+               ATTRIB_COUNT,
+       };
+
+       GLuint va;
+       GLuint handle[ATTRIB_COUNT];
+       bool dirty;
+
+};
+
+}
+
+#endif
diff --git a/src/model/Shape.hpp b/src/model/Shape.hpp
new file mode 100644 (file)
index 0000000..5d4036b
--- /dev/null
@@ -0,0 +1,99 @@
+#ifndef BLANK_MODEL_SHAPE_HPP_
+#define BLANK_MODEL_SHAPE_HPP_
+
+#include "BlockModel.hpp"
+#include "Model.hpp"
+#include "OutlineModel.hpp"
+
+#include <vector>
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class Ray;
+
+struct Shape {
+
+       /// the number of vertices (and normals) this shape has
+       size_t VertexCount() const noexcept { return vtx_pos.size(); }
+       /// the number of vertex indices this shape has
+       size_t VertexIndexCount() const noexcept { return vtx_idx.size(); }
+
+       const Model::Normal &VertexNormal(size_t idx) const noexcept { return vtx_nrm[idx]; }
+       Model::Normal VertexNormal(
+               size_t idx, const glm::mat4 &transform
+       ) const noexcept {
+               return Model::Normal(transform * glm::vec4(vtx_nrm[idx], 0.0f));
+       }
+
+       /// fill given buffers with this shape's elements with an
+       /// optional transform and offset
+       void Vertices(
+               Model::Positions &vertex,
+               Model::Normals &normal,
+               Model::Indices &index
+       ) const;
+       void Vertices(
+               Model::Positions &vertex,
+               Model::Normals &normal,
+               Model::Indices &index,
+               const glm::mat4 &transform,
+               Model::Index idx_offset = 0
+       ) const;
+       void Vertices(
+               BlockModel::Positions &vertex,
+               BlockModel::Indices &index,
+               const glm::mat4 &transform,
+               BlockModel::Index idx_offset = 0
+       ) const;
+
+       /// the number of vertices this shape's outline has
+       size_t OutlineCount() const { return out_pos.size(); }
+       /// the number of vertex indices this shape's outline has
+       size_t OutlineIndexCount() const { return out_idx.size(); }
+
+       /// fill given buffers with this shape's outline's elements with
+       /// an optional offset
+       void Outline(
+               OutlineModel::Positions &vertex,
+               OutlineModel::Indices &index,
+               const OutlineModel::Position &offset = { 0.0f, 0.0f, 0.0f },
+               OutlineModel::Index idx_offset = 0
+       ) const;
+
+       /// Check if given ray would pass though this shape if it were
+       /// transformed with given matrix.
+       /// If true, dist and normal hold the intersection distance and
+       /// normal, otherwise their content is undefined.
+       virtual bool Intersects(
+               const Ray &,
+               const glm::mat4 &,
+               float &dist,
+               glm::vec3 &normal
+       ) const noexcept = 0;
+
+protected:
+       void SetShape(const Model::Positions &pos, const Model::Normals &nrm, const Model::Indices &idx) {
+               vtx_pos = pos;
+               vtx_nrm = nrm;
+               vtx_idx = idx;
+       }
+       void SetOutline(const OutlineModel::Positions &pos, const OutlineModel::Indices &idx) {
+               out_pos = pos;
+               out_idx = idx;
+       }
+
+private:
+       Model::Positions vtx_pos;
+       Model::Normals vtx_nrm;
+       Model::Indices vtx_idx;
+
+       OutlineModel::Positions out_pos;
+       OutlineModel::Indices out_idx;
+
+};
+
+}
+
+#endif
diff --git a/src/model/geometry.cpp b/src/model/geometry.cpp
new file mode 100644 (file)
index 0000000..416d930
--- /dev/null
@@ -0,0 +1,106 @@
+#include "geometry.hpp"
+
+#include <limits>
+
+
+namespace blank {
+
+bool Intersection(
+       const Ray &ray,
+       const AABB &aabb,
+       const glm::mat4 &M,
+       float *dist,
+       glm::vec3 *normal
+) noexcept {
+       float t_min = 0.0f;
+       float t_max = std::numeric_limits<float>::infinity();
+       const glm::vec3 aabb_pos(M[3].x, M[3].y, M[3].z);
+       const glm::vec3 delta = aabb_pos - ray.orig;
+
+       glm::vec3 t1(t_min, t_min, t_min), t2(t_max, t_max, t_max);
+
+       for (int i = 0; i < 3; ++i) {
+               const glm::vec3 axis(M[i].x, M[i].y, M[i].z);
+               const float e = glm::dot(axis, delta);
+               const float f = glm::dot(axis, ray.dir);
+
+               if (std::abs(f) > std::numeric_limits<float>::epsilon()) {
+                       t1[i] = (e + aabb.min[i]) / f;
+                       t2[i] = (e + aabb.max[i]) / f;
+
+                       t_min = std::max(t_min, std::min(t1[i], t2[i]));
+                       t_max = std::min(t_max, std::max(t1[i], t2[i]));
+
+                       if (t_max < t_min) {
+                               return false;
+                       }
+               } else {
+                       if (aabb.min[i] - e < 0.0f || -aabb.max[i] - e > 0.0f) {
+                               return false;
+                       }
+               }
+       }
+
+       glm::vec3 min_all(min(t1, t2));
+
+       if (dist) {
+               *dist = t_min;
+       }
+       if (normal) {
+               glm::vec4 norm(0.0f);
+               if (min_all.x > min_all.y) {
+                       if (min_all.x > min_all.z) {
+                               norm.x = t2.x < t1.x ? 1 : -1;
+                       } else {
+                               norm.z = t2.z < t1.z ? 1 : -1;
+                       }
+               } else if (min_all.y > min_all.z) {
+                       norm.y = t2.y < t1.y ? 1 : -1;
+               } else {
+                       norm.z = t2.z < t1.z ? 1 : -1;
+               }
+               norm = M * norm;
+               *normal = glm::vec3(norm);
+       }
+       return true;
+}
+
+bool CullTest(const AABB &box, const glm::mat4 &MVP) noexcept {
+       // transform corners into clip space
+       glm::vec4 corners[8] = {
+               { box.min.x, box.min.y, box.min.z, 1.0f },
+               { box.min.x, box.min.y, box.max.z, 1.0f },
+               { box.min.x, box.max.y, box.min.z, 1.0f },
+               { box.min.x, box.max.y, box.max.z, 1.0f },
+               { box.max.x, box.min.y, box.min.z, 1.0f },
+               { box.max.x, box.min.y, box.max.z, 1.0f },
+               { box.max.x, box.max.y, box.min.z, 1.0f },
+               { box.max.x, box.max.y, box.max.z, 1.0f },
+       };
+       for (glm::vec4 &corner : corners) {
+               corner = MVP * corner;
+               corner /= corner.w;
+       }
+
+       int hits[6] = { 0, 0, 0, 0, 0, 0 };
+
+       // check how many corners lie outside
+       for (const glm::vec4 &corner : corners) {
+               if (corner.x >  1.0f) ++hits[0];
+               if (corner.x < -1.0f) ++hits[1];
+               if (corner.y >  1.0f) ++hits[2];
+               if (corner.y < -1.0f) ++hits[3];
+               if (corner.z >  1.0f) ++hits[4];
+               if (corner.z < -1.0f) ++hits[5];
+       }
+
+       // if all corners are outside any given clip plane, the test is true
+       for (int hit : hits) {
+               if (hit == 8) return true;
+       }
+
+       // otherwise the box might still get culled completely, but can't say for sure ;)
+       return false;
+}
+
+}
diff --git a/src/model/geometry.hpp b/src/model/geometry.hpp
new file mode 100644 (file)
index 0000000..d6e3d28
--- /dev/null
@@ -0,0 +1,43 @@
+#ifndef BLANK_MODEL_GEOMETRY_H_
+#define BLANK_MODEL_GEOMETRY_H_
+
+#include <algorithm>
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+constexpr float PI = 3.141592653589793238462643383279502884;
+constexpr float PI_0p25 = PI * 0.25f;
+constexpr float PI_0p5 = PI * 0.5f;
+constexpr float PI_1p5 = PI * 1.5f;
+constexpr float PI_2p0 = PI * 2.0f;
+
+struct AABB {
+       glm::vec3 min;
+       glm::vec3 max;
+
+       void Adjust() noexcept {
+               if (max.x < min.x) std::swap(max.x, min.x);
+               if (max.y < min.y) std::swap(max.y, min.y);
+               if (max.z < min.z) std::swap(max.z, min.z);
+       }
+};
+
+struct Ray {
+       glm::vec3 orig;
+       glm::vec3 dir;
+};
+
+bool Intersection(
+       const Ray &,
+       const AABB &,
+       const glm::mat4 &M,
+       float *dist = nullptr,
+       glm::vec3 *normal = nullptr) noexcept;
+
+bool CullTest(const AABB &box, const glm::mat4 &MVP) noexcept;
+
+}
+
+#endif
diff --git a/src/model/model.cpp b/src/model/model.cpp
new file mode 100644 (file)
index 0000000..423e00d
--- /dev/null
@@ -0,0 +1,290 @@
+#include "BlockModel.hpp"
+#include "Model.hpp"
+#include "OutlineModel.hpp"
+
+#include <iostream>
+
+
+namespace blank {
+
+Model::Model() noexcept
+: va(0)
+, handle{}
+, count(0) {
+       glGenVertexArrays(1, &va);
+       glGenBuffers(ATTRIB_COUNT, handle);
+}
+
+Model::~Model() noexcept {
+       glDeleteBuffers(ATTRIB_COUNT, handle);
+       glDeleteVertexArrays(1, &va);
+}
+
+Model::Model(Model &&other) noexcept
+: va(other.va)
+, count(other.count) {
+       other.va = 0;
+       for (int i = 0; i < ATTRIB_COUNT; ++i) {
+               handle[i] = other.handle[i];
+               other.handle[i] = 0;
+       }
+}
+
+Model &Model::operator =(Model &&other) noexcept {
+       std::swap(va, other.va);
+       for (int i = 0; i < ATTRIB_COUNT; ++i) {
+               std::swap(handle[i], other.handle[i]);
+       }
+       count = other.count;
+       return *this;
+}
+
+void Model::Update(const Buffer &buf) noexcept {
+       glBindVertexArray(va);
+       glBindBuffer(GL_ARRAY_BUFFER, handle[ATTRIB_VERTEX]);
+       glBufferData(GL_ARRAY_BUFFER, buf.vertices.size() * sizeof(glm::vec3), buf.vertices.data(), GL_STATIC_DRAW);
+       glEnableVertexAttribArray(ATTRIB_VERTEX);
+       glVertexAttribPointer(
+               ATTRIB_VERTEX, // location (for shader)
+               3,             // size
+               GL_FLOAT,      // type
+               GL_FALSE,      // normalized
+               0,             // stride
+               nullptr        // offset
+       );
+
+#ifndef NDEBUG
+       if (buf.colors.size() < buf.vertices.size()) {
+               std::cerr << "Model: not enough colors!" << std::endl;
+       }
+#endif
+       glBindBuffer(GL_ARRAY_BUFFER, handle[ATTRIB_COLOR]);
+       glBufferData(GL_ARRAY_BUFFER, buf.colors.size() * sizeof(glm::vec3), buf.colors.data(), GL_STATIC_DRAW);
+       glEnableVertexAttribArray(ATTRIB_COLOR);
+       glVertexAttribPointer(
+               ATTRIB_COLOR,  // location (for shader)
+               3,             // size
+               GL_FLOAT,      // type
+               GL_FALSE,      // normalized
+               0,             // stride
+               nullptr        // offset
+       );
+
+#ifndef NDEBUG
+       if (buf.normals.size() < buf.vertices.size()) {
+               std::cerr << "Model: not enough normals!" << std::endl;
+       }
+#endif
+       glBindBuffer(GL_ARRAY_BUFFER, handle[ATTRIB_NORMAL]);
+       glBufferData(GL_ARRAY_BUFFER, buf.normals.size() * sizeof(glm::vec3), buf.normals.data(), GL_STATIC_DRAW);
+       glEnableVertexAttribArray(ATTRIB_NORMAL);
+       glVertexAttribPointer(
+               ATTRIB_NORMAL, // location (for shader)
+               3,             // size
+               GL_FLOAT,      // type
+               GL_FALSE,      // normalized
+               0,             // stride
+               nullptr        // offset
+       );
+
+       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle[ATTRIB_INDEX]);
+       glBufferData(GL_ELEMENT_ARRAY_BUFFER, buf.indices.size() * sizeof(Index), buf.indices.data(), GL_STATIC_DRAW);
+       count = buf.indices.size();
+}
+
+
+void Model::Draw() const noexcept {
+       glBindVertexArray(va);
+       glDrawElements(
+               GL_TRIANGLES,    // how
+               count,           // count
+               GL_UNSIGNED_INT, // type
+               nullptr          // offset
+       );
+}
+
+
+BlockModel::BlockModel() noexcept
+: va(0)
+, handle{}
+, count(0) {
+       glGenVertexArrays(1, &va);
+       glGenBuffers(ATTRIB_COUNT, handle);
+}
+
+BlockModel::~BlockModel() noexcept {
+       glDeleteBuffers(ATTRIB_COUNT, handle);
+       glDeleteVertexArrays(1, &va);
+}
+
+BlockModel::BlockModel(BlockModel &&other) noexcept
+: va(other.va)
+, count(other.count) {
+       other.va = 0;
+       for (int i = 0; i < ATTRIB_COUNT; ++i) {
+               handle[i] = other.handle[i];
+               other.handle[i] = 0;
+       }
+}
+
+BlockModel &BlockModel::operator =(BlockModel &&other) noexcept {
+       std::swap(va, other.va);
+       for (int i = 0; i < ATTRIB_COUNT; ++i) {
+               std::swap(handle[i], other.handle[i]);
+       }
+       count = other.count;
+       return *this;
+}
+
+void BlockModel::Update(const Buffer &buf) noexcept {
+       glBindVertexArray(va);
+       glBindBuffer(GL_ARRAY_BUFFER, handle[ATTRIB_VERTEX]);
+       glBufferData(GL_ARRAY_BUFFER, buf.vertices.size() * sizeof(glm::vec3), buf.vertices.data(), GL_STATIC_DRAW);
+       glEnableVertexAttribArray(ATTRIB_VERTEX);
+       glVertexAttribPointer(
+               ATTRIB_VERTEX, // location (for shader)
+               3,             // size
+               GL_FLOAT,      // type
+               GL_FALSE,      // normalized
+               0,             // stride
+               nullptr        // offset
+       );
+
+#ifndef NDEBUG
+       if (buf.colors.size() < buf.vertices.size()) {
+               std::cerr << "BlockModel: not enough colors!" << std::endl;
+       }
+#endif
+       glBindBuffer(GL_ARRAY_BUFFER, handle[ATTRIB_COLOR]);
+       glBufferData(GL_ARRAY_BUFFER, buf.colors.size() * sizeof(glm::vec3), buf.colors.data(), GL_STATIC_DRAW);
+       glEnableVertexAttribArray(ATTRIB_COLOR);
+       glVertexAttribPointer(
+               ATTRIB_COLOR,  // location (for shader)
+               3,             // size
+               GL_FLOAT,      // type
+               GL_FALSE,      // normalized
+               0,             // stride
+               nullptr        // offset
+       );
+
+#ifndef NDEBUG
+       if (buf.lights.size() < buf.vertices.size()) {
+               std::cerr << "BlockModel: not enough lights!" << std::endl;
+       }
+#endif
+       glBindBuffer(GL_ARRAY_BUFFER, handle[ATTRIB_LIGHT]);
+       glBufferData(GL_ARRAY_BUFFER, buf.lights.size() * sizeof(float), buf.lights.data(), GL_STATIC_DRAW);
+       glEnableVertexAttribArray(ATTRIB_LIGHT);
+       glVertexAttribPointer(
+               ATTRIB_LIGHT, // location (for shader)
+               1,            // size
+               GL_FLOAT,     // type
+               GL_FALSE,     // normalized
+               0,            // stride
+               nullptr       // offset
+       );
+
+       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle[ATTRIB_INDEX]);
+       glBufferData(GL_ELEMENT_ARRAY_BUFFER, buf.indices.size() * sizeof(Index), buf.indices.data(), GL_STATIC_DRAW);
+       count = buf.indices.size();
+}
+
+
+void BlockModel::Draw() const noexcept {
+       glBindVertexArray(va);
+       glDrawElements(
+               GL_TRIANGLES,    // how
+               count,           // count
+               GL_UNSIGNED_INT, // type
+               nullptr          // offset
+       );
+}
+
+OutlineModel::OutlineModel() noexcept
+: vertices()
+, colors()
+, indices()
+, va(0)
+, handle{}
+, dirty(false) {
+       glGenVertexArrays(1, &va);
+       glGenBuffers(ATTRIB_COUNT, handle);
+}
+
+OutlineModel::~OutlineModel() noexcept {
+       glDeleteBuffers(ATTRIB_COUNT, handle);
+       glDeleteVertexArrays(1, &va);
+}
+
+
+void OutlineModel::Clear() noexcept {
+       vertices.clear();
+       colors.clear();
+       indices.clear();
+       Invalidate();
+}
+
+void OutlineModel::Reserve(int v, int i) {
+       vertices.reserve(v);
+       colors.reserve(v);
+       indices.reserve(i);
+}
+
+
+void OutlineModel::Update() noexcept {
+       glBindBuffer(GL_ARRAY_BUFFER, handle[ATTRIB_VERTEX]);
+       glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(glm::vec3), vertices.data(), GL_STATIC_DRAW);
+       glEnableVertexAttribArray(ATTRIB_VERTEX);
+       glVertexAttribPointer(
+               ATTRIB_VERTEX, // location (for shader)
+               3,             // size
+               GL_FLOAT,      // type
+               GL_FALSE,      // normalized
+               0,             // stride
+               nullptr        // offset
+       );
+
+#ifndef NDEBUG
+       if (colors.size() < vertices.size()) {
+               std::cerr << "OutlineModel: not enough colors!" << std::endl;
+               colors.resize(vertices.size(), { 1, 0, 1 });
+       }
+#endif
+       glBindBuffer(GL_ARRAY_BUFFER, handle[ATTRIB_COLOR]);
+       glBufferData(GL_ARRAY_BUFFER, colors.size() * sizeof(glm::vec3), colors.data(), GL_STATIC_DRAW);
+       glEnableVertexAttribArray(ATTRIB_COLOR);
+       glVertexAttribPointer(
+               ATTRIB_COLOR,  // location (for shader)
+               3,             // size
+               GL_FLOAT,      // type
+               GL_FALSE,      // normalized
+               0,             // stride
+               nullptr        // offset
+       );
+
+       glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle[ATTRIB_INDEX]);
+       glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(Index), indices.data(), GL_STATIC_DRAW);
+
+       dirty = false;
+}
+
+
+void OutlineModel::Draw() noexcept {
+       glBindVertexArray(va);
+
+       if (dirty) {
+               Update();
+       }
+
+       glEnable(GL_LINE_SMOOTH);
+       glLineWidth(2.0f);
+
+       glDrawElements(
+               GL_LINES,          // how
+               indices.size(),    // count
+               GL_UNSIGNED_SHORT, // type
+               nullptr            // offset
+       );
+}
+
+}
diff --git a/src/model/shape.cpp b/src/model/shape.cpp
new file mode 100644 (file)
index 0000000..1c83149
--- /dev/null
@@ -0,0 +1,329 @@
+#include "Shape.hpp"
+#include "shapes.hpp"
+
+
+namespace blank {
+
+void Shape::Vertices(
+       Model::Positions &vertex,
+       Model::Normals &normal,
+       Model::Indices &index
+) const {
+       for (const auto &pos : vtx_pos) {
+               vertex.emplace_back(pos);
+       }
+       for (const auto &nrm : vtx_nrm) {
+               normal.emplace_back(nrm);
+       }
+       for (auto idx : vtx_idx) {
+               index.emplace_back(idx);
+       }
+}
+
+void Shape::Vertices(
+       Model::Positions &vertex,
+       Model::Normals &normal,
+       Model::Indices &index,
+       const glm::mat4 &transform,
+       Model::Index idx_offset
+) const {
+       for (const auto &pos : vtx_pos) {
+               vertex.emplace_back(transform * glm::vec4(pos, 1.0f));
+       }
+       for (const auto &nrm : vtx_nrm) {
+               normal.emplace_back(transform * glm::vec4(nrm, 0.0f));
+       }
+       for (auto idx : vtx_idx) {
+               index.emplace_back(idx_offset + idx);
+       }
+}
+
+void Shape::Vertices(
+       BlockModel::Positions &vertex,
+       BlockModel::Indices &index,
+       const glm::mat4 &transform,
+       BlockModel::Index idx_offset
+) const {
+       for (const auto &pos : vtx_pos) {
+               vertex.emplace_back(transform * glm::vec4(pos, 1.0f));
+       }
+       for (auto idx : vtx_idx) {
+               index.emplace_back(idx_offset + idx);
+       }
+}
+
+void Shape::Outline(
+       OutlineModel::Positions &vertex,
+       OutlineModel::Indices &index,
+       const OutlineModel::Position &elem_offset,
+       OutlineModel::Index idx_offset
+) const {
+       for (const auto &pos : out_pos) {
+               vertex.emplace_back(elem_offset + pos);
+       }
+       for (auto idx : out_idx) {
+               index.emplace_back(idx_offset + idx);
+       }
+}
+
+
+NullShape::NullShape()
+: Shape() {
+
+}
+
+bool NullShape::Intersects(
+       const Ray &,
+       const glm::mat4 &,
+       float &, glm::vec3 &
+) const noexcept {
+       return false;
+}
+
+
+CuboidShape::CuboidShape(const AABB &b)
+: Shape()
+, bb(b) {
+       bb.Adjust();
+       SetShape({
+               { bb.min.x, bb.min.y, bb.max.z }, // front
+               { bb.max.x, bb.min.y, bb.max.z },
+               { bb.min.x, bb.max.y, bb.max.z },
+               { bb.max.x, bb.max.y, bb.max.z },
+               { bb.min.x, bb.min.y, bb.min.z }, // back
+               { bb.min.x, bb.max.y, bb.min.z },
+               { bb.max.x, bb.min.y, bb.min.z },
+               { bb.max.x, bb.max.y, bb.min.z },
+               { bb.min.x, bb.max.y, bb.min.z }, // top
+               { bb.min.x, bb.max.y, bb.max.z },
+               { bb.max.x, bb.max.y, bb.min.z },
+               { bb.max.x, bb.max.y, bb.max.z },
+               { bb.min.x, bb.min.y, bb.min.z }, // bottom
+               { bb.max.x, bb.min.y, bb.min.z },
+               { bb.min.x, bb.min.y, bb.max.z },
+               { bb.max.x, bb.min.y, bb.max.z },
+               { bb.min.x, bb.min.y, bb.min.z }, // left
+               { bb.min.x, bb.min.y, bb.max.z },
+               { bb.min.x, bb.max.y, bb.min.z },
+               { bb.min.x, bb.max.y, bb.max.z },
+               { bb.max.x, bb.min.y, bb.min.z }, // right
+               { bb.max.x, bb.max.y, bb.min.z },
+               { bb.max.x, bb.min.y, bb.max.z },
+               { bb.max.x, bb.max.y, bb.max.z },
+       }, {
+               {  0.0f,  0.0f,  1.0f }, // front
+               {  0.0f,  0.0f,  1.0f },
+               {  0.0f,  0.0f,  1.0f },
+               {  0.0f,  0.0f,  1.0f },
+               {  0.0f,  0.0f, -1.0f }, // back
+               {  0.0f,  0.0f, -1.0f },
+               {  0.0f,  0.0f, -1.0f },
+               {  0.0f,  0.0f, -1.0f },
+               {  0.0f,  1.0f,  0.0f }, // top
+               {  0.0f,  1.0f,  0.0f },
+               {  0.0f,  1.0f,  0.0f },
+               {  0.0f,  1.0f,  0.0f },
+               {  0.0f, -1.0f,  0.0f }, // bottom
+               {  0.0f, -1.0f,  0.0f },
+               {  0.0f, -1.0f,  0.0f },
+               {  0.0f, -1.0f,  0.0f },
+               { -1.0f,  0.0f,  0.0f }, // left
+               { -1.0f,  0.0f,  0.0f },
+               { -1.0f,  0.0f,  0.0f },
+               { -1.0f,  0.0f,  0.0f },
+               {  1.0f,  0.0f,  0.0f }, // right
+               {  1.0f,  0.0f,  0.0f },
+               {  1.0f,  0.0f,  0.0f },
+               {  1.0f,  0.0f,  0.0f },
+       }, {
+                 0,  1,  2,  2,  1,  3, // front
+                 4,  5,  6,  6,  5,  7, // back
+                 8,  9, 10, 10,  9, 11, // top
+                12, 13, 14, 14, 13, 15, // bottom
+                16, 17, 18, 18, 17, 19, // left
+                20, 21, 22, 22, 21, 23, // right
+       });
+       SetOutline({
+               { bb.min.x, bb.min.y, bb.min.z }, // back
+               { bb.max.x, bb.min.y, bb.min.z },
+               { bb.min.x, bb.max.y, bb.min.z },
+               { bb.max.x, bb.max.y, bb.min.z },
+               { bb.min.x, bb.min.y, bb.max.z }, // front
+               { bb.max.x, bb.min.y, bb.max.z },
+               { bb.min.x, bb.max.y, bb.max.z },
+               { bb.max.x, bb.max.y, bb.max.z },
+       }, {
+               0, 1, 1, 3, 3, 2, 2, 0, // back
+               4, 5, 5, 7, 7, 6, 6, 4, // front
+               0, 4, 1, 5, 2, 6, 3, 7, // sides
+       });
+}
+
+bool CuboidShape::Intersects(
+       const Ray &ray,
+       const glm::mat4 &M,
+       float &dist, glm::vec3 &normal
+) const noexcept {
+       return Intersection(ray, bb, M, &dist, &normal);
+}
+
+
+StairShape::StairShape(const AABB &bb, const glm::vec2 &clip)
+: Shape()
+, top({ { bb.min.x, clip.y, bb.min.z }, { bb.max.x, bb.max.y, clip.x } })
+, bot({ bb.min, { bb.max.x, clip.y, bb.max.z } }) {
+       SetShape({
+               { top.min.x, top.min.y, top.max.z }, // front, upper
+               { top.max.x, top.min.y, top.max.z },
+               { top.min.x, top.max.y, top.max.z },
+               { top.max.x, top.max.y, top.max.z },
+               { bot.min.x, bot.min.y, bot.max.z }, // front, lower
+               { bot.max.x, bot.min.y, bot.max.z },
+               { bot.min.x, bot.max.y, bot.max.z },
+               { bot.max.x, bot.max.y, bot.max.z },
+               { bot.min.x, bot.min.y, bot.min.z }, // back
+               { bot.min.x, top.max.y, bot.min.z },
+               { top.max.x, bot.min.y, bot.min.z },
+               { top.max.x, top.max.y, bot.min.z },
+               { top.min.x, top.max.y, top.min.z }, // top, upper
+               { top.min.x, top.max.y, top.max.z },
+               { top.max.x, top.max.y, top.min.z },
+               { top.max.x, top.max.y, top.max.z },
+               { bot.min.x, bot.max.y, top.max.z }, // top, lower
+               { bot.min.x, bot.max.y, bot.max.z },
+               { bot.max.x, bot.max.y, top.max.z },
+               { bot.max.x, bot.max.y, bot.max.z },
+               { bot.min.x, bot.min.y, bot.min.z }, // bottom
+               { bot.max.x, bot.min.y, bot.min.z },
+               { bot.min.x, bot.min.y, bot.max.z },
+               { bot.max.x, bot.min.y, bot.max.z },
+               { top.min.x, top.min.y, top.min.z }, // left, upper
+               { top.min.x, top.min.y, top.max.z },
+               { top.min.x, top.max.y, top.min.z },
+               { top.min.x, top.max.y, top.max.z },
+               { bot.min.x, bot.min.y, bot.min.z }, // left, lower
+               { bot.min.x, bot.min.y, bot.max.z },
+               { bot.min.x, bot.max.y, bot.min.z },
+               { bot.min.x, bot.max.y, bot.max.z },
+               { top.max.x, top.min.y, top.min.z }, // right, upper
+               { top.max.x, top.max.y, top.min.z },
+               { top.max.x, top.min.y, top.max.z },
+               { top.max.x, top.max.y, top.max.z },
+               { bot.max.x, bot.min.y, bot.min.z }, // right, lower
+               { bot.max.x, bot.max.y, bot.min.z },
+               { bot.max.x, bot.min.y, bot.max.z },
+               { bot.max.x, bot.max.y, bot.max.z },
+       }, {
+               {  0.0f,  0.0f,  1.0f }, // front x2
+               {  0.0f,  0.0f,  1.0f },
+               {  0.0f,  0.0f,  1.0f },
+               {  0.0f,  0.0f,  1.0f },
+               {  0.0f,  0.0f,  1.0f },
+               {  0.0f,  0.0f,  1.0f },
+               {  0.0f,  0.0f,  1.0f },
+               {  0.0f,  0.0f,  1.0f },
+               {  0.0f,  0.0f, -1.0f }, // back
+               {  0.0f,  0.0f, -1.0f },
+               {  0.0f,  0.0f, -1.0f },
+               {  0.0f,  0.0f, -1.0f },
+               {  0.0f,  1.0f,  0.0f }, // top x2
+               {  0.0f,  1.0f,  0.0f },
+               {  0.0f,  1.0f,  0.0f },
+               {  0.0f,  1.0f,  0.0f },
+               {  0.0f,  1.0f,  0.0f },
+               {  0.0f,  1.0f,  0.0f },
+               {  0.0f,  1.0f,  0.0f },
+               {  0.0f,  1.0f,  0.0f },
+               {  0.0f, -1.0f,  0.0f }, // bottom
+               {  0.0f, -1.0f,  0.0f },
+               {  0.0f, -1.0f,  0.0f },
+               {  0.0f, -1.0f,  0.0f },
+               { -1.0f,  0.0f,  0.0f }, // left x2
+               { -1.0f,  0.0f,  0.0f },
+               { -1.0f,  0.0f,  0.0f },
+               { -1.0f,  0.0f,  0.0f },
+               { -1.0f,  0.0f,  0.0f },
+               { -1.0f,  0.0f,  0.0f },
+               { -1.0f,  0.0f,  0.0f },
+               { -1.0f,  0.0f,  0.0f },
+               {  1.0f,  0.0f,  0.0f }, // right x2
+               {  1.0f,  0.0f,  0.0f },
+               {  1.0f,  0.0f,  0.0f },
+               {  1.0f,  0.0f,  0.0f },
+               {  1.0f,  0.0f,  0.0f },
+               {  1.0f,  0.0f,  0.0f },
+               {  1.0f,  0.0f,  0.0f },
+               {  1.0f,  0.0f,  0.0f },
+       }, {
+                0,  1,  2,  2,  1,  3, // front, upper
+                4,  5,  6,  6,  5,  7, // front, lower
+                8,  9, 10, 10,  9, 11, // back
+               12, 13, 14, 14, 13, 15, // top, upper
+               16, 17, 18, 18, 17, 19, // top, lower
+               20, 21, 22, 22, 21, 23, // bottom
+               24, 25, 26, 26, 25, 27, // left, upper
+               28, 29, 30, 30, 29, 31, // left, lower
+               32, 33, 34, 34, 33, 35, // right, upper
+               36, 37, 38, 38, 37, 39, // right, lower
+       });
+       SetOutline({
+               { bot.min.x, bot.min.y, bot.min.z }, // bottom
+               { bot.max.x, bot.min.y, bot.min.z },
+               { bot.min.x, bot.min.y, bot.max.z },
+               { bot.max.x, bot.min.y, bot.max.z },
+               { bot.min.x, bot.max.y, top.max.z }, // middle
+               { bot.max.x, bot.max.y, top.max.z },
+               { bot.min.x, bot.max.y, bot.max.z },
+               { bot.max.x, bot.max.y, bot.max.z },
+               { top.min.x, top.max.y, top.min.z }, // top
+               { top.max.x, top.max.y, top.min.z },
+               { top.min.x, top.max.y, top.max.z },
+               { top.max.x, top.max.y, top.max.z },
+       }, {
+                0,  1,  1,  3,  3,  2,  2,  0, // bottom
+                4,  5,  5,  7,  7,  6,  6,  4, // middle
+                8,  9,  9, 11, 11, 10, 10 , 8, // top
+                0,  8,  4, 10,  2,  6, // verticals, btf
+                1,  9,  5, 11,  3,  7,
+       //       5,  8,  7, 10,
+       //       1,  9,  3, 11,
+       });
+}
+
+bool StairShape::Intersects(
+       const Ray &ray,
+       const glm::mat4 &M,
+       float &dist,
+       glm::vec3 &norm
+) const noexcept {
+       float top_dist, bot_dist;
+       glm::vec3 top_norm, bot_norm;
+       bool top_hit = Intersection(ray, top, M, &top_dist, &top_norm);
+       bool bot_hit = Intersection(ray, bot, M, &bot_dist, &bot_norm);
+
+       if (top_hit) {
+               if (bot_hit) {
+                       if (top_dist < bot_dist) {
+                               dist = top_dist;
+                               norm = top_norm;
+                               return true;
+                       } else {
+                               dist = bot_dist;
+                               norm = bot_norm;
+                               return true;
+                       }
+               } else {
+                       dist = top_dist;
+                       norm = top_norm;
+                       return true;
+               }
+       } else if (bot_hit) {
+               dist = bot_dist;
+               norm = bot_norm;
+               return true;
+       } else {
+               return false;
+       }
+}
+
+}
diff --git a/src/model/shapes.hpp b/src/model/shapes.hpp
new file mode 100644 (file)
index 0000000..b5e965a
--- /dev/null
@@ -0,0 +1,53 @@
+#ifndef BLANK_MODEL_SHAPES_HPP_
+#define BLANK_MODEL_SHAPES_HPP_
+
+#include "geometry.hpp"
+#include "Shape.hpp"
+
+#include <vector>
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class NullShape
+: public Shape {
+
+public:
+       NullShape();
+
+       bool Intersects(const Ray &, const glm::mat4 &, float &, glm::vec3 &) const noexcept override;
+
+};
+
+
+class CuboidShape
+: public Shape {
+
+public:
+       CuboidShape(const AABB &bounds);
+
+       bool Intersects(const Ray &, const glm::mat4 &, float &, glm::vec3 &) const noexcept override;
+
+private:
+       AABB bb;
+
+};
+
+
+class StairShape
+: public Shape {
+
+public:
+       StairShape(const AABB &bounds, const glm::vec2 &clip);
+
+       bool Intersects(const Ray &, const glm::mat4 &, float &, glm::vec3 &) const noexcept override;
+
+private:
+       AABB top, bot;
+
+};
+
+}
+
+#endif
diff --git a/src/noise.cpp b/src/noise.cpp
deleted file mode 100644 (file)
index d653d5d..0000000
+++ /dev/null
@@ -1,202 +0,0 @@
-#include "noise.hpp"
-
-#include <cmath>
-
-
-namespace {
-
-constexpr float one_third = 1.0f/3.0f;
-constexpr float one_sixth = 1.0f/6.0f;
-
-}
-
-namespace blank {
-
-GaloisLFSR::GaloisLFSR(std::uint64_t seed) noexcept
-: state(seed) {
-
-}
-
-bool GaloisLFSR::operator ()() noexcept {
-       bool result = state & 1;
-       state >>= 1;
-       if (result) {
-               state |= 0x8000000000000000;
-               state ^= mask;
-       } else {
-               state &= 0x7FFFFFFFFFFFFFFF;
-       }
-       return result;
-}
-
-
-SimplexNoise::SimplexNoise(unsigned int 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 },
-}) {
-       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);
-
-       glm::vec3 second, third;
-
-       if (relative.x >= relative.y) {
-               if (relative.y >= relative.z) {
-                       second = { 1, 0, 0 };
-                       third = { 1, 1, 0 };
-               } else if (relative.x >= relative.z) {
-                       second = { 1, 0, 0 };
-                       third = { 1, 0, 1 };
-               } else {
-                       second = { 0, 0, 1 };
-                       third = { 1, 0, 1 };
-               }
-       } else if (relative.y < relative.z) {
-               second = { 0, 0, 1 };
-               third = { 0, 1, 1 };
-       } else if (relative.x < relative.z) {
-               second = { 0, 1, 0 };
-               third = { 0, 1, 1 };
-       } else {
-               second = { 0, 1, 0 };
-               third = { 1, 1, 0 };
-       }
-
-       glm::vec3 offset[4] = {
-               in - unskewed,
-               relative - second + one_sixth,
-               relative - third + 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 = 0.6f - dot(offset[0], offset[0]);
-       if (t > 0.0f) {
-               t *= t;
-               int corner = Perm12(index[0] + Perm(index[1] + Perm(index[2])));
-               n += t * t * dot(Grad(corner), offset[0]);
-       }
-
-       // 1
-       t = 0.6f - dot(offset[1], offset[1]);
-       if (t > 0.0f) {
-               t *= t;
-               int corner = Perm12(index[0] + int(second.x) + Perm(index[1] + int(second.y) + Perm(index[2] + int(second.z))));
-               n += t * t * dot(Grad(corner), offset[1]);
-       }
-
-       // 2
-       t = 0.6f - dot(offset[2], offset[2]);
-       if (t > 0.0f) {
-               t *= t;
-               int corner = Perm12(index[0] + int(third.x) + Perm(index[1] + int(third.y) + Perm(index[2] + int(third.z))));
-               n += t * t * dot(Grad(corner), offset[2]);
-       }
-
-       // 3
-       t = 0.6f - dot(offset[3], offset[3]);
-       if (t > 0.0f) {
-               t *= t;
-               int 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];
-}
-
-
-WorleyNoise::WorleyNoise(unsigned int seed) noexcept
-: seed(seed)
-, num_points(8) {
-
-}
-
-float WorleyNoise::operator ()(const glm::vec3 &in) const noexcept {
-       glm::vec3 center = floor(in);
-
-       float closest = 1.0f;  // cannot be farther away than 1.0
-
-       for (int z = -1; z <= 1; ++z) {
-               for (int y = -1; y <= 1; ++y) {
-                       for (int x = -1; x <= 1; ++x) {
-                               glm::vec3 cube(center.x + x, center.y + y, center.z + z);
-                               unsigned int cube_rand =
-                                       (unsigned(cube.x) * 130223) ^
-                                       (unsigned(cube.y) * 159899) ^
-                                       (unsigned(cube.z) * 190717) ^
-                                       seed;
-
-                               for (int i = 0; i < num_points; ++i) {
-                                       glm::vec3 point(cube);
-                                       cube_rand = 190667 * cube_rand + 109807;
-                                       point.x += float(cube_rand % 262144) / 262144.0f;
-                                       cube_rand = 135899 * cube_rand + 189169;
-                                       point.y += float(cube_rand % 262144) / 262144.0f;
-                                       cube_rand = 159739 * cube_rand + 112139;
-                                       point.z += float(cube_rand % 262144) / 262144.0f;
-
-                                       glm::vec3 diff(in - point);
-                                       float distance = sqrt(dot(diff, diff));
-                                       if (distance < closest) {
-                                               closest = distance;
-                                       }
-                               }
-                       }
-               }
-       }
-
-       // closest ranges (0, 1), so normalizing to (-1,1) is trivial
-       // though heavily biased towards lower numbers
-       return 2.0f * closest - 1.0f;
-}
-
-}
diff --git a/src/noise.hpp b/src/noise.hpp
deleted file mode 100644 (file)
index cbfd48e..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-#ifndef BLANK_NOISE_HPP_
-#define BLANK_NOISE_HPP_
-
-#include <cstdint>
-#include <limits>
-#include <glm/glm.hpp>
-
-
-namespace blank {
-
-class GaloisLFSR {
-
-public:
-       // seed should be non-zero
-       explicit GaloisLFSR(std::uint64_t seed) noexcept;
-
-       // get the next bit
-       bool operator ()() noexcept;
-
-       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);
-       }
-
-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;
-
-};
-
-
-/// (3D only) adaptation of Stefan Gustavson's SimplexNoise java class
-class SimplexNoise {
-
-public:
-       explicit SimplexNoise(unsigned int 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];
-
-};
-
-
-/// implementation of Worley noise (aka Cell or Voroni noise)
-class WorleyNoise {
-
-public:
-       explicit WorleyNoise(unsigned int seed) noexcept;
-
-       float operator ()(const glm::vec3 &) const noexcept;
-
-private:
-       const unsigned int seed;
-       const int num_points;
-
-};
-
-
-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
diff --git a/src/rand/GaloisLFSR.hpp b/src/rand/GaloisLFSR.hpp
new file mode 100644 (file)
index 0000000..88a1d37
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef BLANK_RAND_GALOISLFSR_HPP_
+#define BLANK_RAND_GALOISLFSR_HPP_
+
+#include <cstdint>
+#include <limits>
+
+
+namespace blank {
+
+class GaloisLFSR {
+
+public:
+       // seed should be non-zero
+       explicit GaloisLFSR(std::uint64_t seed) noexcept;
+
+       // get the next bit
+       bool operator ()() noexcept;
+
+       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);
+       }
+
+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 (file)
index 0000000..7d5832f
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef BLANK_RAND_OCTAVENOISE_HPP_
+#define BLANK_RAND_OCTAVENOISE_HPP_
+
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+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
diff --git a/src/rand/SimplexNoise.hpp b/src/rand/SimplexNoise.hpp
new file mode 100644 (file)
index 0000000..7a7242b
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef BLANK_RAND_SIMPLEXNOISE_HPP_
+#define BLANK_RAND_SIMPLEXNOISE_HPP_
+
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+/// (3D only) adaptation of Stefan Gustavson's SimplexNoise java class
+class SimplexNoise {
+
+public:
+       explicit SimplexNoise(unsigned int 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];
+
+};
+
+}
+
+#endif
diff --git a/src/rand/WorleyNoise.hpp b/src/rand/WorleyNoise.hpp
new file mode 100644 (file)
index 0000000..20631c4
--- /dev/null
@@ -0,0 +1,25 @@
+#ifndef BLANK_RAND_WORLEYNOISE_HPP_
+#define BLANK_RAND_WORLEYNOISE_HPP_
+
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+/// implementation of Worley noise (aka Cell or Voroni noise)
+class WorleyNoise {
+
+public:
+       explicit WorleyNoise(unsigned int seed) noexcept;
+
+       float operator ()(const glm::vec3 &) const noexcept;
+
+private:
+       const unsigned int seed;
+       const int num_points;
+
+};
+
+}
+
+#endif
diff --git a/src/rand/noise.cpp b/src/rand/noise.cpp
new file mode 100644 (file)
index 0000000..de037ef
--- /dev/null
@@ -0,0 +1,204 @@
+#include "GaloisLFSR.hpp"
+#include "SimplexNoise.hpp"
+#include "WorleyNoise.hpp"
+
+#include <cmath>
+
+
+namespace {
+
+constexpr float one_third = 1.0f/3.0f;
+constexpr float one_sixth = 1.0f/6.0f;
+
+}
+
+namespace blank {
+
+GaloisLFSR::GaloisLFSR(std::uint64_t seed) noexcept
+: state(seed) {
+
+}
+
+bool GaloisLFSR::operator ()() noexcept {
+       bool result = state & 1;
+       state >>= 1;
+       if (result) {
+               state |= 0x8000000000000000;
+               state ^= mask;
+       } else {
+               state &= 0x7FFFFFFFFFFFFFFF;
+       }
+       return result;
+}
+
+
+SimplexNoise::SimplexNoise(unsigned int 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 },
+}) {
+       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);
+
+       glm::vec3 second, third;
+
+       if (relative.x >= relative.y) {
+               if (relative.y >= relative.z) {
+                       second = { 1, 0, 0 };
+                       third = { 1, 1, 0 };
+               } else if (relative.x >= relative.z) {
+                       second = { 1, 0, 0 };
+                       third = { 1, 0, 1 };
+               } else {
+                       second = { 0, 0, 1 };
+                       third = { 1, 0, 1 };
+               }
+       } else if (relative.y < relative.z) {
+               second = { 0, 0, 1 };
+               third = { 0, 1, 1 };
+       } else if (relative.x < relative.z) {
+               second = { 0, 1, 0 };
+               third = { 0, 1, 1 };
+       } else {
+               second = { 0, 1, 0 };
+               third = { 1, 1, 0 };
+       }
+
+       glm::vec3 offset[4] = {
+               in - unskewed,
+               relative - second + one_sixth,
+               relative - third + 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 = 0.6f - dot(offset[0], offset[0]);
+       if (t > 0.0f) {
+               t *= t;
+               int corner = Perm12(index[0] + Perm(index[1] + Perm(index[2])));
+               n += t * t * dot(Grad(corner), offset[0]);
+       }
+
+       // 1
+       t = 0.6f - dot(offset[1], offset[1]);
+       if (t > 0.0f) {
+               t *= t;
+               int corner = Perm12(index[0] + int(second.x) + Perm(index[1] + int(second.y) + Perm(index[2] + int(second.z))));
+               n += t * t * dot(Grad(corner), offset[1]);
+       }
+
+       // 2
+       t = 0.6f - dot(offset[2], offset[2]);
+       if (t > 0.0f) {
+               t *= t;
+               int corner = Perm12(index[0] + int(third.x) + Perm(index[1] + int(third.y) + Perm(index[2] + int(third.z))));
+               n += t * t * dot(Grad(corner), offset[2]);
+       }
+
+       // 3
+       t = 0.6f - dot(offset[3], offset[3]);
+       if (t > 0.0f) {
+               t *= t;
+               int 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];
+}
+
+
+WorleyNoise::WorleyNoise(unsigned int seed) noexcept
+: seed(seed)
+, num_points(8) {
+
+}
+
+float WorleyNoise::operator ()(const glm::vec3 &in) const noexcept {
+       glm::vec3 center = floor(in);
+
+       float closest = 1.0f;  // cannot be farther away than 1.0
+
+       for (int z = -1; z <= 1; ++z) {
+               for (int y = -1; y <= 1; ++y) {
+                       for (int x = -1; x <= 1; ++x) {
+                               glm::vec3 cube(center.x + x, center.y + y, center.z + z);
+                               unsigned int cube_rand =
+                                       (unsigned(cube.x) * 130223) ^
+                                       (unsigned(cube.y) * 159899) ^
+                                       (unsigned(cube.z) * 190717) ^
+                                       seed;
+
+                               for (int i = 0; i < num_points; ++i) {
+                                       glm::vec3 point(cube);
+                                       cube_rand = 190667 * cube_rand + 109807;
+                                       point.x += float(cube_rand % 262144) / 262144.0f;
+                                       cube_rand = 135899 * cube_rand + 189169;
+                                       point.y += float(cube_rand % 262144) / 262144.0f;
+                                       cube_rand = 159739 * cube_rand + 112139;
+                                       point.z += float(cube_rand % 262144) / 262144.0f;
+
+                                       glm::vec3 diff(in - point);
+                                       float distance = sqrt(dot(diff, diff));
+                                       if (distance < closest) {
+                                               closest = distance;
+                                       }
+                               }
+                       }
+               }
+       }
+
+       // closest ranges (0, 1), so normalizing to (-1,1) is trivial
+       // though heavily biased towards lower numbers
+       return 2.0f * closest - 1.0f;
+}
+
+}
diff --git a/src/runtime.cpp b/src/runtime.cpp
deleted file mode 100644 (file)
index 7475c90..0000000
+++ /dev/null
@@ -1,166 +0,0 @@
-#include "runtime.hpp"
-
-#include "app.hpp"
-
-#include <cctype>
-#include <cstdlib>
-#include <iostream>
-
-using namespace std;
-
-
-namespace blank {
-
-Runtime::Runtime() noexcept
-: name("blank")
-, mode(NORMAL)
-, n(0)
-, t(0)
-, config() {
-
-}
-
-
-void Runtime::ReadArgs(int argc, const char *const *argv) {
-       if (argc <= 0) return;
-       name = argv[0];
-
-       bool options = true;
-       bool error = false;
-
-       for (int i = 1; i < argc; ++i) {
-               const char *arg = argv[i];
-               if (!arg || arg[0] == '\0') {
-                       cerr << "warning: found empty argument at position " << i << endl;
-                       continue;
-               }
-               if (options && arg[0] == '-') {
-                       if (arg[1] == '\0') {
-                               cerr << "warning: incomplete option list at position " << i << endl;
-                       } else if (arg[1] == '-') {
-                               if (arg[2] == '\0') {
-                                       // stopper
-                                       options = false;
-                               } else {
-                                       // long option
-                                       if (strcmp(arg + 2, "no-vsync") == 0) {
-                                               config.vsync = false;
-                                       } else if (strcmp(arg + 2, "no-keyboard") == 0) {
-                                               config.interface.keyboard_disabled = true;
-                                       } else if (strcmp(arg + 2, "no-mouse") == 0) {
-                                               config.interface.mouse_disabled = true;
-                                       } else if (strcmp(arg + 2, "no-hud") == 0) {
-                                               config.interface.visual_disabled = true;
-                                       } else {
-                                               cerr << "unknown option " << arg << endl;
-                                               error = true;
-                                       }
-                               }
-                       } else {
-                               // short options
-                               for (int j = 1; arg[j] != '\0'; ++j) {
-                                       switch (arg[j]) {
-                                               case 'd':
-                                                       config.doublebuf = false;
-                                                       break;
-                                               case 'm':
-                                                       ++i;
-                                                       if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
-                                                               cerr << "missing argument to -m" << endl;
-                                                               error = true;
-                                                       } else {
-                                                               config.multisampling = strtoul(argv[i], nullptr, 10);
-                                                       }
-                                                       break;
-                                               case 'n':
-                                                       ++i;
-                                                       if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
-                                                               cerr << "missing argument to -n" << endl;
-                                                               error = true;
-                                                       } else {
-                                                               n = strtoul(argv[i], nullptr, 10);
-                                                       }
-                                                       break;
-                                               case 's':
-                                                       ++i;
-                                                       if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
-                                                               cerr << "missing argument to -s" << endl;
-                                                               error = true;
-                                                       } else {
-                                                               config.world.gen.solid_seed = strtoul(argv[i], nullptr, 10);
-                                                               config.world.gen.type_seed = config.world.gen.solid_seed;
-                                                       }
-                                                       break;
-                                               case 't':
-                                                       ++i;
-                                                       if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
-                                                               cerr << "missing argument to -t" << endl;
-                                                               error = true;
-                                                       } else {
-                                                               t = strtoul(argv[i], nullptr, 10);
-                                                       }
-                                                       break;
-                                               case '-':
-                                                       // stopper
-                                                       options = false;
-                                                       break;
-                                               default:
-                                                       cerr << "unknown option " << arg[j] << endl;
-                                                       error = true;
-                                                       break;
-                                       }
-                               }
-                       }
-               } else if (isdigit(arg[0])) {
-                       // positional number interpreted as -n
-                       n = strtoul(arg, nullptr, 10);
-               } else {
-                       cerr << "unable to interpret argument "
-                               << i << " (" << arg << ")" << endl;
-                       error = true;
-               }
-       }
-
-       if (error) {
-               mode = ERROR;
-               return;
-       }
-
-       if (n > 0) {
-               if (t > 0) {
-                       mode = FIXED_FRAME_LIMIT;
-               } else {
-                       mode = FRAME_LIMIT;
-               }
-       } else if (t > 0) {
-               mode = TIME_LIMIT;
-       } else {
-               mode = NORMAL;
-       }
-}
-
-int Runtime::Execute() {
-       if (mode == ERROR) {
-               return 1;
-       }
-
-       Application app(config);
-       switch (mode) {
-               default:
-               case NORMAL:
-                       app.Run();
-                       break;
-               case FRAME_LIMIT:
-                       app.RunN(n);
-                       break;
-               case TIME_LIMIT:
-                       app.RunT(t);
-                       break;
-               case FIXED_FRAME_LIMIT:
-                       app.RunS(n, t);
-                       break;
-       }
-       return 0;
-}
-
-}
diff --git a/src/runtime.hpp b/src/runtime.hpp
deleted file mode 100644 (file)
index 014cd32..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-#ifndef BLANK_RUNTIME_HPP_
-#define BLANK_RUNTIME_HPP_
-
-#include "app.hpp"
-
-#include <cstddef>
-
-
-namespace blank {
-
-class Runtime {
-
-public:
-       enum Mode {
-               NORMAL,
-               FRAME_LIMIT,
-               TIME_LIMIT,
-               FIXED_FRAME_LIMIT,
-               ERROR,
-       };
-
-       Runtime() noexcept;
-
-       void ReadArgs(int argc, const char *const *argv);
-
-       int Execute();
-
-private:
-       const char *name;
-       Mode mode;
-       std::size_t n;
-       std::size_t t;
-       Application::Config config;
-
-};
-
-}
-
-#endif
diff --git a/src/shader.cpp b/src/shader.cpp
deleted file mode 100644 (file)
index b2ccb8b..0000000
+++ /dev/null
@@ -1,355 +0,0 @@
-#include "shader.hpp"
-
-#include "init.hpp"
-
-#include <algorithm>
-#include <iostream>
-#include <memory>
-#include <ostream>
-#include <stdexcept>
-#include <string>
-
-
-namespace {
-
-void gl_error(std::string msg) {
-       const GLubyte *errBegin = gluErrorString(glGetError());
-       if (errBegin && *errBegin != '\0') {
-               const GLubyte *errEnd = errBegin;
-               while (*errEnd != '\0') {
-                       ++errEnd;
-               }
-               msg += ": ";
-               msg.append(errBegin, errEnd);
-       }
-       throw std::runtime_error(msg);
-}
-
-}
-
-namespace blank {
-
-Shader::Shader(GLenum type)
-: handle(glCreateShader(type)) {
-       if (handle == 0) {
-               gl_error("glCreateShader");
-       }
-}
-
-Shader::~Shader() {
-       if (handle != 0) {
-               glDeleteShader(handle);
-       }
-}
-
-Shader::Shader(Shader &&other) noexcept
-: handle(other.handle) {
-       other.handle = 0;
-}
-
-Shader &Shader::operator =(Shader &&other) noexcept {
-       std::swap(handle, other.handle);
-       return *this;
-}
-
-
-void Shader::Source(const GLchar *src) noexcept {
-       const GLchar* src_arr[] = { src };
-       glShaderSource(handle, 1, src_arr, nullptr);
-}
-
-void Shader::Compile() noexcept {
-       glCompileShader(handle);
-}
-
-bool Shader::Compiled() const noexcept {
-       GLint compiled = GL_FALSE;
-       glGetShaderiv(handle, GL_COMPILE_STATUS, &compiled);
-       return compiled == GL_TRUE;
-}
-
-void Shader::Log(std::ostream &out) const {
-       int log_len = 0, max_len = 0;
-       glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &max_len);
-       std::unique_ptr<char[]> log(new char[max_len]);
-       glGetShaderInfoLog(handle, max_len, &log_len, log.get());
-       out.write(log.get(), log_len);
-}
-
-
-void Shader::AttachToProgram(GLuint id) const noexcept {
-       glAttachShader(id, handle);
-}
-
-
-Program::Program()
-: handle(glCreateProgram()) {
-       if (handle == 0) {
-               gl_error("glCreateProgram");
-       }
-}
-
-Program::~Program() {
-       if (handle != 0) {
-               glDeleteProgram(handle);
-       }
-}
-
-
-const Shader &Program::LoadShader(GLenum type, const GLchar *src) {
-       shaders.emplace_back(type);
-       Shader &shader = shaders.back();
-       shader.Source(src);
-       shader.Compile();
-       if (!shader.Compiled()) {
-               shader.Log(std::cerr);
-               throw std::runtime_error("compile shader");
-       }
-       Attach(shader);
-       return shader;
-}
-
-void Program::Attach(Shader &shader) noexcept {
-       shader.AttachToProgram(handle);
-}
-
-void Program::Link() noexcept {
-       glLinkProgram(handle);
-}
-
-bool Program::Linked() const noexcept {
-       GLint linked = GL_FALSE;
-       glGetProgramiv(handle, GL_LINK_STATUS, &linked);
-       return linked == GL_TRUE;
-}
-
-void Program::Log(std::ostream &out) const {
-       int log_len = 0, max_len = 0;
-       glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &max_len);
-       std::unique_ptr<char[]> log(new char[max_len]);
-       glGetProgramInfoLog(handle, max_len, &log_len, log.get());
-       out.write(log.get(), log_len);
-}
-
-
-GLint Program::AttributeLocation(const GLchar *name) const noexcept {
-       return glGetAttribLocation(handle, name);
-}
-
-GLint Program::UniformLocation(const GLchar *name) const noexcept {
-       return glGetUniformLocation(handle, name);
-}
-
-
-DirectionalLighting::DirectionalLighting()
-: program()
-, light_direction(1.0f, 3.0f, 2.0f)
-, light_color(0.9f, 0.9f, 0.9f)
-, vp(1.0f)
-, m_handle(0)
-, mv_handle(0)
-, mvp_handle(0)
-, light_direction_handle(0)
-, light_color_handle(0)
-, fog_density_handle(0) {
-       program.LoadShader(
-               GL_VERTEX_SHADER,
-               "#version 330 core\n"
-               "layout(location = 0) in vec3 vtx_position;\n"
-               "layout(location = 1) in vec3 vtx_color;\n"
-               "layout(location = 2) in vec3 vtx_normal;\n"
-               "uniform mat4 M;\n"
-               "uniform mat4 MV;\n"
-               "uniform mat4 MVP;\n"
-               "out vec3 frag_color;\n"
-               "out vec3 vtx_viewspace;\n"
-               "out vec3 normal;\n"
-               "void main() {\n"
-                       "gl_Position = MVP * vec4(vtx_position, 1);\n"
-                       "vtx_viewspace = (MV * vec4(vtx_position, 1)).xyz;\n"
-                       "normal = (M * vec4(vtx_normal, 0)).xyz;\n"
-                       "frag_color = vtx_color;\n"
-               "}\n"
-       );
-       program.LoadShader(
-               GL_FRAGMENT_SHADER,
-               "#version 330 core\n"
-               "in vec3 frag_color;\n"
-               "in vec3 vtx_viewspace;\n"
-               "in vec3 normal;\n"
-               "uniform vec3 light_direction;\n"
-               "uniform vec3 light_color;\n"
-               "uniform float fog_density;\n"
-               "out vec3 color;\n"
-               "void main() {\n"
-                       "vec3 ambient = vec3(0.1, 0.1, 0.1) * frag_color;\n"
-                       // this should be the same as the clear color, otherwise looks really weird
-                       "vec3 fog_color = vec3(0, 0, 0);\n"
-                       "float e = 2.718281828;\n"
-                       "vec3 n = normalize(normal);\n"
-                       "vec3 l = normalize(light_direction);\n"
-                       "float cos_theta = clamp(dot(n, l), 0, 1);\n"
-                       "vec3 reflect_color = ambient + frag_color * light_color * cos_theta;\n"
-                       "float value = pow(e, -pow(fog_density * length(vtx_viewspace), 5));"
-                       "color = mix(fog_color, reflect_color, value);\n"
-               "}\n"
-       );
-       program.Link();
-       if (!program.Linked()) {
-               program.Log(std::cerr);
-               throw std::runtime_error("link program");
-       }
-
-       m_handle = program.UniformLocation("M");
-       mv_handle = program.UniformLocation("MV");
-       mvp_handle = program.UniformLocation("MVP");
-       light_direction_handle = program.UniformLocation("light_direction");
-       light_color_handle = program.UniformLocation("light_color");
-       fog_density_handle = program.UniformLocation("fog_density");
-}
-
-
-void DirectionalLighting::Activate() noexcept {
-       GLContext::EnableDepthTest();
-       GLContext::EnableBackfaceCulling();
-       program.Use();
-
-       glUniform3f(light_direction_handle, light_direction.x, light_direction.y, light_direction.z);
-       glUniform3f(light_color_handle, light_color.x, light_color.y, light_color.z);
-}
-
-void DirectionalLighting::SetM(const glm::mat4 &m) noexcept {
-       glm::mat4 mv(view * m);
-       glm::mat4 mvp(vp * m);
-       glUniformMatrix4fv(m_handle, 1, GL_FALSE, &m[0][0]);
-       glUniformMatrix4fv(mv_handle, 1, GL_FALSE, &mv[0][0]);
-       glUniformMatrix4fv(mvp_handle, 1, GL_FALSE, &mvp[0][0]);
-}
-
-void DirectionalLighting::SetLightDirection(const glm::vec3 &dir) noexcept {
-       light_direction = -dir;
-       glUniform3f(light_direction_handle, light_direction.x, light_direction.y, light_direction.z);
-}
-
-void DirectionalLighting::SetFogDensity(float f) noexcept {
-       fog_density = f;
-       glUniform1f(fog_density_handle, fog_density);
-}
-
-void DirectionalLighting::SetProjection(const glm::mat4 &p) noexcept {
-       projection = p;
-       vp = p * view;
-}
-
-void DirectionalLighting::SetView(const glm::mat4 &v) noexcept {
-       view = v;
-       vp = projection * v;
-}
-
-void DirectionalLighting::SetVP(const glm::mat4 &v, const glm::mat4 &p) noexcept {
-       projection = p;
-       view = v;
-       vp = p * v;
-}
-
-void DirectionalLighting::SetMVP(const glm::mat4 &m, const glm::mat4 &v, const glm::mat4 &p) noexcept {
-       SetVP(v, p);
-       SetM(m);
-}
-
-
-BlockLighting::BlockLighting()
-: program()
-, vp(1.0f)
-, mv_handle(0)
-, mvp_handle(0)
-, fog_density_handle(0) {
-       program.LoadShader(
-               GL_VERTEX_SHADER,
-               "#version 330 core\n"
-               "layout(location = 0) in vec3 vtx_position;\n"
-               "layout(location = 1) in vec3 vtx_color;\n"
-               "layout(location = 2) in float vtx_light;\n"
-               "uniform mat4 MV;\n"
-               "uniform mat4 MVP;\n"
-               "out vec3 frag_color;\n"
-               "out vec3 vtx_viewspace;\n"
-               "out float frag_light;\n"
-               "void main() {\n"
-                       "gl_Position = MVP * vec4(vtx_position, 1);\n"
-                       "frag_color = vtx_color;\n"
-                       "vtx_viewspace = (MV * vec4(vtx_position, 1)).xyz;\n"
-                       "frag_light = vtx_light;\n"
-               "}\n"
-       );
-       program.LoadShader(
-               GL_FRAGMENT_SHADER,
-               "#version 330 core\n"
-               "in vec3 frag_color;\n"
-               "in vec3 vtx_viewspace;\n"
-               "in float frag_light;\n"
-               "uniform float fog_density;\n"
-               "out vec3 color;\n"
-               "void main() {\n"
-                       "vec3 ambient = vec3(0.1, 0.1, 0.1) * frag_color;\n"
-                       "float light_power = clamp(pow(0.8, 15 - frag_light), 0, 1);\n"
-                       "vec3 fog_color = vec3(0, 0, 0);\n"
-                       "float e = 2.718281828;\n"
-                       //"vec3 reflect_color = ambient + frag_color * light_power;\n"
-                       "vec3 reflect_color = frag_color * light_power;\n"
-                       "float value = pow(e, -pow(fog_density * length(vtx_viewspace), 5));"
-                       "color = mix(fog_color, reflect_color, value);\n"
-               "}\n"
-       );
-       program.Link();
-       if (!program.Linked()) {
-               program.Log(std::cerr);
-               throw std::runtime_error("link program");
-       }
-
-       mv_handle = program.UniformLocation("MV");
-       mvp_handle = program.UniformLocation("MVP");
-       fog_density_handle = program.UniformLocation("fog_density");
-}
-
-
-void BlockLighting::Activate() noexcept {
-       GLContext::EnableDepthTest();
-       GLContext::EnableBackfaceCulling();
-       program.Use();
-}
-
-void BlockLighting::SetM(const glm::mat4 &m) noexcept {
-       glm::mat4 mv(view * m);
-       glm::mat4 mvp(vp * m);
-       glUniformMatrix4fv(mv_handle, 1, GL_FALSE, &mv[0][0]);
-       glUniformMatrix4fv(mvp_handle, 1, GL_FALSE, &mvp[0][0]);
-}
-
-void BlockLighting::SetFogDensity(float f) noexcept {
-       fog_density = f;
-       glUniform1f(fog_density_handle, fog_density);
-}
-
-void BlockLighting::SetProjection(const glm::mat4 &p) noexcept {
-       projection = p;
-       vp = p * view;
-}
-
-void BlockLighting::SetView(const glm::mat4 &v) noexcept {
-       view = v;
-       vp = projection * v;
-}
-
-void BlockLighting::SetVP(const glm::mat4 &v, const glm::mat4 &p) noexcept {
-       projection = p;
-       view = v;
-       vp = p * v;
-}
-
-void BlockLighting::SetMVP(const glm::mat4 &m, const glm::mat4 &v, const glm::mat4 &p) noexcept {
-       SetVP(v, p);
-       SetM(m);
-}
-
-}
diff --git a/src/shader.hpp b/src/shader.hpp
deleted file mode 100644 (file)
index df625c6..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-#ifndef BLANK_SHADER_HPP_
-#define BLANK_SHADER_HPP_
-
-#include <iosfwd>
-#include <list>
-#include <GL/glew.h>
-#include <glm/glm.hpp>
-
-
-namespace blank {
-
-class Shader {
-
-public:
-       explicit Shader(GLenum type);
-       ~Shader();
-
-       Shader(Shader &&) noexcept;
-       Shader &operator =(Shader &&) noexcept;
-
-       Shader(const Shader &) = delete;
-       Shader &operator =(const Shader &) = delete;
-
-       void Source(const GLchar *src) noexcept;
-       void Compile() noexcept;
-       bool Compiled() const noexcept;
-       void Log(std::ostream &) const;
-
-       void AttachToProgram(GLuint id) const noexcept;
-
-private:
-       GLuint handle;
-
-};
-
-
-class Program {
-
-public:
-       Program();
-       ~Program();
-
-       Program(const Program &) = delete;
-       Program &operator =(const Program &) = delete;
-
-       const Shader &LoadShader(GLenum type, const GLchar *src);
-       void Attach(Shader &) noexcept;
-       void Link() noexcept;
-       bool Linked() const noexcept;
-       void Log(std::ostream &) const;
-
-       GLint AttributeLocation(const GLchar *name) const noexcept;
-       GLint UniformLocation(const GLchar *name) const noexcept;
-
-       void Use() const noexcept { glUseProgram(handle); }
-
-private:
-       GLuint handle;
-       std::list<Shader> shaders;
-
-};
-
-
-class DirectionalLighting {
-
-public:
-       DirectionalLighting();
-
-       void Activate() noexcept;
-
-       void SetLightDirection(const glm::vec3 &) noexcept;
-
-       void SetFogDensity(float) noexcept;
-
-       void SetM(const glm::mat4 &m) noexcept;
-       void SetProjection(const glm::mat4 &p) noexcept;
-       void SetView(const glm::mat4 &v) noexcept;
-       void SetVP(const glm::mat4 &v, const glm::mat4 &p) noexcept;
-       void SetMVP(const glm::mat4 &m, const glm::mat4 &v, const glm::mat4 &p) noexcept;
-
-       const glm::mat4 &Projection() const noexcept { return projection; }
-       const glm::mat4 &View() const noexcept { return view; }
-       const glm::mat4 &GetVP() const noexcept { return vp; }
-
-private:
-       Program program;
-
-       glm::vec3 light_direction;
-       glm::vec3 light_color;
-
-       float fog_density;
-
-       glm::mat4 projection;
-       glm::mat4 view;
-       glm::mat4 vp;
-
-       GLuint m_handle;
-       GLuint mv_handle;
-       GLuint mvp_handle;
-       GLuint light_direction_handle;
-       GLuint light_color_handle;
-       GLuint fog_density_handle;
-
-};
-
-class BlockLighting {
-
-public:
-       BlockLighting();
-
-       void Activate() noexcept;
-
-       void SetFogDensity(float) noexcept;
-
-       void SetM(const glm::mat4 &m) noexcept;
-       void SetProjection(const glm::mat4 &p) noexcept;
-       void SetView(const glm::mat4 &v) noexcept;
-       void SetVP(const glm::mat4 &v, const glm::mat4 &p) noexcept;
-       void SetMVP(const glm::mat4 &m, const glm::mat4 &v, const glm::mat4 &p) noexcept;
-
-       const glm::mat4 &Projection() const noexcept { return projection; }
-       const glm::mat4 &View() const noexcept { return view; }
-       const glm::mat4 &GetVP() const noexcept { return vp; }
-
-private:
-       Program program;
-
-       float fog_density;
-
-       glm::mat4 projection;
-       glm::mat4 view;
-       glm::mat4 vp;
-
-       GLuint mv_handle;
-       GLuint mvp_handle;
-       GLuint light_direction_handle;
-       GLuint light_color_handle;
-       GLuint fog_density_handle;
-
-};
-
-}
-
-#endif
diff --git a/src/shape.cpp b/src/shape.cpp
deleted file mode 100644 (file)
index 59e4f1f..0000000
+++ /dev/null
@@ -1,328 +0,0 @@
-#include "shape.hpp"
-
-
-namespace blank {
-
-void Shape::Vertices(
-       Model::Positions &vertex,
-       Model::Normals &normal,
-       Model::Indices &index
-) const {
-       for (const auto &pos : vtx_pos) {
-               vertex.emplace_back(pos);
-       }
-       for (const auto &nrm : vtx_nrm) {
-               normal.emplace_back(nrm);
-       }
-       for (auto idx : vtx_idx) {
-               index.emplace_back(idx);
-       }
-}
-
-void Shape::Vertices(
-       Model::Positions &vertex,
-       Model::Normals &normal,
-       Model::Indices &index,
-       const glm::mat4 &transform,
-       Model::Index idx_offset
-) const {
-       for (const auto &pos : vtx_pos) {
-               vertex.emplace_back(transform * glm::vec4(pos, 1.0f));
-       }
-       for (const auto &nrm : vtx_nrm) {
-               normal.emplace_back(transform * glm::vec4(nrm, 0.0f));
-       }
-       for (auto idx : vtx_idx) {
-               index.emplace_back(idx_offset + idx);
-       }
-}
-
-void Shape::Vertices(
-       BlockModel::Positions &vertex,
-       BlockModel::Indices &index,
-       const glm::mat4 &transform,
-       BlockModel::Index idx_offset
-) const {
-       for (const auto &pos : vtx_pos) {
-               vertex.emplace_back(transform * glm::vec4(pos, 1.0f));
-       }
-       for (auto idx : vtx_idx) {
-               index.emplace_back(idx_offset + idx);
-       }
-}
-
-void Shape::Outline(
-       OutlineModel::Positions &vertex,
-       OutlineModel::Indices &index,
-       const OutlineModel::Position &elem_offset,
-       OutlineModel::Index idx_offset
-) const {
-       for (const auto &pos : out_pos) {
-               vertex.emplace_back(elem_offset + pos);
-       }
-       for (auto idx : out_idx) {
-               index.emplace_back(idx_offset + idx);
-       }
-}
-
-
-NullShape::NullShape()
-: Shape() {
-
-}
-
-bool NullShape::Intersects(
-       const Ray &,
-       const glm::mat4 &,
-       float &, glm::vec3 &
-) const noexcept {
-       return false;
-}
-
-
-CuboidShape::CuboidShape(const AABB &b)
-: Shape()
-, bb(b) {
-       bb.Adjust();
-       SetShape({
-               { bb.min.x, bb.min.y, bb.max.z }, // front
-               { bb.max.x, bb.min.y, bb.max.z },
-               { bb.min.x, bb.max.y, bb.max.z },
-               { bb.max.x, bb.max.y, bb.max.z },
-               { bb.min.x, bb.min.y, bb.min.z }, // back
-               { bb.min.x, bb.max.y, bb.min.z },
-               { bb.max.x, bb.min.y, bb.min.z },
-               { bb.max.x, bb.max.y, bb.min.z },
-               { bb.min.x, bb.max.y, bb.min.z }, // top
-               { bb.min.x, bb.max.y, bb.max.z },
-               { bb.max.x, bb.max.y, bb.min.z },
-               { bb.max.x, bb.max.y, bb.max.z },
-               { bb.min.x, bb.min.y, bb.min.z }, // bottom
-               { bb.max.x, bb.min.y, bb.min.z },
-               { bb.min.x, bb.min.y, bb.max.z },
-               { bb.max.x, bb.min.y, bb.max.z },
-               { bb.min.x, bb.min.y, bb.min.z }, // left
-               { bb.min.x, bb.min.y, bb.max.z },
-               { bb.min.x, bb.max.y, bb.min.z },
-               { bb.min.x, bb.max.y, bb.max.z },
-               { bb.max.x, bb.min.y, bb.min.z }, // right
-               { bb.max.x, bb.max.y, bb.min.z },
-               { bb.max.x, bb.min.y, bb.max.z },
-               { bb.max.x, bb.max.y, bb.max.z },
-       }, {
-               {  0.0f,  0.0f,  1.0f }, // front
-               {  0.0f,  0.0f,  1.0f },
-               {  0.0f,  0.0f,  1.0f },
-               {  0.0f,  0.0f,  1.0f },
-               {  0.0f,  0.0f, -1.0f }, // back
-               {  0.0f,  0.0f, -1.0f },
-               {  0.0f,  0.0f, -1.0f },
-               {  0.0f,  0.0f, -1.0f },
-               {  0.0f,  1.0f,  0.0f }, // top
-               {  0.0f,  1.0f,  0.0f },
-               {  0.0f,  1.0f,  0.0f },
-               {  0.0f,  1.0f,  0.0f },
-               {  0.0f, -1.0f,  0.0f }, // bottom
-               {  0.0f, -1.0f,  0.0f },
-               {  0.0f, -1.0f,  0.0f },
-               {  0.0f, -1.0f,  0.0f },
-               { -1.0f,  0.0f,  0.0f }, // left
-               { -1.0f,  0.0f,  0.0f },
-               { -1.0f,  0.0f,  0.0f },
-               { -1.0f,  0.0f,  0.0f },
-               {  1.0f,  0.0f,  0.0f }, // right
-               {  1.0f,  0.0f,  0.0f },
-               {  1.0f,  0.0f,  0.0f },
-               {  1.0f,  0.0f,  0.0f },
-       }, {
-                 0,  1,  2,  2,  1,  3, // front
-                 4,  5,  6,  6,  5,  7, // back
-                 8,  9, 10, 10,  9, 11, // top
-                12, 13, 14, 14, 13, 15, // bottom
-                16, 17, 18, 18, 17, 19, // left
-                20, 21, 22, 22, 21, 23, // right
-       });
-       SetOutline({
-               { bb.min.x, bb.min.y, bb.min.z }, // back
-               { bb.max.x, bb.min.y, bb.min.z },
-               { bb.min.x, bb.max.y, bb.min.z },
-               { bb.max.x, bb.max.y, bb.min.z },
-               { bb.min.x, bb.min.y, bb.max.z }, // front
-               { bb.max.x, bb.min.y, bb.max.z },
-               { bb.min.x, bb.max.y, bb.max.z },
-               { bb.max.x, bb.max.y, bb.max.z },
-       }, {
-               0, 1, 1, 3, 3, 2, 2, 0, // back
-               4, 5, 5, 7, 7, 6, 6, 4, // front
-               0, 4, 1, 5, 2, 6, 3, 7, // sides
-       });
-}
-
-bool CuboidShape::Intersects(
-       const Ray &ray,
-       const glm::mat4 &M,
-       float &dist, glm::vec3 &normal
-) const noexcept {
-       return Intersection(ray, bb, M, &dist, &normal);
-}
-
-
-StairShape::StairShape(const AABB &bb, const glm::vec2 &clip)
-: Shape()
-, top({ { bb.min.x, clip.y, bb.min.z }, { bb.max.x, bb.max.y, clip.x } })
-, bot({ bb.min, { bb.max.x, clip.y, bb.max.z } }) {
-       SetShape({
-               { top.min.x, top.min.y, top.max.z }, // front, upper
-               { top.max.x, top.min.y, top.max.z },
-               { top.min.x, top.max.y, top.max.z },
-               { top.max.x, top.max.y, top.max.z },
-               { bot.min.x, bot.min.y, bot.max.z }, // front, lower
-               { bot.max.x, bot.min.y, bot.max.z },
-               { bot.min.x, bot.max.y, bot.max.z },
-               { bot.max.x, bot.max.y, bot.max.z },
-               { bot.min.x, bot.min.y, bot.min.z }, // back
-               { bot.min.x, top.max.y, bot.min.z },
-               { top.max.x, bot.min.y, bot.min.z },
-               { top.max.x, top.max.y, bot.min.z },
-               { top.min.x, top.max.y, top.min.z }, // top, upper
-               { top.min.x, top.max.y, top.max.z },
-               { top.max.x, top.max.y, top.min.z },
-               { top.max.x, top.max.y, top.max.z },
-               { bot.min.x, bot.max.y, top.max.z }, // top, lower
-               { bot.min.x, bot.max.y, bot.max.z },
-               { bot.max.x, bot.max.y, top.max.z },
-               { bot.max.x, bot.max.y, bot.max.z },
-               { bot.min.x, bot.min.y, bot.min.z }, // bottom
-               { bot.max.x, bot.min.y, bot.min.z },
-               { bot.min.x, bot.min.y, bot.max.z },
-               { bot.max.x, bot.min.y, bot.max.z },
-               { top.min.x, top.min.y, top.min.z }, // left, upper
-               { top.min.x, top.min.y, top.max.z },
-               { top.min.x, top.max.y, top.min.z },
-               { top.min.x, top.max.y, top.max.z },
-               { bot.min.x, bot.min.y, bot.min.z }, // left, lower
-               { bot.min.x, bot.min.y, bot.max.z },
-               { bot.min.x, bot.max.y, bot.min.z },
-               { bot.min.x, bot.max.y, bot.max.z },
-               { top.max.x, top.min.y, top.min.z }, // right, upper
-               { top.max.x, top.max.y, top.min.z },
-               { top.max.x, top.min.y, top.max.z },
-               { top.max.x, top.max.y, top.max.z },
-               { bot.max.x, bot.min.y, bot.min.z }, // right, lower
-               { bot.max.x, bot.max.y, bot.min.z },
-               { bot.max.x, bot.min.y, bot.max.z },
-               { bot.max.x, bot.max.y, bot.max.z },
-       }, {
-               {  0.0f,  0.0f,  1.0f }, // front x2
-               {  0.0f,  0.0f,  1.0f },
-               {  0.0f,  0.0f,  1.0f },
-               {  0.0f,  0.0f,  1.0f },
-               {  0.0f,  0.0f,  1.0f },
-               {  0.0f,  0.0f,  1.0f },
-               {  0.0f,  0.0f,  1.0f },
-               {  0.0f,  0.0f,  1.0f },
-               {  0.0f,  0.0f, -1.0f }, // back
-               {  0.0f,  0.0f, -1.0f },
-               {  0.0f,  0.0f, -1.0f },
-               {  0.0f,  0.0f, -1.0f },
-               {  0.0f,  1.0f,  0.0f }, // top x2
-               {  0.0f,  1.0f,  0.0f },
-               {  0.0f,  1.0f,  0.0f },
-               {  0.0f,  1.0f,  0.0f },
-               {  0.0f,  1.0f,  0.0f },
-               {  0.0f,  1.0f,  0.0f },
-               {  0.0f,  1.0f,  0.0f },
-               {  0.0f,  1.0f,  0.0f },
-               {  0.0f, -1.0f,  0.0f }, // bottom
-               {  0.0f, -1.0f,  0.0f },
-               {  0.0f, -1.0f,  0.0f },
-               {  0.0f, -1.0f,  0.0f },
-               { -1.0f,  0.0f,  0.0f }, // left x2
-               { -1.0f,  0.0f,  0.0f },
-               { -1.0f,  0.0f,  0.0f },
-               { -1.0f,  0.0f,  0.0f },
-               { -1.0f,  0.0f,  0.0f },
-               { -1.0f,  0.0f,  0.0f },
-               { -1.0f,  0.0f,  0.0f },
-               { -1.0f,  0.0f,  0.0f },
-               {  1.0f,  0.0f,  0.0f }, // right x2
-               {  1.0f,  0.0f,  0.0f },
-               {  1.0f,  0.0f,  0.0f },
-               {  1.0f,  0.0f,  0.0f },
-               {  1.0f,  0.0f,  0.0f },
-               {  1.0f,  0.0f,  0.0f },
-               {  1.0f,  0.0f,  0.0f },
-               {  1.0f,  0.0f,  0.0f },
-       }, {
-                0,  1,  2,  2,  1,  3, // front, upper
-                4,  5,  6,  6,  5,  7, // front, lower
-                8,  9, 10, 10,  9, 11, // back
-               12, 13, 14, 14, 13, 15, // top, upper
-               16, 17, 18, 18, 17, 19, // top, lower
-               20, 21, 22, 22, 21, 23, // bottom
-               24, 25, 26, 26, 25, 27, // left, upper
-               28, 29, 30, 30, 29, 31, // left, lower
-               32, 33, 34, 34, 33, 35, // right, upper
-               36, 37, 38, 38, 37, 39, // right, lower
-       });
-       SetOutline({
-               { bot.min.x, bot.min.y, bot.min.z }, // bottom
-               { bot.max.x, bot.min.y, bot.min.z },
-               { bot.min.x, bot.min.y, bot.max.z },
-               { bot.max.x, bot.min.y, bot.max.z },
-               { bot.min.x, bot.max.y, top.max.z }, // middle
-               { bot.max.x, bot.max.y, top.max.z },
-               { bot.min.x, bot.max.y, bot.max.z },
-               { bot.max.x, bot.max.y, bot.max.z },
-               { top.min.x, top.max.y, top.min.z }, // top
-               { top.max.x, top.max.y, top.min.z },
-               { top.min.x, top.max.y, top.max.z },
-               { top.max.x, top.max.y, top.max.z },
-       }, {
-                0,  1,  1,  3,  3,  2,  2,  0, // bottom
-                4,  5,  5,  7,  7,  6,  6,  4, // middle
-                8,  9,  9, 11, 11, 10, 10 , 8, // top
-                0,  8,  4, 10,  2,  6, // verticals, btf
-                1,  9,  5, 11,  3,  7,
-       //       5,  8,  7, 10,
-       //       1,  9,  3, 11,
-       });
-}
-
-bool StairShape::Intersects(
-       const Ray &ray,
-       const glm::mat4 &M,
-       float &dist,
-       glm::vec3 &norm
-) const noexcept {
-       float top_dist, bot_dist;
-       glm::vec3 top_norm, bot_norm;
-       bool top_hit = Intersection(ray, top, M, &top_dist, &top_norm);
-       bool bot_hit = Intersection(ray, bot, M, &bot_dist, &bot_norm);
-
-       if (top_hit) {
-               if (bot_hit) {
-                       if (top_dist < bot_dist) {
-                               dist = top_dist;
-                               norm = top_norm;
-                               return true;
-                       } else {
-                               dist = bot_dist;
-                               norm = bot_norm;
-                               return true;
-                       }
-               } else {
-                       dist = top_dist;
-                       norm = top_norm;
-                       return true;
-               }
-       } else if (bot_hit) {
-               dist = bot_dist;
-               norm = bot_norm;
-               return true;
-       } else {
-               return false;
-       }
-}
-
-}
diff --git a/src/shape.hpp b/src/shape.hpp
deleted file mode 100644 (file)
index 4120f76..0000000
+++ /dev/null
@@ -1,135 +0,0 @@
-#ifndef BLANK_SHAPE_HPP_
-#define BLANK_SHAPE_HPP_
-
-#include "geometry.hpp"
-#include "model.hpp"
-
-#include <vector>
-#include <glm/glm.hpp>
-
-
-namespace blank {
-
-struct Shape {
-
-       /// the number of vertices (and normals) this shape has
-       size_t VertexCount() const noexcept { return vtx_pos.size(); }
-       /// the number of vertex indices this shape has
-       size_t VertexIndexCount() const noexcept { return vtx_idx.size(); }
-
-       const Model::Normal &VertexNormal(size_t idx) const noexcept { return vtx_nrm[idx]; }
-       Model::Normal VertexNormal(
-               size_t idx, const glm::mat4 &transform
-       ) const noexcept {
-               return Model::Normal(transform * glm::vec4(vtx_nrm[idx], 0.0f));
-       }
-
-       /// fill given buffers with this shape's elements with an
-       /// optional transform and offset
-       void Vertices(
-               Model::Positions &vertex,
-               Model::Normals &normal,
-               Model::Indices &index
-       ) const;
-       void Vertices(
-               Model::Positions &vertex,
-               Model::Normals &normal,
-               Model::Indices &index,
-               const glm::mat4 &transform,
-               Model::Index idx_offset = 0
-       ) const;
-       void Vertices(
-               BlockModel::Positions &vertex,
-               BlockModel::Indices &index,
-               const glm::mat4 &transform,
-               BlockModel::Index idx_offset = 0
-       ) const;
-
-       /// the number of vertices this shape's outline has
-       size_t OutlineCount() const { return out_pos.size(); }
-       /// the number of vertex indices this shape's outline has
-       size_t OutlineIndexCount() const { return out_idx.size(); }
-
-       /// fill given buffers with this shape's outline's elements with
-       /// an optional offset
-       void Outline(
-               OutlineModel::Positions &vertex,
-               OutlineModel::Indices &index,
-               const OutlineModel::Position &offset = { 0.0f, 0.0f, 0.0f },
-               OutlineModel::Index idx_offset = 0
-       ) const;
-
-       /// Check if given ray would pass though this shape if it were
-       /// transformed with given matrix.
-       /// If true, dist and normal hold the intersection distance and
-       /// normal, otherwise their content is undefined.
-       virtual bool Intersects(
-               const Ray &,
-               const glm::mat4 &,
-               float &dist,
-               glm::vec3 &normal
-       ) const noexcept = 0;
-
-protected:
-       void SetShape(const Model::Positions &pos, const Model::Normals &nrm, const Model::Indices &idx) {
-               vtx_pos = pos;
-               vtx_nrm = nrm;
-               vtx_idx = idx;
-       }
-       void SetOutline(const OutlineModel::Positions &pos, const OutlineModel::Indices &idx) {
-               out_pos = pos;
-               out_idx = idx;
-       }
-
-private:
-       Model::Positions vtx_pos;
-       Model::Normals vtx_nrm;
-       Model::Indices vtx_idx;
-
-       OutlineModel::Positions out_pos;
-       OutlineModel::Indices out_idx;
-
-};
-
-
-class NullShape
-: public Shape {
-
-public:
-       NullShape();
-
-       bool Intersects(const Ray &, const glm::mat4 &, float &, glm::vec3 &) const noexcept override;
-
-};
-
-
-class CuboidShape
-: public Shape {
-
-public:
-       CuboidShape(const AABB &bounds);
-
-       bool Intersects(const Ray &, const glm::mat4 &, float &, glm::vec3 &) const noexcept override;
-
-private:
-       AABB bb;
-
-};
-
-
-class StairShape
-: public Shape {
-
-public:
-       StairShape(const AABB &bounds, const glm::vec2 &clip);
-
-       bool Intersects(const Ray &, const glm::mat4 &, float &, glm::vec3 &) const noexcept override;
-
-private:
-       AABB top, bot;
-
-};
-
-}
-
-#endif
diff --git a/src/timer.hpp b/src/timer.hpp
deleted file mode 100644 (file)
index c5445b2..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-#ifndef BLANK_TIMER_HPP
-#define BLANK_TIMER_HPP
-
-
-namespace blank {
-
-class IntervalTimer {
-
-public:
-       explicit IntervalTimer(int interval_ms) noexcept
-       : intv(interval_ms) { }
-
-       void Start() noexcept {
-               speed = 1;
-       }
-       void Stop() noexcept {
-               value = 0;
-               speed = 0;
-       }
-
-       bool Running() const noexcept {
-               return speed != 0;
-       }
-       bool Hit() const noexcept {
-               return Running() && value % intv < last_dt;
-       }
-       int Elapsed() const noexcept {
-               return value;
-       }
-       int Iteration() const noexcept {
-               return value / intv;
-       }
-
-       void Update(int dt) noexcept {
-               value += dt * speed;
-               last_dt = dt;
-       }
-
-private:
-       int intv;
-       int value = 0;
-       int speed = 0;
-       int last_dt = 0;
-
-};
-
-}
-
-#endif
diff --git a/src/ui/HUD.hpp b/src/ui/HUD.hpp
new file mode 100644 (file)
index 0000000..ca6b754
--- /dev/null
@@ -0,0 +1,50 @@
+#ifndef BLANK_UI_HUD_H_
+#define BLANK_UI_HUD_H_
+
+#include "../model/Model.hpp"
+#include "../model/OutlineModel.hpp"
+
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class Block;
+class BlockTypeRegistry;
+class DirectionalLighting;
+
+class HUD {
+
+public:
+       explicit HUD(const BlockTypeRegistry &);
+
+       HUD(const HUD &) = delete;
+       HUD &operator =(const HUD &) = delete;
+
+       void Viewport(float width, float height) noexcept;
+       void Viewport(float x, float y, float width, float height) noexcept;
+
+       void Display(const Block &);
+
+       void Render(DirectionalLighting &) noexcept;
+
+private:
+       const BlockTypeRegistry &types;
+
+       Model block;
+       Model::Buffer block_buf;
+       glm::mat4 block_transform;
+       bool block_visible;
+
+       OutlineModel crosshair;
+       glm::mat4 crosshair_transform;
+
+       float near, far;
+       glm::mat4 projection;
+       glm::mat4 view;
+
+};
+
+}
+
+#endif
diff --git a/src/ui/Interface.hpp b/src/ui/Interface.hpp
new file mode 100644 (file)
index 0000000..0c140fb
--- /dev/null
@@ -0,0 +1,94 @@
+#ifndef BLANK_UI_INTERFACE_HPP_
+#define BLANK_UI_INTERFACE_HPP_
+
+#include "HUD.hpp"
+#include "../app/FPSController.hpp"
+#include "../app/IntervalTimer.hpp"
+#include "../model/geometry.hpp"
+#include "../model/OutlineModel.hpp"
+#include "../world/Block.hpp"
+
+#include <SDL.h>
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class Chunk;
+class DirectionalLighting;
+class World;
+
+class Interface {
+
+public:
+       struct Config {
+               float move_velocity = 0.005f;
+               float pitch_sensitivity = -0.0025f;
+               float yaw_sensitivity = -0.001f;
+
+               bool keyboard_disabled = false;
+               bool mouse_disabled = false;
+               bool visual_disabled = false;
+       };
+
+       Interface(const Config &, World &);
+
+       void HandlePress(const SDL_KeyboardEvent &);
+       void HandleRelease(const SDL_KeyboardEvent &);
+       void Handle(const SDL_MouseMotionEvent &);
+       void HandlePress(const SDL_MouseButtonEvent &);
+       void HandleRelease(const SDL_MouseButtonEvent &);
+       void Handle(const SDL_MouseWheelEvent &);
+       void Handle(const SDL_WindowEvent &) noexcept;
+
+       void FaceBlock();
+       void TurnBlock();
+
+       void PickBlock();
+       void PlaceBlock();
+       void RemoveBlock() noexcept;
+
+       void PrintBlockInfo();
+       void PrintChunkInfo();
+       void PrintLightInfo();
+       void PrintSelectionInfo();
+       void Print(const Block &);
+
+       void SelectNext();
+       void SelectPrevious();
+
+       void Update(int dt);
+
+       void Render(DirectionalLighting &) noexcept;
+
+private:
+       void CheckAim();
+
+private:
+       World &world;
+       FPSController ctrl;
+       HUD hud;
+
+       Ray aim;
+       Chunk *aim_chunk;
+       int aim_block;
+       glm::vec3 aim_normal;
+
+       OutlineModel outline;
+       glm::mat4 outline_transform;
+
+       Config config;
+
+       IntervalTimer place_timer;
+       IntervalTimer remove_timer;
+
+       Block remove;
+       Block selection;
+
+       glm::tvec3<int> fwd, rev;
+
+};
+
+}
+
+#endif
diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp
new file mode 100644 (file)
index 0000000..410072d
--- /dev/null
@@ -0,0 +1,382 @@
+#include "HUD.hpp"
+#include "Interface.hpp"
+
+#include "../app/init.hpp"
+#include "../graphics/DirectionalLighting.hpp"
+#include "../model/shapes.hpp"
+#include "../world/World.hpp"
+
+#include <iostream>
+#include <glm/gtc/matrix_transform.hpp>
+#include <glm/gtx/io.hpp>
+
+
+namespace blank {
+
+HUD::HUD(const BlockTypeRegistry &types)
+: types(types)
+, block()
+, block_buf()
+, block_transform(1.0f)
+, block_visible(false)
+, crosshair()
+, crosshair_transform(1.0f)
+, near(100.0f)
+, far(-100.0f)
+, projection(glm::ortho(0.0f, 1.0f, 1.0f, 0.0f, near, far))
+, view(glm::translate(glm::mat4(1.0f), glm::vec3(-0.5f, -0.5f, 0))) {
+       block_transform = glm::translate(block_transform, glm::vec3(50.0f, 50.0f, 0.0f));
+       block_transform = glm::scale(block_transform, glm::vec3(50.0f));
+       block_transform = glm::rotate(block_transform, 3.5f, glm::vec3(1.0f, 0.0f, 0.0f));
+       block_transform = glm::rotate(block_transform, 0.35f, glm::vec3(0.0f, 1.0f, 0.0f));
+
+       crosshair.vertices = std::vector<glm::vec3>({
+               { -10.0f,   0.0f, 0.0f }, { 10.0f,  0.0f, 0.0f },
+               {   0.0f, -10.0f, 0.0f }, {  0.0f, 10.0f, 0.0f },
+       });
+       crosshair.indices = std::vector<OutlineModel::Index>({
+               0, 1, 2, 3
+       });
+       crosshair.colors.resize(4, { 10.0f, 10.0f, 10.0f });
+       crosshair.Invalidate();
+}
+
+
+void HUD::Viewport(float width, float height) noexcept {
+       Viewport(0, 0, width, height);
+}
+
+void HUD::Viewport(float x, float y, float width, float height) noexcept {
+       projection = glm::ortho(x, width, height, y, near, far);
+       crosshair_transform = glm::translate(glm::mat4(1.0f), glm::vec3(width * 0.5f, height * 0.5f, 0.0f));
+}
+
+
+void HUD::Display(const Block &b) {
+       const BlockType &type = types.Get(b.type);
+
+       block_buf.Clear();
+       type.FillModel(block_buf, b.Transform());
+       block.Update(block_buf);
+       block_visible = type.visible;
+}
+
+
+void HUD::Render(DirectionalLighting &program) noexcept {
+       if (block_visible) {
+               program.SetLightDirection({ 1.0f, 3.0f, 5.0f });
+               // disable distance fog
+               program.SetFogDensity(0.0f);
+               GLContext::ClearDepthBuffer();
+               program.SetMVP(block_transform, view, projection);
+               block.Draw();
+               program.SetM(crosshair_transform);
+               crosshair.Draw();
+       }
+}
+
+
+Interface::Interface(const Config &config, World &world)
+: world(world)
+, ctrl(world.Player())
+, hud(world.BlockTypes())
+, aim{{ 0, 0, 0 }, { 0, 0, -1 }}
+, aim_chunk(nullptr)
+, aim_block(0)
+, aim_normal()
+, outline()
+, outline_transform(1.0f)
+, config(config)
+, place_timer(256)
+, remove_timer(256)
+, remove(0)
+, selection(1)
+, fwd(0)
+, rev(0) {
+       hud.Viewport(960, 600);
+       hud.Display(selection);
+}
+
+
+void Interface::HandlePress(const SDL_KeyboardEvent &event) {
+       if (config.keyboard_disabled) return;
+
+       switch (event.keysym.sym) {
+               case SDLK_w:
+                       rev.z = 1;
+                       break;
+               case SDLK_s:
+                       fwd.z = 1;
+                       break;
+               case SDLK_a:
+                       rev.x = 1;
+                       break;
+               case SDLK_d:
+                       fwd.x = 1;
+                       break;
+               case SDLK_SPACE:
+                       fwd.y = 1;
+                       break;
+               case SDLK_LSHIFT:
+                       rev.y = 1;
+                       break;
+
+               case SDLK_q:
+                       FaceBlock();
+                       break;
+               case SDLK_e:
+                       TurnBlock();
+                       break;
+
+               case SDLK_b:
+                       PrintBlockInfo();
+                       break;
+               case SDLK_c:
+                       PrintChunkInfo();
+                       break;
+               case SDLK_l:
+                       PrintLightInfo();
+                       break;
+               case SDLK_p:
+                       PrintSelectionInfo();
+                       break;
+       }
+}
+
+void Interface::HandleRelease(const SDL_KeyboardEvent &event) {
+       if (config.keyboard_disabled) return;
+
+       switch (event.keysym.sym) {
+               case SDLK_w:
+                       rev.z = 0;
+                       break;
+               case SDLK_s:
+                       fwd.z = 0;
+                       break;
+               case SDLK_a:
+                       rev.x = 0;
+                       break;
+               case SDLK_d:
+                       fwd.x = 0;
+                       break;
+               case SDLK_SPACE:
+                       fwd.y = 0;
+                       break;
+               case SDLK_LSHIFT:
+                       rev.y = 0;
+                       break;
+       }
+}
+
+void Interface::FaceBlock() {
+       selection.SetFace(Block::Face((selection.GetFace() + 1) % Block::FACE_COUNT));
+       hud.Display(selection);
+}
+
+void Interface::TurnBlock() {
+       selection.SetTurn(Block::Turn((selection.GetTurn() + 1) % Block::TURN_COUNT));
+       hud.Display(selection);
+}
+
+void Interface::PrintBlockInfo() {
+       std::cout << std::endl;
+       if (!aim_chunk) {
+               std::cout << "not looking at any block" << std::endl;
+               Ray aim = ctrl.Aim();
+               std::cout << "aim ray: " << aim.orig << ", " << aim.dir << std::endl;
+               return;
+       }
+       std::cout << "looking at block " << aim_block
+               << " " << Chunk::ToCoords(aim_block)
+               << " of chunk " << aim_chunk->Position()
+               << std::endl;
+       Print(aim_chunk->BlockAt(aim_block));
+}
+
+void Interface::PrintChunkInfo() {
+       std::cout << std::endl;
+       if (!aim_chunk) {
+               std::cout << "not looking at any block" << std::endl;
+               return;
+       }
+       std::cout << "looking at chunk "
+               << aim_chunk->Position()
+               << std::endl;
+
+       std::cout << "  neighbors:" << std::endl;
+       if (aim_chunk->HasNeighbor(Block::FACE_LEFT)) {
+               std::cout << " left  " << aim_chunk->GetNeighbor(Block::FACE_LEFT).Position() << std::endl;
+       }
+       if (aim_chunk->HasNeighbor(Block::FACE_RIGHT)) {
+               std::cout << " right " << aim_chunk->GetNeighbor(Block::FACE_RIGHT).Position() << std::endl;
+       }
+       if (aim_chunk->HasNeighbor(Block::FACE_UP)) {
+               std::cout << " up    " << aim_chunk->GetNeighbor(Block::FACE_UP).Position() << std::endl;
+       }
+       if (aim_chunk->HasNeighbor(Block::FACE_DOWN)) {
+               std::cout << " down  " << aim_chunk->GetNeighbor(Block::FACE_DOWN).Position() << std::endl;
+       }
+       if (aim_chunk->HasNeighbor(Block::FACE_FRONT)) {
+               std::cout << " front " << aim_chunk->GetNeighbor(Block::FACE_FRONT).Position() << std::endl;
+       }
+       if (aim_chunk->HasNeighbor(Block::FACE_BACK)) {
+               std::cout << " back  " << aim_chunk->GetNeighbor(Block::FACE_BACK).Position() << std::endl;
+       }
+       std::cout << std::endl;
+}
+
+void Interface::PrintLightInfo() {
+       std::cout
+               << "light level " << world.PlayerChunk().GetLight(world.Player().Position())
+               << " at position " << world.Player().Position()
+               << std::endl;
+}
+
+void Interface::PrintSelectionInfo() {
+       std::cout << std::endl;
+       Print(selection);
+}
+
+void Interface::Print(const Block &block) {
+       std::cout << "type: " << block.type
+               << ", face: " << block.GetFace()
+               << ", turn: " << block.GetTurn()
+               << std::endl;
+}
+
+
+void Interface::Handle(const SDL_MouseMotionEvent &event) {
+       if (config.mouse_disabled) return;
+       ctrl.RotateYaw(event.xrel * config.yaw_sensitivity);
+       ctrl.RotatePitch(event.yrel * config.pitch_sensitivity);
+}
+
+void Interface::HandlePress(const SDL_MouseButtonEvent &event) {
+       if (config.mouse_disabled) return;
+
+       if (event.button == SDL_BUTTON_LEFT) {
+               RemoveBlock();
+               remove_timer.Start();
+       } else if (event.button == SDL_BUTTON_MIDDLE) {
+               PickBlock();
+       } else if (event.button == SDL_BUTTON_RIGHT) {
+               PlaceBlock();
+               place_timer.Start();
+       }
+}
+
+void Interface::HandleRelease(const SDL_MouseButtonEvent &event) {
+       if (config.mouse_disabled) return;
+
+       if (event.button == SDL_BUTTON_LEFT) {
+               remove_timer.Stop();
+       } else if (event.button == SDL_BUTTON_RIGHT) {
+               place_timer.Stop();
+       }
+}
+
+void Interface::PickBlock() {
+       if (!aim_chunk) return;
+       selection = aim_chunk->BlockAt(aim_block);
+       hud.Display(selection);
+}
+
+void Interface::PlaceBlock() {
+       if (!aim_chunk) return;
+       Chunk *mod_chunk = aim_chunk;
+       glm::vec3 next_pos = Chunk::ToCoords(aim_block) + aim_normal;
+       if (!Chunk::InBounds(next_pos)) {
+               mod_chunk = &world.Next(*aim_chunk, aim_normal);
+               next_pos -= aim_normal * glm::vec3(Chunk::Extent());
+       }
+       mod_chunk->SetBlock(next_pos, selection);
+       mod_chunk->Invalidate();
+}
+
+void Interface::RemoveBlock() noexcept {
+       if (!aim_chunk) return;
+       aim_chunk->SetBlock(aim_block, remove);
+       aim_chunk->Invalidate();
+}
+
+
+void Interface::Handle(const SDL_MouseWheelEvent &event) {
+       if (config.mouse_disabled) return;
+
+       if (event.y < 0) {
+               SelectNext();
+       } else if (event.y > 0) {
+               SelectPrevious();
+       }
+}
+
+void Interface::SelectNext() {
+       ++selection.type;
+       if (size_t(selection.type) >= world.BlockTypes().Size()) {
+               selection.type = 1;
+       }
+       hud.Display(selection);
+}
+
+void Interface::SelectPrevious() {
+       --selection.type;
+       if (selection.type <= 0) {
+               selection.type = world.BlockTypes().Size() - 1;
+       }
+       hud.Display(selection);
+}
+
+void Interface::Handle(const SDL_WindowEvent &event) noexcept {
+       if (event.event == SDL_WINDOWEVENT_RESIZED) {
+               hud.Viewport(event.data1, event.data2);
+       }
+}
+
+
+void Interface::Update(int dt) {
+       ctrl.Velocity(glm::vec3(fwd - rev) * config.move_velocity);
+       ctrl.Update(dt);
+
+       place_timer.Update(dt);
+       remove_timer.Update(dt);
+
+       aim = ctrl.Aim();
+       CheckAim();
+
+       if (remove_timer.Hit()) {
+               RemoveBlock();
+               CheckAim();
+       }
+
+       if (place_timer.Hit()) {
+               PlaceBlock();
+               CheckAim();
+       }
+}
+
+void Interface::CheckAim() {
+       float dist;
+       if (world.Intersection(aim, glm::mat4(1.0f), &aim_chunk, &aim_block, &dist, &aim_normal)) {
+               outline.Clear();
+               aim_chunk->Type(aim_chunk->BlockAt(aim_block)).FillOutlineModel(outline);
+               outline_transform = glm::scale(glm::vec3(1.0002f));
+               outline_transform *= aim_chunk->Transform(world.Player().ChunkCoords());
+               outline_transform *= aim_chunk->ToTransform(Chunk::ToPos(aim_block), aim_block);
+       } else {
+               aim_chunk = nullptr;
+       }
+}
+
+
+void Interface::Render(DirectionalLighting &program) noexcept {
+       if (config.visual_disabled) return;
+
+       if (aim_chunk) {
+               program.SetM(outline_transform);
+               outline.Draw();
+       }
+
+       hud.Render(program);
+}
+
+}
diff --git a/src/world.cpp b/src/world.cpp
deleted file mode 100644 (file)
index 137fd2a..0000000
+++ /dev/null
@@ -1,230 +0,0 @@
-#include "world.hpp"
-
-#include <limits>
-#include <glm/gtx/transform.hpp>
-
-
-namespace blank {
-
-World::World(const Config &config)
-: blockType()
-, blockShape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }})
-, stairShape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }}, { 0.0f, 0.0f })
-, slabShape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.0f, 0.5f }})
-, generate(config.gen)
-, chunks(config.load, blockType, generate)
-, player()
-, entities()
-, light_direction(config.light_direction)
-, fog_density(config.fog_density) {
-       BlockType::Faces block_fill = {  true,  true,  true,  true,  true,  true };
-       BlockType::Faces slab_fill  = { false,  true, false, false, false, false };
-       BlockType::Faces stair_fill = { false,  true, false, false, false,  true };
-
-       { // white block
-               BlockType type(true, { 1.0f, 1.0f, 1.0f }, &blockShape);
-               type.block_light = true;
-               type.fill = block_fill;
-               blockType.Add(type);
-       }
-       { // white slab
-               BlockType type(true, { 1.0f, 1.0f, 1.0f }, &slabShape);
-               type.block_light = true;
-               type.fill = slab_fill;
-               blockType.Add(type);
-       }
-       { // white stair
-               BlockType type(true, { 1.0f, 1.0f, 1.0f }, &stairShape);
-               type.block_light = true;
-               type.fill = stair_fill;
-               blockType.Add(type);
-       }
-
-       { // red block
-               BlockType type(true, { 1.0f, 0.0f, 0.0f }, &blockShape);
-               type.block_light = true;
-               type.fill = block_fill;
-               blockType.Add(type);
-       }
-       { // red slab
-               BlockType type(true, { 1.0f, 0.0f, 0.0f }, &slabShape);
-               type.block_light = true;
-               type.fill = slab_fill;
-               blockType.Add(type);
-       }
-       { // red stair
-               BlockType type(true, { 1.0f, 0.0f, 0.0f }, &stairShape);
-               type.block_light = true;
-               type.fill = stair_fill;
-               blockType.Add(type);
-       }
-
-       { // green block
-               BlockType type(true, { 0.0f, 1.0f, 0.0f }, &blockShape);
-               type.block_light = true;
-               type.fill = block_fill;
-               blockType.Add(type);
-       }
-       { // green slab
-               BlockType type(true, { 0.0f, 1.0f, 0.0f }, &slabShape);
-               type.block_light = true;
-               type.fill = slab_fill;
-               blockType.Add(type);
-       }
-       { // green stair
-               BlockType type(true, { 0.0f, 1.0f, 0.0f }, &stairShape);
-               type.block_light = true;
-               type.fill = stair_fill;
-               blockType.Add(type);
-       }
-
-       { // blue block
-               BlockType type(true, { 0.0f, 0.0f, 1.0f }, &blockShape);
-               type.block_light = true;
-               type.fill = block_fill;
-               blockType.Add(type);
-       }
-       { // blue slab
-               BlockType type(true, { 0.0f, 0.0f, 1.0f }, &slabShape);
-               type.block_light = true;
-               type.fill = slab_fill;
-               blockType.Add(type);
-       }
-       { // blue stair
-               BlockType type(true, { 0.0f, 0.0f, 1.0f }, &stairShape);
-               type.block_light = true;
-               type.fill = stair_fill;
-               blockType.Add(type);
-       }
-
-       { // glowing yellow block
-               BlockType type(true, { 1.0f, 1.0f, 0.0f }, &blockShape);
-               type.luminosity = 15;
-               type.block_light = true;
-               type.fill = block_fill;
-               blockType.Add(type);
-       }
-
-       generate.Space(0);
-       generate.Light(13);
-       generate.Solids({ 1, 4, 7, 10 });
-
-       player = &AddEntity();
-       player->Position(config.spawn);
-
-       chunks.GenerateSurrounding(player->ChunkCoords());
-}
-
-
-namespace {
-
-struct Candidate {
-       Chunk *chunk;
-       float dist;
-};
-
-std::vector<Candidate> candidates;
-
-}
-
-bool World::Intersection(
-               const Ray &ray,
-               const glm::mat4 &M,
-               Chunk **chunk,
-               int *blkid,
-               float *dist,
-               glm::vec3 *normal) {
-       candidates.clear();
-
-       for (Chunk &cur_chunk : chunks.Loaded()) {
-               float cur_dist;
-               if (cur_chunk.Intersection(ray, M * cur_chunk.Transform(player->ChunkCoords()), cur_dist)) {
-                       candidates.push_back({ &cur_chunk, cur_dist });
-               }
-       }
-
-       if (candidates.empty()) return false;
-
-       Chunk *closest_chunk = nullptr;
-       float closest_dist = std::numeric_limits<float>::infinity();
-       int closest_blkid = -1;
-       glm::vec3 closest_normal;
-
-       for (Candidate &cand : candidates) {
-               if (cand.dist > closest_dist) continue;
-               int cur_blkid;
-               float cur_dist;
-               glm::vec3 cur_normal;
-               if (cand.chunk->Intersection(ray, M * cand.chunk->Transform(player->ChunkCoords()), cur_blkid, cur_dist, cur_normal)) {
-                       if (cur_dist < closest_dist) {
-                               closest_chunk = cand.chunk;
-                               closest_blkid = cur_blkid;
-                               closest_dist = cur_dist;
-                               closest_normal = cur_normal;
-                       }
-               }
-       }
-
-       if (chunk) {
-               *chunk = closest_chunk;
-       }
-       if (blkid) {
-               *blkid = closest_blkid;
-       }
-       if (dist) {
-               *dist = closest_dist;
-       }
-       if (normal) {
-               *normal = closest_normal;
-       }
-       return closest_chunk;
-}
-
-
-Chunk &World::PlayerChunk() {
-       return chunks.ForceLoad(player->ChunkCoords());
-}
-
-Chunk &World::Next(const Chunk &to, const glm::tvec3<int> &dir) {
-       const Chunk::Pos tgt_pos = to.Position() + dir;
-       return chunks.ForceLoad(tgt_pos);
-}
-
-
-void World::Update(int dt) {
-       for (Entity &entity : entities) {
-               entity.Update(dt);
-       }
-       chunks.Rebase(player->ChunkCoords());
-       chunks.Update();
-}
-
-
-void World::Render(BlockLighting &chunk_prog, DirectionalLighting &entity_prog) {
-       chunk_prog.Activate();
-       chunk_prog.SetFogDensity(fog_density);
-       chunk_prog.SetView(glm::inverse(player->Transform(player->ChunkCoords())));
-
-       for (Chunk &chunk : chunks.Loaded()) {
-               glm::mat4 m(chunk.Transform(player->ChunkCoords()));
-               chunk_prog.SetM(m);
-               glm::mat4 mvp(chunk_prog.GetVP() * m);
-               if (!CullTest(Chunk::Bounds(), mvp)) {
-                       chunk.Draw();
-               }
-       }
-
-       entity_prog.Activate();
-       entity_prog.SetLightDirection(light_direction);
-       entity_prog.SetFogDensity(fog_density);
-       entity_prog.SetView(glm::inverse(player->Transform(player->ChunkCoords())));
-
-       for (Entity &entity : entities) {
-               if (entity.HasShape()) {
-                       entity_prog.SetM(entity.Transform(player->ChunkCoords()));
-                       entity.Draw();
-               }
-       }
-}
-
-}
diff --git a/src/world.hpp b/src/world.hpp
deleted file mode 100644 (file)
index 1d43fbe..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-#ifndef BLANK_WORLD_HPP_
-#define BLANK_WORLD_HPP_
-
-#include "block.hpp"
-#include "chunk.hpp"
-#include "entity.hpp"
-#include "generator.hpp"
-#include "shader.hpp"
-#include "shape.hpp"
-
-#include <list>
-#include <glm/glm.hpp>
-
-
-namespace blank {
-
-class World {
-
-public:
-       struct Config {
-               // initial player position
-               glm::vec3 spawn = { 4.0f, 4.0f, 4.0f };
-               // direction facing towards(!) the light
-               glm::vec3 light_direction = { -1.0f, -3.0f, -2.0f };
-               // fade out reaches 1/e (0.3679) at 1/fog_density,
-               // gets less than 0.01 at e/(2 * fog_density)
-               // I chose 0.011 because it yields 91 and 124 for those, so
-               // slightly less than 6 and 8 chunks
-               float fog_density = 0.011f;
-
-               Generator::Config gen = Generator::Config();
-               ChunkLoader::Config load = ChunkLoader::Config();
-       };
-
-       explicit World(const Config &);
-
-       bool Intersection(
-               const Ray &,
-               const glm::mat4 &M,
-               Chunk **chunk = nullptr,
-               int *blkid = nullptr,
-               float *dist = nullptr,
-               glm::vec3 *normal = nullptr);
-
-       BlockTypeRegistry &BlockTypes() { return blockType; }
-
-       Entity &Player() { return *player; }
-       Entity &AddEntity() { entities.emplace_back(); return entities.back(); }
-
-       Chunk &PlayerChunk();
-       Chunk &Next(const Chunk &to, const glm::tvec3<int> &dir);
-
-       void Update(int dt);
-
-       void Render(BlockLighting &, DirectionalLighting &);
-
-private:
-       BlockTypeRegistry blockType;
-       CuboidShape blockShape;
-       StairShape stairShape;
-       CuboidShape slabShape;
-
-       Generator generate;
-       ChunkLoader chunks;
-
-       Entity *player;
-       std::list<Entity> entities;
-
-       glm::vec3 light_direction;
-       float fog_density;
-
-};
-
-}
-
-#endif
diff --git a/src/world/Block.hpp b/src/world/Block.hpp
new file mode 100644 (file)
index 0000000..74af3e4
--- /dev/null
@@ -0,0 +1,135 @@
+#ifndef BLANK_WORLD_BLOCK_HPP_
+#define BLANK_WORLD_BLOCK_HPP_
+
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+/// single 1x1x1 cube
+struct Block {
+
+       using Type = unsigned short;
+       using Pos = glm::vec3;
+
+       enum Face {
+               FACE_UP,
+               FACE_DOWN,
+               FACE_RIGHT,
+               FACE_LEFT,
+               FACE_FRONT,
+               FACE_BACK,
+               FACE_COUNT,
+       };
+       enum Turn {
+               TURN_NONE,
+               TURN_LEFT,
+               TURN_AROUND,
+               TURN_RIGHT,
+               TURN_COUNT,
+       };
+
+       static constexpr int ORIENT_COUNT = FACE_COUNT * TURN_COUNT;
+
+       Type type;
+       unsigned char orient;
+
+       constexpr explicit Block(Type type = 0, Face face = FACE_UP, Turn turn = TURN_NONE) noexcept
+       : type(type), orient(face * TURN_COUNT + turn) { }
+
+       const glm::mat4 &Transform() const noexcept { return orient2transform[orient]; }
+
+       Face GetFace() const noexcept { return Face(orient / TURN_COUNT); }
+       void SetFace(Face face) noexcept { orient = face * TURN_COUNT + GetTurn(); }
+       Turn GetTurn() const noexcept { return Turn(orient % TURN_COUNT); }
+       void SetTurn(Turn turn) noexcept { orient = GetFace() * TURN_COUNT + turn; }
+
+       Face OrientedFace(Face f) const noexcept { return orient2face[orient][f]; }
+
+       static Face Opposite(Face f) noexcept {
+               return Face(f ^ 1);
+       }
+
+       static int Axis(Face f) noexcept {
+               switch (f) {
+                       case FACE_UP:
+                       case FACE_DOWN:
+                               return 1;
+                       default:
+                       case FACE_RIGHT:
+                       case FACE_LEFT:
+                               return 0;
+                       case FACE_FRONT:
+                       case FACE_BACK:
+                               return 2;
+               }
+       }
+
+       static glm::tvec3<int> FaceNormal(Face face) noexcept {
+               return face2normal[face];
+       }
+
+       static Face NormalFace(const glm::vec3 &norm) noexcept {
+               const glm::vec3 anorm(abs(norm));
+               if (anorm.x > anorm.y) {
+                       if (anorm.x > anorm.z) {
+                               return norm.x > 0.0f ? FACE_RIGHT : FACE_LEFT;
+                       } else {
+                               return norm.z > 0.0f ? FACE_FRONT : FACE_BACK;
+                       }
+               } else {
+                       if (anorm.y > anorm.z) {
+                               return norm.y > 0.0f ? FACE_UP : FACE_DOWN;
+                       } else {
+                               return norm.z > 0.0f ? FACE_FRONT : FACE_BACK;
+                       }
+               }
+       }
+
+       struct FaceSet {
+
+               explicit FaceSet(unsigned char v = 0)
+               : value(v) { }
+
+               bool IsSet(Face f) const {
+                       return value & Mask(f);
+               }
+               void Set(Face f) {
+                       value |= Mask(f);
+               }
+               void Unset(Face f) {
+                       value |= ~Mask(f);
+               }
+
+               void Clear() {
+                       value = 0;
+               }
+               void Fill() {
+                       value = Mask(FACE_COUNT) - 1;
+               }
+
+               bool Empty() const {
+                       return value == 0;
+               }
+               bool All() const {
+                       return value == Mask(FACE_COUNT) - 1;
+               }
+
+               unsigned char Mask(Face f) const {
+                       return 1 << f;
+               }
+
+               unsigned char value;
+
+       };
+
+private:
+       static const glm::tvec3<int> face2normal[6];
+       static const glm::mat4 orient2transform[ORIENT_COUNT];
+       static const Face orient2face[ORIENT_COUNT][FACE_COUNT];
+
+};
+
+}
+
+#endif
diff --git a/src/world/BlockLookup.hpp b/src/world/BlockLookup.hpp
new file mode 100644 (file)
index 0000000..4c8b12b
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef BLANK_WORLD_BLOCKLOOKUP_HPP_
+#define BLANK_WORLD_BLOCKLOOKUP_HPP_
+
+#include "Block.hpp"
+#include "Chunk.hpp"
+
+
+namespace blank {
+
+class BlockLookup {
+
+public:
+       // resolve chunk/position from oob coordinates
+       BlockLookup(Chunk *c, const Chunk::Pos &p) noexcept;
+
+       // resolve chunk/position from ib coordinates and direction
+       BlockLookup(Chunk *c, const Chunk::Pos &p, Block::Face dir) noexcept;
+
+       // check if lookup was successful
+       operator bool() const { return chunk; }
+
+       // only valid if lookup was successful
+       Chunk &GetChunk() const noexcept { return *chunk; }
+       const Chunk::Pos &GetBlockPos() const noexcept { return pos; }
+       const Block &GetBlock() const noexcept { return GetChunk().BlockAt(GetBlockPos()); }
+       const BlockType &GetType() const noexcept { return GetChunk().Type(GetBlock()); }
+       int GetLight() const noexcept { return GetChunk().GetLight(GetBlockPos()); }
+
+       // traverse in given direction
+       BlockLookup Next(Block::Face f) const { return BlockLookup(chunk, pos, f); }
+
+private:
+       Chunk *chunk;
+       Chunk::Pos pos;
+
+};
+
+}
+
+#endif
diff --git a/src/world/BlockType.hpp b/src/world/BlockType.hpp
new file mode 100644 (file)
index 0000000..344bec7
--- /dev/null
@@ -0,0 +1,75 @@
+#ifndef BLANK_WORLD_BLOCKTYPE_HPP_
+#define BLANK_WORLD_BLOCKTYPE_HPP_
+
+#include "Block.hpp"
+#include "../model/BlockModel.hpp"
+#include "../model/Model.hpp"
+#include "../model/OutlineModel.hpp"
+#include "../model/shapes.hpp"
+
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+/// single 1x1x1 cube
+/// attributes of a type of block
+struct BlockType {
+
+       const Shape *shape;
+       glm::vec3 color;
+       glm::vec3 outline_color;
+
+       Block::Type id;
+
+       int luminosity;
+
+       bool visible;
+       bool block_light;
+
+       struct Faces {
+               bool face[Block::FACE_COUNT];
+               Faces &operator =(const Faces &other) noexcept {
+                       for (int i = 0; i < Block::FACE_COUNT; ++i) {
+                               face[i] = other.face[i];
+                       }
+                       return *this;
+               }
+               bool operator [](Block::Face f) const noexcept {
+                       return face[f];
+               }
+       } fill;
+
+       explicit BlockType(
+               bool v = false,
+               const glm::vec3 &color = { 1, 1, 1 },
+               const Shape *shape = &DEFAULT_SHAPE
+       ) noexcept;
+
+       static const NullShape DEFAULT_SHAPE;
+
+       bool FaceFilled(const Block &block, Block::Face face) const noexcept {
+               return fill[block.OrientedFace(face)];
+       }
+
+       void FillModel(
+               Model::Buffer &m,
+               const glm::mat4 &transform = glm::mat4(1.0f),
+               Model::Index idx_offset = 0
+       ) const noexcept;
+       void FillBlockModel(
+               BlockModel::Buffer &m,
+               const glm::mat4 &transform = glm::mat4(1.0f),
+               BlockModel::Index idx_offset = 0
+       ) const noexcept;
+       void FillOutlineModel(
+               OutlineModel &m,
+               const glm::vec3 &pos_offset = { 0, 0, 0 },
+               OutlineModel::Index idx_offset = 0
+       ) const noexcept;
+
+};
+
+}
+
+#endif
diff --git a/src/world/BlockTypeRegistry.hpp b/src/world/BlockTypeRegistry.hpp
new file mode 100644 (file)
index 0000000..0a1ebf0
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef BLANK_WORLD_BLOCKTYPEREGISTRY_HPP_
+#define BLANK_WORLD_BLOCKTYPEREGISTRY_HPP_
+
+#include "BlockType.hpp"
+
+#include <vector>
+
+
+namespace blank {
+
+class BlockTypeRegistry {
+
+public:
+       BlockTypeRegistry();
+
+public:
+       Block::Type Add(const BlockType &);
+
+       size_t Size() const noexcept { return types.size(); }
+
+       BlockType &operator [](Block::Type id) { return types[id]; }
+       const BlockType &Get(Block::Type id) const { return types[id]; }
+
+private:
+       std::vector<BlockType> types;
+
+};
+
+}
+
+#endif
diff --git a/src/world/Chunk.hpp b/src/world/Chunk.hpp
new file mode 100644 (file)
index 0000000..8493ef6
--- /dev/null
@@ -0,0 +1,162 @@
+#ifndef BLANK_WORLD_CHUNK_HPP_
+#define BLANK_WORLD_CHUNK_HPP_
+
+#include "Block.hpp"
+#include "BlockTypeRegistry.hpp"
+#include "../model/BlockModel.hpp"
+#include "../model/geometry.hpp"
+
+#include <glm/glm.hpp>
+#include <glm/gtx/transform.hpp>
+
+
+namespace blank {
+
+class BlockType;
+
+/// cube of size 16 (256 tiles, 4096 blocks)
+class Chunk {
+
+public:
+       using Pos = glm::tvec3<int>;
+
+public:
+       explicit Chunk(const BlockTypeRegistry &) noexcept;
+
+       Chunk(Chunk &&) noexcept;
+       Chunk &operator =(Chunk &&) noexcept;
+
+       static constexpr int width = 16;
+       static constexpr int height = 16;
+       static constexpr int depth = 16;
+       static Pos Extent() noexcept { return { width, height, depth }; }
+       static constexpr int size = width * height * depth;
+
+       static AABB Bounds() noexcept { return AABB{ { 0, 0, 0 }, Extent() }; }
+
+       static constexpr bool InBounds(const Block::Pos &pos) noexcept {
+               return
+                       pos.x >= 0 && pos.x < width &&
+                       pos.y >= 0 && pos.y < height &&
+                       pos.z >= 0 && pos.z < depth;
+       }
+       static constexpr bool InBounds(const Pos &pos) noexcept {
+               return
+                       pos.x >= 0 && pos.x < width &&
+                       pos.y >= 0 && pos.y < height &&
+                       pos.z >= 0 && pos.z < depth;
+       }
+       static constexpr int ToIndex(const Pos &pos) noexcept {
+               return pos.x + pos.y * width + pos.z * width * height;
+       }
+       static constexpr bool InBounds(int idx) noexcept {
+               return idx >= 0 && idx < size;
+       }
+       static Block::Pos ToCoords(int idx) noexcept {
+               return Block::Pos(
+                       0.5f + (idx % width),
+                       0.5f + ((idx / width) % height),
+                       0.5f + (idx / (width * height))
+               );
+       }
+       static Block::Pos ToCoords(const Pos &pos) noexcept {
+               return Block::Pos(pos) + 0.5f;
+       }
+       static Pos ToPos(int idx) noexcept {
+               return Pos(
+                       (idx % width),
+                       ((idx / width) % height),
+                       (idx / (width * height))
+               );
+       }
+       glm::mat4 ToTransform(const Pos &pos, int idx) const noexcept;
+
+       static constexpr bool IsBorder(int idx) noexcept {
+               return
+                       idx < width * height ||                    // low Z plane
+                       idx % width == 0 ||                          // low X plane
+                       (idx / (width * height)) == depth - 1 || // high Z plane
+                       idx % width == width - 1 ||                // high X plane
+                       (idx / width) % height == 0 ||             // low Y plane
+                       (idx / width) % height == height - 1;    // high Y plane
+       }
+
+       bool IsSurface(int index) const noexcept { return IsSurface(ToPos(index)); }
+       bool IsSurface(const Block::Pos &pos) const noexcept { return IsSurface(Pos(pos)); }
+       bool IsSurface(const Pos &pos) const noexcept;
+
+       void SetNeighbor(Chunk &) noexcept;
+       bool HasNeighbor(Block::Face f) const noexcept { return neighbor[f]; }
+       Chunk &GetNeighbor(Block::Face f) noexcept { return *neighbor[f]; }
+       const Chunk &GetNeighbor(Block::Face f) const noexcept { return *neighbor[f]; }
+       void ClearNeighbors() noexcept;
+       void Unlink() noexcept;
+       void Relink() noexcept;
+
+       // check which faces of a block at given index are obstructed (and therefore invisible)
+       Block::FaceSet Obstructed(const Pos &) const noexcept;
+
+       void Invalidate() noexcept { dirty = true; }
+
+       void SetBlock(int index, const Block &) noexcept;
+       void SetBlock(const Block::Pos &pos, const Block &block) noexcept { SetBlock(ToIndex(pos), block); }
+       void SetBlock(const Pos &pos, const Block &block) noexcept { SetBlock(ToIndex(pos), block); }
+
+       const Block &BlockAt(int index) const noexcept { return blocks[index]; }
+       const Block &BlockAt(const Block::Pos &pos) const noexcept { return BlockAt(ToIndex(pos)); }
+       const Block &BlockAt(const Pos &pos) const noexcept { return BlockAt(ToIndex(pos)); }
+
+       const BlockType &Type(const Block &b) const noexcept { return types->Get(b.type); }
+       const BlockType &Type(int index) const noexcept { return Type(BlockAt(index)); }
+
+       void SetLight(int index, int level) noexcept;
+       void SetLight(const Pos &pos, int level) noexcept { SetLight(ToIndex(pos), level); }
+       void SetLight(const Block::Pos &pos, int level) noexcept { SetLight(ToIndex(pos), level); }
+
+       int GetLight(int index) const noexcept;
+       int GetLight(const Pos &pos) const noexcept { return GetLight(ToIndex(pos)); }
+       int GetLight(const Block::Pos &pos) const noexcept { return GetLight(ToIndex(pos)); }
+
+       float GetVertexLight(const Pos &, const BlockModel::Position &, const Model::Normal &) const noexcept;
+
+       bool Intersection(
+               const Ray &ray,
+               const glm::mat4 &M,
+               float &dist
+       ) const noexcept {
+               return blank::Intersection(ray, Bounds(), M, &dist);
+       }
+
+       bool Intersection(
+               const Ray &,
+               const glm::mat4 &M,
+               int &blkid,
+               float &dist,
+               glm::vec3 &normal) const noexcept;
+
+       void Position(const Pos &pos) noexcept { position = pos; }
+       const Pos &Position() const noexcept { return position; }
+       glm::mat4 Transform(const Pos &offset) const noexcept {
+               return glm::translate((position - offset) * Extent());
+       }
+
+       void CheckUpdate() noexcept;
+       void Draw() noexcept;
+
+private:
+       void Update() noexcept;
+
+private:
+       const BlockTypeRegistry *types;
+       Chunk *neighbor[Block::FACE_COUNT];
+       Block blocks[16 * 16 * 16];
+       unsigned char light[16 * 16 * 16];
+       BlockModel model;
+       Pos position;
+       bool dirty;
+
+};
+
+}
+
+#endif
diff --git a/src/world/ChunkLoader.hpp b/src/world/ChunkLoader.hpp
new file mode 100644 (file)
index 0000000..b1d0c7b
--- /dev/null
@@ -0,0 +1,59 @@
+#ifndef BLANK_WORLD_CHUNKLOADER_HPP_
+#define BLANK_WORLD_CHUNKLOADER_HPP_
+
+#include "Chunk.hpp"
+
+#include <list>
+
+
+namespace blank {
+
+class BlockTypeRegistry;
+class Generator;
+
+class ChunkLoader {
+
+public:
+       struct Config {
+               int load_dist = 6;
+               int unload_dist = 8;
+       };
+
+       ChunkLoader(const Config &, const BlockTypeRegistry &, const Generator &) noexcept;
+
+       void Generate(const Chunk::Pos &from, const Chunk::Pos &to);
+       void GenerateSurrounding(const Chunk::Pos &);
+
+       std::list<Chunk> &Loaded() noexcept { return loaded; }
+
+       Chunk *Loaded(const Chunk::Pos &) noexcept;
+       bool Queued(const Chunk::Pos &) noexcept;
+       bool Known(const Chunk::Pos &) noexcept;
+       Chunk &ForceLoad(const Chunk::Pos &);
+
+       void Rebase(const Chunk::Pos &);
+       void Update();
+
+private:
+       Chunk &Generate(const Chunk::Pos &pos);
+       void Insert(Chunk &) noexcept;
+       void Remove(Chunk &) noexcept;
+
+private:
+       Chunk::Pos base;
+
+       const BlockTypeRegistry &reg;
+       const Generator &gen;
+
+       std::list<Chunk> loaded;
+       std::list<Chunk::Pos> to_generate;
+       std::list<Chunk> to_free;
+
+       int load_dist;
+       int unload_dist;
+
+};
+
+}
+
+#endif
diff --git a/src/world/Entity.cpp b/src/world/Entity.cpp
new file mode 100644 (file)
index 0000000..6b23cac
--- /dev/null
@@ -0,0 +1,114 @@
+#include "Entity.hpp"
+
+#include "../model/geometry.hpp"
+#include "../model/Shape.hpp"
+
+#include <cmath>
+#include <glm/gtx/transform.hpp>
+
+namespace {
+
+blank::Model::Buffer model_buffer;
+
+}
+
+namespace blank {
+
+Entity::Entity() noexcept
+: shape(nullptr)
+, model()
+, velocity(0, 0, 0)
+, position(0, 0, 0)
+, chunk(0, 0, 0)
+, angular_velocity(1.0f, 0.0f, 0.0f, 0.0f)
+, rotation(1.0f) {
+
+}
+
+
+void Entity::SetShape(const Shape *s, const glm::vec3 &color) {
+       shape = s;
+       model_buffer.Clear();
+       shape->Vertices(model_buffer.vertices, model_buffer.normals, model_buffer.indices);
+       model_buffer.colors.resize(shape->VertexCount(), color);
+       model.Update(model_buffer);
+}
+
+void Entity::SetShapeless() noexcept {
+       shape = nullptr;
+}
+
+
+void Entity::Velocity(const glm::vec3 &vel) noexcept {
+       velocity = vel;
+}
+
+void Entity::Position(const Block::Pos &pos) noexcept {
+       position = pos;
+       while (position.x >= Chunk::width) {
+               position.x -= Chunk::width;
+               ++chunk.x;
+       }
+       while (position.x < 0) {
+               position.x += Chunk::width;
+               --chunk.x;
+       }
+       while (position.y >= Chunk::height) {
+               position.y -= Chunk::height;
+               ++chunk.y;
+       }
+       while (position.y < 0) {
+               position.y += Chunk::height;
+               --chunk.y;
+       }
+       while (position.z >= Chunk::depth) {
+               position.z -= Chunk::depth;
+               ++chunk.z;
+       }
+       while (position.z < 0) {
+               position.z += Chunk::depth;
+               --chunk.z;
+       }
+}
+
+void Entity::Move(const glm::vec3 &delta) noexcept {
+       Position(position + delta);
+}
+
+void Entity::AngularVelocity(const glm::quat &v) noexcept {
+       angular_velocity = v;
+}
+
+void Entity::Rotation(const glm::mat4 &rot) noexcept {
+       rotation = rot;
+}
+
+void Entity::Rotate(const glm::quat &delta) noexcept {
+       Rotation(rotation * glm::mat4_cast(delta));
+}
+
+glm::mat4 Entity::Transform(const Chunk::Pos &chunk_offset) const noexcept {
+       const glm::vec3 chunk_pos = (chunk - chunk_offset) * Chunk::Extent();
+       return glm::translate(position + chunk_pos) * rotation;
+}
+
+Ray Entity::Aim(const Chunk::Pos &chunk_offset) const noexcept {
+       glm::mat4 transform = Transform(chunk_offset);
+       glm::vec4 from = transform * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
+       from /= from.w;
+       glm::vec4 to = transform * glm::vec4(0.0f, 0.0f, -1.0f, 1.0f);
+       to /= to.w;
+       return Ray{ glm::vec3(from), glm::normalize(glm::vec3(to - from)) };
+}
+
+void Entity::Update(int dt) noexcept {
+       Move(velocity * float(dt));
+       Rotate(angular_velocity * float(dt));
+}
+
+
+void Entity::Draw() noexcept {
+       model.Draw();
+}
+
+}
diff --git a/src/world/Entity.hpp b/src/world/Entity.hpp
new file mode 100644 (file)
index 0000000..83c1f6f
--- /dev/null
@@ -0,0 +1,65 @@
+#ifndef BLANK_WORLD_ENTITY_HPP_
+#define BLANK_WORLD_ENTITY_HPP_
+
+#include "Block.hpp"
+#include "Chunk.hpp"
+#include "../model/Model.hpp"
+
+#include <glm/glm.hpp>
+#include <glm/gtc/quaternion.hpp>
+
+
+namespace blank {
+
+class Ray;
+class Shape;
+
+class Entity {
+
+public:
+       Entity() noexcept;
+
+       bool HasShape() const noexcept { return shape; }
+       const Shape *GetShape() const noexcept { return shape; }
+       void SetShape(const Shape *, const glm::vec3 &color);
+       void SetShapeless() noexcept;
+
+       const glm::vec3 &Velocity() const noexcept { return velocity; }
+       void Velocity(const glm::vec3 &) noexcept;
+
+       const Block::Pos &Position() const noexcept { return position; }
+       void Position(const Block::Pos &) noexcept;
+       void Move(const glm::vec3 &delta) noexcept;
+
+       const Chunk::Pos ChunkCoords() const noexcept { return chunk; }
+
+       const glm::quat &AngularVelocity() const noexcept { return angular_velocity; }
+       void AngularVelocity(const glm::quat &) noexcept;
+
+       const glm::mat4 &Rotation() const noexcept { return rotation; }
+       void Rotation(const glm::mat4 &) noexcept;
+       void Rotate(const glm::quat &delta) noexcept;
+
+       glm::mat4 Transform(const Chunk::Pos &chunk_offset) const noexcept;
+       Ray Aim(const Chunk::Pos &chunk_offset) const noexcept;
+
+       void Update(int dt) noexcept;
+
+       void Draw() noexcept;
+
+private:
+       const Shape *shape;
+       Model model;
+
+       glm::vec3 velocity;
+       Block::Pos position;
+       Chunk::Pos chunk;
+
+       glm::quat angular_velocity;
+       glm::mat4 rotation;
+
+};
+
+}
+
+#endif
diff --git a/src/world/Generator.cpp b/src/world/Generator.cpp
new file mode 100644 (file)
index 0000000..c49dce1
--- /dev/null
@@ -0,0 +1,54 @@
+#include "Generator.hpp"
+
+#include "Chunk.hpp"
+#include "../rand/OctaveNoise.hpp"
+
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+Generator::Generator(const Config &config) noexcept
+: solidNoise(config.solid_seed)
+, typeNoise(config.type_seed)
+, stretch(1.0f/config.stretch)
+, solid_threshold(config.solid_threshold)
+, space(0)
+, light(0)
+, solids() {
+
+}
+
+
+void Generator::operator ()(Chunk &chunk) const noexcept {
+       Chunk::Pos pos(chunk.Position());
+       glm::vec3 coords(pos * Chunk::Extent());
+       for (int z = 0; z < Chunk::depth; ++z) {
+               for (int y = 0; y < Chunk::height; ++y) {
+                       for (int x = 0; x < Chunk::width; ++x) {
+                               Block::Pos block_pos(x, y, z);
+                               glm::vec3 gen_pos = (coords + block_pos) * stretch;
+                               float val = OctaveNoise(solidNoise, coords + block_pos, 3, 0.5f, stretch, 2.0f);
+                               if (val > solid_threshold) {
+                                       int type_val = int((typeNoise(gen_pos) + 1.0f) * solids.size()) % solids.size();
+                                       chunk.SetBlock(block_pos, Block(solids[type_val]));
+                               } else {
+                                       chunk.SetBlock(block_pos, Block(space));
+                               }
+                       }
+               }
+       }
+       unsigned int random = 263167 * pos.x + 2097593 * pos.y + 426389 * pos.z;
+       for (int index = 0; index < Chunk::size; ++index) {
+               if (chunk.IsSurface(index)) {
+                       random = random * 666649 + 7778777;
+                       if ((random % 32) == 0) {
+                               chunk.SetBlock(index, Block(light));
+                       }
+               }
+       }
+       chunk.Invalidate();
+       chunk.CheckUpdate();
+}
+
+}
diff --git a/src/world/Generator.hpp b/src/world/Generator.hpp
new file mode 100644 (file)
index 0000000..a2ae18a
--- /dev/null
@@ -0,0 +1,48 @@
+#ifndef BLANK_WORLD_GENERATOR_HPP_
+#define BLANK_WORLD_GENERATOR_HPP_
+
+#include "Block.hpp"
+#include "../rand/SimplexNoise.hpp"
+#include "../rand/WorleyNoise.hpp"
+
+#include <vector>
+
+
+namespace blank {
+
+class Chunk;
+
+class Generator {
+
+public:
+       struct Config {
+               unsigned int solid_seed = 0;
+               unsigned int type_seed = 0;
+               float stretch = 64.0f;
+               float solid_threshold = 0.5f;
+       };
+
+       explicit Generator(const Config &) noexcept;
+
+       void operator ()(Chunk &) const noexcept;
+
+       void Space(Block::Type t) noexcept { space = t; }
+       void Light(Block::Type t) noexcept { light = t; }
+       void Solids(const std::vector<Block::Type> &s) { solids = s; }
+
+private:
+       SimplexNoise solidNoise;
+       WorleyNoise typeNoise;
+
+       float stretch;
+       float solid_threshold;
+
+       Block::Type space;
+       Block::Type light;
+       std::vector<Block::Type> solids;
+
+};
+
+}
+
+#endif
diff --git a/src/world/World.cpp b/src/world/World.cpp
new file mode 100644 (file)
index 0000000..e426f07
--- /dev/null
@@ -0,0 +1,233 @@
+#include "World.hpp"
+
+#include "../graphics/BlockLighting.hpp"
+#include "../graphics/DirectionalLighting.hpp"
+
+#include <limits>
+#include <glm/gtx/transform.hpp>
+
+
+namespace blank {
+
+World::World(const Config &config)
+: blockType()
+, blockShape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }})
+, stairShape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }}, { 0.0f, 0.0f })
+, slabShape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.0f, 0.5f }})
+, generate(config.gen)
+, chunks(config.load, blockType, generate)
+, player()
+, entities()
+, light_direction(config.light_direction)
+, fog_density(config.fog_density) {
+       BlockType::Faces block_fill = {  true,  true,  true,  true,  true,  true };
+       BlockType::Faces slab_fill  = { false,  true, false, false, false, false };
+       BlockType::Faces stair_fill = { false,  true, false, false, false,  true };
+
+       { // white block
+               BlockType type(true, { 1.0f, 1.0f, 1.0f }, &blockShape);
+               type.block_light = true;
+               type.fill = block_fill;
+               blockType.Add(type);
+       }
+       { // white slab
+               BlockType type(true, { 1.0f, 1.0f, 1.0f }, &slabShape);
+               type.block_light = true;
+               type.fill = slab_fill;
+               blockType.Add(type);
+       }
+       { // white stair
+               BlockType type(true, { 1.0f, 1.0f, 1.0f }, &stairShape);
+               type.block_light = true;
+               type.fill = stair_fill;
+               blockType.Add(type);
+       }
+
+       { // red block
+               BlockType type(true, { 1.0f, 0.0f, 0.0f }, &blockShape);
+               type.block_light = true;
+               type.fill = block_fill;
+               blockType.Add(type);
+       }
+       { // red slab
+               BlockType type(true, { 1.0f, 0.0f, 0.0f }, &slabShape);
+               type.block_light = true;
+               type.fill = slab_fill;
+               blockType.Add(type);
+       }
+       { // red stair
+               BlockType type(true, { 1.0f, 0.0f, 0.0f }, &stairShape);
+               type.block_light = true;
+               type.fill = stair_fill;
+               blockType.Add(type);
+       }
+
+       { // green block
+               BlockType type(true, { 0.0f, 1.0f, 0.0f }, &blockShape);
+               type.block_light = true;
+               type.fill = block_fill;
+               blockType.Add(type);
+       }
+       { // green slab
+               BlockType type(true, { 0.0f, 1.0f, 0.0f }, &slabShape);
+               type.block_light = true;
+               type.fill = slab_fill;
+               blockType.Add(type);
+       }
+       { // green stair
+               BlockType type(true, { 0.0f, 1.0f, 0.0f }, &stairShape);
+               type.block_light = true;
+               type.fill = stair_fill;
+               blockType.Add(type);
+       }
+
+       { // blue block
+               BlockType type(true, { 0.0f, 0.0f, 1.0f }, &blockShape);
+               type.block_light = true;
+               type.fill = block_fill;
+               blockType.Add(type);
+       }
+       { // blue slab
+               BlockType type(true, { 0.0f, 0.0f, 1.0f }, &slabShape);
+               type.block_light = true;
+               type.fill = slab_fill;
+               blockType.Add(type);
+       }
+       { // blue stair
+               BlockType type(true, { 0.0f, 0.0f, 1.0f }, &stairShape);
+               type.block_light = true;
+               type.fill = stair_fill;
+               blockType.Add(type);
+       }
+
+       { // glowing yellow block
+               BlockType type(true, { 1.0f, 1.0f, 0.0f }, &blockShape);
+               type.luminosity = 15;
+               type.block_light = true;
+               type.fill = block_fill;
+               blockType.Add(type);
+       }
+
+       generate.Space(0);
+       generate.Light(13);
+       generate.Solids({ 1, 4, 7, 10 });
+
+       player = &AddEntity();
+       player->Position(config.spawn);
+
+       chunks.GenerateSurrounding(player->ChunkCoords());
+}
+
+
+namespace {
+
+struct Candidate {
+       Chunk *chunk;
+       float dist;
+};
+
+std::vector<Candidate> candidates;
+
+}
+
+bool World::Intersection(
+               const Ray &ray,
+               const glm::mat4 &M,
+               Chunk **chunk,
+               int *blkid,
+               float *dist,
+               glm::vec3 *normal) {
+       candidates.clear();
+
+       for (Chunk &cur_chunk : chunks.Loaded()) {
+               float cur_dist;
+               if (cur_chunk.Intersection(ray, M * cur_chunk.Transform(player->ChunkCoords()), cur_dist)) {
+                       candidates.push_back({ &cur_chunk, cur_dist });
+               }
+       }
+
+       if (candidates.empty()) return false;
+
+       Chunk *closest_chunk = nullptr;
+       float closest_dist = std::numeric_limits<float>::infinity();
+       int closest_blkid = -1;
+       glm::vec3 closest_normal;
+
+       for (Candidate &cand : candidates) {
+               if (cand.dist > closest_dist) continue;
+               int cur_blkid;
+               float cur_dist;
+               glm::vec3 cur_normal;
+               if (cand.chunk->Intersection(ray, M * cand.chunk->Transform(player->ChunkCoords()), cur_blkid, cur_dist, cur_normal)) {
+                       if (cur_dist < closest_dist) {
+                               closest_chunk = cand.chunk;
+                               closest_blkid = cur_blkid;
+                               closest_dist = cur_dist;
+                               closest_normal = cur_normal;
+                       }
+               }
+       }
+
+       if (chunk) {
+               *chunk = closest_chunk;
+       }
+       if (blkid) {
+               *blkid = closest_blkid;
+       }
+       if (dist) {
+               *dist = closest_dist;
+       }
+       if (normal) {
+               *normal = closest_normal;
+       }
+       return closest_chunk;
+}
+
+
+Chunk &World::PlayerChunk() {
+       return chunks.ForceLoad(player->ChunkCoords());
+}
+
+Chunk &World::Next(const Chunk &to, const glm::tvec3<int> &dir) {
+       const Chunk::Pos tgt_pos = to.Position() + dir;
+       return chunks.ForceLoad(tgt_pos);
+}
+
+
+void World::Update(int dt) {
+       for (Entity &entity : entities) {
+               entity.Update(dt);
+       }
+       chunks.Rebase(player->ChunkCoords());
+       chunks.Update();
+}
+
+
+void World::Render(BlockLighting &chunk_prog, DirectionalLighting &entity_prog) {
+       chunk_prog.Activate();
+       chunk_prog.SetFogDensity(fog_density);
+       chunk_prog.SetView(glm::inverse(player->Transform(player->ChunkCoords())));
+
+       for (Chunk &chunk : chunks.Loaded()) {
+               glm::mat4 m(chunk.Transform(player->ChunkCoords()));
+               chunk_prog.SetM(m);
+               glm::mat4 mvp(chunk_prog.GetVP() * m);
+               if (!CullTest(Chunk::Bounds(), mvp)) {
+                       chunk.Draw();
+               }
+       }
+
+       entity_prog.Activate();
+       entity_prog.SetLightDirection(light_direction);
+       entity_prog.SetFogDensity(fog_density);
+       entity_prog.SetView(glm::inverse(player->Transform(player->ChunkCoords())));
+
+       for (Entity &entity : entities) {
+               if (entity.HasShape()) {
+                       entity_prog.SetM(entity.Transform(player->ChunkCoords()));
+                       entity.Draw();
+               }
+       }
+}
+
+}
diff --git a/src/world/World.hpp b/src/world/World.hpp
new file mode 100644 (file)
index 0000000..ae70494
--- /dev/null
@@ -0,0 +1,78 @@
+#ifndef BLANK_WORLD_WORLD_HPP_
+#define BLANK_WORLD_WORLD_HPP_
+
+#include "BlockTypeRegistry.hpp"
+#include "ChunkLoader.hpp"
+#include "Entity.hpp"
+#include "Generator.hpp"
+#include "../model/shapes.hpp"
+
+#include <list>
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class BlockLighting;
+class DirectionalLighting;
+
+class World {
+
+public:
+       struct Config {
+               // initial player position
+               glm::vec3 spawn = { 4.0f, 4.0f, 4.0f };
+               // direction facing towards(!) the light
+               glm::vec3 light_direction = { -1.0f, -3.0f, -2.0f };
+               // fade out reaches 1/e (0.3679) at 1/fog_density,
+               // gets less than 0.01 at e/(2 * fog_density)
+               // I chose 0.011 because it yields 91 and 124 for those, so
+               // slightly less than 6 and 8 chunks
+               float fog_density = 0.011f;
+
+               Generator::Config gen = Generator::Config();
+               ChunkLoader::Config load = ChunkLoader::Config();
+       };
+
+       explicit World(const Config &);
+
+       bool Intersection(
+               const Ray &,
+               const glm::mat4 &M,
+               Chunk **chunk = nullptr,
+               int *blkid = nullptr,
+               float *dist = nullptr,
+               glm::vec3 *normal = nullptr);
+
+       BlockTypeRegistry &BlockTypes() { return blockType; }
+
+       Entity &Player() { return *player; }
+       Entity &AddEntity() { entities.emplace_back(); return entities.back(); }
+
+       Chunk &PlayerChunk();
+       Chunk &Next(const Chunk &to, const glm::tvec3<int> &dir);
+
+       void Update(int dt);
+
+       void Render(BlockLighting &, DirectionalLighting &);
+
+private:
+       BlockTypeRegistry blockType;
+       CuboidShape blockShape;
+       StairShape stairShape;
+       CuboidShape slabShape;
+
+       Generator generate;
+       ChunkLoader chunks;
+
+       Entity *player;
+       std::list<Entity> entities;
+
+       glm::vec3 light_direction;
+       float fog_density;
+
+};
+
+}
+
+#endif
diff --git a/src/world/block.cpp b/src/world/block.cpp
new file mode 100644 (file)
index 0000000..6cbaa26
--- /dev/null
@@ -0,0 +1,130 @@
+#include "Block.hpp"
+#include "BlockType.hpp"
+#include "BlockTypeRegistry.hpp"
+
+#include "../model/geometry.hpp"
+
+#include <glm/gtx/euler_angles.hpp>
+#include <glm/gtx/transform.hpp>
+
+
+namespace blank {
+
+const NullShape BlockType::DEFAULT_SHAPE;
+
+BlockType::BlockType(bool v, const glm::vec3 &col, const Shape *s) noexcept
+: shape(s)
+, color(col)
+, outline_color(-1, -1, -1)
+, id(0)
+, luminosity(0)
+, visible(v)
+, block_light(false)
+, fill({ false, false, false, false, false, false }) {
+
+}
+
+void BlockType::FillModel(
+       Model::Buffer &buf,
+       const glm::mat4 &transform,
+       Model::Index idx_offset
+) const noexcept {
+       shape->Vertices(buf.vertices, buf.normals, buf.indices, transform, idx_offset);
+       buf.colors.insert(buf.colors.end(), shape->VertexCount(), color);
+}
+
+void BlockType::FillBlockModel(
+       BlockModel::Buffer &buf,
+       const glm::mat4 &transform,
+       BlockModel::Index idx_offset
+) const noexcept {
+       shape->Vertices(buf.vertices, buf.indices, transform, idx_offset);
+       buf.colors.insert(buf.colors.end(), shape->VertexCount(), color);
+}
+
+void BlockType::FillOutlineModel(
+       OutlineModel &model,
+       const glm::vec3 &pos_offset,
+       OutlineModel::Index idx_offset
+) const noexcept {
+       shape->Outline(model.vertices, model.indices, pos_offset, idx_offset);
+       model.colors.insert(model.colors.end(), shape->OutlineCount(), outline_color);
+}
+
+
+BlockTypeRegistry::BlockTypeRegistry() {
+       Add(BlockType());
+}
+
+Block::Type BlockTypeRegistry::Add(const BlockType &t) {
+       int id = types.size();
+       types.push_back(t);
+       types.back().id = id;
+       return id;
+}
+
+
+const glm::mat4 Block::orient2transform[ORIENT_COUNT] = {
+       {  1,  0,  0,  0,  0,  1,  0,  0,  0,  0,  1,  0,  0,  0,  0,  1, }, // face: up,    turn: none
+       {  0,  0, -1,  0,  0,  1,  0,  0,  1,  0,  0,  0,  0,  0,  0,  1, }, // face: up,    turn: left
+       { -1,  0,  0,  0,  0,  1,  0,  0,  0,  0, -1,  0,  0,  0,  0,  1, }, // face: up,    turn: around
+       {  0,  0,  1,  0,  0,  1,  0,  0, -1,  0,  0,  0,  0,  0,  0,  1, }, // face: up,    turn: right
+       {  1,  0,  0,  0,  0, -1,  0,  0,  0,  0, -1,  0,  0,  0,  0,  1, }, // face: down,  turn: none
+       {  0,  0, -1,  0,  0, -1,  0,  0, -1,  0,  0,  0,  0,  0,  0,  1, }, // face: down,  turn: left
+       { -1,  0,  0,  0,  0, -1,  0,  0,  0,  0,  1,  0,  0,  0,  0,  1, }, // face: down,  turn: around
+       {  0,  0,  1,  0,  0, -1,  0,  0,  1,  0,  0,  0,  0,  0,  0,  1, }, // face: down,  turn: right
+       {  0, -1,  0,  0,  1,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  1, }, // face: right, turn: none
+       {  0, -1,  0,  0,  0,  0, -1,  0,  1,  0,  0,  0,  0,  0,  0,  1, }, // face: right, turn: left
+       {  0, -1,  0,  0, -1,  0,  0,  0,  0,  0, -1,  0,  0,  0,  0,  1, }, // face: right, turn: around
+       {  0, -1,  0,  0,  0,  0,  1,  0, -1,  0,  0,  0,  0,  0,  0,  1, }, // face: right, turn: right
+       {  0,  1,  0,  0, -1,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  1, }, // face: left,  turn: none
+       {  0,  1,  0,  0,  0,  0,  1,  0,  1,  0,  0,  0,  0,  0,  0,  1, }, // face: left,  turn: left
+       {  0,  1,  0,  0,  1,  0,  0,  0,  0,  0, -1,  0,  0,  0,  0,  1, }, // face: left,  turn: around
+       {  0,  1,  0,  0,  0,  0, -1,  0, -1,  0,  0,  0,  0,  0,  0,  1, }, // face: left,  turn: right
+       {  1,  0,  0,  0,  0,  0,  1,  0,  0, -1,  0,  0,  0,  0,  0,  1, }, // face: front, turn: none
+       {  0,  0, -1,  0,  1,  0,  0,  0,  0, -1,  0,  0,  0,  0,  0,  1, }, // face: front, turn: left
+       { -1,  0,  0,  0,  0,  0, -1,  0,  0, -1,  0,  0,  0,  0,  0,  1, }, // face: front, turn: around
+       {  0,  0,  1,  0, -1,  0,  0,  0,  0, -1,  0,  0,  0,  0,  0,  1, }, // face: front, turn: right
+       {  1,  0,  0,  0,  0,  0, -1,  0,  0,  1,  0,  0,  0,  0,  0,  1, }, // face: back,  turn: none
+       {  0,  0, -1,  0, -1,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  1, }, // face: back,  turn: left
+       { -1,  0,  0,  0,  0,  0,  1,  0,  0,  1,  0,  0,  0,  0,  0,  1, }, // face: back,  turn: around
+       {  0,  0,  1,  0,  1,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  1, }, // face: back,  turn: right
+};
+
+const glm::tvec3<int> Block::face2normal[FACE_COUNT] = {
+       {  0,  1,  0 },
+       {  0, -1,  0 },
+       {  1,  0,  0 },
+       { -1,  0,  0 },
+       {  0,  0,  1 },
+       {  0,  0, -1 },
+};
+
+const Block::Face Block::orient2face[ORIENT_COUNT][FACE_COUNT] = {
+       { FACE_UP,    FACE_DOWN,  FACE_RIGHT, FACE_LEFT,  FACE_FRONT, FACE_BACK,  }, // face: up,    turn: none
+       { FACE_UP,    FACE_DOWN,  FACE_FRONT, FACE_BACK,  FACE_LEFT,  FACE_RIGHT, }, // face: up,    turn: left
+       { FACE_UP,    FACE_DOWN,  FACE_LEFT,  FACE_RIGHT, FACE_BACK,  FACE_FRONT, }, // face: up,    turn: around
+       { FACE_UP,    FACE_DOWN,  FACE_BACK,  FACE_FRONT, FACE_RIGHT, FACE_LEFT,  }, // face: up,    turn: right
+       { FACE_DOWN,  FACE_UP,    FACE_RIGHT, FACE_LEFT,  FACE_BACK,  FACE_FRONT, }, // face: down,  turn: none
+       { FACE_DOWN,  FACE_UP,    FACE_BACK,  FACE_FRONT, FACE_LEFT,  FACE_RIGHT, }, // face: down,  turn: left
+       { FACE_DOWN,  FACE_UP,    FACE_LEFT,  FACE_RIGHT, FACE_FRONT, FACE_BACK,  }, // face: down,  turn: around
+       { FACE_DOWN,  FACE_UP,    FACE_FRONT, FACE_BACK,  FACE_RIGHT, FACE_LEFT,  }, // face: down,  turn: right
+       { FACE_LEFT,  FACE_RIGHT, FACE_UP,    FACE_DOWN,  FACE_FRONT, FACE_BACK,  }, // face: right, turn: none
+       { FACE_LEFT,  FACE_RIGHT, FACE_FRONT, FACE_BACK,  FACE_DOWN,  FACE_UP,    }, // face: right, turn: left
+       { FACE_LEFT,  FACE_RIGHT, FACE_DOWN,  FACE_UP,    FACE_BACK,  FACE_FRONT, }, // face: right, turn: around
+       { FACE_LEFT,  FACE_RIGHT, FACE_BACK,  FACE_FRONT, FACE_UP,    FACE_DOWN,  }, // face: right, turn: right
+       { FACE_RIGHT, FACE_LEFT,  FACE_DOWN,  FACE_UP,    FACE_FRONT, FACE_BACK,  }, // face: left,  turn: none
+       { FACE_RIGHT, FACE_LEFT,  FACE_FRONT, FACE_BACK,  FACE_UP,    FACE_DOWN,  }, // face: left,  turn: left
+       { FACE_RIGHT, FACE_LEFT,  FACE_UP,    FACE_DOWN,  FACE_BACK,  FACE_FRONT, }, // face: left,  turn: around
+       { FACE_RIGHT, FACE_LEFT,  FACE_BACK,  FACE_FRONT, FACE_DOWN,  FACE_UP,    }, // face: left,  turn: right
+       { FACE_BACK,  FACE_FRONT, FACE_RIGHT, FACE_LEFT,  FACE_UP,    FACE_DOWN,  }, // face: front, turn: none
+       { FACE_BACK,  FACE_FRONT, FACE_UP,    FACE_DOWN,  FACE_LEFT,  FACE_RIGHT, }, // face: front, turn: left
+       { FACE_BACK,  FACE_FRONT, FACE_LEFT,  FACE_RIGHT, FACE_DOWN,  FACE_UP,    }, // face: front, turn: around
+       { FACE_BACK,  FACE_FRONT, FACE_DOWN,  FACE_UP,    FACE_RIGHT, FACE_LEFT,  }, // face: front, turn: right
+       { FACE_FRONT, FACE_BACK,  FACE_RIGHT, FACE_LEFT,  FACE_DOWN,  FACE_UP,    }, // face: back,  turn: none
+       { FACE_FRONT, FACE_BACK,  FACE_DOWN,  FACE_UP,    FACE_LEFT,  FACE_RIGHT, }, // face: back,  turn: left
+       { FACE_FRONT, FACE_BACK,  FACE_LEFT,  FACE_RIGHT, FACE_UP,    FACE_DOWN,  }, // face: back,  turn: around
+       { FACE_FRONT, FACE_BACK,  FACE_UP,    FACE_DOWN,  FACE_RIGHT, FACE_LEFT,  }, // face: back,  turn: right
+};
+
+}
diff --git a/src/world/chunk.cpp b/src/world/chunk.cpp
new file mode 100644 (file)
index 0000000..50595f4
--- /dev/null
@@ -0,0 +1,843 @@
+#include "BlockLookup.hpp"
+#include "Chunk.hpp"
+#include "ChunkLoader.hpp"
+
+#include "Generator.hpp"
+
+#include <algorithm>
+#include <limits>
+#include <queue>
+
+
+namespace blank {
+
+constexpr int Chunk::width;
+constexpr int Chunk::height;
+constexpr int Chunk::depth;
+constexpr int Chunk::size;
+
+
+Chunk::Chunk(const BlockTypeRegistry &types) noexcept
+: types(&types)
+, neighbor{0}
+, blocks{}
+, light{0}
+, model()
+, position(0, 0, 0)
+, dirty(false) {
+
+}
+
+Chunk::Chunk(Chunk &&other) noexcept
+: types(other.types)
+, model(std::move(other.model))
+, position(other.position)
+, dirty(other.dirty) {
+       std::copy(other.neighbor, other.neighbor + sizeof(neighbor), neighbor);
+       std::copy(other.blocks, other.blocks + sizeof(blocks), blocks);
+       std::copy(other.light, other.light + sizeof(light), light);
+}
+
+Chunk &Chunk::operator =(Chunk &&other) noexcept {
+       types = other.types;
+       std::copy(other.neighbor, other.neighbor + sizeof(neighbor), neighbor);
+       std::copy(other.blocks, other.blocks + sizeof(blocks), blocks);
+       std::copy(other.light, other.light + sizeof(light), light);
+       model = std::move(other.model);
+       position = other.position;
+       dirty = other.dirty;
+       return *this;
+}
+
+
+namespace {
+
+struct SetNode {
+
+       Chunk *chunk;
+       Chunk::Pos pos;
+
+       SetNode(Chunk *chunk, Chunk::Pos pos)
+       : chunk(chunk), pos(pos) { }
+
+       int Get() const noexcept { return chunk->GetLight(pos); }
+       void Set(int level) noexcept { chunk->SetLight(pos, level); }
+
+       bool HasNext(Block::Face face) noexcept {
+               const BlockLookup next(chunk, pos, face);
+               return next && !next.GetType().block_light;
+       }
+       SetNode GetNext(Block::Face face) noexcept {
+               const BlockLookup next(chunk, pos, face);
+               return SetNode(&next.GetChunk(), next.GetBlockPos());
+       }
+
+};
+
+struct UnsetNode
+: public SetNode {
+
+       int level;
+
+       UnsetNode(Chunk *chunk, Chunk::Pos pos)
+       : SetNode(chunk, pos), level(Get()) { }
+
+       UnsetNode(const SetNode &set)
+       : SetNode(set), level(Get()) { }
+
+
+       bool HasNext(Block::Face face) noexcept {
+               const BlockLookup next(chunk, pos, face);
+               return next;
+       }
+       UnsetNode GetNext(Block::Face face) noexcept { return UnsetNode(SetNode::GetNext(face)); }
+
+};
+
+std::queue<SetNode> light_queue;
+std::queue<UnsetNode> dark_queue;
+
+void work_light() noexcept {
+       while (!light_queue.empty()) {
+               SetNode node = light_queue.front();
+               light_queue.pop();
+
+               int level = node.Get() - 1;
+               for (int face = 0; face < Block::FACE_COUNT; ++face) {
+                       if (node.HasNext(Block::Face(face))) {
+                               SetNode other = node.GetNext(Block::Face(face));
+                               if (other.Get() < level) {
+                                       other.Set(level);
+                                       light_queue.emplace(other);
+                               }
+                       }
+               }
+       }
+}
+
+void work_dark() noexcept {
+       while (!dark_queue.empty()) {
+               UnsetNode node = dark_queue.front();
+               dark_queue.pop();
+
+               for (int face = 0; face < Block::FACE_COUNT; ++face) {
+                       if (node.HasNext(Block::Face(face))) {
+                               UnsetNode other = node.GetNext(Block::Face(face));
+                               // TODO: if there a light source here with the same level this will err
+                               if (other.Get() != 0 && other.Get() < node.level) {
+                                       other.Set(0);
+                                       dark_queue.emplace(other);
+                               } else {
+                                       light_queue.emplace(other);
+                               }
+                       }
+               }
+       }
+}
+
+}
+
+void Chunk::SetBlock(int index, const Block &block) noexcept {
+       const BlockType &old_type = Type(blocks[index]);
+       const BlockType &new_type = Type(block);
+
+       blocks[index] = block;
+
+       if (&old_type == &new_type) return;
+
+       if (new_type.luminosity > old_type.luminosity) {
+               // light added
+               SetLight(index, new_type.luminosity);
+               light_queue.emplace(this, ToPos(index));
+               work_light();
+       } else if (new_type.luminosity < old_type.luminosity) {
+               // light removed
+               dark_queue.emplace(this, ToPos(index));
+               SetLight(index, 0);
+               work_dark();
+               SetLight(index, new_type.luminosity);
+               light_queue.emplace(this, ToPos(index));
+               work_light();
+       } else if (new_type.block_light && !old_type.block_light) {
+               // obstacle added
+               if (GetLight(index) > 0) {
+                       dark_queue.emplace(this, ToPos(index));
+                       SetLight(index, 0);
+                       work_dark();
+                       work_light();
+               }
+       } else if (!new_type.block_light && old_type.block_light) {
+               // obstacle removed
+               int level = 0;
+               for (int face = 0; face < Block::FACE_COUNT; ++face) {
+                       BlockLookup next_block(this, ToPos(index), Block::Face(face));
+                       if (next_block) {
+                               level = std::min(level, next_block.GetLight());
+                       }
+               }
+               if (level > 1) {
+                       SetLight(index, level - 1);
+                       light_queue.emplace(this, ToPos(index));
+                       work_light();
+               }
+       }
+}
+
+void Chunk::SetNeighbor(Chunk &other) noexcept {
+       if (other.position == position + Pos(-1, 0, 0)) {
+               if (neighbor[Block::FACE_LEFT] != &other) {
+                       neighbor[Block::FACE_LEFT] = &other;
+                       other.neighbor[Block::FACE_RIGHT] = this;
+                       for (int z = 0; z < depth; ++z) {
+                               for (int y = 0; y < height; ++y) {
+                                       Pos my_pos(0, y, z);
+                                       Pos other_pos(width - 1, y, z);
+                                       if (GetLight(my_pos) > 0) {
+                                               light_queue.emplace(this, my_pos);
+                                       }
+                                       if (other.GetLight(other_pos) > 0) {
+                                               light_queue.emplace(&other, other_pos);
+                                       }
+                               }
+                       }
+                       work_light();
+               }
+       } else if (other.position == position + Pos(1, 0, 0)) {
+               if (neighbor[Block::FACE_RIGHT] != &other) {
+                       neighbor[Block::FACE_RIGHT] = &other;
+                       other.neighbor[Block::FACE_LEFT] = this;
+                       for (int z = 0; z < depth; ++z) {
+                               for (int y = 0; y < height; ++y) {
+                                       Pos my_pos(width - 1, y, z);
+                                       Pos other_pos(0, y, z);
+                                       if (GetLight(my_pos) > 0) {
+                                               light_queue.emplace(this, my_pos);
+                                       }
+                                       if (other.GetLight(other_pos) > 0) {
+                                               light_queue.emplace(&other, other_pos);
+                                       }
+                               }
+                       }
+                       work_light();
+               }
+       } else if (other.position == position + Pos(0, -1, 0)) {
+               if (neighbor[Block::FACE_DOWN] != &other) {
+                       neighbor[Block::FACE_DOWN] = &other;
+                       other.neighbor[Block::FACE_UP] = this;
+                       for (int z = 0; z < depth; ++z) {
+                               for (int x = 0; x < width; ++x) {
+                                       Pos my_pos(x, 0, z);
+                                       Pos other_pos(x, height - 1, z);
+                                       if (GetLight(my_pos) > 0) {
+                                               light_queue.emplace(this, my_pos);
+                                       }
+                                       if (other.GetLight(other_pos) > 0) {
+                                               light_queue.emplace(&other, other_pos);
+                                       }
+                               }
+                       }
+                       work_light();
+               }
+       } else if (other.position == position + Pos(0, 1, 0)) {
+               if (neighbor[Block::FACE_UP] != &other) {
+                       neighbor[Block::FACE_UP] = &other;
+                       other.neighbor[Block::FACE_DOWN] = this;
+                       for (int z = 0; z < depth; ++z) {
+                               for (int x = 0; x < width; ++x) {
+                                       Pos my_pos(x, height - 1, z);
+                                       Pos other_pos(x, 0, z);
+                                       if (GetLight(my_pos) > 0) {
+                                               light_queue.emplace(this, my_pos);
+                                       }
+                                       if (other.GetLight(other_pos) > 0) {
+                                               light_queue.emplace(&other, other_pos);
+                                       }
+                               }
+                       }
+                       work_light();
+               }
+       } else if (other.position == position + Pos(0, 0, -1)) {
+               if (neighbor[Block::FACE_BACK] != &other) {
+                       neighbor[Block::FACE_BACK] = &other;
+                       other.neighbor[Block::FACE_FRONT] = this;
+                       for (int y = 0; y < height; ++y) {
+                               for (int x = 0; x < width; ++x) {
+                                       Pos my_pos(x, y, 0);
+                                       Pos other_pos(x, y, depth - 1);
+                                       if (GetLight(my_pos) > 0) {
+                                               light_queue.emplace(this, my_pos);
+                                       }
+                                       if (other.GetLight(other_pos) > 0) {
+                                               light_queue.emplace(&other, other_pos);
+                                       }
+                               }
+                       }
+                       work_light();
+               }
+       } else if (other.position == position + Pos(0, 0, 1)) {
+               if (neighbor[Block::FACE_FRONT] != &other) {
+                       neighbor[Block::FACE_FRONT] = &other;
+                       other.neighbor[Block::FACE_BACK] = this;
+                       for (int y = 0; y < height; ++y) {
+                               for (int x = 0; x < width; ++x) {
+                                       Pos my_pos(x, y, depth - 1);
+                                       Pos other_pos(x, y, 0);
+                                       if (GetLight(my_pos) > 0) {
+                                               light_queue.emplace(this, my_pos);
+                                       }
+                                       if (other.GetLight(other_pos) > 0) {
+                                               light_queue.emplace(&other, other_pos);
+                                       }
+                               }
+                       }
+                       work_light();
+               }
+       }
+}
+
+void Chunk::ClearNeighbors() noexcept {
+       for (int i = 0; i < Block::FACE_COUNT; ++i) {
+               neighbor[i] = nullptr;
+       }
+}
+
+void Chunk::Unlink() noexcept {
+       for (int face = 0; face < Block::FACE_COUNT; ++face) {
+               if (neighbor[face]) {
+                       neighbor[face]->neighbor[Block::Opposite(Block::Face(face))] = nullptr;
+               }
+       }
+}
+
+void Chunk::Relink() noexcept {
+       for (int face = 0; face < Block::FACE_COUNT; ++face) {
+               if (neighbor[face]) {
+                       neighbor[face]->neighbor[Block::Opposite(Block::Face(face))] = this;
+               }
+       }
+}
+
+
+void Chunk::SetLight(int index, int level) noexcept {
+       if (light[index] != level) {
+               light[index] = level;
+               Invalidate();
+       }
+}
+
+int Chunk::GetLight(int index) const noexcept {
+       return light[index];
+}
+
+float Chunk::GetVertexLight(const Pos &pos, const BlockModel::Position &vtx, const Model::Normal &norm) const noexcept {
+       int index = ToIndex(pos);
+       float light = GetLight(index);
+
+       Block::Face direct_face(Block::NormalFace(norm));
+       // tis okay
+       BlockLookup direct(const_cast<Chunk *>(this), pos, Block::NormalFace(norm));
+       if (direct) {
+               float direct_light = direct.GetLight();
+               if (direct_light > light) {
+                       light = direct_light;
+               }
+       } else {
+               return light;
+       }
+
+       if (Type(BlockAt(index)).luminosity > 0 || direct.GetType().block_light) {
+               return light;
+       }
+
+       Block::Face edge[2];
+       switch (Block::Axis(direct_face)) {
+               case 0: // X
+                       edge[0] = (vtx.y - pos.y) > 0.5f ? Block::FACE_UP : Block::FACE_DOWN;
+                       edge[1] = (vtx.z - pos.z) > 0.5f ? Block::FACE_FRONT : Block::FACE_BACK;
+                       break;
+               case 1: // Y
+                       edge[0] = (vtx.z - pos.z) > 0.5f ? Block::FACE_FRONT : Block::FACE_BACK;
+                       edge[1] = (vtx.x - pos.x) > 0.5f ? Block::FACE_RIGHT : Block::FACE_LEFT;
+                       break;
+               case 2: // Z
+                       edge[0] = (vtx.x - pos.x) > 0.5f ? Block::FACE_RIGHT : Block::FACE_LEFT;
+                       edge[1] = (vtx.y - pos.y) > 0.5f ? Block::FACE_UP : Block::FACE_DOWN;
+                       break;
+       }
+
+       int num = 1;
+       int occlusion = 0;
+
+       BlockLookup next[2] = {
+               direct.Next(edge[0]),
+               direct.Next(edge[1]),
+       };
+
+       if (next[0]) {
+               if (next[0].GetType().block_light) {
+                       ++occlusion;
+               } else {
+                       light += next[0].GetLight();
+                       ++num;
+               }
+       }
+       if (next[1]) {
+               if (next[1].GetType().block_light) {
+                       ++occlusion;
+               } else {
+                       light += next[1].GetLight();
+                       ++num;
+               }
+       }
+       if (occlusion < 2) {
+               if (next[0]) {
+                       BlockLookup corner = next[0].Next(edge[1]);
+                       if (corner) {
+                               if (corner.GetType().block_light) {
+                                       ++occlusion;
+                               } else {
+                                       light += corner.GetLight();
+                                       ++num;
+                               }
+                       }
+               } else if (next[1]) {
+                       BlockLookup corner = next[1].Next(edge[0]);
+                       if (corner) {
+                               if (corner.GetType().block_light) {
+                                       ++occlusion;
+                               } else {
+                                       light += corner.GetLight();
+                                       ++num;
+                               }
+                       }
+               }
+       } else {
+               ++occlusion;
+       }
+
+       return (light / num) - (occlusion * 0.8f);
+}
+
+
+bool Chunk::IsSurface(const Pos &pos) const noexcept {
+       const Block &block = BlockAt(pos);
+       if (!Type(block).visible) {
+               return false;
+       }
+       for (int face = 0; face < Block::FACE_COUNT; ++face) {
+               BlockLookup next = BlockLookup(const_cast<Chunk *>(this), pos, Block::Face(face));
+               if (!next || !next.GetType().visible) {
+                       return true;
+               }
+       }
+       return false;
+}
+
+
+void Chunk::Draw() noexcept {
+       if (dirty) {
+               Update();
+       }
+       model.Draw();
+}
+
+
+bool Chunk::Intersection(
+       const Ray &ray,
+       const glm::mat4 &M,
+       int &blkid,
+       float &dist,
+       glm::vec3 &normal
+) const noexcept {
+       // TODO: should be possible to heavily optimize this
+       int idx = 0;
+       blkid = -1;
+       dist = std::numeric_limits<float>::infinity();
+       for (int z = 0; z < depth; ++z) {
+               for (int y = 0; y < height; ++y) {
+                       for (int x = 0; x < width; ++x, ++idx) {
+                               const BlockType &type = Type(idx);
+                               if (!type.visible) {
+                                       continue;
+                               }
+                               float cur_dist;
+                               glm::vec3 cur_norm;
+                               if (type.shape->Intersects(ray, M * ToTransform(Pos(x, y, z), idx), cur_dist, cur_norm)) {
+                                       if (cur_dist < dist) {
+                                               blkid = idx;
+                                               dist = cur_dist;
+                                               normal = cur_norm;
+                                       }
+                               }
+                       }
+               }
+       }
+
+       if (blkid < 0) {
+               return false;
+       } else {
+               normal = glm::vec3(BlockAt(blkid).Transform() * glm::vec4(normal, 0.0f));
+               return true;
+       }
+}
+
+
+namespace {
+
+BlockModel::Buffer buf;
+
+}
+
+void Chunk::CheckUpdate() noexcept {
+       if (dirty) {
+               Update();
+       }
+}
+
+void Chunk::Update() noexcept {
+       int vtx_count = 0, idx_count = 0;
+       for (const auto &block : blocks) {
+               const Shape *shape = Type(block).shape;
+               vtx_count += shape->VertexCount();
+               idx_count += shape->VertexIndexCount();
+       }
+       buf.Clear();
+       buf.Reserve(vtx_count, idx_count);
+
+       int idx = 0;
+       BlockModel::Index vtx_counter = 0;
+       for (size_t z = 0; z < depth; ++z) {
+               for (size_t y = 0; y < height; ++y) {
+                       for (size_t x = 0; x < width; ++x, ++idx) {
+                               const BlockType &type = Type(BlockAt(idx));
+                               const Pos pos(x, y, z);
+
+                               if (!type.visible || Obstructed(pos).All()) continue;
+
+                               type.FillBlockModel(buf, ToTransform(pos, idx), vtx_counter);
+                               size_t vtx_begin = vtx_counter;
+                               vtx_counter += type.shape->VertexCount();
+
+                               for (size_t vtx = vtx_begin; vtx < vtx_counter; ++vtx) {
+                                       buf.lights.emplace_back(GetVertexLight(
+                                               pos,
+                                               buf.vertices[vtx],
+                                               type.shape->VertexNormal(vtx - vtx_begin, BlockAt(idx).Transform())
+                                       ));
+                               }
+                       }
+               }
+       }
+
+       model.Update(buf);
+       dirty = false;
+}
+
+Block::FaceSet Chunk::Obstructed(const Pos &pos) const noexcept {
+       Block::FaceSet result;
+
+       for (int f = 0; f < Block::FACE_COUNT; ++f) {
+               Block::Face face = Block::Face(f);
+               BlockLookup next(const_cast<Chunk *>(this), pos, face);
+               if (next && next.GetType().FaceFilled(next.GetBlock(), Block::Opposite(face))) {
+                       result.Set(face);
+               }
+       }
+
+       return result;
+}
+
+glm::mat4 Chunk::ToTransform(const Pos &pos, int idx) const noexcept {
+       return glm::translate(ToCoords(pos)) * BlockAt(idx).Transform();
+}
+
+
+BlockLookup::BlockLookup(Chunk *c, const Chunk::Pos &p) noexcept
+: chunk(c), pos(p) {
+       while (pos.x >= Chunk::width) {
+               if (chunk->HasNeighbor(Block::FACE_RIGHT)) {
+                       chunk = &chunk->GetNeighbor(Block::FACE_RIGHT);
+                       pos.x -= Chunk::width;
+               } else {
+                       chunk = nullptr;
+                       return;
+               }
+       }
+       while (pos.x < 0) {
+               if (chunk->HasNeighbor(Block::FACE_LEFT)) {
+                       chunk = &chunk->GetNeighbor(Block::FACE_LEFT);
+                       pos.x += Chunk::width;
+               } else {
+                       chunk = nullptr;
+                       return;
+               }
+       }
+       while (pos.y >= Chunk::height) {
+               if (chunk->HasNeighbor(Block::FACE_UP)) {
+                       chunk = &chunk->GetNeighbor(Block::FACE_UP);
+                       pos.y -= Chunk::height;
+               } else {
+                       chunk = nullptr;
+                       return;
+               }
+       }
+       while (pos.y < 0) {
+               if (chunk->HasNeighbor(Block::FACE_DOWN)) {
+                       chunk = &chunk->GetNeighbor(Block::FACE_DOWN);
+                       pos.y += Chunk::height;
+               } else {
+                       chunk = nullptr;
+                       return;
+               }
+       }
+       while (pos.z >= Chunk::depth) {
+               if (chunk->HasNeighbor(Block::FACE_FRONT)) {
+                       chunk = &chunk->GetNeighbor(Block::FACE_FRONT);
+                       pos.z -= Chunk::depth;
+               } else {
+                       chunk = nullptr;
+                       return;
+               }
+       }
+       while (pos.z < 0) {
+               if (chunk->HasNeighbor(Block::FACE_BACK)) {
+                       chunk = &chunk->GetNeighbor(Block::FACE_BACK);
+                       pos.z += Chunk::depth;
+               } else {
+                       chunk = nullptr;
+                       return;
+               }
+       }
+}
+
+BlockLookup::BlockLookup(Chunk *c, const Chunk::Pos &p, Block::Face face) noexcept
+: chunk(c), pos(p) {
+       pos += Block::FaceNormal(face);
+       if (!Chunk::InBounds(pos)) {
+               pos -= Block::FaceNormal(face) * Chunk::Extent();
+               chunk = &chunk->GetNeighbor(face);
+       }
+}
+
+
+ChunkLoader::ChunkLoader(const Config &config, const BlockTypeRegistry &reg, const Generator &gen) noexcept
+: base(0, 0, 0)
+, reg(reg)
+, gen(gen)
+, loaded()
+, to_generate()
+, to_free()
+, load_dist(config.load_dist)
+, unload_dist(config.unload_dist) {
+
+}
+
+namespace {
+
+struct ChunkLess {
+
+       explicit ChunkLess(const Chunk::Pos &base) noexcept
+       : base(base) { }
+
+       bool operator ()(const Chunk::Pos &a, const Chunk::Pos &b) const noexcept {
+               Chunk::Pos da(base - a);
+               Chunk::Pos db(base - b);
+               return
+                       da.x * da.x + da.y * da.y + da.z * da.z <
+                       db.x * db.x + db.y * db.y + db.z * db.z;
+       }
+
+       Chunk::Pos base;
+
+};
+
+}
+
+void ChunkLoader::Generate(const Chunk::Pos &from, const Chunk::Pos &to) {
+       for (int z = from.z; z < to.z; ++z) {
+               for (int y = from.y; y < to.y; ++y) {
+                       for (int x = from.x; x < to.x; ++x) {
+                               Chunk::Pos pos(x, y, z);
+                               if (Known(pos)) {
+                                       continue;
+                               } else if (pos == base) {
+                                       Generate(pos);
+
+                               //      light testing
+                               //      for (int i = 0; i < 16; ++i) {
+                               //              for (int j = 0; j < 16; ++j) {
+                               //                      loaded.back().SetBlock(Chunk::Pos{  i, j,  0 }, Block(1));
+                               //                      loaded.back().SetBlock(Chunk::Pos{  i, j, 15 }, Block(1));
+                               //                      loaded.back().SetBlock(Chunk::Pos{  0, j,  i }, Block(1));
+                               //                      loaded.back().SetBlock(Chunk::Pos{ 15, j,  i }, Block(1));
+                               //              }
+                               //      }
+                               //      loaded.back().SetBlock(Chunk::Pos{  1,  0,  1 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{ 14,  0,  1 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{  1,  0, 14 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{ 14,  0, 14 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{  1, 15,  1 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{ 14, 15,  1 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{  1, 15, 14 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{ 14, 15, 14 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{  7,  7,  0 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{  8,  7,  0 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{  7,  8,  0 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{  8,  8,  0 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{  7,  7, 15 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{  8,  7, 15 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{  7,  8, 15 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{  8,  8, 15 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{  0,  7,  7 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{  0,  7,  8 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{  0,  8,  7 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{  0,  8,  8 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{ 15,  7,  7 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{ 15,  7,  8 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{ 15,  8,  7 }, Block(13));
+                               //      loaded.back().SetBlock(Chunk::Pos{ 15,  8,  8 }, Block(13));
+                               //      loaded.back().Invalidate();
+                               //      loaded.back().CheckUpdate();
+
+                               //      orientation testing
+                               //      for (int i = 0; i < Block::FACE_COUNT; ++i) {
+                               //              for (int j = 0; j < Block::TURN_COUNT; ++j) {
+                               //                      loaded.back().BlockAt(512 * j + 2 * i) = Block(3 * (j + 1), Block::Face(i), Block::Turn(j));
+                               //              }
+                               //      }
+                               //      loaded.back().Invalidate();
+                               //      loaded.back().CheckUpdate();
+                               } else {
+                                       to_generate.emplace_back(pos);
+                               }
+                       }
+               }
+       }
+       to_generate.sort(ChunkLess(base));
+}
+
+Chunk &ChunkLoader::Generate(const Chunk::Pos &pos) {
+       loaded.emplace_back(reg);
+       Chunk &chunk = loaded.back();
+       chunk.Position(pos);
+       gen(chunk);
+       Insert(chunk);
+       return chunk;
+}
+
+void ChunkLoader::Insert(Chunk &chunk) noexcept {
+       for (Chunk &other : loaded) {
+               chunk.SetNeighbor(other);
+       }
+}
+
+void ChunkLoader::Remove(Chunk &chunk) noexcept {
+       chunk.Unlink();
+}
+
+Chunk *ChunkLoader::Loaded(const Chunk::Pos &pos) noexcept {
+       for (Chunk &chunk : loaded) {
+               if (chunk.Position() == pos) {
+                       return &chunk;
+               }
+       }
+       return nullptr;
+}
+
+bool ChunkLoader::Queued(const Chunk::Pos &pos) noexcept {
+       for (const Chunk::Pos &chunk : to_generate) {
+               if (chunk == pos) {
+                       return true;
+               }
+       }
+       return nullptr;
+}
+
+bool ChunkLoader::Known(const Chunk::Pos &pos) noexcept {
+       if (Loaded(pos)) return true;
+       return Queued(pos);
+}
+
+Chunk &ChunkLoader::ForceLoad(const Chunk::Pos &pos) {
+       Chunk *chunk = Loaded(pos);
+       if (chunk) {
+               return *chunk;
+       }
+
+       for (auto iter(to_generate.begin()), end(to_generate.end()); iter != end; ++iter) {
+               if (*iter == pos) {
+                       to_generate.erase(iter);
+                       break;
+               }
+       }
+
+       return Generate(pos);
+}
+
+void ChunkLoader::Rebase(const Chunk::Pos &new_base) {
+       if (new_base == base) {
+               return;
+       }
+       base = new_base;
+
+       // unload far away chunks
+       for (auto iter(loaded.begin()), end(loaded.end()); iter != end;) {
+               if (std::abs(base.x - iter->Position().x) > unload_dist
+                               || std::abs(base.y - iter->Position().y) > unload_dist
+                               || std::abs(base.z - iter->Position().z) > unload_dist) {
+                       auto saved = iter;
+                       Remove(*saved);
+                       ++iter;
+                       to_free.splice(to_free.end(), loaded, saved);
+               } else {
+                       ++iter;
+               }
+       }
+       // abort far away queued chunks
+       for (auto iter(to_generate.begin()), end(to_generate.end()); iter != end;) {
+               if (std::abs(base.x - iter->x) > unload_dist
+                               || std::abs(base.y - iter->y) > unload_dist
+                               || std::abs(base.z - iter->z) > unload_dist) {
+                       iter = to_generate.erase(iter);
+               } else {
+                       ++iter;
+               }
+       }
+       // add missing new chunks
+       GenerateSurrounding(base);
+}
+
+void ChunkLoader::GenerateSurrounding(const Chunk::Pos &pos) {
+       const Chunk::Pos offset(load_dist, load_dist, load_dist);
+       Generate(pos - offset, pos + offset);
+}
+
+void ChunkLoader::Update() {
+       if (to_generate.empty()) {
+               return;
+       }
+
+       Chunk::Pos pos(to_generate.front());
+       to_generate.pop_front();
+
+       for (auto iter(to_free.begin()), end(to_free.end()); iter != end; ++iter) {
+               if (iter->Position() == pos) {
+                       iter->Relink();
+                       loaded.splice(loaded.end(), to_free, iter);
+                       return;
+               }
+       }
+
+       if (to_free.empty()) {
+               loaded.emplace_back(reg);
+       } else {
+               to_free.front().ClearNeighbors();
+               loaded.splice(loaded.end(), to_free, to_free.begin());
+       }
+       Chunk &chunk = loaded.back();
+       chunk.Position(pos);
+       gen(chunk);
+       Insert(chunk);
+}
+
+}