]> git.localhorst.tv Git - blobs.git/commitdiff
simple planet render
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Mon, 6 Nov 2017 21:08:25 +0000 (22:08 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Mon, 6 Nov 2017 21:08:25 +0000 (22:08 +0100)
37 files changed:
src/app/Application.hpp
src/app/Assets.hpp [new file with mode: 0644]
src/app/MasterState.hpp [new file with mode: 0644]
src/app/State.hpp
src/app/app.cpp
src/app/error.cpp [new file with mode: 0644]
src/app/error.hpp [new file with mode: 0644]
src/app/init.cpp
src/app/init.hpp
src/app/states.cpp [new file with mode: 0644]
src/blobs.cpp
src/graphics/ArrayTexture.hpp [new file with mode: 0644]
src/graphics/Camera.hpp [new file with mode: 0644]
src/graphics/CubeMap.hpp [new file with mode: 0644]
src/graphics/Font.hpp [new file with mode: 0644]
src/graphics/Format.hpp [new file with mode: 0644]
src/graphics/PlanetSurface.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/SimpleVAO.hpp [new file with mode: 0644]
src/graphics/Texture.hpp [new file with mode: 0644]
src/graphics/TextureBase.hpp [new file with mode: 0644]
src/graphics/Viewport.hpp [new file with mode: 0644]
src/graphics/buffer.hpp [new file with mode: 0644]
src/graphics/const.hpp [new file with mode: 0644]
src/graphics/gl_traits.cpp [new file with mode: 0644]
src/graphics/gl_traits.hpp [new file with mode: 0644]
src/graphics/glm.hpp [new file with mode: 0644]
src/graphics/render.cpp [new file with mode: 0644]
src/graphics/shader.cpp [new file with mode: 0644]
src/graphics/viewport.cpp [new file with mode: 0644]
src/world/Body.hpp [new file with mode: 0644]
src/world/Planet.hpp
src/world/Simulation.hpp [new file with mode: 0644]
src/world/Sun.hpp [new file with mode: 0644]
src/world/sim.cpp [new file with mode: 0644]
src/world/world.cpp

index 4ed4b769d916d4f043928d3364ca8b49ff8a8808..040935f714d2a2a7081470a4dd31ba660faec024 100644 (file)
@@ -5,14 +5,18 @@
 
 
 namespace blobs {
+namespace graphics {
+       class Viewport;
+}
 namespace app {
 
 class State;
+class Window;
 
 class Application {
 
 public:
-       Application();
+       Application(Window &, graphics::Viewport &);
        ~Application();
 
        Application(const Application &) = delete;
@@ -36,6 +40,8 @@ public:
        void HandleEvents();
 
 private:
+       Window &window;
+       graphics::Viewport &viewport;
        std::stack<State *> states;
 
 };
diff --git a/src/app/Assets.hpp b/src/app/Assets.hpp
new file mode 100644 (file)
index 0000000..ed3c2e2
--- /dev/null
@@ -0,0 +1,35 @@
+#ifndef BLOBS_APP_ASSETS_HPP_
+#define BLOBS_APP_ASSETS_HPP_
+
+#include "../graphics/ArrayTexture.hpp"
+#include "../graphics/PlanetSurface.hpp"
+
+
+namespace blobs {
+namespace app {
+
+struct Assets {
+
+       struct {
+               graphics::ArrayTexture tiles;
+       } textures;
+
+       struct {
+               graphics::PlanetSurface planet_surface;
+       } shaders;
+
+       Assets();
+       ~Assets();
+
+       Assets(const Assets &) = delete;
+       Assets &operator =(const Assets &) = delete;
+
+       Assets(Assets &&) = delete;
+       Assets &operator =(Assets &&) = delete;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/app/MasterState.hpp b/src/app/MasterState.hpp
new file mode 100644 (file)
index 0000000..fb245cb
--- /dev/null
@@ -0,0 +1,57 @@
+#ifndef BLOBS_APP_MASTERSTATE_HPP_
+#define BLOBS_APP_MASTERSTATE_HPP_
+
+#include "State.hpp"
+
+#include "Assets.hpp"
+#include "../graphics/Camera.hpp"
+
+
+namespace blobs {
+namespace world {
+       class Body;
+       class Simulation;
+}
+namespace app {
+
+class MasterState
+: public State {
+
+public:
+       MasterState(Assets &, world::Simulation &) noexcept;
+       ~MasterState() noexcept;
+
+       MasterState(const MasterState &) = delete;
+       MasterState &operator =(const MasterState &) = delete;
+
+       MasterState(MasterState &&) = delete;
+       MasterState &operator =(MasterState &&) = delete;
+
+public:
+       void SetReference(world::Body &r) { reference = &r; }
+
+private:
+       void OnResize(int w, int h) override;
+
+       void OnUpdate(int dt) override;
+       void OnRender(graphics::Viewport &) override;
+
+       void Tick();
+       int FrameMS() const noexcept;
+
+private:
+       Assets &assets;
+       world::Simulation &sim;
+       world::Body *reference;
+
+       graphics::Camera cam;
+
+       int remain;
+       int thirds;
+
+};
+
+}
+}
+
+#endif
index 4462904ad5146f13b6cd5a21f961ef8ae110a329..dc73e21fc90546920c8ea9b4bb830f730986ac4a 100644 (file)
@@ -5,6 +5,9 @@
 
 
 namespace blobs {
+namespace graphics {
+       class Viewport;
+}
 namespace app {
 
 class Application;
@@ -16,7 +19,7 @@ class State {
        void Handle(const SDL_Event &);
        void Handle(const SDL_WindowEvent &);
        void Update(int dt);
-       void Render();
+       void Render(graphics::Viewport &);
 
        virtual void OnEnter() { }
        virtual void OnResume() { }
@@ -25,13 +28,26 @@ class State {
 
        virtual void OnFocus() { }
        virtual void OnBlur() { }
-       virtual void OnResize() { }
+       virtual void OnResize(int w, int h) { }
+
+       virtual void OnKeyDown(const SDL_KeyboardEvent &) { }
+       virtual void OnKeyUp(const SDL_KeyboardEvent &) { }
+       virtual void OnMouseDown(const SDL_MouseButtonEvent &) { }
+       virtual void OnMouseUp(const SDL_MouseButtonEvent &) { }
+       virtual void OnMouseMotion(const SDL_MouseMotionEvent &) { }
+       virtual void OnMouseWheel(const SDL_MouseWheelEvent &) { }
+       virtual void OnQuit();
 
-       virtual void OnEvent(const SDL_Event &);
-       virtual void OnUpdate(int dt);
-       virtual void OnRender();
+       virtual void OnUpdate(int dt) { }
+       virtual void OnRender(graphics::Viewport &) { }
 
        int ref_count = 0;
+       Application *app = nullptr;
+
+protected:
+       Application &App() {
+               return *app;
+       }
 
 };
 
index a7bbf39321db2de6f685892696fb2576eeed1893..064786fb82baf42403588ec4f789c23b3e1f6d2e 100644 (file)
@@ -1,14 +1,20 @@
 #include "Application.hpp"
+#include "Assets.hpp"
 #include "State.hpp"
 
+#include "init.hpp"
+#include "../graphics/Viewport.hpp"
+
 #include <SDL.h>
 
 
 namespace blobs {
 namespace app {
 
-Application::Application()
-: states() {
+Application::Application(Window &win, graphics::Viewport &vp)
+: window(win)
+, viewport(vp)
+, states() {
 }
 
 Application::~Application() {
@@ -16,6 +22,7 @@ Application::~Application() {
 
 
 void Application::PushState(State *s) {
+       s->app = this;
        if (!states.empty()) {
                states.top()->OnPause();
        }
@@ -39,6 +46,7 @@ State *Application::PopState() {
 }
 
 State *Application::SwitchState(State *s_new) {
+       s_new->app = this;
        State *s_old = states.top();
        states.top() = s_new;
        --s_old->ref_count;
@@ -78,23 +86,49 @@ void Application::Loop(int dt) {
        if (!HasState()) return;
        GetState().Update(dt);
        if (!HasState()) return;
-       GetState().Render();
+       viewport.Clear();
+       GetState().Render(viewport);
+       window.Flip();
 }
 
 void Application::HandleEvents() {
        SDL_Event event;
        while (HasState() && SDL_PollEvent(&event)) {
+               if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
+                       viewport.Resize(event.window.data1, event.window.data2);
+               }
                GetState().Handle(event);
        }
 }
 
 void State::Handle(const SDL_Event &event) {
        switch (event.type) {
+               case SDL_KEYDOWN:
+                       OnKeyDown(event.key);
+                       break;
+               case SDL_KEYUP:
+                       OnKeyUp(event.key);
+                       break;
+               case SDL_MOUSEBUTTONDOWN:
+                       OnMouseDown(event.button);
+                       break;
+               case SDL_MOUSEBUTTONUP:
+                       OnMouseUp(event.button);
+                       break;
+               case SDL_MOUSEMOTION:
+                       OnMouseMotion(event.motion);
+                       break;
+               case SDL_MOUSEWHEEL:
+                       OnMouseWheel(event.wheel);
+                       break;
+               case SDL_QUIT:
+                       OnQuit();
+                       break;
                case SDL_WINDOWEVENT:
                        Handle(event.window);
                        break;
                default:
-                       OnEvent(event);
+                       // ignore
                        break;
        }
 }
@@ -108,8 +142,7 @@ void State::Handle(const SDL_WindowEvent &event) {
                        OnBlur();
                        break;
                case SDL_WINDOWEVENT_RESIZED:
-                       //env.viewport.Resize(event.data1, event.data2);
-                       OnResize();
+                       OnResize(event.data1, event.data2);
                        break;
                default:
                        break;
@@ -120,8 +153,32 @@ void State::Update(int dt) {
        OnUpdate(dt);
 }
 
-void State::Render() {
-       OnRender();
+void State::Render(graphics::Viewport &viewport) {
+       OnRender(viewport);
+}
+
+void State::OnQuit() {
+       while (App().HasState()) {
+               App().PopState();
+       }
+}
+
+
+Assets::Assets() {
+       graphics::Format format;
+       textures.tiles.Bind();
+       textures.tiles.Reserve(1, 1, 3, format);
+       std::uint8_t texdata[] = {
+               0xFF, 0x00, 0x00, 0xFF,
+               0x00, 0xFF, 0x00, 0xFF,
+               0x00, 0x00, 0xFF, 0xFF,
+       };
+       textures.tiles.Data(0, format, texdata);
+       textures.tiles.Data(1, format, texdata + 4);
+       textures.tiles.Data(2, format, texdata + 8);
+}
+
+Assets::~Assets() {
 }
 
 }
diff --git a/src/app/error.cpp b/src/app/error.cpp
new file mode 100644 (file)
index 0000000..d4fe7ed
--- /dev/null
@@ -0,0 +1,87 @@
+#include "error.hpp"
+
+#include <alut.h>
+#include <SDL.h>
+#include <SDL_net.h>
+#include <GL/glew.h>
+
+using std::string;
+using std::runtime_error;
+
+
+namespace {
+
+string sdl_error_append(string msg) {
+       const char *error = SDL_GetError();
+       if (*error != '\0') {
+               msg += ": ";
+               msg += error;
+               SDL_ClearError();
+       }
+       return msg;
+}
+
+string net_error_append(string msg) {
+       const char *error = SDLNet_GetError();
+       if (*error != '\0') {
+               msg += ": ";
+               msg += error;
+       }
+       return msg;
+}
+
+string alut_error_append(ALenum num, string msg) {
+       const char *error = alutGetErrorString(num);
+       if (*error != '\0') {
+               msg += ": ";
+               msg += error;
+       }
+       return msg;
+}
+
+string error_append(string msg, const char *err) {
+       if (err && *err) {
+               msg += ": ";
+               msg += err;
+       }
+       return msg;
+}
+
+}
+
+namespace blobs {
+namespace app {
+
+AlutError::AlutError(ALenum num)
+: runtime_error(alutGetErrorString(num)) {
+}
+
+AlutError::AlutError(ALenum num, const string &msg)
+: runtime_error(alut_error_append(num, msg)) {
+}
+
+
+GLError::GLError(const char *msg)
+: runtime_error(error_append(msg, reinterpret_cast<const char *>(gluErrorString(glGetError())))) {
+}
+
+
+NetError::NetError()
+: runtime_error(SDLNet_GetError()) {
+}
+
+NetError::NetError(const string &msg)
+: runtime_error(net_error_append(msg)) {
+}
+
+
+SDLError::SDLError()
+: runtime_error(SDL_GetError()) {
+}
+
+SDLError::SDLError(const string &msg)
+: runtime_error(sdl_error_append(msg)) {
+}
+
+}
+}
diff --git a/src/app/error.hpp b/src/app/error.hpp
new file mode 100644 (file)
index 0000000..d04e30c
--- /dev/null
@@ -0,0 +1,49 @@
+#ifndef BLOBS_APP_ERROR_HPP_
+#define BLOBS_APP_ERROR_HPP_
+
+#include <al.h>
+#include <stdexcept>
+
+
+namespace blobs {
+namespace app {
+
+class AlutError
+: public std::runtime_error {
+
+public:
+       explicit AlutError(ALenum);
+       AlutError(ALenum, const std::string &);
+
+};
+
+class GLError
+: public std::runtime_error {
+
+public:
+       explicit GLError(const char *msg);
+
+};
+
+class SDLError
+: public std::runtime_error {
+
+public:
+       SDLError();
+       explicit SDLError(const std::string &);
+
+};
+
+class NetError
+: public std::runtime_error {
+
+public:
+       NetError();
+       explicit NetError(const std::string &);
+
+};
+
+}
+}
+
+#endif
index 721452f466ff81ee16fe09243676dc6048ded620..5e9bcba47e46cee1face8752b1bf88e088cdfda3 100644 (file)
@@ -1,5 +1,7 @@
 #include "init.hpp"
 
+#include "error.hpp"
+
 #include <algorithm>
 #include <iostream>
 #include <alut.h>
 #include <GL/glew.h>
 
 
-namespace {
-
-std::string sdl_error_append(std::string msg) {
-       const char *error = SDL_GetError();
-       if (*error != '\0') {
-               msg += ": ";
-               msg += error;
-               SDL_ClearError();
-       }
-       return msg;
-}
-
-std::string net_error_append(std::string msg) {
-       const char *error = SDLNet_GetError();
-       if (*error != '\0') {
-               msg += ": ";
-               msg += error;
-       }
-       return msg;
-}
-
-std::string alut_error_append(ALenum num, std::string msg) {
-       const char *error = alutGetErrorString(num);
-       if (*error != '\0') {
-               msg += ": ";
-               msg += error;
-       }
-       return msg;
-}
-
-}
 
 namespace blobs {
 namespace app {
 
-AlutError::AlutError(ALenum num)
-: std::runtime_error(alutGetErrorString(num)) {
-
-}
-
-AlutError::AlutError(ALenum num, const std::string &msg)
-: std::runtime_error(alut_error_append(num, msg)) {
-
-}
-
-
-NetError::NetError()
-: std::runtime_error(SDLNet_GetError()) {
-
-}
-
-NetError::NetError(const std::string &msg)
-: std::runtime_error(net_error_append(msg)) {
-
-}
-
-
-SDLError::SDLError()
-: std::runtime_error(SDL_GetError()) {
-
-}
-
-SDLError::SDLError(const std::string &msg)
-: std::runtime_error(sdl_error_append(msg)) {
-
-}
-
-
 InitSDL::InitSDL() {
        if (SDL_Init(SDL_INIT_EVENTS) != 0) {
                throw SDLError("SDL_Init(SDL_INIT_EVENTS)");
@@ -177,11 +115,11 @@ InitGL::InitGL(bool double_buffer, int sample_size) {
 }
 
 
-Window::Window()
+Window::Window(int w, int h)
 : handle(SDL_CreateWindow(
        "blobs",
        SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
-       960, 600,
+       w, h,
        SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE
 )) {
        if (!handle) {
@@ -246,20 +184,15 @@ InitGLEW::InitGLEW() {
 }
 
 
-InitHeadless::InitHeadless()
-: init_sdl()
-, init_net() {
-
-}
-
 Init::Init(bool double_buffer, int sample_size)
 : init_video()
 , init_img()
 , init_ttf()
 , init_gl(double_buffer, sample_size)
-, window()
+, window(960, 540)
 , ctx(window.Handle())
-, init_glew() {
+, init_glew()
+, viewport(960, 540) {
 
 }
 
index 63334e89aa46d403ab7930ba5dd3ba586f5b90f8..f1378e0c6d5a63ad81fc844ec3dbcef1ed891269 100644 (file)
@@ -1,43 +1,16 @@
 #ifndef BLOBS_APP_INIT_HPP_
 #define BLOBS_APP_INIT_HPP_
 
+#include "../graphics/Viewport.hpp"
+
 #include <al.h>
 #include <SDL.h>
-#include <stdexcept>
 #include <string>
 
 
 namespace blobs {
 namespace app {
 
-class AlutError
-: public std::runtime_error {
-
-public:
-       explicit AlutError(ALenum);
-       AlutError(ALenum, const std::string &);
-
-};
-
-class SDLError
-: public std::runtime_error {
-
-public:
-       SDLError();
-       explicit SDLError(const std::string &);
-
-};
-
-class NetError
-: public std::runtime_error {
-
-public:
-       NetError();
-       explicit NetError(const std::string &);
-
-};
-
-
 class InitSDL {
 
 public:
@@ -124,7 +97,7 @@ public:
 class Window {
 
 public:
-       Window();
+       Window(int w, int h);
        ~Window();
 
        Window(const Window &) = delete;
@@ -172,15 +145,6 @@ public:
 };
 
 
-struct InitHeadless {
-
-       InitHeadless();
-
-       InitSDL init_sdl;
-       InitNet init_net;
-
-};
-
 struct Init {
 
        Init(bool double_buffer = true, int sample_size = 1);
@@ -193,6 +157,7 @@ struct Init {
        Window window;
        GLContext ctx;
        InitGLEW init_glew;
+       graphics::Viewport viewport;
 
 };
 
diff --git a/src/app/states.cpp b/src/app/states.cpp
new file mode 100644 (file)
index 0000000..0298e0c
--- /dev/null
@@ -0,0 +1,57 @@
+#include "MasterState.hpp"
+
+#include "../world/Body.hpp"
+#include "../world/Simulation.hpp"
+
+#include <glm/gtx/transform.hpp>
+
+
+namespace blobs {
+namespace app {
+
+MasterState::MasterState(Assets &assets, world::Simulation &sim) noexcept
+: State()
+, assets(assets)
+, sim(sim)
+, reference(&sim.Root())
+, cam()
+, remain(0)
+, thirds(0) {
+       cam.View(glm::translate(glm::vec3(-3.0f, -2.0f, -10.0f)));
+}
+
+MasterState::~MasterState() noexcept {
+}
+
+
+void MasterState::OnResize(int w, int h) {
+       cam.Aspect(float(w), float(h));
+}
+
+void MasterState::OnUpdate(int dt) {
+       remain += dt;
+       while (remain >= FrameMS()) {
+               Tick();
+       }
+}
+
+void MasterState::Tick() {
+       sim.Tick();
+       remain -= FrameMS();
+       thirds = (thirds + 1) % 3;
+}
+
+int MasterState::FrameMS() const noexcept {
+       return thirds == 0 ? 16 : 17;
+}
+
+
+void MasterState::OnRender(graphics::Viewport &viewport) {
+       assets.shaders.planet_surface.Activate();
+       assets.shaders.planet_surface.SetMVP(glm::mat4(1.0f), cam.View(), cam.Projection());
+       assets.shaders.planet_surface.SetTexture(assets.textures.tiles);
+       reference->Draw(assets, viewport);
+}
+
+}
+}
index 6aa0aa4a6dcfabf30cdd33f2cde09fae033698b6..8577e32a901cdc5fcda828dbec7eb51a4dd6b432 100644 (file)
@@ -1,16 +1,33 @@
 #include "app/Application.hpp"
+#include "app/Assets.hpp"
 #include "app/init.hpp"
+#include "app/MasterState.hpp"
 #include "world/Planet.hpp"
+#include "world/Simulation.hpp"
+#include "world/Sun.hpp"
 
-#include <exception>
-#include <iostream>
+#include <cstdint>
 
 
 using namespace blobs;
 
 int main(int argc, char *argv[]) {
        app::Init init;
-       world::Planet planet(1); // r=1 should be a 3³
+       app::Assets assets;
 
-       app::Application app;
+       world::Sun sun;
+       world::Simulation sim(sun);
+       world::Planet planet(3);
+       world::GenerateTest(planet);
+       planet.SetParent(sun);
+
+       app::MasterState state(assets, sim);
+       state.SetReference(planet);
+       planet.BuildVAOs();
+
+       app::Application app(init.window, init.viewport);
+       app.PushState(&state);
+       app.Run();
+
+       return 0;
 }
diff --git a/src/graphics/ArrayTexture.hpp b/src/graphics/ArrayTexture.hpp
new file mode 100644 (file)
index 0000000..26d7b5f
--- /dev/null
@@ -0,0 +1,47 @@
+#ifndef BLOBS_GRAPHICS_ARRAYTEXTURE_HPP_
+#define BLOBS_GRAPHICS_ARRAYTEXTURE_HPP_
+
+#include "Format.hpp"
+#include "TextureBase.hpp"
+
+#include <GL/glew.h>
+
+struct SDL_Surface;
+
+
+namespace blobs {
+namespace graphics {
+
+class ArrayTexture
+: public TextureBase<GL_TEXTURE_2D_ARRAY> {
+
+public:
+       ArrayTexture();
+       ~ArrayTexture();
+
+       ArrayTexture(ArrayTexture &&) noexcept;
+       ArrayTexture &operator =(ArrayTexture &&) noexcept;
+
+       ArrayTexture(const ArrayTexture &) = delete;
+       ArrayTexture &operator =(const ArrayTexture &) = delete;
+
+public:
+       GLsizei Width() const noexcept { return width; }
+       GLsizei Height() const noexcept { return height; }
+       GLsizei Depth() const noexcept { return depth; }
+
+       void Reserve(GLsizei w, GLsizei h, GLsizei d, const Format &) noexcept;
+       void Data(GLsizei l, const SDL_Surface &);
+       void Data(GLsizei l, const Format &, GLvoid *data) noexcept;
+
+private:
+       GLsizei width, height, depth;
+
+       Format format;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/graphics/Camera.hpp b/src/graphics/Camera.hpp
new file mode 100644 (file)
index 0000000..7cc141e
--- /dev/null
@@ -0,0 +1,49 @@
+#ifndef BLOBS_GRAPHICS_CAMERA_HPP_
+#define BLOBS_GRAPHICS_CAMERA_HPP_
+
+#include "glm.hpp"
+
+
+namespace blobs {
+namespace graphics {
+
+class Camera {
+
+public:
+       Camera() noexcept;
+       ~Camera() noexcept;
+
+       Camera(const Camera &) = delete;
+       Camera &operator =(const Camera &) = delete;
+
+       Camera(Camera &&) = delete;
+       Camera &operator =(Camera &&) = delete;
+
+public:
+       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() const noexcept { return projection; }
+       const glm::mat4 &View() const noexcept { return view; }
+       void View(const glm::mat4 &v) noexcept;
+
+private:
+       void UpdateProjection() noexcept;
+
+private:
+       float fov;
+       float aspect;
+       float near;
+       float far;
+
+       glm::mat4 projection;
+       glm::mat4 view;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/graphics/CubeMap.hpp b/src/graphics/CubeMap.hpp
new file mode 100644 (file)
index 0000000..0b426d6
--- /dev/null
@@ -0,0 +1,47 @@
+#ifndef BLOBS_GRAPHICS_CUBEMAP_HPP_
+#define BLOBS_GRAPHICS_CUBEMAP_HPP_
+
+#include "Format.hpp"
+#include "TextureBase.hpp"
+
+#include <GL/glew.h>
+
+struct SDL_Surface;
+
+
+namespace blobs {
+namespace graphics {
+
+class CubeMap
+: public TextureBase<GL_TEXTURE_CUBE_MAP> {
+
+public:
+       enum Face {
+               RIGHT = GL_TEXTURE_CUBE_MAP_POSITIVE_X,
+               LEFT = GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+               TOP = GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
+               BOTTOM = GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
+               BACK = GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
+               FRONT = GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
+       };
+
+public:
+       CubeMap();
+       ~CubeMap();
+
+       CubeMap(CubeMap &&) noexcept;
+       CubeMap &operator =(CubeMap &&) noexcept;
+
+       CubeMap(const CubeMap &) = delete;
+       CubeMap &operator =(const CubeMap &) = delete;
+
+public:
+       void Data(Face, const SDL_Surface &);
+       void Data(Face, GLsizei w, GLsizei h, const Format &, GLvoid *data) noexcept;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/graphics/Font.hpp b/src/graphics/Font.hpp
new file mode 100644 (file)
index 0000000..ce9cfd9
--- /dev/null
@@ -0,0 +1,75 @@
+#ifndef BLOBS_GRAPHICS_FONT_HPP_
+#define BLOBS_GRAPHICS_FONT_HPP_
+
+#include "glm.hpp"
+
+#include <SDL_ttf.h>
+
+
+namespace blobs {
+namespace graphics {
+
+class Texture;
+
+class Font {
+
+public:
+       enum FontStyle {
+               STYLE_NORMAL = TTF_STYLE_NORMAL,
+               STYLE_BOLD = TTF_STYLE_BOLD,
+               STYLE_ITALIC = TTF_STYLE_ITALIC,
+               STYLE_UNDERLINE = TTF_STYLE_UNDERLINE,
+               STYLE_STRIKE = TTF_STYLE_STRIKETHROUGH,
+       };
+       enum FontHinting {
+               HINT_NORMAL = TTF_HINTING_NORMAL,
+               HINT_LIGHT = TTF_HINTING_LIGHT,
+               HINT_MONO = TTF_HINTING_MONO,
+               HINT_NONE = TTF_HINTING_NONE,
+       };
+
+public:
+       Font(const char *src, int size, long index = 0);
+       ~Font();
+
+       Font(Font &&) noexcept;
+       Font &operator =(Font &&) noexcept;
+
+       Font(const Font &) = delete;
+       Font &operator =(const Font &) = delete;
+
+public:
+       int Style() const noexcept;
+       void Style(int) const noexcept;
+       int Outline() const noexcept;
+       void Outline(int) noexcept;
+
+       int Hinting() const noexcept;
+       void Hinting(int) const noexcept;
+       bool Kerning() const noexcept;
+       void Kerning(bool) noexcept;
+
+       int Height() const noexcept;
+       int Ascent() const noexcept;
+       int Descent() const noexcept;
+       int LineSkip() const noexcept;
+
+       const char *FamilyName() const noexcept;
+       const char *StyleName() const noexcept;
+
+       bool HasGlyph(Uint16) const noexcept;
+
+       glm::ivec2 TextSize(const char *) const;
+
+       Texture Render(const char *) const;
+       void Render(const char *, Texture &) const;
+
+private:
+       TTF_Font *handle;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/graphics/Format.hpp b/src/graphics/Format.hpp
new file mode 100644 (file)
index 0000000..52942d8
--- /dev/null
@@ -0,0 +1,29 @@
+#ifndef BLOBS_GRAPHICS_FORMAT_HPP_
+#define BLOBS_GRAPHICS_FORMAT_HPP_
+
+#include <SDL.h>
+#include <GL/glew.h>
+
+
+namespace blobs {
+namespace graphics {
+
+struct Format {
+
+       GLenum format;
+       GLenum type;
+       GLenum internal;
+
+       SDL_PixelFormat sdl_format;
+
+       Format() noexcept;
+       explicit Format(const SDL_PixelFormat &) noexcept;
+
+       bool Compatible(const Format &other) const noexcept;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/graphics/PlanetSurface.hpp b/src/graphics/PlanetSurface.hpp
new file mode 100644 (file)
index 0000000..2e73fbc
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef BLOBS_GRAPHICS_PLANETSURFACE_HPP_
+#define BLOBS_GRAPHICS_PLANETSURFACE_HPP_
+
+#include "Program.hpp"
+
+
+namespace blobs {
+namespace graphics {
+
+class ArrayTexture;
+
+class PlanetSurface {
+
+public:
+       PlanetSurface();
+       ~PlanetSurface();
+
+       PlanetSurface(const PlanetSurface &) = delete;
+       PlanetSurface &operator =(const PlanetSurface &) = delete;
+
+       PlanetSurface(PlanetSurface &&) = delete;
+       PlanetSurface &operator =(PlanetSurface &&) = delete;
+
+public:
+       void Activate() noexcept;
+
+       void SetMVP(const glm::mat4 &m, const glm::mat4 &v, const glm::mat4 &p) noexcept;
+       void SetNormal(const glm::vec3 &) noexcept;
+       void SetTexture(ArrayTexture &) noexcept;
+
+private:
+       Program prog;
+
+       GLuint m_handle;
+       GLuint mv_handle;
+       GLuint mvp_handle;
+       GLuint sampler_handle;
+       GLuint normal_handle;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/graphics/Program.hpp b/src/graphics/Program.hpp
new file mode 100644 (file)
index 0000000..7e0e93b
--- /dev/null
@@ -0,0 +1,51 @@
+#ifndef BLOBS_GRAPHICS_PROGRAM_HPP_
+#define BLOBS_GRAPHICS_PROGRAM_HPP_
+
+#include "glm.hpp"
+
+#include <iosfwd>
+#include <list>
+#include <GL/glew.h>
+
+
+namespace blobs {
+namespace graphics {
+
+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 Uniform(GLint, GLint) noexcept;
+       void Uniform(GLint, float) noexcept;
+       void Uniform(GLint, const glm::vec3 &) noexcept;
+       void Uniform(GLint, const glm::vec4 &) noexcept;
+       void Uniform(GLint, const glm::mat4 &) 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..8c72da1
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef BLOBS_GRAPHICS_SHADER_HPP_
+#define BLOBS_GRAPHICS_SHADER_HPP_
+
+#include <iosfwd>
+#include <GL/glew.h>
+
+
+namespace blobs {
+namespace graphics {
+
+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/SimpleVAO.hpp b/src/graphics/SimpleVAO.hpp
new file mode 100644 (file)
index 0000000..963c015
--- /dev/null
@@ -0,0 +1,88 @@
+#ifndef BLOBS_GRAPHICS_VAO_HPP_
+#define BLOBS_GRAPHICS_VAO_HPP_
+
+#include "buffer.hpp"
+#include "gl_traits.hpp"
+
+#include <GL/glew.h>
+
+
+namespace blobs {
+namespace graphics {
+
+/// Simple vertex array object based on indexed draw calls with attributes
+/// interleaved in a single buffer.
+template<class Attributes, class Element>
+class SimpleVAO {
+
+public:
+       SimpleVAO()
+       : vao(0)
+       , buffers{0} {
+               glGenVertexArrays(1, &vao);
+               glGenBuffers(2, buffers);
+       }
+       ~SimpleVAO() noexcept {
+               glDeleteBuffers(2, buffers);
+               glDeleteVertexArrays(1, &vao);
+       }
+
+       SimpleVAO(const SimpleVAO &) = delete;
+       SimpleVAO &operator =(const SimpleVAO &) = delete;
+
+public:
+       void Bind() const noexcept {
+               glBindVertexArray(vao);
+       }
+       void Unbind() const noexcept {
+               glBindVertexArray(0);
+       }
+       void BindAttributes() const noexcept {
+               glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
+       }
+       void BindElements() const noexcept {
+               glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]);
+       }
+       void EnableAttribute(GLuint index) noexcept {
+               glEnableVertexAttribArray(index);
+       }
+       void DisableAttribute(GLuint index) noexcept {
+               glDisableVertexAttribArray(index);
+       }
+       template<class Attribute>
+       void AttributePointer(GLuint index, bool normalized, std::size_t offset) noexcept {
+               glVertexAttribPointer(
+                       index,
+                       gl_traits<Attribute>::size,
+                       gl_traits<Attribute>::type,
+                       normalized,
+                       sizeof(Attributes),
+                       reinterpret_cast<const void *>(offset)
+               );
+       }
+       void ReserveAttributes(std::size_t size, GLenum usage) noexcept {
+               glBufferData(GL_ARRAY_BUFFER, size * sizeof(Attributes), nullptr, usage);
+       }
+       MappedBuffer<Attributes> MapAttributes(GLenum access) {
+               return MappedBuffer<Attributes>(GL_ARRAY_BUFFER, access);
+       }
+       void ReserveElements(std::size_t size, GLenum usage) noexcept {
+               glBufferData(GL_ELEMENT_ARRAY_BUFFER, size * sizeof(Element), nullptr, usage);
+       }
+       MappedBuffer<Element> MapElements(GLenum access) {
+               return MappedBuffer<Element>(GL_ELEMENT_ARRAY_BUFFER, access);
+       }
+       void DrawTriangles(std::size_t size, std::size_t offset = 0) const noexcept {
+               glDrawElements(GL_TRIANGLES, size, gl_traits<Element>::type, ((Element *) nullptr) + offset);
+       }
+
+private:
+       GLuint vao;
+       GLuint buffers[2];
+
+};
+
+}
+}
+
+#endif
diff --git a/src/graphics/Texture.hpp b/src/graphics/Texture.hpp
new file mode 100644 (file)
index 0000000..6ad7eb9
--- /dev/null
@@ -0,0 +1,48 @@
+#ifndef BLOBS_GRAPHICS_TEXTURE_HPP_
+#define BLOBS_GRAPHICS_TEXTURE_HPP_
+
+#include "TextureBase.hpp"
+
+#include <GL/glew.h>
+
+struct SDL_Surface;
+
+
+namespace blobs {
+namespace graphics {
+
+struct Format;
+
+class Texture
+: public TextureBase<GL_TEXTURE_2D> {
+
+public:
+       Texture();
+       ~Texture();
+
+       Texture(Texture &&) noexcept;
+       Texture &operator =(Texture &&) noexcept;
+
+       Texture(const Texture &) = delete;
+       Texture &operator =(const Texture &) = delete;
+
+public:
+       GLsizei Width() const noexcept { return width; }
+       GLsizei Height() const noexcept { return height; }
+
+       void Data(const SDL_Surface &, bool pad2 = true) noexcept;
+       void Data(GLsizei w, GLsizei h, const Format &, GLvoid *data) noexcept;
+
+       static void UnpackAlignment(GLint) noexcept;
+       static int UnpackAlignmentFromPitch(int) noexcept;
+       static void UnpackRowLength(GLint) noexcept;
+
+private:
+       GLsizei width, height;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/graphics/TextureBase.hpp b/src/graphics/TextureBase.hpp
new file mode 100644 (file)
index 0000000..9129441
--- /dev/null
@@ -0,0 +1,71 @@
+#ifndef BLOBS_GRAPHICS_TEXTUREBASE_HPP_
+#define BLOBS_GRAPHICS_TEXTUREBASE_HPP_
+
+#include <GL/glew.h>
+
+
+namespace blobs {
+namespace graphics {
+
+template<GLenum TARGET, GLsizei COUNT = 1>
+class TextureBase {
+
+public:
+       TextureBase();
+       ~TextureBase();
+
+       TextureBase(TextureBase &&other) noexcept;
+       TextureBase &operator =(TextureBase &&) noexcept;
+
+       TextureBase(const TextureBase &) = delete;
+       TextureBase &operator =(const TextureBase &) = delete;
+
+public:
+       void Bind(GLsizei which = 0) noexcept {
+               glBindTexture(TARGET, handle[which]);
+       }
+
+       void FilterNearest() noexcept {
+               glTexParameteri(TARGET, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+               glTexParameteri(TARGET, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+       }
+       void FilterLinear() noexcept {
+               glTexParameteri(TARGET, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+               glTexParameteri(TARGET, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+       }
+       void FilterTrilinear() noexcept {
+               glTexParameteri(TARGET, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+               glTexParameteri(TARGET, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+               glGenerateMipmap(TARGET);
+       }
+
+       void WrapEdge() noexcept {
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+       }
+       void WrapBorder() noexcept {
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER);
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
+       }
+       void WrapRepeat() noexcept {
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_R, GL_REPEAT);
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_S, GL_REPEAT);
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_T, GL_REPEAT);
+       }
+       void WrapMirror() noexcept {
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT);
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
+               glTexParameteri(TARGET, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
+       }
+
+private:
+       GLuint handle[COUNT];
+
+};
+
+}
+}
+
+#endif
diff --git a/src/graphics/Viewport.hpp b/src/graphics/Viewport.hpp
new file mode 100644 (file)
index 0000000..78178a6
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef BLOBS_GRAPHICS_VIEWPORT_HPP_
+#define BLOBS_GRAPHICS_VIEWPORT_HPP_
+
+
+namespace blobs {
+namespace graphics {
+
+class Viewport {
+
+public:
+       Viewport(int width, int height);
+       ~Viewport();
+
+       Viewport(const Viewport &) = delete;
+       Viewport &operator =(const Viewport &) = delete;
+
+       Viewport(Viewport &&) = delete;
+       Viewport &operator =(Viewport &&) = delete;
+
+public:
+       int Width() const {
+               return width;
+       }
+       int Height() const {
+               return height;
+       }
+       void Resize(int w, int h);
+
+       void Clear();
+
+private:
+       int width;
+       int height;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/graphics/buffer.hpp b/src/graphics/buffer.hpp
new file mode 100644 (file)
index 0000000..0d93920
--- /dev/null
@@ -0,0 +1,62 @@
+#ifndef BLOBS_GRAPHICS_BUFFER_HPP_
+#define BLOBS_GRAPHICS_BUFFER_HPP_
+
+#include "../app/error.hpp"
+
+#include <algorithm>
+#include <GL/glew.h>
+
+
+namespace blobs {
+namespace graphics {
+
+template<class T>
+class MappedBuffer {
+
+public:
+       MappedBuffer(GLenum target, GLenum access)
+       : buf(reinterpret_cast<T *>(glMapBuffer(target, access)))
+       , target(target) {
+               if (!buf) {
+                       throw app::GLError("failed to map buffer");
+               }
+       }
+       MappedBuffer()
+       : buf(nullptr)
+       , target(0) {
+       }
+       ~MappedBuffer() noexcept {
+               if (buf) {
+                       glUnmapBuffer(target);
+               }
+       }
+
+       MappedBuffer(MappedBuffer<T> &&other) noexcept
+       : buf(other.buf)
+       , target(other.target) {
+               other.buf = nullptr;
+       }
+       MappedBuffer<T> &operator =(MappedBuffer<T> &&other) noexcept {
+               std::swap(buf, other.buf);
+               std::swap(target, other.target);
+       }
+
+       MappedBuffer(const MappedBuffer<T> &) = delete;
+       MappedBuffer<T> &operator =(const MappedBuffer<T> &) = delete;
+
+       explicit operator bool() const noexcept { return buf; }
+
+public:
+       T &operator [](std::size_t i) noexcept { return buf[i]; }
+       const T &operator [](std::size_t i) const noexcept { return buf[i]; }
+
+private:
+       T *buf;
+       GLenum target;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/graphics/const.hpp b/src/graphics/const.hpp
new file mode 100644 (file)
index 0000000..a65dc10
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef BLOBS_GRAPHICS_CONST_HPP_
+#define BLOBS_GRAPHICS_CONST_HPP_
+
+
+namespace blobs {
+namespace graphics {
+
+constexpr double PI = 3.141592653589793238462643383279502884;
+constexpr double PI_0p25 = PI * 0.25;
+constexpr double PI_0p5 = PI * 0.5;
+constexpr double PI_1p5 = PI * 1.5;
+constexpr double PI_2p0 = PI * 2.0;
+
+constexpr double PI_inv = 1.0 / PI;
+constexpr double PI_0p5_inv = 1.0 / PI_0p5;
+
+}
+}
+
+#endif
diff --git a/src/graphics/gl_traits.cpp b/src/graphics/gl_traits.cpp
new file mode 100644 (file)
index 0000000..b99e62f
--- /dev/null
@@ -0,0 +1,32 @@
+#include "gl_traits.hpp"
+
+
+namespace blobs {
+namespace graphics {
+
+constexpr GLint gl_traits<signed char>::size;
+constexpr GLenum gl_traits<signed char>::type;
+
+constexpr GLint gl_traits<unsigned char>::size;
+constexpr GLenum gl_traits<unsigned char>::type;
+
+constexpr GLint gl_traits<short>::size;
+constexpr GLenum gl_traits<short>::type;
+
+constexpr GLint gl_traits<unsigned short>::size;
+constexpr GLenum gl_traits<unsigned short>::type;
+
+constexpr GLint gl_traits<int>::size;
+constexpr GLenum gl_traits<int>::type;
+
+constexpr GLint gl_traits<unsigned int>::size;
+constexpr GLenum gl_traits<unsigned int>::type;
+
+constexpr GLint gl_traits<float>::size;
+constexpr GLenum gl_traits<float>::type;
+
+constexpr GLint gl_traits<double>::size;
+constexpr GLenum gl_traits<double>::type;
+
+}
+}
diff --git a/src/graphics/gl_traits.hpp b/src/graphics/gl_traits.hpp
new file mode 100644 (file)
index 0000000..a399893
--- /dev/null
@@ -0,0 +1,122 @@
+#ifndef BLOBS_GRAPHICS_GL_TRAITS_HPP_
+#define BLOBS_GRAPHICS_GL_TRAITS_HPP_
+
+#include "glm.hpp"
+
+#include <GL/glew.h>
+
+
+namespace blobs {
+namespace graphics {
+
+template<class T>
+struct gl_traits {
+
+       /// number of components per generic attribute
+       /// must be 1, 2, 3, 4
+       // static constexpr GLint size;
+
+       /// component type
+       /// accepted values are:
+       ///   GL_BYTE, GL_UNSIGNED_BYTE,
+       ///   GL_SHORT, GL_UNSIGNED_SHORT,
+       ///   GL_INT, GL_UNSIGNED_INT,
+       ///   GL_HALF_FLOAT, GL_FLOAT, GL_DOUBLE,
+       ///   GL_FIXED, GL_INT_2_10_10_10_REV, GL_UNSIGNED_INT_2_10_10_10_REV
+       // static constexpr GLenum type;
+
+};
+
+
+// basic types
+
+template<> struct gl_traits<signed char> {
+       static constexpr GLint size = 1;
+       static constexpr GLenum type = GL_BYTE;
+};
+
+template<> struct gl_traits<unsigned char> {
+       static constexpr GLint size = 1;
+       static constexpr GLenum type = GL_UNSIGNED_BYTE;
+};
+
+template<> struct gl_traits<short> {
+       static constexpr GLint size = 1;
+       static constexpr GLenum type = GL_SHORT;
+};
+
+template<> struct gl_traits<unsigned short> {
+       static constexpr GLint size = 1;
+       static constexpr GLenum type = GL_UNSIGNED_SHORT;
+};
+
+template<> struct gl_traits<int> {
+       static constexpr GLint size = 1;
+       static constexpr GLenum type = GL_INT;
+};
+
+template<> struct gl_traits<unsigned int> {
+       static constexpr GLint size = 1;
+       static constexpr GLenum type = GL_UNSIGNED_INT;
+};
+
+template<> struct gl_traits<float> {
+       static constexpr GLint size = 1;
+       static constexpr GLenum type = GL_FLOAT;
+};
+
+template<> struct gl_traits<double> {
+       static constexpr GLint size = 1;
+       static constexpr GLenum type = GL_DOUBLE;
+};
+
+// composite types
+
+template<>
+template<class T, glm::precision P>
+struct gl_traits<glm::tvec1<T, P>> {
+       static constexpr GLint size = 1;
+       static constexpr GLenum type = gl_traits<T>::type;
+};
+template<class T, glm::precision P>
+constexpr GLint gl_traits<glm::tvec1<T, P>>::size;
+template<class T, glm::precision P>
+constexpr GLenum gl_traits<glm::tvec1<T, P>>::type;
+
+template<>
+template<class T, glm::precision P>
+struct gl_traits<glm::tvec2<T, P>> {
+       static constexpr GLint size = 2;
+       static constexpr GLenum type = gl_traits<T>::type;
+};
+template<class T, glm::precision P>
+constexpr GLint gl_traits<glm::tvec2<T, P>>::size;
+template<class T, glm::precision P>
+constexpr GLenum gl_traits<glm::tvec2<T, P>>::type;
+
+template<>
+template<class T, glm::precision P>
+struct gl_traits<glm::tvec3<T, P>> {
+       static constexpr GLint size = 3;
+       static constexpr GLenum type = gl_traits<T>::type;
+};
+template<class T, glm::precision P>
+constexpr GLint gl_traits<glm::tvec3<T, P>>::size;
+template<class T, glm::precision P>
+constexpr GLenum gl_traits<glm::tvec3<T, P>>::type;
+
+template<>
+template<class T, glm::precision P>
+struct gl_traits<glm::tvec4<T, P>> {
+       static constexpr GLint size = 4;
+       static constexpr GLenum type = gl_traits<T>::type;
+};
+template<class T, glm::precision P>
+constexpr GLint gl_traits<glm::tvec4<T, P>>::size;
+template<class T, glm::precision P>
+constexpr GLenum gl_traits<glm::tvec4<T, P>>::type;
+
+}
+}
+
+#endif
diff --git a/src/graphics/glm.hpp b/src/graphics/glm.hpp
new file mode 100644 (file)
index 0000000..1c39ba5
--- /dev/null
@@ -0,0 +1,23 @@
+#ifndef BLOBS_GRAPHICS_GLM_HPP_
+#define BLOBS_GRAPHICS_GLM_HPP_
+
+#ifndef GLM_FORCE_RADIANS
+#  define GLM_FORCE_RADIANS 1
+#endif
+
+#include <glm/glm.hpp>
+
+// GLM moved tvec[1234] from glm::detail to glm in 0.9.6
+
+#if GLM_VERSION < 96
+
+namespace glm {
+       using tvec1 = detail::tvec1;
+       using tvec2 = detail::tvec2;
+       using tvec3 = detail::tvec3;
+       using tvec4 = detail::tvec4;
+}
+
+#endif
+
+#endif
diff --git a/src/graphics/render.cpp b/src/graphics/render.cpp
new file mode 100644 (file)
index 0000000..5a0e492
--- /dev/null
@@ -0,0 +1,469 @@
+#include "ArrayTexture.hpp"
+#include "CubeMap.hpp"
+#include "Font.hpp"
+#include "Format.hpp"
+#include "Texture.hpp"
+#include "TextureBase.hpp"
+#include "Viewport.hpp"
+
+#include "../app/error.hpp"
+
+#include <algorithm>
+#include <cstring>
+#include <iostream>
+#include <memory>
+#include <stdexcept>
+
+
+namespace blobs {
+namespace graphics {
+
+Font::Font(const char *src, int size, long index)
+: handle(TTF_OpenFontIndex(src, size, index)) {
+       if (!handle) {
+               throw std::runtime_error(TTF_GetError());
+       }
+}
+
+Font::~Font() {
+       if (handle) {
+               TTF_CloseFont(handle);
+       }
+}
+
+Font::Font(Font &&other) noexcept
+: handle(other.handle) {
+       other.handle = nullptr;
+}
+
+Font &Font::operator =(Font &&other) noexcept {
+       std::swap(handle, other.handle);
+       return *this;
+}
+
+
+int Font::Style() const noexcept {
+       return TTF_GetFontStyle(handle);
+}
+
+void Font::Style(int s) const noexcept {
+       TTF_SetFontStyle(handle, s);
+}
+
+int Font::Outline() const noexcept {
+       return TTF_GetFontOutline(handle);
+}
+
+void Font::Outline(int px) noexcept {
+       TTF_SetFontOutline(handle, px);
+}
+
+
+int Font::Hinting() const noexcept {
+       return TTF_GetFontHinting(handle);
+}
+
+void Font::Hinting(int h) const noexcept {
+       TTF_SetFontHinting(handle, h);
+}
+
+bool Font::Kerning() const noexcept {
+       return TTF_GetFontKerning(handle);
+}
+
+void Font::Kerning(bool b) noexcept {
+       TTF_SetFontKerning(handle, b);
+}
+
+
+int Font::Height() const noexcept {
+       return TTF_FontHeight(handle);
+}
+
+int Font::Ascent() const noexcept {
+       return TTF_FontAscent(handle);
+}
+
+int Font::Descent() const noexcept {
+       return TTF_FontDescent(handle);
+}
+
+int Font::LineSkip() const noexcept {
+       return TTF_FontLineSkip(handle);
+}
+
+
+const char *Font::FamilyName() const noexcept {
+       return TTF_FontFaceFamilyName(handle);
+}
+
+const char *Font::StyleName() const noexcept {
+       return TTF_FontFaceStyleName(handle);
+}
+
+
+bool Font::HasGlyph(Uint16 c) const noexcept {
+       return TTF_GlyphIsProvided(handle, c);
+}
+
+
+glm::ivec2 Font::TextSize(const char *text) const {
+       glm::ivec2 size;
+       if (TTF_SizeUTF8(handle, text, &size.x, &size.y) != 0) {
+               throw std::runtime_error(TTF_GetError());
+       }
+       return size;
+}
+
+Texture Font::Render(const char *text) const {
+       Texture tex;
+       Render(text, tex);
+       return tex;
+}
+
+void Font::Render(const char *text, Texture &tex) const {
+       SDL_Surface *srf = TTF_RenderUTF8_Blended(handle, text, { 0xFF, 0xFF, 0xFF, 0xFF });
+       if (!srf) {
+               throw std::runtime_error(TTF_GetError());
+       }
+       tex.Bind();
+       tex.Data(*srf, false);
+       tex.FilterLinear();
+       SDL_FreeSurface(srf);
+}
+
+Format::Format() noexcept
+: format(GL_BGRA)
+, type(GL_UNSIGNED_INT_8_8_8_8_REV)
+, internal(GL_RGBA8) {
+       sdl_format.format = SDL_PIXELFORMAT_ARGB8888;
+       sdl_format.palette = nullptr;
+       sdl_format.BitsPerPixel = 32;
+       sdl_format.BytesPerPixel = 4;
+       sdl_format.Rmask = 0x00FF0000;
+       sdl_format.Gmask = 0x0000FF00;
+       sdl_format.Bmask = 0x000000FF;
+       sdl_format.Amask = 0xFF000000;
+       sdl_format.Rloss = 0;
+       sdl_format.Gloss = 0;
+       sdl_format.Bloss = 0;
+       sdl_format.Aloss = 0;
+       sdl_format.Rshift = 16;
+       sdl_format.Gshift = 8;
+       sdl_format.Bshift = 0;
+       sdl_format.Ashift = 24;
+       sdl_format.refcount = 1;
+       sdl_format.next = nullptr;
+}
+
+Format::Format(const SDL_PixelFormat &fmt) noexcept
+: sdl_format(fmt) {
+       if (fmt.BytesPerPixel == 4) {
+               if (fmt.Amask == 0xFF) {
+                       if (fmt.Rmask == 0xFF00) {
+                               format = GL_BGRA;
+                       } else {
+                               format = GL_RGBA;
+                       }
+                       type = GL_UNSIGNED_INT_8_8_8_8;
+               } else {
+                       if (fmt.Rmask == 0xFF) {
+                               format = GL_RGBA;
+                       } else {
+                               format = GL_BGRA;
+                       }
+                       type = GL_UNSIGNED_INT_8_8_8_8_REV;
+               }
+               internal = GL_RGBA8;
+       } else {
+               if (fmt.Rmask == 0xFF) {
+                       format = GL_RGB;
+               } else {
+                       format = GL_BGR;
+               }
+               type = GL_UNSIGNED_BYTE;
+               internal = GL_RGB8;
+       }
+}
+
+bool Format::Compatible(const Format &other) const noexcept {
+       return format == other.format && type == other.type && internal == other.internal;
+}
+
+
+template<GLenum TARGET, GLsizei COUNT>
+TextureBase<TARGET, COUNT>::TextureBase() {
+       glGenTextures(COUNT, handle);
+}
+
+template<GLenum TARGET, GLsizei COUNT>
+TextureBase<TARGET, COUNT>::~TextureBase() {
+       glDeleteTextures(COUNT, handle);
+}
+
+template<GLenum TARGET, GLsizei COUNT>
+TextureBase<TARGET, COUNT>::TextureBase(TextureBase &&other) noexcept {
+       std::memcpy(handle, other.handle, sizeof(handle));
+       std::memset(other.handle, 0, sizeof(handle));
+}
+
+template<GLenum TARGET, GLsizei COUNT>
+TextureBase<TARGET, COUNT> &TextureBase<TARGET, COUNT>::operator =(TextureBase &&other) noexcept {
+       std::swap(handle, other.handle);
+       return *this;
+}
+
+
+Texture::Texture()
+: TextureBase()
+, width(0)
+, height(0) {
+
+}
+
+Texture::~Texture() {
+
+}
+
+Texture::Texture(Texture &&other) noexcept
+: TextureBase(std::move(other)) {
+       width = other.width;
+       height = other.height;
+}
+
+Texture &Texture::operator =(Texture &&other) noexcept {
+       TextureBase::operator =(std::move(other));
+       width = other.width;
+       height = other.height;
+       return *this;
+}
+
+
+namespace {
+       bool ispow2(unsigned int i) {
+               // don't care about i == 0 here
+               return !(i & (i - 1));
+       }
+}
+
+void Texture::Data(const SDL_Surface &srf, bool pad2) noexcept {
+       Format format(*srf.format);
+
+       if (!pad2 || (ispow2(srf.w) && ispow2(srf.h))) {
+               int align = UnpackAlignmentFromPitch(srf.pitch);
+
+               int pitch = (srf.w * srf.format->BytesPerPixel + align - 1) / align * align;
+               if (srf.pitch - pitch >= align) {
+                       UnpackRowLength(srf.pitch / srf.format->BytesPerPixel);
+               } else {
+                       UnpackRowLength(0);
+               }
+
+               Data(srf.w, srf.h, format, srf.pixels);
+
+               UnpackRowLength(0);
+       } else if (srf.w > (1 << 30) || srf.h > (1 << 30)) {
+               // That's at least one gigapixel in either or both dimensions.
+               // If this is not an error, that's an insanely large or high
+               // resolution texture.
+#ifndef NDEBUG
+               std::cerr << "texture size exceeds 2^30, aborting data import" << std::endl;
+#endif
+       } else {
+               GLsizei width = 1, height = 1;
+               while (width < srf.w) {
+                       width <<= 1;
+               }
+               while (height < srf.h) {
+                       height <<= 1;
+               }
+               size_t pitch = width * srf.format->BytesPerPixel;
+               size_t size = pitch * height;
+               size_t row_pad = pitch - srf.pitch;
+               std::unique_ptr<unsigned char[]> data(new unsigned char[size]);
+               unsigned char *src = reinterpret_cast<unsigned char *>(srf.pixels);
+               unsigned char *dst = data.get();
+               for (int row = 0; row < srf.h; ++row) {
+                       std::memcpy(dst, src, srf.pitch);
+                       src += srf.pitch;
+                       dst += srf.pitch;
+                       std::memset(dst, 0, row_pad);
+                       dst += row_pad;
+               }
+               std::memset(dst, 0, (height - srf.h) * pitch);
+               UnpackAlignmentFromPitch(pitch);
+               Data(width, height, format, data.get());
+       }
+
+       UnpackAlignment(4);
+}
+
+void Texture::Data(GLsizei w, GLsizei h, const Format &format, GLvoid *data) noexcept {
+       glTexImage2D(
+               GL_TEXTURE_2D,
+               0, format.internal,
+               w, h,
+               0,
+               format.format, format.type,
+               data
+       );
+       width = w;
+       height = h;
+}
+
+
+void Texture::UnpackAlignment(GLint i) noexcept {
+       glPixelStorei(GL_UNPACK_ALIGNMENT, i);
+}
+
+int Texture::UnpackAlignmentFromPitch(int pitch) noexcept {
+       int align = 8;
+       while (pitch % align) {
+               align >>= 1;
+       }
+       UnpackAlignment(align);
+       return align;
+}
+
+void Texture::UnpackRowLength(GLint i) noexcept {
+       glPixelStorei(GL_UNPACK_ROW_LENGTH, i);
+}
+
+
+ArrayTexture::ArrayTexture()
+: TextureBase()
+, width(0)
+, height(0)
+, depth(0) {
+
+}
+
+ArrayTexture::~ArrayTexture() {
+
+}
+
+ArrayTexture::ArrayTexture(ArrayTexture &&other) noexcept
+: TextureBase(std::move(other)) {
+       width = other.width;
+       height = other.height;
+       depth = other.depth;
+}
+
+ArrayTexture &ArrayTexture::operator =(ArrayTexture &&other) noexcept {
+       TextureBase::operator =(std::move(other));
+       width = other.width;
+       height = other.height;
+       depth = other.depth;
+       return *this;
+}
+
+
+void ArrayTexture::Reserve(GLsizei w, GLsizei h, GLsizei d, const Format &f) noexcept {
+       glTexStorage3D(
+               GL_TEXTURE_2D_ARRAY, // which
+               1,                   // mipmap count
+               f.internal,          // format
+               w, h,                // dimensions
+               d                    // layer count
+       );
+       width = w;
+       height = h;
+       depth = d;
+       format = f;
+}
+
+void ArrayTexture::Data(GLsizei l, const SDL_Surface &srf) {
+       Format fmt(*srf.format);
+       if (format.Compatible(fmt)) {
+               Data(l, fmt, srf.pixels);
+       } else {
+               SDL_Surface *converted = SDL_ConvertSurface(
+                       const_cast<SDL_Surface *>(&srf),
+                       &format.sdl_format,
+                       0
+               );
+               if (!converted) {
+                       throw app::SDLError("SDL_ConvertSurface");
+               }
+               Format new_fmt(*converted->format);
+               if (!format.Compatible(new_fmt)) {
+                       SDL_FreeSurface(converted);
+                       throw std::runtime_error("unable to convert texture input");
+               }
+               Data(l, new_fmt, converted->pixels);
+               SDL_FreeSurface(converted);
+       }
+}
+
+void ArrayTexture::Data(GLsizei l, const Format &f, GLvoid *data) noexcept {
+       glTexSubImage3D(
+               GL_TEXTURE_2D_ARRAY, // which
+               0,                   // mipmap lavel
+               0, 0,                // dest X and Y offset
+               l,                   // layer offset
+               width, height,
+               1,                   // layer count
+               f.format, f.type,
+               data
+       );
+}
+
+
+CubeMap::CubeMap()
+: TextureBase() {
+
+}
+
+CubeMap::~CubeMap() {
+
+}
+
+CubeMap::CubeMap(CubeMap &&other) noexcept
+: TextureBase(std::move(other)) {
+
+}
+
+CubeMap &CubeMap::operator =(CubeMap &&other) noexcept {
+       TextureBase::operator =(std::move(other));
+       return *this;
+}
+
+
+void CubeMap::Data(Face f, const SDL_Surface &srf) {
+       Format format;
+       Format fmt(*srf.format);
+       if (format.Compatible(fmt)) {
+               Data(f, srf.w, srf.h, fmt, srf.pixels);
+       } else {
+               SDL_Surface *converted = SDL_ConvertSurface(
+                       const_cast<SDL_Surface *>(&srf),
+                       &format.sdl_format,
+                       0
+               );
+               if (!converted) {
+                       throw app::SDLError("SDL_ConvertSurface");
+               }
+               Format new_fmt(*converted->format);
+               if (!format.Compatible(new_fmt)) {
+                       SDL_FreeSurface(converted);
+                       throw std::runtime_error("unable to convert texture input");
+               }
+               Data(f, converted->w, converted->h, new_fmt, converted->pixels);
+               SDL_FreeSurface(converted);
+       }
+}
+
+void CubeMap::Data(Face face, GLsizei w, GLsizei h, const Format &f, GLvoid *data) noexcept {
+       glTexImage2D(
+               face,             // which
+               0,                // mipmap level
+               f.internal,       // internal format
+               w, h,             // size
+               0,                // border
+               f.format, f.type, // pixel format
+               data              // pixel data
+       );
+}
+
+}
+}
diff --git a/src/graphics/shader.cpp b/src/graphics/shader.cpp
new file mode 100644 (file)
index 0000000..ca74766
--- /dev/null
@@ -0,0 +1,251 @@
+#include "PlanetSurface.hpp"
+#include "Program.hpp"
+#include "Shader.hpp"
+
+#include "ArrayTexture.hpp"
+#include "../app/init.hpp"
+
+#include <algorithm>
+#include <iostream>
+#include <memory>
+#include <ostream>
+#include <stdexcept>
+#include <string>
+#include <glm/gtc/type_ptr.hpp>
+
+
+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 blobs {
+namespace graphics {
+
+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);
+}
+
+
+void Program::Uniform(GLint loc, GLint val) noexcept {
+       glUniform1i(loc, val);
+}
+
+void Program::Uniform(GLint loc, float val) noexcept {
+       glUniform1f(loc, val);
+}
+
+void Program::Uniform(GLint loc, const glm::vec3 &val) noexcept {
+       glUniform3fv(loc, 1, glm::value_ptr(val));
+}
+
+void Program::Uniform(GLint loc, const glm::vec4 &val) noexcept {
+       glUniform4fv(loc, 1, glm::value_ptr(val));
+}
+
+void Program::Uniform(GLint loc, const glm::mat4 &val) noexcept {
+       glUniformMatrix4fv(loc, 1, GL_FALSE, glm::value_ptr(val));
+}
+
+
+PlanetSurface::PlanetSurface()
+: prog() {
+       prog.LoadShader(
+               GL_VERTEX_SHADER,
+               "#version 330 core\n"
+
+               "layout(location = 0) in vec3 vtx_position;\n"
+               "layout(location = 1) in vec3 vtx_tex_uv;\n"
+
+               "uniform mat4 M;\n"
+               "uniform mat4 MV;\n"
+               "uniform mat4 MVP;\n"
+
+               "out vec3 frag_tex_uv;\n"
+               "out vec3 vtx_viewspace;\n"
+
+               "void main() {\n"
+                       "gl_Position = MVP * vec4(vtx_position, 1);\n"
+                       "vtx_viewspace = (MV * vec4(vtx_position, 1)).xyz;\n"
+                       "frag_tex_uv = vtx_tex_uv;\n"
+               "}\n"
+       );
+       prog.LoadShader(
+               GL_FRAGMENT_SHADER,
+               "#version 330 core\n"
+
+               "in vec3 vtx_viewspace;\n"
+               "in vec3 frag_tex_uv;\n"
+
+               "uniform sampler2DArray tex_sampler;\n"
+               "uniform vec3 normal;\n"
+
+               "out vec3 color;\n"
+
+               "void main() {\n"
+                       "vec3 tex_color = texture(tex_sampler, frag_tex_uv).rgb;\n"
+                       // TODO: lighting
+                       "color = tex_color;\n"
+               "}\n"
+       );
+       prog.Link();
+       if (!prog.Linked()) {
+               prog.Log(std::cerr);
+               throw std::runtime_error("link program");
+       }
+       m_handle = prog.UniformLocation("M");
+       mv_handle = prog.UniformLocation("MV");
+       mvp_handle = prog.UniformLocation("MVP");
+       sampler_handle = prog.UniformLocation("tex_sampler");
+       normal_handle = prog.UniformLocation("normal");
+}
+
+PlanetSurface::~PlanetSurface() {
+}
+
+void PlanetSurface::Activate() noexcept {
+       prog.Use();
+       glEnable(GL_DEPTH_TEST);
+       glDepthFunc(GL_LESS);
+       glDisable(GL_CULL_FACE);
+       glDisable(GL_BLEND);
+}
+
+void PlanetSurface::SetMVP(const glm::mat4 &m, const glm::mat4 &v, const glm::mat4 &p) noexcept {
+       prog.Uniform(m_handle, m);
+       glm::mat4 mv(v * m);
+       prog.Uniform(mv_handle, mv);
+       prog.Uniform(mvp_handle, p * mv);
+}
+
+void PlanetSurface::SetNormal(const glm::vec3 &n) noexcept {
+       prog.Uniform(normal_handle, n);
+}
+
+void PlanetSurface::SetTexture(ArrayTexture &tex) noexcept {
+       glActiveTexture(GL_TEXTURE0);
+       tex.Bind();
+       prog.Uniform(sampler_handle, GLint(0));
+}
+
+}
+}
diff --git a/src/graphics/viewport.cpp b/src/graphics/viewport.cpp
new file mode 100644 (file)
index 0000000..499a227
--- /dev/null
@@ -0,0 +1,77 @@
+#include "Camera.hpp"
+#include "Viewport.hpp"
+
+#include "const.hpp"
+
+#include <GL/glew.h>
+#include <glm/gtx/transform.hpp>
+
+
+namespace blobs {
+namespace graphics {
+
+Camera::Camera() noexcept
+: fov(PI_0p25)
+, aspect(1.0f)
+, near(0.1f)
+, far(256.0f)
+, projection(glm::perspective(fov, aspect, near, far))
+, view(1.0f) {
+
+}
+
+Camera::~Camera() noexcept {
+}
+
+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 n, float f) noexcept {
+       near = n;
+       far = f;
+       UpdateProjection();
+}
+
+void Camera::View(const glm::mat4 &v) noexcept {
+       view = v;
+}
+
+void Camera::UpdateProjection() noexcept {
+       projection = glm::perspective(fov, aspect, near, far);
+}
+
+
+Viewport::Viewport(int w, int h)
+: width(w)
+, height(h) {
+       Resize(w, h);
+       glClearColor(0.0, 0.0, 0.0, 1.0);
+}
+
+Viewport::~Viewport() {
+}
+
+
+void Viewport::Resize(int w, int h) {
+       width = w;
+       height = h;
+       glViewport(0, 0, w, h);
+}
+
+void Viewport::Clear() {
+       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+}
+
+}
+}
diff --git a/src/world/Body.hpp b/src/world/Body.hpp
new file mode 100644 (file)
index 0000000..e3fd8de
--- /dev/null
@@ -0,0 +1,52 @@
+#ifndef BLOBS_WORLD_BODY_HPP_
+#define BLOBS_WORLD_BODY_HPP_
+
+#include <vector>
+
+
+namespace blobs {
+namespace app {
+       class Assets;
+}
+namespace graphics {
+       class Viewport;
+}
+namespace world {
+
+class Body {
+
+public:
+       Body();
+       ~Body();
+
+       Body(const Body &) = delete;
+       Body &operator =(const Body &) = delete;
+
+       Body(Body &&) = delete;
+       Body &operator =(Body &&) = delete;
+
+public:
+       bool HasParent() const { return parent; }
+       Body &Parent() { return *parent; }
+       const Body &Parent() const { return *parent; }
+       void SetParent(Body &);
+       void UnsetParent();
+
+       virtual void Draw(app::Assets &, graphics::Viewport &) { }
+
+private:
+       void AddChild(Body &);
+       void RemoveChild(Body &);
+
+private:
+       Body *parent;
+       std::vector<Body *> children;
+       double mass;
+       double radius;
+
+};
+
+}
+}
+
+#endif
index 6106ccf06aa8a770d5928b4e1def95a1c8463a7c..0c468f5f0214d1c52ef2677a47d10ced809be72e 100644 (file)
@@ -1,10 +1,15 @@
 #ifndef BLOBS_WORLD_PLANET_HPP_
 #define BLOBS_WORLD_PLANET_HPP_
 
+#include "Body.hpp"
+
 #include "Tile.hpp"
+#include "../graphics/glm.hpp"
+#include "../graphics/SimpleVAO.hpp"
 
 #include <cassert>
 #include <memory>
+#include <GL/glew.h>
 
 
 namespace blobs {
@@ -12,20 +17,21 @@ namespace world {
 
 struct Tile;
 
-/// A planet has six surfaces, numbered 0 to 5, each with tiles from
-/// +radius to -radius.
-class Planet {
+/// A planet has six surfaces, numbered 0 to 5, each filled with
+/// sidelength² tiles.
+class Planet
+: public Body {
 
 public:
-       explicit Planet(int radius);
+       explicit Planet(int sidelength);
        ~Planet();
 
-       Planet(Planet &&);
-       Planet &operator =(Planet &&);
-
        Planet(const Planet &) = delete;
        Planet &operator =(const Planet &) = delete;
 
+       Planet(Planet &&) = delete;
+       Planet &operator =(Planet &&) = delete;
+
 public:
        /// Get the tile at given surface and coordinates.
        Tile &TileAt(int surface, int x, int y) {
@@ -38,35 +44,36 @@ public:
        /// Convert coordinates into a tile index.
        int IndexOf(int surface, int x, int y) const {
                assert(0 <= surface && surface <= 5);
-               assert(-radius <= x && x <= radius);
-               assert(-radius <= y && y <= radius);
-               return surface * SurfaceArea() + ToOffset(y) * SideLength() + ToOffset(x);
-       }
-       /// Convert coordinate into offset
-       int ToOffset(int c) const {
-               return c + radius;
-       }
-       /// The "radius" of the planet.
-       int Radius() const {
-               return radius;
+               assert(0 <= x && x <= sidelength);
+               assert(0 <= y && y <= sidelength);
+               return surface * TilesPerSurface() + y * SideLength() + x;
        }
        /// The length of the side of each surface.
        int SideLength() const {
-               return 2 * radius + 1;
+               return sidelength;
        }
-       /// The area (or number of tiles) of one surface
-       int SurfaceArea() const {
+       /// The number of tiles of one surface.
+       int TilesPerSurface() const {
                return SideLength() * SideLength();
        }
-       /// Total area of all surfaces combined.
-       int TotalArea() const {
-               return 6 * SurfaceArea();
+       /// Total number of tiles of all surfaces combined.
+       int TilesTotal() const {
+               return 6 * TilesPerSurface();
        }
 
+       void BuildVAOs();
+       void Draw(app::Assets &, graphics::Viewport &) override;
+
 private:
-       int radius;
+       int sidelength;
        std::unique_ptr<Tile []> tiles;
 
+       struct Attributes {
+               glm::vec3 position;
+               glm::vec3 tex_coord;
+       };
+       graphics::SimpleVAO<Attributes, unsigned int> vao;
+
 };
 
 void GenerateTest(Planet &);
diff --git a/src/world/Simulation.hpp b/src/world/Simulation.hpp
new file mode 100644 (file)
index 0000000..2acf549
--- /dev/null
@@ -0,0 +1,36 @@
+#ifndef BLOBS_WORLD_SIMULATION_HPP_
+#define BLOBS_WORLD_SIMULATION_HPP_
+
+
+namespace blobs {
+namespace world {
+
+class Body;
+
+class Simulation {
+
+public:
+       explicit Simulation(Body &root);
+       ~Simulation();
+
+       Simulation(const Simulation &) = delete;
+       Simulation &operator =(const Simulation &) = delete;
+
+       Simulation(Simulation &&) = delete;
+       Simulation &operator =(Simulation &&) = delete;
+
+public:
+       void Tick();
+
+       Body &Root() { return root; }
+       const Body &Root() const { return root; }
+
+private:
+       Body &root;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/world/Sun.hpp b/src/world/Sun.hpp
new file mode 100644 (file)
index 0000000..a47c039
--- /dev/null
@@ -0,0 +1,28 @@
+#ifndef BLOBS_WORLD_SUN_HPP_
+#define BLOBS_WORLD_SUN_HPP_
+
+#include "Body.hpp"
+
+
+namespace blobs {
+namespace world {
+
+class Sun
+: public Body {
+
+public:
+       Sun();
+       ~Sun();
+
+       Sun(const Sun &) = delete;
+       Sun &operator =(const Sun &) = delete;
+
+       Sun(Sun &&) = delete;
+       Sun &operator =(Sun &&) = delete;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/world/sim.cpp b/src/world/sim.cpp
new file mode 100644 (file)
index 0000000..0e0b466
--- /dev/null
@@ -0,0 +1,19 @@
+#include "Simulation.hpp"
+
+
+namespace blobs {
+namespace world {
+
+Simulation::Simulation(Body &r)
+: root(r) {
+}
+
+Simulation::~Simulation() {
+}
+
+
+void Simulation::Tick() {
+}
+
+}
+}
index cd18fb2f7526b40418c74cca3b3bd86c2a6fb087..7029667b0a06ebd26c31b7e24065f9977275f9af 100644 (file)
+#include "Body.hpp"
 #include "Planet.hpp"
+#include "Sun.hpp"
 #include "Tile.hpp"
 
+#include "../app/Assets.hpp"
+#include "../graphics/Viewport.hpp"
+
 #include <algorithm>
 
 
 namespace blobs {
 namespace world {
 
-Planet::Planet(int radius)
-: radius(radius)
-, tiles(new Tile[TotalArea()]) {
+Body::Body()
+: parent(nullptr)
+, children()
+, mass(1.0)
+, radius(1.0) {
+}
+
+Body::~Body() {
+}
+
+void Body::SetParent(Body &p) {
+       if (HasParent()) {
+               UnsetParent();
+       }
+       parent = &p;
+       parent->AddChild(*this);
+}
+
+void Body::UnsetParent() {
+       if (!HasParent()) return;
+       parent->RemoveChild(*this);
+       parent = nullptr;
+}
+
+void Body::AddChild(Body &c) {
+       children.push_back(&c);
+}
+
+void Body::RemoveChild(Body &c) {
+       auto entry = std::find(children.begin(), children.end(), &c);
+       if (entry != children.end()) {
+               children.erase(entry);
+       }
+}
+
 
+Planet::Planet(int sidelength)
+: Body()
+, sidelength(sidelength)
+, tiles(new Tile[TilesTotal()])
+, vao() {
 }
 
 Planet::~Planet() {
 }
 
-Planet::Planet(Planet &&other)
-: radius(other.radius)
-, tiles(other.tiles.release()) {
+void Planet::BuildVAOs() {
+       vao.Bind();
+       vao.BindAttributes();
+       vao.EnableAttribute(0);
+       vao.EnableAttribute(1);
+       vao.AttributePointer<glm::vec3>(0, false, offsetof(Attributes, position));
+       vao.AttributePointer<glm::vec3>(1, false, offsetof(Attributes, tex_coord));
+       vao.ReserveAttributes(TilesTotal() * 4, GL_STATIC_DRAW);
+       {
+               auto attrib = vao.MapAttributes(GL_WRITE_ONLY);
+               float offset = sidelength * 0.5f;
+
+               for (int index = 0, surface = 0; surface < 6; ++surface) {
+                       for (int y = 0; y < sidelength; ++y) {
+                               for (int x = 0; x < sidelength; ++x, ++index) {
+                                       float tex = TileAt(surface, x, y).type;
+                                       attrib[4 * index + 0].position[(surface + 0) % 3] = x + 0 - offset;
+                                       attrib[4 * index + 0].position[(surface + 1) % 3] = y + 0 - offset;
+                                       attrib[4 * index + 0].position[(surface + 2) % 3] = surface < 3 ? offset : -offset;
+                                       attrib[4 * index + 0].tex_coord[0] = 0.0f;
+                                       attrib[4 * index + 0].tex_coord[1] = 0.0f;
+                                       attrib[4 * index + 0].tex_coord[2] = tex;
+
+                                       attrib[4 * index + 1].position[(surface + 0) % 3] = x + 0 - offset;
+                                       attrib[4 * index + 1].position[(surface + 1) % 3] = y + 1 - offset;
+                                       attrib[4 * index + 1].position[(surface + 2) % 3] = surface < 3 ? offset : -offset;
+                                       attrib[4 * index + 1].tex_coord[0] = 0.0f;
+                                       attrib[4 * index + 1].tex_coord[1] = 1.0f;
+                                       attrib[4 * index + 1].tex_coord[2] = tex;
+
+                                       attrib[4 * index + 2].position[(surface + 0) % 3] = x + 1 - offset;
+                                       attrib[4 * index + 2].position[(surface + 1) % 3] = y + 0 - offset;
+                                       attrib[4 * index + 2].position[(surface + 2) % 3] = surface < 3 ? offset : -offset;
+                                       attrib[4 * index + 2].tex_coord[0] = 1.0f;
+                                       attrib[4 * index + 2].tex_coord[1] = 0.0f;
+                                       attrib[4 * index + 2].tex_coord[2] = tex;
+
+                                       attrib[4 * index + 3].position[(surface + 0) % 3] = x + 1 - offset;
+                                       attrib[4 * index + 3].position[(surface + 1) % 3] = y + 1 - offset;
+                                       attrib[4 * index + 3].position[(surface + 2) % 3] = surface < 3 ? offset : -offset;
+                                       attrib[4 * index + 3].tex_coord[0] = 1.0f;
+                                       attrib[4 * index + 3].tex_coord[1] = 1.0f;
+                                       attrib[4 * index + 3].tex_coord[2] = tex;
+                               }
+                       }
+               }
+       }
+       vao.BindElements();
+       vao.ReserveElements(TilesTotal() * 6, GL_STATIC_DRAW);
+       {
+               auto element = vao.MapElements(GL_WRITE_ONLY);
+               for (int index = 0, surface = 0; surface < 6; ++surface) {
+                       for (int y = 0; y < sidelength; ++y) {
+                               for (int x = 0; x < sidelength; ++x, ++index) {
+                                       element[6 * index + 0] = 4 * index + 0;
+                                       element[6 * index + 1] = 4 * index + 1;
+                                       element[6 * index + 2] = 4 * index + 2;
+                                       element[6 * index + 3] = 4 * index + 2;
+                                       element[6 * index + 4] = 4 * index + 1;
+                                       element[6 * index + 5] = 4 * index + 3;
+                               }
+                       }
+               }
+       }
+       vao.Unbind();
 }
 
-Planet &Planet::operator =(Planet &&other) {
-       radius = other.radius;
-       std::swap(tiles, other.tiles);
-       return *this;
+void Planet::Draw(app::Assets &assets, graphics::Viewport &viewport) {
+       vao.Bind();
+       // TODO: premultiply normal with model matrix (i.e. just take it from M)
+       assets.shaders.planet_surface.SetNormal(glm::vec3(0.0f, 0.0f, 1.0f));
+       vao.DrawTriangles(TilesTotal() * 4, TilesTotal() * 4 * 0);
+       assets.shaders.planet_surface.SetNormal(glm::vec3(1.0f, 0.0f, 0.0f));
+       vao.DrawTriangles(TilesTotal() * 4, TilesTotal() * 4 * 1);
+       assets.shaders.planet_surface.SetNormal(glm::vec3(0.0f, 1.0f, 0.0f));
+       vao.DrawTriangles(TilesTotal() * 4, TilesTotal() * 4 * 2);
+       assets.shaders.planet_surface.SetNormal(glm::vec3(0.0f, 0.0f, -1.0f));
+       vao.DrawTriangles(TilesTotal() * 4, TilesTotal() * 4 * 3);
+       assets.shaders.planet_surface.SetNormal(glm::vec3(-1.0f, 0.0f, 0.0f));
+       vao.DrawTriangles(TilesTotal() * 4, TilesTotal() * 4 * 4);
+       assets.shaders.planet_surface.SetNormal(glm::vec3(0.0f, -1.0f, 0.0f));
+       vao.DrawTriangles(TilesTotal() * 4, TilesTotal() * 4 * 5);
 }
 
 
 void GenerateTest(Planet &p) {
        for (int surface = 0; surface <= 5; ++surface) {
-               for (int y = -p.Radius(); y <= p.Radius(); ++y) {
-                       for (int x = -p.Radius(); x <= p.Radius(); ++x) {
-                               p.TileAt(surface, x, y).type = (x == 0) + (y == 0);
+               for (int y = 0; y < p.SideLength(); ++y) {
+                       for (int x = 0; x < p.SideLength(); ++x) {
+                               p.TileAt(surface, x, y).type = (x == p.SideLength()/2) + (y == p.SideLength()/2);
                        }
                }
        }
+       p.BuildVAOs();
+}
+
+
+Sun::Sun()
+: Body() {
+}
+
+Sun::~Sun() {
 }
 
 }