From: Daniel Karbach Date: Wed, 10 Jun 2015 14:57:25 +0000 (+0200) Subject: some code reorganization X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;h=b7d09e1e35ef90282c97509e0020b20db3c7ea9f;p=blank.git some code reorganization --- diff --git a/Makefile b/Makefile index 36c7048..d063a9a 100644 --- 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 926a355..6234fd0 100644 --- 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 index 57651e3..0000000 --- a/src/app.cpp +++ /dev/null @@ -1,153 +0,0 @@ -#include "app.hpp" - -#include -#include - - -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 index e6226dc..0000000 --- a/src/app.hpp +++ /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 index 0000000..67438db --- /dev/null +++ b/src/app/Application.cpp @@ -0,0 +1,156 @@ +#include "Application.hpp" + +#include "../world/BlockType.hpp" +#include "../world/Entity.hpp" + +#include +#include + + +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 index 0000000..c8236ff --- /dev/null +++ b/src/app/Application.hpp @@ -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 index 0000000..88dca64 --- /dev/null +++ b/src/app/FPSController.hpp @@ -0,0 +1,44 @@ +#ifndef BLANK_APP_FPSCONTROLLER_HPP_ +#define BLANK_APP_FPSCONTROLLER_HPP_ + +#include "../model/geometry.hpp" +#include "../world/Entity.hpp" + +#include + + +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 index 0000000..e5d0248 --- /dev/null +++ b/src/app/IntervalTimer.hpp @@ -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 index 0000000..5660ab3 --- /dev/null +++ b/src/app/RandomWalk.hpp @@ -0,0 +1,27 @@ +#ifndef BLANK_APP_RANDOMWALK_HPP_ +#define BLANK_APP_RANDOMWALK_HPP_ + +#include + + +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 index 0000000..8ddab26 --- /dev/null +++ b/src/app/Runtime.cpp @@ -0,0 +1,164 @@ +#include "Runtime.hpp" + +#include +#include +#include + +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 index 0000000..2fdf2e5 --- /dev/null +++ b/src/app/Runtime.hpp @@ -0,0 +1,39 @@ +#ifndef BLANK_RUNTIME_HPP_ +#define BLANK_RUNTIME_HPP_ + +#include "Application.hpp" + +#include + + +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 index 0000000..9cf2d6c --- /dev/null +++ b/src/app/controller.cpp @@ -0,0 +1,100 @@ +#include "FPSController.hpp" +#include "RandomWalk.hpp" + +#include +#include + + +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 index 0000000..a56674c --- /dev/null +++ b/src/app/init.cpp @@ -0,0 +1,185 @@ +#include "init.hpp" + +#include +#include +#include +#include +#include +#include + + +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 index 0000000..98a30ed --- /dev/null +++ b/src/app/init.hpp @@ -0,0 +1,109 @@ +#ifndef BLANK_APP_INIT_HPP_ +#define BLANK_APP_INIT_HPP_ + +#include + + +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 index 0000000..d11ee9b --- /dev/null +++ b/src/blank.cpp @@ -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 index ab090da..0000000 --- a/src/block.cpp +++ /dev/null @@ -1,128 +0,0 @@ -#include "block.hpp" - -#include "geometry.hpp" - -#include -#include - - -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 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 index 84efa48..0000000 --- a/src/block.hpp +++ /dev/null @@ -1,217 +0,0 @@ -#ifndef BLANK_BLOCK_HPP_ -#define BLANK_BLOCK_HPP_ - -#include "geometry.hpp" -#include "model.hpp" -#include "shape.hpp" - -#include -#include - - -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 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 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 types; - -}; - -} - -#endif diff --git a/src/camera.cpp b/src/camera.cpp deleted file mode 100644 index f4e3171..0000000 --- a/src/camera.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "camera.hpp" - -#include "geometry.hpp" - -#include -#include - - -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 index 717da24..0000000 --- a/src/camera.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef BLANK_CAMERA_HPP_ -#define BLANK_CAMERA_HPP_ - -#include - - -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 index 3824a47..0000000 --- a/src/chunk.cpp +++ /dev/null @@ -1,841 +0,0 @@ -#include "chunk.hpp" - -#include "generator.hpp" - -#include -#include -#include - - -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 light_queue; -std::queue 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(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(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::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(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 ®, 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 index d86f417..0000000 --- a/src/chunk.hpp +++ /dev/null @@ -1,236 +0,0 @@ -#ifndef BLANK_CHUNK_HPP_ -#define BLANK_CHUNK_HPP_ - -#include "block.hpp" -#include "geometry.hpp" -#include "model.hpp" - -#include -#include -#include -#include - - -namespace blank { - -/// cube of size 16 (256 tiles, 4096 blocks) -class Chunk { - -public: - using Pos = glm::tvec3; - -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 &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 ® - const Generator &gen; - - std::list loaded; - std::list to_generate; - std::list to_free; - - int load_dist; - int unload_dist; - -}; - -} - -#endif diff --git a/src/controller.cpp b/src/controller.cpp deleted file mode 100644 index e86551f..0000000 --- a/src/controller.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "controller.hpp" - -#include -#include - - -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 index f674e23..0000000 --- a/src/controller.hpp +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef BLANK_CONTROLLER_HPP_ -#define BLANK_CONTROLLER_HPP_ - -#include "entity.hpp" -#include "geometry.hpp" - -#include - - -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 index 275c846..0000000 --- a/src/entity.cpp +++ /dev/null @@ -1,114 +0,0 @@ -#include "entity.hpp" - -#include "geometry.hpp" -#include "shape.hpp" - -#include -#include - -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 index 20c7630..0000000 --- a/src/entity.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef BLANK_ENTITY_HPP_ -#define BLANK_ENTITY_HPP_ - -#include "block.hpp" -#include "chunk.hpp" -#include "model.hpp" - -#include -#include - - -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 index 3d61b75..0000000 --- a/src/generator.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "generator.hpp" - -#include - - -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 index dedfb72..0000000 --- a/src/generator.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef BLANK_GENERATOR_HPP_ -#define BLANK_GENERATOR_HPP_ - -#include "block.hpp" -#include "chunk.hpp" -#include "noise.hpp" - -#include - - -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 &s) { solids = s; } - -private: - SimplexNoise solidNoise; - WorleyNoise typeNoise; - - float stretch; - float solid_threshold; - - Block::Type space; - Block::Type light; - std::vector solids; - -}; - -} - -#endif diff --git a/src/geometry.cpp b/src/geometry.cpp deleted file mode 100644 index 416d930..0000000 --- a/src/geometry.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include "geometry.hpp" - -#include - - -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::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::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 index df75f22..0000000 --- a/src/geometry.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef BLANK_GEOMETRY_H_ -#define BLANK_GEOMETRY_H_ - -#include -#include - - -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 index 0000000..8f7f01d --- /dev/null +++ b/src/graphics/BlockLighting.hpp @@ -0,0 +1,50 @@ +#ifndef BLANK_GRAPHICS_BLOCKLIGHTING_HPP_ +#define BLANK_GRAPHICS_BLOCKLIGHTING_HPP_ + +#include "Program.hpp" + +#include +#include + + +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 index 0000000..5f61d26 --- /dev/null +++ b/src/graphics/Camera.cpp @@ -0,0 +1,55 @@ +#include "Camera.hpp" + +#include "../model/geometry.hpp" + +#include +#include + + +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 index 0000000..2fabfc6 --- /dev/null +++ b/src/graphics/Camera.hpp @@ -0,0 +1,40 @@ +#ifndef BLANK_GRAPHICS_CAMERA_HPP_ +#define BLANK_GRAPHICS_CAMERA_HPP_ + +#include + + +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 index 0000000..df89053 --- /dev/null +++ b/src/graphics/DirectionalLighting.hpp @@ -0,0 +1,56 @@ +#ifndef BLANK_GRAPHICS_DIRECTIONALLIGHTING_HPP_ +#define BLANK_GRAPHICS_DIRECTIONALLIGHTING_HPP_ + +#include "Program.hpp" + +#include +#include + + +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 index 0000000..a67a0b3 --- /dev/null +++ b/src/graphics/Program.hpp @@ -0,0 +1,41 @@ +#ifndef BLANK_GRAPHICS_PROGRAM_HPP_ +#define BLANK_GRAPHICS_PROGRAM_HPP_ + +#include +#include +#include + + +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 shaders; + +}; + +} + +#endif diff --git a/src/graphics/Shader.hpp b/src/graphics/Shader.hpp new file mode 100644 index 0000000..2fab0c7 --- /dev/null +++ b/src/graphics/Shader.hpp @@ -0,0 +1,36 @@ +#ifndef BLANK_GRAPHICS_SHADER_HPP_ +#define BLANK_GRAPHICS_SHADER_HPP_ + +#include +#include + + +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 index 0000000..45634a9 --- /dev/null +++ b/src/graphics/shader.cpp @@ -0,0 +1,358 @@ +#include "BlockLighting.hpp" +#include "DirectionalLighting.hpp" +#include "Program.hpp" +#include "Shader.hpp" + +#include "../app/init.hpp" + +#include +#include +#include +#include +#include +#include + + +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 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 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 index 0f496c6..0000000 --- a/src/hud.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "hud.hpp" - -#include "block.hpp" -#include "init.hpp" -#include "shader.hpp" -#include "shape.hpp" - -#include - - -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({ - { -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({ - 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 index 984a24d..0000000 --- a/src/hud.hpp +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef BLANK_HUD_H_ -#define BLANK_HUD_H_ - -#include "model.hpp" -#include "world.hpp" - -#include - - -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 index a56674c..0000000 --- a/src/init.cpp +++ /dev/null @@ -1,185 +0,0 @@ -#include "init.hpp" - -#include -#include -#include -#include -#include -#include - - -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 index 30da3be..0000000 --- a/src/init.hpp +++ /dev/null @@ -1,109 +0,0 @@ -#ifndef BLANK_INIT_HPP_ -#define BLANK_INIT_HPP_ - -#include - - -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 index a5dcc0e..0000000 --- a/src/interface.cpp +++ /dev/null @@ -1,315 +0,0 @@ -#include "interface.hpp" - -#include "world.hpp" - -#include -#include -#include - - -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 index 9f43f4f..0000000 --- a/src/interface.hpp +++ /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 -#include - - -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 fwd, rev; - -}; - -} - -#endif diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index 3cf7bf9..0000000 --- a/src/main.cpp +++ /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 index 4e75674..0000000 --- a/src/model.cpp +++ /dev/null @@ -1,288 +0,0 @@ -#include "model.hpp" - -#include - - -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 index 096e1cf..0000000 --- a/src/model.hpp +++ /dev/null @@ -1,194 +0,0 @@ -#ifndef BLANK_MODEL_HPP_ -#define BLANK_MODEL_HPP_ - -#include -#include -#include - - -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; - using Colors = std::vector; - using Normals = std::vector; - using Indices = std::vector; - -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; - using Colors = std::vector; - using Lights = std::vector; - using Indices = std::vector; - -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; - using Colors = std::vector; - using Indices = std::vector; - -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 index 0000000..d1a37c9 --- /dev/null +++ b/src/model/BlockModel.hpp @@ -0,0 +1,79 @@ +#ifndef BLANK_MODEL_BLOCKMODEL_HPP_ +#define BLANK_MODEL_BLOCKMODEL_HPP_ + +#include +#include +#include + + +namespace blank { + +class BlockModel { + +public: + using Position = glm::vec3; + using Color = glm::vec3; + using Light = float; + using Index = unsigned int; + + using Positions = std::vector; + using Colors = std::vector; + using Lights = std::vector; + using Indices = std::vector; + +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 index 0000000..95ad086 --- /dev/null +++ b/src/model/Model.hpp @@ -0,0 +1,79 @@ +#ifndef BLANK_MODEL_MODEL_HPP_ +#define BLANK_MODEL_MODEL_HPP_ + +#include +#include +#include + + +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; + using Colors = std::vector; + using Normals = std::vector; + using Indices = std::vector; + +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 index 0000000..308ab51 --- /dev/null +++ b/src/model/OutlineModel.hpp @@ -0,0 +1,60 @@ +#ifndef BLANK_MODEL_OUTLINEMODEL_HPP_ +#define BLANK_MODEL_OUTLINEMODEL_HPP_ + +#include +#include +#include + + +namespace blank { + +class OutlineModel { + +public: + using Position = glm::vec3; + using Color = glm::vec3; + using Index = unsigned short; + + using Positions = std::vector; + using Colors = std::vector; + using Indices = std::vector; + +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 index 0000000..5d4036b --- /dev/null +++ b/src/model/Shape.hpp @@ -0,0 +1,99 @@ +#ifndef BLANK_MODEL_SHAPE_HPP_ +#define BLANK_MODEL_SHAPE_HPP_ + +#include "BlockModel.hpp" +#include "Model.hpp" +#include "OutlineModel.hpp" + +#include +#include + + +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 index 0000000..416d930 --- /dev/null +++ b/src/model/geometry.cpp @@ -0,0 +1,106 @@ +#include "geometry.hpp" + +#include + + +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::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::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 index 0000000..d6e3d28 --- /dev/null +++ b/src/model/geometry.hpp @@ -0,0 +1,43 @@ +#ifndef BLANK_MODEL_GEOMETRY_H_ +#define BLANK_MODEL_GEOMETRY_H_ + +#include +#include + + +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 index 0000000..423e00d --- /dev/null +++ b/src/model/model.cpp @@ -0,0 +1,290 @@ +#include "BlockModel.hpp" +#include "Model.hpp" +#include "OutlineModel.hpp" + +#include + + +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 index 0000000..1c83149 --- /dev/null +++ b/src/model/shape.cpp @@ -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 index 0000000..b5e965a --- /dev/null +++ b/src/model/shapes.hpp @@ -0,0 +1,53 @@ +#ifndef BLANK_MODEL_SHAPES_HPP_ +#define BLANK_MODEL_SHAPES_HPP_ + +#include "geometry.hpp" +#include "Shape.hpp" + +#include +#include + + +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 index d653d5d..0000000 --- a/src/noise.cpp +++ /dev/null @@ -1,202 +0,0 @@ -#include "noise.hpp" - -#include - - -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 index cbfd48e..0000000 --- a/src/noise.hpp +++ /dev/null @@ -1,99 +0,0 @@ -#ifndef BLANK_NOISE_HPP_ -#define BLANK_NOISE_HPP_ - -#include -#include -#include - - -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 - T operator ()(T &out) noexcept { - constexpr int num_bits = - std::numeric_limits::digits + - std::numeric_limits::is_signed; - for (int i = 0; i < num_bits; ++i) { - operator ()(); - } - return out = static_cast(state); - } - -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 -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 index 0000000..88a1d37 --- /dev/null +++ b/src/rand/GaloisLFSR.hpp @@ -0,0 +1,38 @@ +#ifndef BLANK_RAND_GALOISLFSR_HPP_ +#define BLANK_RAND_GALOISLFSR_HPP_ + +#include +#include + + +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 + T operator ()(T &out) noexcept { + constexpr int num_bits = + std::numeric_limits::digits + + std::numeric_limits::is_signed; + for (int i = 0; i < num_bits; ++i) { + operator ()(); + } + return out = static_cast(state); + } + +private: + std::uint64_t state; + // bits 64, 63, 61, and 60 set to 1 (counting from 1 lo to hi) + static constexpr std::uint64_t mask = 0xD800000000000000; + +}; +} + +#endif diff --git a/src/rand/OctaveNoise.hpp b/src/rand/OctaveNoise.hpp new file mode 100644 index 0000000..7d5832f --- /dev/null +++ b/src/rand/OctaveNoise.hpp @@ -0,0 +1,33 @@ +#ifndef BLANK_RAND_OCTAVENOISE_HPP_ +#define BLANK_RAND_OCTAVENOISE_HPP_ + +#include + + +namespace blank { + +template +float OctaveNoise( + const Noise &noise, + const glm::vec3 &in, + int num, + float persistence, + float frequency = 1.0f, + float amplitude = 1.0f, + float growth = 2.0f +) { + float total = 0.0f; + float max = 0.0f; + for (int i = 0; i < num; ++i) { + total += noise(in * frequency) * amplitude; + max += amplitude; + amplitude *= persistence; + frequency *= growth; + } + + return total / max; +} + +} + +#endif diff --git a/src/rand/SimplexNoise.hpp b/src/rand/SimplexNoise.hpp new file mode 100644 index 0000000..7a7242b --- /dev/null +++ b/src/rand/SimplexNoise.hpp @@ -0,0 +1,31 @@ +#ifndef BLANK_RAND_SIMPLEXNOISE_HPP_ +#define BLANK_RAND_SIMPLEXNOISE_HPP_ + +#include + + +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 index 0000000..20631c4 --- /dev/null +++ b/src/rand/WorleyNoise.hpp @@ -0,0 +1,25 @@ +#ifndef BLANK_RAND_WORLEYNOISE_HPP_ +#define BLANK_RAND_WORLEYNOISE_HPP_ + +#include + + +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 index 0000000..de037ef --- /dev/null +++ b/src/rand/noise.cpp @@ -0,0 +1,204 @@ +#include "GaloisLFSR.hpp" +#include "SimplexNoise.hpp" +#include "WorleyNoise.hpp" + +#include + + +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 index 7475c90..0000000 --- a/src/runtime.cpp +++ /dev/null @@ -1,166 +0,0 @@ -#include "runtime.hpp" - -#include "app.hpp" - -#include -#include -#include - -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 index 014cd32..0000000 --- a/src/runtime.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef BLANK_RUNTIME_HPP_ -#define BLANK_RUNTIME_HPP_ - -#include "app.hpp" - -#include - - -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 index b2ccb8b..0000000 --- a/src/shader.cpp +++ /dev/null @@ -1,355 +0,0 @@ -#include "shader.hpp" - -#include "init.hpp" - -#include -#include -#include -#include -#include -#include - - -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 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 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 index df625c6..0000000 --- a/src/shader.hpp +++ /dev/null @@ -1,144 +0,0 @@ -#ifndef BLANK_SHADER_HPP_ -#define BLANK_SHADER_HPP_ - -#include -#include -#include -#include - - -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 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 index 59e4f1f..0000000 --- a/src/shape.cpp +++ /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 index 4120f76..0000000 --- a/src/shape.hpp +++ /dev/null @@ -1,135 +0,0 @@ -#ifndef BLANK_SHAPE_HPP_ -#define BLANK_SHAPE_HPP_ - -#include "geometry.hpp" -#include "model.hpp" - -#include -#include - - -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 index c5445b2..0000000 --- a/src/timer.hpp +++ /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 index 0000000..ca6b754 --- /dev/null +++ b/src/ui/HUD.hpp @@ -0,0 +1,50 @@ +#ifndef BLANK_UI_HUD_H_ +#define BLANK_UI_HUD_H_ + +#include "../model/Model.hpp" +#include "../model/OutlineModel.hpp" + +#include + + +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 index 0000000..0c140fb --- /dev/null +++ b/src/ui/Interface.hpp @@ -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 +#include + + +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 fwd, rev; + +}; + +} + +#endif diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp new file mode 100644 index 0000000..410072d --- /dev/null +++ b/src/ui/ui.cpp @@ -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 +#include +#include + + +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({ + { -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({ + 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 index 137fd2a..0000000 --- a/src/world.cpp +++ /dev/null @@ -1,230 +0,0 @@ -#include "world.hpp" - -#include -#include - - -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 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::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 &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 index 1d43fbe..0000000 --- a/src/world.hpp +++ /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 -#include - - -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 &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 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 index 0000000..74af3e4 --- /dev/null +++ b/src/world/Block.hpp @@ -0,0 +1,135 @@ +#ifndef BLANK_WORLD_BLOCK_HPP_ +#define BLANK_WORLD_BLOCK_HPP_ + +#include + + +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 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 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 index 0000000..4c8b12b --- /dev/null +++ b/src/world/BlockLookup.hpp @@ -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 index 0000000..344bec7 --- /dev/null +++ b/src/world/BlockType.hpp @@ -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 + + +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 index 0000000..0a1ebf0 --- /dev/null +++ b/src/world/BlockTypeRegistry.hpp @@ -0,0 +1,31 @@ +#ifndef BLANK_WORLD_BLOCKTYPEREGISTRY_HPP_ +#define BLANK_WORLD_BLOCKTYPEREGISTRY_HPP_ + +#include "BlockType.hpp" + +#include + + +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 types; + +}; + +} + +#endif diff --git a/src/world/Chunk.hpp b/src/world/Chunk.hpp new file mode 100644 index 0000000..8493ef6 --- /dev/null +++ b/src/world/Chunk.hpp @@ -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 +#include + + +namespace blank { + +class BlockType; + +/// cube of size 16 (256 tiles, 4096 blocks) +class Chunk { + +public: + using Pos = glm::tvec3; + +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 index 0000000..b1d0c7b --- /dev/null +++ b/src/world/ChunkLoader.hpp @@ -0,0 +1,59 @@ +#ifndef BLANK_WORLD_CHUNKLOADER_HPP_ +#define BLANK_WORLD_CHUNKLOADER_HPP_ + +#include "Chunk.hpp" + +#include + + +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 &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 ® + const Generator &gen; + + std::list loaded; + std::list to_generate; + std::list 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 index 0000000..6b23cac --- /dev/null +++ b/src/world/Entity.cpp @@ -0,0 +1,114 @@ +#include "Entity.hpp" + +#include "../model/geometry.hpp" +#include "../model/Shape.hpp" + +#include +#include + +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 index 0000000..83c1f6f --- /dev/null +++ b/src/world/Entity.hpp @@ -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 +#include + + +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 index 0000000..c49dce1 --- /dev/null +++ b/src/world/Generator.cpp @@ -0,0 +1,54 @@ +#include "Generator.hpp" + +#include "Chunk.hpp" +#include "../rand/OctaveNoise.hpp" + +#include + + +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 index 0000000..a2ae18a --- /dev/null +++ b/src/world/Generator.hpp @@ -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 + + +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 &s) { solids = s; } + +private: + SimplexNoise solidNoise; + WorleyNoise typeNoise; + + float stretch; + float solid_threshold; + + Block::Type space; + Block::Type light; + std::vector solids; + +}; + +} + +#endif diff --git a/src/world/World.cpp b/src/world/World.cpp new file mode 100644 index 0000000..e426f07 --- /dev/null +++ b/src/world/World.cpp @@ -0,0 +1,233 @@ +#include "World.hpp" + +#include "../graphics/BlockLighting.hpp" +#include "../graphics/DirectionalLighting.hpp" + +#include +#include + + +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 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::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 &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 index 0000000..ae70494 --- /dev/null +++ b/src/world/World.hpp @@ -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 +#include + + +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 &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 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 index 0000000..6cbaa26 --- /dev/null +++ b/src/world/block.cpp @@ -0,0 +1,130 @@ +#include "Block.hpp" +#include "BlockType.hpp" +#include "BlockTypeRegistry.hpp" + +#include "../model/geometry.hpp" + +#include +#include + + +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 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 index 0000000..50595f4 --- /dev/null +++ b/src/world/chunk.cpp @@ -0,0 +1,843 @@ +#include "BlockLookup.hpp" +#include "Chunk.hpp" +#include "ChunkLoader.hpp" + +#include "Generator.hpp" + +#include +#include +#include + + +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 light_queue; +std::queue 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(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(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::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(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 ®, 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); +} + +}