]> git.localhorst.tv Git - orbi.git/commitdiff
initial collision tests
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Mon, 7 Apr 2014 07:42:42 +0000 (09:42 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Tue, 8 Apr 2014 09:03:16 +0000 (11:03 +0200)
44 files changed:
.gitignore [new file with mode: 0644]
build/base.mk [new file with mode: 0644]
build/collision.mk [new file with mode: 0644]
build/config.mk [new file with mode: 0644]
build/debug/Makefile [new file with mode: 0644]
build/orbi.mk [new file with mode: 0644]
build/release/Makefile [new file with mode: 0644]
build/rules.mk [new file with mode: 0644]
build/targets.mk [new file with mode: 0644]
build/test.mk [new file with mode: 0644]
src/app/Application.cpp [new file with mode: 0644]
src/app/Application.h [new file with mode: 0644]
src/app/IMG.cpp [new file with mode: 0644]
src/app/IMG.h [new file with mode: 0644]
src/app/SDL.cpp [new file with mode: 0644]
src/app/SDL.h [new file with mode: 0644]
src/collision.cpp [new file with mode: 0644]
src/graphics/Camera.cpp [new file with mode: 0644]
src/graphics/Camera.h [new file with mode: 0644]
src/graphics/Canvas.cpp [new file with mode: 0644]
src/graphics/Canvas.h [new file with mode: 0644]
src/graphics/Color.h [new file with mode: 0644]
src/graphics/Moveable.h [new file with mode: 0644]
src/graphics/Rect.h [new file with mode: 0644]
src/graphics/Sprite.cpp [new file with mode: 0644]
src/graphics/Sprite.h [new file with mode: 0644]
src/graphics/Texture.cpp [new file with mode: 0644]
src/graphics/Texture.h [new file with mode: 0644]
src/graphics/Vector.h [new file with mode: 0644]
src/graphics/Window.cpp [new file with mode: 0644]
src/graphics/Window.h [new file with mode: 0644]
src/graphics/const.h [new file with mode: 0644]
src/orbi.cpp [new file with mode: 0644]
src/world/AABB.cpp [new file with mode: 0644]
src/world/AABB.h [new file with mode: 0644]
src/world/Entity.cpp [new file with mode: 0644]
src/world/Entity.h [new file with mode: 0644]
src/world/Tile.h [new file with mode: 0644]
src/world/TileClass.h [new file with mode: 0644]
src/world/Tileset.cpp [new file with mode: 0644]
src/world/Tileset.h [new file with mode: 0644]
src/world/World.cpp [new file with mode: 0644]
src/world/World.h [new file with mode: 0644]
tests/test-all.cpp [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..310f039
--- /dev/null
@@ -0,0 +1,10 @@
+*.d
+*.o
+*.swp
+*.swo
+
+/build/*/collision
+/build/*/orbi
+/build/*/test-all
+
+/data
diff --git a/build/base.mk b/build/base.mk
new file mode 100644 (file)
index 0000000..027286e
--- /dev/null
@@ -0,0 +1,15 @@
+BASE_DIRS := $(shell cd $(TOP); find src -type d)
+BASE_SRCS := $(shell cd $(TOP); find src -mindepth 2 -type f -name '*.cpp')
+BASE_OBJS := $(BASE_SRCS:.cpp=.o)
+
+ALL_DIRS += $(BASE_DIRS)
+ALL_OBJS += $(BASE_OBJS)
+
+BASE_FLAGS = \
+       $(CPPFLAGS) \
+       $(CXXFLAGS) \
+       $(strip \
+               $(CPPUNIT_FLAGS) \
+               $(SDL_FLAGS) \
+               $(SDL_IMG_FLAGS) \
+       )
diff --git a/build/collision.mk b/build/collision.mk
new file mode 100644 (file)
index 0000000..4f6df22
--- /dev/null
@@ -0,0 +1,20 @@
+COLLISION_SRCS := \
+       src/collision.cpp \
+       $(shell cd $(TOP); echo \
+               src/*/*.cpp \
+       )
+
+COLLISION_OBJS = $(COLLISION_SRCS:%.cpp=%.o)
+
+ALL_EXES += collision
+ALL_OBJS += $(COLLISION_OBJS)
+
+COLLISION_LIBS = $(strip \
+               $(SDL_LIBS) \
+               $(SDL_IMG_LIBS) \
+               )
+
+collision: $(COLLISION_OBJS)
+       -@$(MKDIR) "$(@D)"
+       @echo "link: $@"
+       $(VERBOSE) $(CXX) -o "$@" $(LDFLAGS) $^ $(COLLISION_LIBS)
diff --git a/build/config.mk b/build/config.mk
new file mode 100644 (file)
index 0000000..e80cd03
--- /dev/null
@@ -0,0 +1,32 @@
+# commands
+CXX = g++ -std=c++11
+RM ?= rm -Rf
+MKDIR ?= mkdir -p
+RMDIR ?= rmdir -p --ignore-fail-on-non-empty
+
+# names and pathes
+BUILD := $(dir $(lastword $(MAKEFILE_LIST)))
+TOP := $(BUILD)..
+srcdir = $(TOP)/src
+
+# flags
+CPPFLAGS ?=
+CXXFLAGS ?=
+LDFLAGS ?=
+
+#CXXFLAGS += -Wall -Werror
+
+# libraries
+SDL_FLAGS = $(shell pkg-config --cflags sdl2)
+SDL_LIBS = $(shell pkg-config --libs sdl2)
+
+SDL_IMG_FLAGS = $(shell pkg-config --cflags SDL2_image)
+SDL_IMG_LIBS = $(shell pkg-config --libs SDL2_image)
+
+CPPUNIT_FLAGS = $(shell pkg-config --cflags cppunit)
+CPPUNIT_LIBS = $(shell pkg-config --libs cppunit)
+
+# set to empty to show tool invocations
+VERBOSE = @
+
+-include $(BUILD)local-config.mk
diff --git a/build/debug/Makefile b/build/debug/Makefile
new file mode 100644 (file)
index 0000000..f5dbeda
--- /dev/null
@@ -0,0 +1,13 @@
+include ../targets.mk
+include ../config.mk
+include ../base.mk
+include ../test.mk
+
+include ../collision.mk
+include ../orbi.mk
+
+include ../rules.mk
+
+CXXFLAGS += -O0 -g3
+
+-include local.mk
diff --git a/build/orbi.mk b/build/orbi.mk
new file mode 100644 (file)
index 0000000..1366554
--- /dev/null
@@ -0,0 +1,25 @@
+ORBI_SRCS := \
+       src/orbi.cpp \
+       $(shell cd $(TOP); echo \
+               src/*/*.cpp \
+       )
+
+ORBI_OBJS = $(ORBI_SRCS:%.cpp=%.o)
+
+ALL_EXES += orbi
+ALL_OBJS += $(ORBI_OBJS)
+
+ORBI_LIBS = $(strip \
+               $(SDL_LIBS) \
+               $(SDL_IMG_LIBS) \
+               )
+
+orbi: $(ORBI_OBJS)
+       -@$(MKDIR) "$(@D)"
+       @echo "link: $@"
+       $(VERBOSE) $(CXX) -o "$@" $(LDFLAGS) $^ $(ORBI_LIBS)
+
+debug:
+       echo $(shell )
+
+.PHONY: debug
diff --git a/build/release/Makefile b/build/release/Makefile
new file mode 100644 (file)
index 0000000..a52cce5
--- /dev/null
@@ -0,0 +1,14 @@
+include ../targets.mk
+include ../config.mk
+include ../base.mk
+include ../test.mk
+
+include ../collision.mk
+include ../orbi.mk
+
+include ../rules.mk
+
+CPPFLAGS += -DNDEBUG
+CXXFLAGS += -O2
+
+-include local.mk
diff --git a/build/rules.mk b/build/rules.mk
new file mode 100644 (file)
index 0000000..f0026a6
--- /dev/null
@@ -0,0 +1,49 @@
+ALL_DEPS = $(ALL_OBJS:.o=.d)
+
+-include $(ALL_DEPS)
+
+$(strip $(sort $(ALL_OBJS))): %.o: $(TOP)/%.cpp
+       -@$(MKDIR) "$(@D)"
+       @echo "compile: $@"
+       $(VERBOSE) $(CXX) -c -o "$@" -MMD -MP -MF"$*.d" -MT"$@" "$<" $(BASE_FLAGS)
+
+all: $(ALL_EXES)
+
+clean-deps:
+       $(VERBOSE) -$(RM) $(ALL_DEPS)
+
+clean-exes:
+       $(VERBOSE) -$(RM) $(ALL_EXES)
+
+clean-objs:
+       $(VERBOSE) -$(RM) $(ALL_OBJS)
+
+clean-other:
+       $(VERBOSE) -$(RM) $(ALL_OTHER)
+
+clean: clean-deps clean-exes clean-objs clean-other
+       $(VERBOSE) -$(RMDIR) $(ALL_DIRS)
+
+config:
+       @echo CXX: $(CXX)
+       @echo
+       @echo CPPFLAGS: $(CPPFLAGS)
+       @echo CXXFLAGS: $(CXXFLAGS)
+       @echo LDFLAGS:  $(LDFLAGS)
+       @echo
+       @echo BOOST_FLAGS:   $(BOOST_FLAGS)
+       @echo BOOST_LIBS:    $(BOOST_LIBS)
+       @echo CPPUNIT_FLAGS: $(CPPUNIT_FLAGS)
+       @echo CPPUNIT_LIBS:  $(CPPUNIT_LIBS)
+       @echo CURL_FLAGS:    $(CURL_FLAGS)
+       @echo CURL_LIBS:     $(CURL_LIBS)
+       @echo MYSQL_FLAGS:   $(MYSQL_FLAGS)
+       @echo MYSQL_LIBS:    $(MYSQL_LIBS)
+       @echo OPENSSL_FLAGS: $(OPENSSL_FLAGS)
+       @echo OPENSSL_LIBS:  $(OPENSSL_LIBS)
+       @echo SDL_FLAGS:     $(SDL_FLAGS)
+       @echo SDL_LIBS:      $(SDL_LIBS)
+       @echo SDL_NET_FLAGS: $(SDL_NET_FLAGS)
+       @echo SDL_NET_LIBS:  $(SDL_NET_LIBS)
+
+.PHONY: clean-deps clean-exes clean-objs config
diff --git a/build/targets.mk b/build/targets.mk
new file mode 100644 (file)
index 0000000..6cc7daa
--- /dev/null
@@ -0,0 +1,12 @@
+all:
+
+clean:
+
+install:
+uninstall:
+
+tests:
+
+test:
+
+.PHONY: all clean install uninstall tests test
diff --git a/build/test.mk b/build/test.mk
new file mode 100644 (file)
index 0000000..ed2793c
--- /dev/null
@@ -0,0 +1,27 @@
+TEST_DIRS := $(shell cd $(TOP); find tests -type d)
+TEST_SRCS := $(shell cd $(TOP); find tests -type f -name '*.cpp')
+TEST_OBJS := $(TEST_SRCS:.cpp=.o)
+
+ALL_DIRS += $(TEST_DIRS)
+ALL_EXES += test-all$(binext)
+ALL_OBJS += $(TEST_OBJS)
+
+TEST_LIBS = $(strip \
+       $(CPPUNIT_LIBS) \
+       $(SDL_LIBS) \
+       $(SDL_IMG_LIBS) \
+)
+
+test-all$(binext): $(BASE_OBJS) $(TEST_OBJS)
+       -@$(MKDIR) "$(@D)"
+       @echo "link: $@"
+       $(VERBOSE) $(CXX) -o "$@" $(LDFLAGS) $^ $(TEST_LIBS)
+
+run-test-all: test-all$(binext)
+       @echo "test: test-all$(binext)"
+       $(VERBOSE) ./test-all$(binext)
+
+tests: test-all$(binext)
+test: run-test-all
+
+.PHONY: run-test-all
diff --git a/src/app/Application.cpp b/src/app/Application.cpp
new file mode 100644 (file)
index 0000000..01568d9
--- /dev/null
@@ -0,0 +1,183 @@
+#include "Application.h"
+
+#include "../graphics/Canvas.h"
+#include "../graphics/Color.h"
+#include "../world/Entity.h"
+#include "../world/Tileset.h"
+#include "../world/World.h"
+
+
+namespace orbi {
+
+Application::Application(Canvas &c, World &w, Tileset &t)
+: canvas(c)
+, world(w)
+, tiles(t)
+, focus(Vector<float>(5, 5), 2)
+, cam(c.Size(), focus.Pos())
+, last(SDL_GetTicks())
+, running(false)
+, paused(false) {
+       cam.SetScale(tiles.TileSize());
+}
+
+
+void Application::Run() {
+       running = true;
+       while (running) {
+               Uint32 now = SDL_GetTicks();
+               int delta = now - last;
+               Loop(delta);
+               last = now;
+       }
+}
+
+
+void Application::Loop(int delta) {
+       HandleEvents();
+
+       if (delta == 0) {
+               SDL_Delay(1);
+               return;
+       } else if (delta > 30) {
+               delta = 30;
+       }
+
+       if (!paused) {
+               Update(delta);
+       }
+
+       Render();
+
+       canvas.Present();
+
+}
+
+
+void Application::HandleEvents() {
+       SDL_Event event;
+       while (SDL_PollEvent(&event)) {
+               switch (event.type) {
+                       case SDL_QUIT:
+                               running = false;
+                               break;
+                       case SDL_WINDOWEVENT:
+                               if (event.window.event == SDL_WINDOWEVENT_RESIZED) {
+                                       cam.Resize(event.window.data1, event.window.data2);
+                               }
+                               break;
+                       case SDL_KEYDOWN:
+                               if (!event.key.repeat) {
+                                       OnKeyDown(event.key);
+                               }
+                               break;
+                       case SDL_KEYUP:
+                               if (!event.key.repeat) {
+                                       OnKeyUp(event.key);
+                               }
+                               break;
+                       default:
+                               // skip event
+                               break;
+               }
+       }
+}
+
+void Application::OnKeyDown(const SDL_KeyboardEvent &e) {
+       switch (e.keysym.sym) {
+               case SDLK_UP:
+                       focus.MoveUp();
+                       break;
+               case SDLK_DOWN:
+                       focus.MoveDown();
+                       break;
+               case SDLK_LEFT:
+                       focus.MoveLeft();
+                       break;
+               case SDLK_RIGHT:
+                       focus.MoveRight();
+                       break;
+               case SDLK_p:
+                       paused = !paused;
+                       break;
+               default:
+                       break;
+       }
+}
+
+void Application::OnKeyUp(const SDL_KeyboardEvent &e) {
+       switch (e.keysym.sym) {
+               case SDLK_UP:
+                       focus.StopUp();
+                       break;
+               case SDLK_DOWN:
+                       focus.StopDown();
+                       break;
+               case SDLK_LEFT:
+                       focus.StopLeft();
+                       break;
+               case SDLK_RIGHT:
+                       focus.StopRight();
+                       break;
+               default:
+                       break;
+       }
+}
+
+
+void Application::Update(int dt) {
+       const float delta = dt / 1e3;
+       cam.Update(delta);
+       world.Update(dt);
+       focus.Update(delta);
+}
+
+
+void Application::Render() {
+       RenderBackground();
+       RenderWorld();
+       RenderEntities();
+       RenderUI();
+}
+
+void Application::RenderBackground() {
+       constexpr Color background(0x00, 0x00, 0x00);
+
+       canvas.SetColor(background);
+       canvas.Fill();
+}
+
+void Application::RenderWorld() {
+       const Vector<int> begin(0, 0);
+       const Vector<int> end(world.Size());
+
+       for (Vector<int> pos(begin); pos.y < end.y; ++pos.y) {
+               for (pos.x = 0; pos.x < end.x; ++pos.x) {
+                       tiles.DrawFG(canvas, cam.ToScreen(pos), world, pos);
+               }
+       }
+}
+
+void Application::RenderEntities() {
+       constexpr Color entityColor(0x00, 0xFA, 0x00);
+       canvas.SetColor(entityColor);
+
+       for (const Entity &e : world.Entities()) {
+               const Vector<float> pos(e.Bounds().Left(), e.Bounds().Top());
+               const Vector<float> size(e.Bounds().Size());
+               canvas.OutlineRect(cam.ToScreen(pos), cam.ToScale(size));
+       }
+}
+
+void Application::RenderUI() {
+       constexpr Color outlineColor(0x00, 0x00, 0xFA);
+       constexpr Color focusColor(0xFA, 0xFA, 0x00);
+
+       canvas.SetColor(outlineColor);
+       canvas.Grid(cam.ToScreen(Vector<int>(0, 0)), cam.ToScale(world.Size()), cam.ToScale(Vector<float>(1, 1)));
+
+       canvas.SetColor(focusColor);
+       canvas.Cross(cam.ToScreen(focus.Pos()), 15);
+}
+
+}
diff --git a/src/app/Application.h b/src/app/Application.h
new file mode 100644 (file)
index 0000000..2b0b361
--- /dev/null
@@ -0,0 +1,57 @@
+#ifndef ORBI_APPLICATION_H_
+#define ORBI_APPLICATION_H_
+
+#include "../graphics/Camera.h"
+#include "../graphics/Moveable.h"
+#include "../graphics/Texture.h"
+#include "../graphics/Vector.h"
+
+#include <SDL.h>
+
+
+namespace orbi {
+
+class Canvas;
+class Tileset;
+class World;
+
+class Application {
+
+public:
+       Application(Canvas &, World &, Tileset &);
+
+public:
+       void Run();
+
+private:
+       void Loop(int delta_ms);
+
+       void HandleEvents();
+       void OnKeyDown(const SDL_KeyboardEvent &);
+       void OnKeyUp(const SDL_KeyboardEvent &);
+
+       void Update(int delta_ms);
+
+       void Render();
+       void RenderBackground();
+       void RenderWorld();
+       void RenderEntities();
+       void RenderUI();
+
+private:
+       Canvas &canvas;
+       World &world;
+       Tileset &tiles;
+
+       Moveable<float> focus;
+       Camera cam;
+
+       Uint32 last;
+       bool running;
+       bool paused;
+
+};
+
+}
+
+#endif
diff --git a/src/app/IMG.cpp b/src/app/IMG.cpp
new file mode 100644 (file)
index 0000000..b5e9cea
--- /dev/null
@@ -0,0 +1,20 @@
+#include "IMG.h"
+
+#include <stdexcept>
+#include <string>
+#include <SDL_image.h>
+
+
+namespace orbi {
+
+IMG::IMG(int flags) {
+       if (IMG_Init(flags) == 0) {
+               throw std::runtime_error(std::string("init IMG: ") + IMG_GetError());
+       }
+}
+
+IMG::~IMG() {
+       IMG_Quit();
+}
+
+}
diff --git a/src/app/IMG.h b/src/app/IMG.h
new file mode 100644 (file)
index 0000000..f8133b9
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef ORBI_IMG_H_
+#define ORBI_IMG_H_
+
+
+namespace orbi {
+
+class IMG {
+
+public:
+       explicit IMG(int flags);
+       ~IMG();
+
+       IMG(const IMG &) = delete;
+       IMG &operator =(const IMG &) = delete;
+
+};
+
+}
+
+#endif
diff --git a/src/app/SDL.cpp b/src/app/SDL.cpp
new file mode 100644 (file)
index 0000000..a7e2dab
--- /dev/null
@@ -0,0 +1,19 @@
+#include "SDL.h"
+
+#include <stdexcept>
+#include <string>
+
+
+namespace orbi {
+
+SDL::SDL(Uint32 flags) {
+       if (SDL_Init(flags) != 0) {
+               throw std::runtime_error(std::string("init SDL: ") + SDL_GetError());
+       }
+}
+
+SDL::~SDL() {
+       SDL_Quit();
+}
+
+}
diff --git a/src/app/SDL.h b/src/app/SDL.h
new file mode 100644 (file)
index 0000000..ce116d5
--- /dev/null
@@ -0,0 +1,22 @@
+#ifndef ORBI_SDL_H_
+#define ORBI_SDL_H_
+
+#include <SDL.h>
+
+
+namespace orbi {
+
+class SDL {
+
+public:
+       explicit SDL(Uint32 flags);
+       ~SDL();
+
+       SDL(const SDL &) = delete;
+       SDL &operator =(const SDL &) = delete;
+
+};
+
+}
+
+#endif
diff --git a/src/collision.cpp b/src/collision.cpp
new file mode 100644 (file)
index 0000000..af4234a
--- /dev/null
@@ -0,0 +1,230 @@
+#include "app/SDL.h"
+#include "graphics/Camera.h"
+#include "graphics/Canvas.h"
+#include "graphics/Color.h"
+#include "graphics/Vector.h"
+#include "graphics/Window.h"
+#include "world/AABB.h"
+
+#include <SDL.h>
+#include <vector>
+
+using namespace orbi;
+using namespace std;
+
+
+namespace {
+
+struct World {
+
+       bool alive;
+
+       Vector<float> focus;
+       Camera cam;
+
+       Vector<int> move;
+
+       AABB controlled;
+       vector<AABB> stationary;
+
+       struct Collision {
+               Vector<float> pos;
+               Vector<float> norm;
+               Vector<float> depth;
+       };
+       vector<Collision> coll;
+
+       World()
+       : alive(true)
+       , focus()
+       , cam(Vector<int>(), focus)
+       , controlled()
+       , stationary()
+       { }
+
+};
+
+void key_down(const SDL_KeyboardEvent &e, World &world) {
+       switch (e.keysym.sym) {
+               case SDLK_UP:
+                       world.move.y -= 1;
+                       break;
+               case SDLK_DOWN:
+                       world.move.y += 1;
+                       break;
+               case SDLK_LEFT:
+                       world.move.x -= 1;
+                       break;
+               case SDLK_RIGHT:
+                       world.move.x += 1;
+                       break;
+               default:
+                       break;
+       }
+}
+
+void key_up(const SDL_KeyboardEvent &e, World &world) {
+       switch (e.keysym.sym) {
+               case SDLK_UP:
+                       world.move.y += 1;
+                       break;
+               case SDLK_DOWN:
+                       world.move.y -= 1;
+                       break;
+               case SDLK_LEFT:
+                       world.move.x += 1;
+                       break;
+               case SDLK_RIGHT:
+                       world.move.x -= 1;
+                       break;
+               default:
+                       break;
+       }
+}
+
+void handle(World &world) {
+       SDL_Event event;
+       while (SDL_PollEvent(&event)) {
+               switch (event.type) {
+                       case SDL_QUIT:
+                               world.alive = false;
+                               break;
+                       case SDL_WINDOWEVENT:
+                               if (event.window.event == SDL_WINDOWEVENT_RESIZED) {
+                                       world.cam.Resize(event.window.data1, event.window.data2);
+                               }
+                               break;
+                       case SDL_KEYDOWN:
+                               if (!event.key.repeat) {
+                                       key_down(event.key, world);
+                               }
+                               break;
+                       case SDL_KEYUP:
+                               if (!event.key.repeat) {
+                                       key_up(event.key, world);
+                               }
+                               break;
+               }
+       }
+}
+
+void update(int dt, World &world) {
+       const Vector<float> speed { 5, 5 };
+
+       const float delta = dt / 1e3;
+
+       world.controlled.Move(Vector<float>(world.move) * speed * delta);
+       world.focus = world.controlled.Center();
+
+       world.coll.clear();
+       for (const AABB &e : world.stationary) {
+               World::Collision coll;
+               if (world.controlled.Intersects(
+                               e,
+                               coll.pos,
+                               coll.norm,
+                               coll.depth)) {
+                       world.coll.push_back(coll);
+               }
+       }
+}
+
+void render(Canvas &canvas, const World &world) {
+       constexpr Color background(0x00, 0x00, 0x00);
+       constexpr Color outlineColor(0x00, 0x00, 0xFA);
+       constexpr Color controlledColor(0xFA, 0xFA, 0x00);
+       constexpr Color entityColor(0x00, 0xFA, 0x00);
+       constexpr Color collisionColor(0xFA, 0x00, 0x00);
+       constexpr Color normalColor(0xFA, 0x00, 0x00);
+
+       canvas.SetColor(background);
+       canvas.Fill();
+
+       canvas.SetColor(outlineColor);
+       canvas.Grid(
+               world.cam.ToScreen(Vector<int>(0, 0)),
+               world.cam.ToScale(Vector<float>(10, 10)),
+               world.cam.ToScale(Vector<float>(1, 1)));
+
+       canvas.SetColor(entityColor);
+       for (const AABB &e : world.stationary) {
+               canvas.OutlineRect(
+                       world.cam.ToScreen(Vector<float>(e.Left(), e.Top())),
+                       world.cam.ToScale(e.Size()));
+       }
+
+       canvas.SetColor(controlledColor);
+       canvas.OutlineRect(
+               world.cam.ToScreen(Vector<float>(world.controlled.Left(), world.controlled.Top())),
+               world.cam.ToScale(world.controlled.Size()));
+
+       if (world.coll.empty()) return;
+
+       for (const World::Collision &c : world.coll) {
+               canvas.SetColor(collisionColor);
+               canvas.Arrow(
+                       world.cam.ToScreen(c.pos),
+                       world.cam.ToScreen(c.pos + c.depth));
+               canvas.SetColor(normalColor);
+               canvas.Arrow(
+                       world.cam.ToScreen(c.pos),
+                       world.cam.ToScreen(c.pos) + Vector<int>(c.norm * 25.0f));
+       }
+}
+
+void run(Canvas &canvas) {
+       World world;
+       world.cam.SetScale(Vector<float>(32, 32));
+       world.controlled.Resize(Vector<float>(2, 3));
+       world.controlled.Move(Vector<float>(1, 1.5));
+
+       AABB e;
+       e.Resize(Vector<float>(2, 2));
+       e.Move(Vector<float>(5, 5));
+       world.stationary.push_back(e);
+       e.Move(Vector<float>(0, 2));
+       world.stationary.push_back(e);
+       e.Move(Vector<float>(-2, 0));
+       world.stationary.push_back(e);
+
+       Uint32 last = SDL_GetTicks();
+       while (world.alive) {
+               handle(world);
+               Uint32 now = SDL_GetTicks();
+
+               int delta = now - last;
+               if (delta == 0) {
+                       SDL_Delay(1);
+                       continue;
+               } else if (delta > 30) {
+                       delta = 30;
+               }
+
+               update(delta, world);
+               render(canvas, world);
+
+               canvas.Present();
+               last = now;
+       }
+}
+
+}
+
+
+int main(int argc, const char *argv[]) {
+       SDL sdl(SDL_INIT_VIDEO);
+
+       Window win(
+               "orbi collision test",
+               Window::POS_UNDEF,
+               Vector<int>(800, 600),
+               SDL_WINDOW_RESIZABLE
+       );
+       Canvas canv(win.CreateCanvas(
+               0
+       ));
+
+       run(canv);
+
+       return 0;
+}
diff --git a/src/graphics/Camera.cpp b/src/graphics/Camera.cpp
new file mode 100644 (file)
index 0000000..5c519a4
--- /dev/null
@@ -0,0 +1,19 @@
+#include "Camera.h"
+
+
+namespace orbi {
+
+Camera::Camera(Vector<int> s, const Vector<float> &t)
+: target(&t)
+, size(s)
+, offset(size / 2) {
+
+}
+
+
+void Camera::Resize(Vector<int> s) {
+       size = s;
+       offset = size / 2;
+}
+
+}
diff --git a/src/graphics/Camera.h b/src/graphics/Camera.h
new file mode 100644 (file)
index 0000000..7e8ba2f
--- /dev/null
@@ -0,0 +1,52 @@
+#ifndef ORBI_CAMERA_H_
+#define ORBI_CAMERA_H_
+
+#include "Vector.h"
+
+
+namespace orbi {
+
+class Camera {
+
+public:
+       Camera(Vector<int> size, const Vector<float> &);
+
+public:
+       void SetTarget(const Vector<float> &t) { target = &t; }
+       void SetScale(Vector<float> s) { scale = s; }
+
+       Vector<int> ScreenSize() const { return size; }
+
+       void Resize(int w, int h) { Resize(Vector<int>(w, h)); }
+       void Resize(Vector<int>);
+       void Update(float deltaT) { } // unused
+
+       /// transform v from world coords to screen coords
+       Vector<int> ToScreen(Vector<float> v) const {
+               return ToScale(v - *target) + offset;
+       }
+       /// scale v from world to screen
+       Vector<int> ToScale(Vector<float> v) const {
+               return Vector<int>(v * scale);
+       }
+
+       /// transform v from screen coords to world coords
+       Vector<float> FromScreen(Vector<int> v) const {
+               return FromScale(v - offset) + *target;
+       }
+       /// scale v from screen to world
+       Vector<float> FromScale(Vector<int> v) const {
+               return Vector<float>(v) / scale;
+       }
+
+private:
+       const Vector<float> *target;
+       Vector<float> scale;
+       Vector<int> size;
+       Vector<int> offset;
+
+};
+
+}
+
+#endif
diff --git a/src/graphics/Canvas.cpp b/src/graphics/Canvas.cpp
new file mode 100644 (file)
index 0000000..3f1c3d1
--- /dev/null
@@ -0,0 +1,264 @@
+#include "Canvas.h"
+
+#include <algorithm>
+#include <cassert>
+#include <stdexcept>
+#include <string>
+
+using std::runtime_error;
+
+
+namespace orbi {
+
+Canvas::Canvas(SDL_Window *win, int index, Uint32 flags)
+: canv(SDL_CreateRenderer(win, index, flags)) {
+       if (!canv) {
+               throw runtime_error(std::string("create canvas: ") + SDL_GetError());
+       }
+}
+
+Canvas::~Canvas() {
+       if (canv) SDL_DestroyRenderer(canv);
+}
+
+Canvas::Canvas(Canvas &&other)
+: canv(other.canv) {
+       other.canv = nullptr;
+}
+
+Canvas &Canvas::operator =(Canvas &&other) {
+       std::swap(canv, other.canv);
+       return *this;
+}
+
+
+void Canvas::Present() {
+       SDL_RenderPresent(canv);
+}
+
+
+Vector<int> Canvas::Size() const {
+       assert(canv);
+       Vector<int> size;
+       SDL_GetRendererOutputSize(canv, &size.x, &size.y);
+       return size;
+}
+
+
+Texture Canvas::CreateStaticTexture(Vector<int> size) {
+       return Texture(canv, Color::Format, SDL_TEXTUREACCESS_STATIC, size);
+}
+
+Texture Canvas::LoadTexture(const char *file) {
+       return Texture(canv, file);
+}
+
+
+void Canvas::Copy(Texture &tex, Vector<int> to) {
+       tex.Copy(canv, to);
+}
+
+void Canvas::Copy(Texture &tex, Rect<int> clip, Vector<int> to) {
+       tex.Copy(canv, clip, to);
+}
+
+
+void Canvas::SetColor(Color c) {
+       SDL_SetRenderDrawColor(canv, c.r, c.g, c.b, c.a);
+}
+
+
+void Canvas::Fill() {
+       SDL_RenderClear(canv);
+}
+
+void Canvas::Outline() {
+       SDL_RenderDrawRect(canv, nullptr);
+}
+
+
+void Canvas::Line(Vector<int> from, Vector<int> to) {
+       SDL_RenderDrawLine(canv, from.x, from.y, to.x, to.y);
+}
+
+void Canvas::FillRect(Vector<int> pos, Vector<int> size) {
+       SDL_Rect destRect;
+       destRect.x = pos.x;
+       destRect.y = pos.y;
+       destRect.w = size.x;
+       destRect.h = size.y;
+       SDL_RenderFillRect(canv, &destRect);
+}
+
+void Canvas::OutlineRect(Vector<int> pos, Vector<int> size) {
+       SDL_Rect destRect;
+       destRect.x = pos.x;
+       destRect.y = pos.y;
+       destRect.w = size.x;
+       destRect.h = size.y;
+       SDL_RenderDrawRect(canv, &destRect);
+}
+
+
+void Canvas::Dot(Vector<int> pos) {
+       SDL_RenderDrawPoint(canv, pos.x, pos.y);
+}
+
+void Canvas::Cross(Vector<int> pos, int extent) {
+       Line(
+               Vector<int>(pos.x - extent, pos.y),
+               Vector<int>(pos.x + extent, pos.y));
+       Line(
+               Vector<int>(pos.x, pos.y - extent),
+               Vector<int>(pos.x, pos.y + extent));
+}
+
+void Canvas::Arrow(Vector<int> from, Vector<int> to) {
+       Line(from, to);
+       Vector<float> delta(to - from);
+       delta = delta / Length(delta);
+
+       Line(to, to + Vector<int>(Rotate90(delta) * 5.0f - (delta * 5.0f)));
+       Line(to, to + Vector<int>(Rotate270(delta) * 5.0f - (delta * 5.0f)));
+}
+
+void Canvas::Triangle(Vector<int> v1, Vector<int> v2, Vector<int> v3) {
+       SDL_Point points[4] = { v1, v2, v3, v1 };
+       SDL_RenderDrawLines(canv, points, 4);
+}
+
+void Canvas::Quad(Vector<int> v1, Vector<int> v2, Vector<int> v3, Vector<int> v4) {
+       SDL_Point points[5] = { v1, v2, v3, v4, v1 };
+       SDL_RenderDrawLines(canv, points, 5);
+}
+
+
+namespace {
+
+template<class Scalar>
+void GridImpl(
+               Canvas &canv,
+               Vector<int> pos,
+               Vector<int> size,
+               Vector<Scalar> step) {
+       Vector<int> from(pos);
+       Vector<int> to(pos + size);
+       Vector<int> clip(canv.Size());
+
+       if (from.x > clip.x || from.y > clip.y || to.x < 0 || to.y < 0) {
+               return;
+       }
+       if (step.x <= 1 || step.y <= 1) {
+               canv.FillRect(pos, size);
+               return;
+       }
+
+       if (from.x < -step.x) {
+               int skip = from.x / -step.x;
+               from.x += skip * step.x;
+       }
+       if (from.y < -step.y) {
+               int skip = from.y / -step.y;
+               from.y += skip * step.y;
+       }
+       if (to.x > clip.x + step.x) {
+               int skip = (to.x - clip.x) / step.x;
+               to.x -= skip * step.x;
+       }
+       if (to.y > clip.y + step.y) {
+               int skip = (to.y - clip.y) / step.y;
+               to.y -= skip * step.y;
+       }
+
+       int width = to.x - from.x;
+       int height = to.y - from.y;
+
+       for (Vector<Scalar> pos(from); pos.x <= to.x; pos.x += step.x) {
+               canv.Line(pos, Vector<int>(pos.x, pos.y + height));
+       }
+       for (Vector<Scalar> pos(from); pos.y <= to.y; pos.y += step.y) {
+               canv.Line(pos, Vector<int>(pos.x + width, pos.y));
+       }
+}
+
+template<class Scalar>
+void Grid2Impl(
+               Canvas &canv,
+               Vector<int> pos,
+               Vector<int> size,
+               Vector<Scalar> step,
+               Vector<int> n,
+               Color c1,
+               Color c2) {
+       Vector<int> from(pos);
+       Vector<int> to(pos + size);
+       Vector<int> clip(canv.Size());
+
+       if (from.x > clip.x || from.y > clip.y || to.x < 0 || to.y < 0) {
+               return;
+       }
+       if (step.x <= 1 || step.y <= 1) {
+               canv.SetColor(c1);
+               canv.FillRect(pos, size);
+               canv.SetColor(c2);
+               GridImpl(canv, pos, size, step * Vector<Scalar>(n));
+               return;
+       }
+
+       Vector<int> i(0, 0);
+
+       if (from.x < -step.x) {
+               int skip = from.x / -step.x;
+               from.x += skip * step.x;
+               i.x += skip;
+       }
+       if (from.y < -step.y) {
+               int skip = from.y / -step.y;
+               from.y += skip * step.y;
+               i.y += skip;
+       }
+       if (to.x > clip.x + step.x) {
+               int skip = (to.x - clip.x) / step.x;
+               to.x -= skip * step.x;
+       }
+       if (to.y > clip.y + step.y) {
+               int skip = (to.y - clip.y) / step.y;
+               to.y -= skip * step.y;
+       }
+
+       int width = to.x - from.x;
+       int height = to.y - from.y;
+
+       for (Vector<Scalar> pos(from); pos.x <= to.x; pos.x += step.x) {
+               canv.SetColor((i.x++ % n.x) ? c1 : c2);
+               canv.Line(pos, Vector<int>(pos.x, pos.y + height));
+       }
+       for (Vector<Scalar> pos(from); pos.y <= to.y; pos.y += step.y) {
+               canv.SetColor((i.y++ % n.y) ? c1 : c2);
+               canv.Line(pos, Vector<int>(pos.x + width, pos.y));
+       }
+}
+
+}
+
+void Canvas::Grid(Vector<int> pos, Vector<int> size, Vector<int> step) {
+       GridImpl(*this, pos, size, step);
+}
+
+void Canvas::Grid(Vector<int> pos, Vector<int> size, Vector<float> step) {
+       GridImpl(*this, pos, size, step);
+}
+
+void Canvas::Grid2(
+               Vector<int> pos, Vector<int> size, Vector<int> step,
+               Vector<int> n, Color c1, Color c2) {
+       Grid2Impl(*this, pos, size, step, n, c1, c2);
+}
+
+void Canvas::Grid2(
+               Vector<int> pos, Vector<int> size, Vector<float> step,
+               Vector<int> n, Color c1, Color c2) {
+       Grid2Impl(*this, pos, size, step, n, c1, c2);
+}
+
+}
diff --git a/src/graphics/Canvas.h b/src/graphics/Canvas.h
new file mode 100644 (file)
index 0000000..f399b36
--- /dev/null
@@ -0,0 +1,67 @@
+#ifndef ORBI_CANVAS_H_
+#define ORBI_CANVAS_H_
+
+#include "Color.h"
+#include "Texture.h"
+#include "Vector.h"
+
+#include <SDL.h>
+
+
+namespace orbi {
+
+class Canvas {
+
+public:
+       Canvas() : canv(nullptr) { }
+       Canvas(SDL_Window *win, int index, Uint32 flags);
+       ~Canvas();
+
+       Canvas(Canvas &&);
+       Canvas &operator =(Canvas &&);
+
+       Canvas(const Canvas &) = delete;
+       Canvas &operator =(const Canvas &) = delete;
+
+public:
+       Vector<int> Size() const;
+
+       void Present();
+
+       Texture CreateStaticTexture(Vector<int> size);
+       Texture LoadTexture(const char *file);
+
+       void Copy(Texture &, Vector<int> to);
+       void Copy(Texture &, Rect<int> clip, Vector<int> to);
+
+       void SetColor(Color);
+
+       void Fill();
+       void Outline();
+
+       void Line(Vector<int> from, Vector<int> to);
+       void FillRect(Vector<int> pos, Vector<int> size);
+       void OutlineRect(Vector<int> pos, Vector<int> size);
+
+       void Dot(Vector<int> pos);
+       void Cross(Vector<int> pos, int extent);
+       void Arrow(Vector<int> from, Vector<int> to);
+       void Triangle(Vector<int>, Vector<int>, Vector<int>);
+       void Quad(Vector<int>, Vector<int>, Vector<int>, Vector<int>);
+
+       void Grid(Vector<int> pos, Vector<int> size, Vector<int> step);
+       void Grid(Vector<int> pos, Vector<int> size, Vector<float> step);
+
+       void Grid2(Vector<int> pos, Vector<int> size, Vector<int> step,
+                       Vector<int> n, Color, Color);
+       void Grid2(Vector<int> pos, Vector<int> size, Vector<float> step,
+                       Vector<int> n, Color, Color);
+
+private:
+       SDL_Renderer *canv;
+
+};
+
+}
+
+#endif
diff --git a/src/graphics/Color.h b/src/graphics/Color.h
new file mode 100644 (file)
index 0000000..8cc7bf2
--- /dev/null
@@ -0,0 +1,25 @@
+#ifndef ORBI_COLOR_H_
+#define ORBI_COLOR_H_
+
+#include <SDL.h>
+
+
+namespace orbi {
+
+struct Color {
+
+       // NOTE: this depends on endianness and should be defined accordingly
+       static constexpr Uint32 Format = SDL_PIXELFORMAT_ABGR8888;
+
+       constexpr Color()
+       : Color(0, 0, 0) { }
+       constexpr Color(Uint8 r, Uint8 g, Uint8 b, Uint8 a = 0xFF)
+       : r(r), g(g), b(b), a(a) { }
+
+       Uint8 r, g, b, a;
+
+};
+
+}
+
+#endif
diff --git a/src/graphics/Moveable.h b/src/graphics/Moveable.h
new file mode 100644 (file)
index 0000000..b99dc11
--- /dev/null
@@ -0,0 +1,44 @@
+#ifndef ORBI_MOVEABLE_H_
+#define ORBI_MOVEABLE_H_
+
+#include "Vector.h"
+
+
+namespace orbi {
+
+template<class Scalar>
+class Moveable {
+
+public:
+       Moveable(Vector<Scalar> pos, Scalar speed)
+       : dir(0, 0), pos(pos), speed(speed) { }
+
+public:
+       const Vector<Scalar> &Pos() const { return pos; }
+       Vector<Scalar> Vel() const { return Vector<Scalar>(dir) * speed; }
+       void SetSpeed(float s) { speed = s; }
+
+       void Update(Scalar delta) {
+               pos += Vel() * delta;
+       }
+
+public:
+       void MoveUp() { dir.y -= 1; }
+       void StopUp() { dir.y += 1; }
+       void MoveDown() { dir.y += 1; }
+       void StopDown() { dir.y -= 1; }
+       void MoveLeft() { dir.x -= 1; }
+       void StopLeft() { dir.x += 1; }
+       void MoveRight() { dir.x += 1; }
+       void StopRight() { dir.x -= 1; }
+
+private:
+       Vector<int> dir;
+       Vector<Scalar> pos;
+       Scalar speed;
+
+};
+
+}
+
+#endif
diff --git a/src/graphics/Rect.h b/src/graphics/Rect.h
new file mode 100644 (file)
index 0000000..4601253
--- /dev/null
@@ -0,0 +1,50 @@
+#ifndef ORBI_RECT_H_
+#define ORBI_RECT_H_
+
+#include "Vector.h"
+
+#include <SDL.h>
+
+
+namespace orbi {
+
+template<class Scalar>
+class Rect {
+
+public:
+       constexpr Rect() : x(0), y(0), w(0), h(0) { }
+       constexpr Rect(Vector<Scalar> pos, Vector<Scalar> size)
+       : x(pos.x), y(pos.y), w(size.x), h(size.y) { }
+
+public:
+       constexpr Vector<Scalar> Pos() const { return Vector<Scalar>(x, y); }
+       constexpr Vector<Scalar> Size() const { return Vector<Scalar>(w, h); }
+
+public:
+       Scalar x;
+       Scalar y;
+       Scalar w;
+       Scalar h;
+
+};
+
+
+/// specialization with same layout as SDL_Rect
+template<>
+class Rect<int>
+: public SDL_Rect {
+
+public:
+       constexpr Rect() : SDL_Rect({0, 0, 0, 0}) { }
+       constexpr Rect(Vector<int> pos, Vector<int> size)
+       : SDL_Rect({pos.x, pos.y, size.x, size.y}) { }
+
+public:
+       constexpr Vector<int> Pos() const { return Vector<int>(x, y); }
+       constexpr Vector<int> Size() const { return Vector<int>(w, h); }
+
+};
+
+}
+
+#endif
diff --git a/src/graphics/Sprite.cpp b/src/graphics/Sprite.cpp
new file mode 100644 (file)
index 0000000..137b75e
--- /dev/null
@@ -0,0 +1,19 @@
+#include "Sprite.h"
+
+#include "Canvas.h"
+
+
+namespace orbi {
+
+Sprite::Sprite(Texture &&tex, Vector<int> size)
+: tex(std::move(tex))
+, size(size) {
+
+}
+
+
+void Sprite::Draw(Vector<int> index, Canvas &canv, Vector<int> at) {
+       canv.Copy(tex, Rect<int>(index * size, size), at);
+}
+
+}
diff --git a/src/graphics/Sprite.h b/src/graphics/Sprite.h
new file mode 100644 (file)
index 0000000..3f8f1bb
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef ORBI_SPRITE_H_
+#define ORBI_SPRITE_H_
+
+#include "Texture.h"
+#include "Vector.h"
+
+
+namespace orbi {
+
+class Canvas;
+
+class Sprite {
+
+public:
+       Sprite(Texture &&, Vector<int> size);
+
+public:
+       Vector<int> Size() const { return size; }
+
+       void Draw(Vector<int> index, Canvas &to, Vector<int> at);
+
+private:
+       Texture tex;
+       Vector<int> size;
+
+};
+
+}
+
+#endif
diff --git a/src/graphics/Texture.cpp b/src/graphics/Texture.cpp
new file mode 100644 (file)
index 0000000..f05f4b6
--- /dev/null
@@ -0,0 +1,92 @@
+#include "Texture.h"
+
+#include <SDL_image.h>
+
+
+namespace orbi {
+
+Texture::Texture()
+: tex(nullptr)
+, format(Color::Format)
+, size(0, 0) {
+
+}
+
+Texture::~Texture() {
+       if (tex) SDL_DestroyTexture(tex);
+}
+
+Texture::Texture(Texture &&other)
+: Texture() {
+       Swap(other);
+}
+
+Texture &Texture::operator =(Texture &&other) {
+       Texture temp(std::move(other));
+       Swap(temp);
+       return *this;
+}
+
+
+Texture::Texture(
+       SDL_Renderer *c,
+       Uint32 f,
+       int u,
+       Vector<int> s)
+: tex(SDL_CreateTexture(c, f, u, s.x, s.y))
+, format(f)
+, size(s) {
+
+}
+
+Texture::Texture(
+       SDL_Renderer *c,
+       const char *file)
+: tex(IMG_LoadTexture(c, file))
+, format()
+, size() {
+       SDL_QueryTexture(tex, &format, nullptr, &size.x, &size.y);
+}
+
+
+void Texture::Swap(Texture &other) {
+       std::swap(tex, other.tex);
+       std::swap(format, other.format);
+       std::swap(size, other.size);
+}
+
+
+void Texture::SetColors(const Color *values) {
+       if (format == Color::Format) {
+               SDL_UpdateTexture(tex, nullptr, values, Size().x * sizeof(Color));
+       } else {
+               // TODO: implement for non-Color pixel formats
+       }
+}
+
+
+void Texture::Fill(SDL_Renderer *canv) {
+       SDL_RenderCopy(canv, tex, nullptr, nullptr);
+}
+
+void Texture::Fill(SDL_Renderer *canv, Rect<int> clip) {
+       SDL_RenderCopy(canv, tex, &clip, nullptr);
+}
+
+void Texture::Copy(SDL_Renderer *canv, Vector<int> to) {
+       Copy(canv, Rect<int>(to, Size()));
+}
+
+void Texture::Copy(SDL_Renderer *canv, Rect<int> to) {
+       SDL_RenderCopy(canv, tex, nullptr, &to);
+}
+
+void Texture::Copy(SDL_Renderer *canv, Rect<int> clip, Vector<int> to) {
+       Copy(canv, clip, Rect<int>(to, clip.Size()));
+}
+
+void Texture::Copy(SDL_Renderer *canv, Rect<int> clip, Rect<int> to) {
+       SDL_RenderCopy(canv, tex, &clip, &to);
+}
+
+}
diff --git a/src/graphics/Texture.h b/src/graphics/Texture.h
new file mode 100644 (file)
index 0000000..e3fe38a
--- /dev/null
@@ -0,0 +1,70 @@
+#ifndef ORBI_TEXTURE_H_
+#define ORBI_TEXTURE_H_
+
+#include "Color.h"
+#include "Rect.h"
+#include "Vector.h"
+
+#include <algorithm>
+#include <SDL.h>
+
+
+namespace orbi {
+
+class Texture {
+
+public:
+       Texture();
+       ~Texture();
+
+       Texture(Texture &&);
+       Texture &operator =(Texture &&);
+
+       Texture(const Texture &) = delete;
+       Texture &operator =(const Texture &) = delete;
+
+       Texture(SDL_Renderer *, Uint32 format, int use, Vector<int> size);
+       Texture(SDL_Renderer *, const char *file);
+
+       void Swap(Texture &);
+
+public:
+       Vector<int> Size() const { return size; }
+
+       /// stretch this texture to completely fill given render target
+       void Fill(SDL_Renderer *);
+       /// stretch given clip to completely fill given render target
+       void Fill(SDL_Renderer *, Rect<int> clip);
+       /// copy entire texture as is to given coordinates
+       void Copy(SDL_Renderer *, Vector<int> to);
+       /// copy entire texture stretched to given rect
+       void Copy(SDL_Renderer *, Rect<int> to);
+       /// copy given clip to given coordinates
+       void Copy(SDL_Renderer *, Rect<int>, Vector<int> to);
+       /// copy given clip stretched to given rect
+       void Copy(SDL_Renderer *, Rect<int> clip, Rect<int> to);
+
+       /// set all color values
+       /// given array must hold at least Size().x * Size().y values
+       void SetColors(const Color *);
+
+private:
+       SDL_Texture *tex;
+       Uint32 format;
+       Vector<int> size;
+
+};
+
+}
+
+
+namespace std {
+
+template<>
+inline void swap<orbi::Texture>(orbi::Texture &lhs, orbi::Texture &rhs) {
+       lhs.Swap(rhs);
+}
+
+}
+
+#endif
diff --git a/src/graphics/Vector.h b/src/graphics/Vector.h
new file mode 100644 (file)
index 0000000..c355091
--- /dev/null
@@ -0,0 +1,251 @@
+#ifndef ORBI_VECTOR_H_
+#define ORBI_VECTOR_H_
+
+#include <algorithm>
+#include <cmath>
+#include <limits>
+#include <ostream>
+#include <SDL.h>
+
+
+namespace orbi {
+
+template<class Scalar>
+class Vector {
+
+public:
+       constexpr Vector() : x(0), y(0) { }
+       constexpr Vector(Scalar x, Scalar y) : x(x), y(y) { }
+
+       template<class Other>
+       constexpr Vector(Vector<Other> other) : x(other.x), y(other.y) { }
+
+       static Vector<Scalar> FromPolar(Scalar rad, Scalar az) {
+               return Vector<Scalar>(rad * std::cos(az), rad * std::sin(az));
+       }
+
+       static constexpr Vector<Scalar> unit45 = Vector<Scalar>(-0.7071, 0.7071);
+
+public:
+       Vector<Scalar> &operator +=(Vector<Scalar> other) {
+               x += other.x;
+               y += other.y;
+               return *this;
+       }
+       Vector<Scalar> &operator -=(Vector<Scalar> other) {
+               x -= other.x;
+               y -= other.y;
+               return *this;
+       }
+       Vector<Scalar> &operator *=(Scalar factor) {
+               x *= factor;
+               y *= factor;
+               return *this;
+       }
+       Vector<Scalar> &operator /=(Scalar factor) {
+               x /= factor;
+               y /= factor;
+               return *this;
+       }
+
+       SDL_Point ToPoint() const {
+               SDL_Point p;
+               p.x = x;
+               p.y = y;
+               return p;
+       }
+
+public:
+       Scalar x;
+       Scalar y;
+
+};
+
+template<class T>
+constexpr Vector<T> Vector<T>::unit45;
+
+/// specialization with same layout as SDL_Point
+template<>
+class Vector<int>
+: public SDL_Point {
+
+public:
+       constexpr Vector() : SDL_Point({0, 0}) { }
+       constexpr Vector(int x, int y) : SDL_Point({x, y}) { }
+
+       template<class Other>
+       constexpr Vector(Vector<Other> other)
+       : SDL_Point({int(other.x), int(other.y)}) { }
+
+public:
+       Vector<int> &operator +=(Vector<int> other) {
+               x += other.x;
+               y += other.y;
+               return *this;
+       }
+       Vector<int> &operator -=(Vector<int> other) {
+               x -= other.x;
+               y -= other.y;
+               return *this;
+       }
+
+       SDL_Point ToPoint() const {
+               return *this;
+       }
+
+};
+
+
+template<class Scalar>
+constexpr Vector<Scalar> operator -(Vector<Scalar> v) {
+       return Vector<Scalar>(-v.x, -v.y);
+}
+
+
+template<class Scalar>
+constexpr Vector<Scalar> operator +(Vector<Scalar> lhs, Vector<Scalar> rhs) {
+       return Vector<Scalar>(lhs.x + rhs.x, lhs.y + rhs.y);
+}
+
+template<class Scalar>
+constexpr Vector<Scalar> operator -(Vector<Scalar> lhs, Vector<Scalar> rhs) {
+       return Vector<Scalar>(lhs.x - rhs.x, lhs.y - rhs.y);
+}
+
+
+template<class Scalar>
+constexpr Vector<Scalar> operator *(Vector<Scalar> lhs, Scalar rhs) {
+       return Vector<Scalar>(lhs.x * rhs, lhs.y * rhs);
+}
+
+template<class Scalar>
+constexpr Vector<Scalar> operator *(Scalar lhs, Vector<Scalar> rhs) {
+       return rhs * lhs;
+}
+template<class Scalar>
+constexpr Vector<Scalar> operator *(Vector<Scalar> lhs, Vector<Scalar> rhs) {
+       return Vector<Scalar>(lhs.x * rhs.x, lhs.y * rhs.y);
+}
+
+
+template<class Scalar>
+constexpr Vector<Scalar> operator /(Vector<Scalar> lhs, Scalar rhs) {
+       return Vector<Scalar>(lhs.x / rhs, lhs.y / rhs);
+}
+
+template<class Scalar>
+constexpr Vector<Scalar> operator /(Scalar lhs, Vector<Scalar> rhs) {
+       return rhs / lhs;
+}
+template<class Scalar>
+constexpr Vector<Scalar> operator /(Vector<Scalar> lhs, Vector<Scalar> rhs) {
+       return Vector<Scalar>(lhs.x / rhs.x, lhs.y / rhs.y);
+}
+
+
+template<class Scalar>
+constexpr bool operator ==(Vector<Scalar> lhs, Vector<Scalar> rhs) {
+       return lhs.x == rhs.x && lhs.y == rhs.y;
+}
+template<class Scalar>
+constexpr bool operator !=(Vector<Scalar> lhs, Vector<Scalar> rhs) {
+       return lhs.x != rhs.x && lhs.y != rhs.y;
+}
+
+
+template<class Scalar>
+constexpr bool IsZero(Vector<Scalar> v) {
+       return std::abs(v.x) < std::numeric_limits<Scalar>::epsilon()
+               && std::abs(v.y) < std::numeric_limits<Scalar>::epsilon();
+}
+
+template<class Scalar>
+constexpr Vector<Scalar> abs(Vector<Scalar> v) {
+       return Vector<Scalar>(std::abs(v.x), std::abs(v.y));
+}
+
+
+template<class Scalar>
+constexpr Scalar Cross2D(Vector<Scalar> lhs, Vector<Scalar> rhs) {
+       return (lhs.x * rhs.y) - (lhs.y * rhs.x);
+}
+template<class Scalar>
+constexpr Scalar Dot(Vector<Scalar> lhs, Vector<Scalar> rhs) {
+       return (lhs.x * rhs.x) + (lhs.y * rhs.y);
+}
+template<class Scalar>
+constexpr Scalar Length(Vector<Scalar> v) {
+       return std::sqrt(Dot(v, v));
+}
+template<class Scalar>
+constexpr Vector<Scalar> Norm(Vector<Scalar> v) {
+       return v / Length(v);
+}
+
+template<class Scalar>
+constexpr Vector<Scalar> Rotate90(Vector<Scalar> v) {
+       return Vector<Scalar>(-v.y, v.x);
+}
+template<class Scalar>
+constexpr Vector<Scalar> Rotate180(Vector<Scalar> v) {
+       return -v;
+}
+template<class Scalar>
+constexpr Vector<Scalar> Rotate270(Vector<Scalar> v) {
+       return Vector<Scalar>(v.y, -v.x);
+}
+template<class Scalar, class Float>
+inline Vector<Scalar> Rotate(Vector<Scalar> v, Float by) {
+       Float sine(std::sin(by));
+       Float cosine(std::cos(by));
+       return Vector<Scalar>(v.x * cosine - v.y * sine, v.x * sine + v.y * cosine);
+}
+
+/// reflect v along normalized n
+template<class Scalar>
+inline Vector<Scalar> Reflect(Vector<Scalar> v, Vector<Scalar> n) {
+       const Scalar dd = Scalar(2) * Dot(v, n);
+       return Vector<Scalar>(v.x - (dd * n.x), v.y - (dd * n.y));
+}
+/// project v onto normalized n
+template<class Scalar>
+inline Vector<Scalar> Project(Vector<Scalar> v, Vector<Scalar> n) {
+       return Dot(v, n) * n;
+}
+
+
+template<class Scalar>
+inline std::ostream &operator <<(std::ostream &out, Vector<Scalar> v) {
+       return out << '<' << v.x << ',' << v.y << '>';
+}
+
+}
+
+
+namespace std {
+
+template<class Scalar>
+constexpr orbi::Vector<Scalar> min(
+               orbi::Vector<Scalar> lhs,
+               orbi::Vector<Scalar> rhs
+) {
+       return orbi::Vector<Scalar>(
+               min(lhs.x, rhs.x),
+               min(lhs.y, rhs.y)
+       );
+}
+
+template<class Scalar>
+constexpr orbi::Vector<Scalar> max(
+               orbi::Vector<Scalar> lhs,
+               orbi::Vector<Scalar> rhs
+) {
+       return orbi::Vector<Scalar>(
+               max(lhs.x, rhs.x),
+               max(lhs.y, rhs.y)
+       );
+}
+
+}
+
+#endif
diff --git a/src/graphics/Window.cpp b/src/graphics/Window.cpp
new file mode 100644 (file)
index 0000000..5aa76bb
--- /dev/null
@@ -0,0 +1,58 @@
+#include "Window.h"
+
+#include <algorithm>
+#include <cassert>
+#include <stdexcept>
+#include <string>
+
+using std::runtime_error;
+
+
+namespace orbi {
+
+const Vector<int> Window::POS_CENTER(
+       SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
+const Vector<int> Window::POS_UNDEF(
+       SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED);
+
+
+Window::Window(
+       const char *title,
+       Vector<int> pos,
+       Vector<int> size,
+       Uint32 flags)
+: win(SDL_CreateWindow(title, pos.x, pos.y, size.x, size.y, flags)) {
+       if (!win) {
+               throw runtime_error(std::string("create window ") + title
+                       + ": " + SDL_GetError());
+       }
+}
+
+Window::~Window() {
+       if (win) SDL_DestroyWindow(win);
+}
+
+Window::Window(Window &&other)
+: win(other.win) {
+       other.win = nullptr;
+}
+
+Window &Window::operator =(Window &&other) {
+       std::swap(win, other.win);
+       return *this;
+}
+
+
+Vector<int> Window::Size() const {
+       assert(win);
+       Vector<int> size;
+       SDL_GetWindowSize(win, &size.x, &size.y);
+       return size;
+}
+
+
+Canvas Window::CreateCanvas(Uint32 flags) {
+       return Canvas(win, -1, flags);
+}
+
+}
diff --git a/src/graphics/Window.h b/src/graphics/Window.h
new file mode 100644 (file)
index 0000000..23d44ca
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef ORBI_WINDOW_H_
+#define ORBI_WINDOW_H
+
+#include "Canvas.h"
+#include "Vector.h"
+
+#include <SDL.h>
+
+
+namespace orbi {
+
+class Window {
+
+public:
+       static const Vector<int> POS_CENTER;
+       static const Vector<int> POS_UNDEF;
+
+public:
+       Window() : win(nullptr) { }
+       Window(const char *title, Vector<int> pos, Vector<int> size, Uint32 flags);
+       ~Window();
+
+       Window(Window &&);
+       Window &operator =(Window &&);
+
+       Window(const Window &) = delete;
+       Window &operator =(const Window &) = delete;
+
+public:
+       Vector<int> Size() const;
+
+       Canvas CreateCanvas(Uint32 flags);
+
+private:
+       SDL_Window *win;
+
+};
+
+}
+
+#endif
diff --git a/src/graphics/const.h b/src/graphics/const.h
new file mode 100644 (file)
index 0000000..eaddf65
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef ORBI_CONST_H_
+#define ORBI_CONST_H_
+
+namespace orbi {
+
+// this is so very darn long so it can easily be changed to double
+constexpr float PI = 3.141592653589793238462643383279502884;
+constexpr float PI2 = 2 * PI;
+
+// gravitational constant in m^3 kg^-1 s^-2 (= N m^2 kg^-2) (for use with F = G ((m1 m2) / r^2)
+constexpr float G = 6.67384e-11;
+
+template<class T>
+constexpr int sigma(T v) {
+       return v > 0 ? 1 : (v < 0 ? -1 : 0);
+}
+
+}
+
+#endif
diff --git a/src/orbi.cpp b/src/orbi.cpp
new file mode 100644 (file)
index 0000000..f410a40
--- /dev/null
@@ -0,0 +1,59 @@
+#include "app/Application.h"
+#include "app/IMG.h"
+#include "app/SDL.h"
+#include "graphics/const.h"
+#include "graphics/Canvas.h"
+#include "graphics/Window.h"
+#include "world/AABB.h"
+#include "world/Entity.h"
+#include "world/Tile.h"
+#include "world/Tileset.h"
+#include "world/World.h"
+
+#include <iostream>
+#include <SDL_image.h>
+
+using namespace orbi;
+using namespace std;
+
+
+int main(int argc, const char *argv[]) {
+       SDL sdl(SDL_INIT_VIDEO);
+       IMG img(IMG_INIT_PNG);
+       Window win(
+               "orbi",
+               Window::POS_UNDEF,
+               Vector<int>(800, 600),
+               SDL_WINDOW_RESIZABLE
+       );
+       Canvas canv(win.CreateCanvas(
+               0
+       ));
+
+       Tileset tiles(
+               canv.LoadTexture("/home/holy/projects/orbi/data/test-tile.png"),
+               Vector<int>(32, 32)
+       );
+
+       World world(Vector<int>(10, 10));
+       world.SetTile(Vector<int>(5, 5), Tile(0));
+       world.SetTile(Vector<int>(5, 6), Tile(0));
+       world.SetTile(Vector<int>(6, 5), Tile(0));
+       world.SetTile(Vector<int>(6, 6), Tile(0));
+       world.SetTile(Vector<int>(6, 7), Tile(0));
+       world.SetTile(Vector<int>(7, 4), Tile(0));
+       world.SetTile(Vector<int>(7, 5), Tile(0));
+       world.SetTile(Vector<int>(8, 5), Tile(0));
+       world.SetTile(Vector<int>(9, 4), Tile(0));
+       world.SetTile(Vector<int>(9, 5), Tile(0));
+       world.SetTile(Vector<int>(9, 6), Tile(0));
+
+       Entity e;
+       e.Bounds() = AABB(Vector<float>(5, 2), Vector<float>(2, 3));
+       world.AddEntity(e);
+
+       Application app(canv, world, tiles);
+       app.Run();
+
+       return 0;
+}
diff --git a/src/world/AABB.cpp b/src/world/AABB.cpp
new file mode 100644 (file)
index 0000000..c54537e
--- /dev/null
@@ -0,0 +1,42 @@
+#include "AABB.h"
+
+namespace orbi {
+
+bool AABB::Intersects(const AABB &other, Vector<float> &p, Vector<float> &n, Vector<float> &d) const {
+       if (Bottom() < other.Top()) return false;
+       if (other.Bottom() < Top()) return false;
+       if (Right() < other.Left()) return false;
+       if (other.Right() < Left()) return false;
+
+       AABB diff;
+       diff.lt.x = std::max(Left(), other.Left());
+       diff.lt.y = std::max(Top(), other.Top());
+       diff.rb.x = std::min(Right(), other.Right());
+       diff.rb.y = std::min(Bottom(), other.Bottom());
+       const Vector<float> sdiff = diff.Size();
+
+       if (sdiff.x < sdiff.y) {
+               if (Center().x < other.Center().x) {
+                       p = Vector<float>(Right(), ((Top() + Bottom()) / 2 + (other.Top() + other.Bottom()) / 2) / 2);
+                       n = Vector<float>(-1, 0);
+                       d = Vector<float>(other.Left() - Right(), 0);
+               } else {
+                       p = Vector<float>(Left(), ((Top() + Bottom()) / 2 + (other.Top() + other.Bottom()) / 2) / 2);
+                       n = Vector<float>(1, 0);
+                       d = Vector<float>(other.Right() - Left(), 0);
+               }
+       } else {
+               if (Center().y < other.Center().y) {
+                       p = Vector<float>(((Left() + Right()) / 2 + (other.Left() + other.Right()) / 2) / 2, Bottom());
+                       n = Vector<float>(0, -1);
+                       d = Vector<float>(0, other.Top() - Bottom());
+               } else {
+                       p = Vector<float>(((Left() + Right()) / 2 + (other.Left() + other.Right()) / 2) / 2, Top());
+                       n = Vector<float>(0, 1);
+                       d = Vector<float>(0, other.Bottom() - Top());
+               }
+       }
+       return true;
+}
+
+}
diff --git a/src/world/AABB.h b/src/world/AABB.h
new file mode 100644 (file)
index 0000000..20580ca
--- /dev/null
@@ -0,0 +1,43 @@
+#ifndef ORBI_AABB_H_
+#define ORBI_AABB_H_
+
+#include "../graphics/Vector.h"
+
+
+namespace orbi {
+
+class AABB {
+
+public:
+       constexpr AABB() { }
+       constexpr AABB(Vector<float> pos, Vector<float> size)
+       : lt(pos), rb(pos + size) { }
+       constexpr AABB(float x, float y, float w, float h)
+       : lt(x, y), rb(x + w, y + h) { }
+
+public:
+       float Left() const { return lt.x; }
+       float Top() const { return lt.y; }
+       float Right() const { return rb.x; }
+       float Bottom() const { return rb.y; }
+
+       Vector<float> Position() const { return lt; }
+       Vector<float> Center() const { return lt + HalfSize(); }
+       Vector<float> HalfSize() const { return Size() / 2.0f; }
+       Vector<float> Size() const { return rb - lt; }
+
+public:
+       void Move(Vector<float> delta) { lt += delta; rb += delta; }
+       void Resize(Vector<float> size) { *this = AABB(lt, size); }
+
+       bool Intersects(const AABB &other, Vector<float> &p, Vector<float> &n, Vector<float> &d) const;
+
+private:
+       Vector<float> lt;
+       Vector<float> rb;
+
+};
+
+}
+
+#endif
diff --git a/src/world/Entity.cpp b/src/world/Entity.cpp
new file mode 100644 (file)
index 0000000..86d3a45
--- /dev/null
@@ -0,0 +1,13 @@
+#include "Entity.h"
+
+namespace orbi {
+
+void Entity::Update(float dt, Vector<float> extAcc, Vector<float> tv) {
+       const Vector<float> totAcc = acc + extAcc;
+       Move((dt * vel) + (totAcc * dt * dt / 2.0f));
+       vel += dt * totAcc;
+       if (vel.x > tv.x) vel.x = tv.x;
+       if (vel.y > tv.y) vel.y = tv.y;
+}
+
+}
diff --git a/src/world/Entity.h b/src/world/Entity.h
new file mode 100644 (file)
index 0000000..f9671ca
--- /dev/null
@@ -0,0 +1,35 @@
+#ifndef ORBI_ENTITY_H_
+#define ORBI_ENTITY_H_
+
+#include "AABB.h"
+#include "../graphics/Vector.h"
+
+
+namespace orbi {
+
+class Entity {
+
+public:
+       constexpr Entity() { }
+
+public:
+       void Update(float dt, Vector<float> extAcc, Vector<float> tv);
+       void Move(Vector<float> delta) { bounds.Move(delta); }
+
+public:
+       AABB &Bounds() { return bounds; }
+       const AABB &Bounds() const { return bounds; }
+
+private:
+       AABB bounds;
+       Vector<float> vel;
+       Vector<float> acc;
+
+       float mass = 1.0f;
+       float elast = 0.75f;
+
+};
+
+}
+
+#endif
diff --git a/src/world/Tile.h b/src/world/Tile.h
new file mode 100644 (file)
index 0000000..1a86db6
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef ORBI_TILE_H_
+#define ORBI_TILE_H_
+
+namespace orbi {
+
+class Tile {
+
+public:
+       explicit Tile(int fg = -1, int bg = -1)
+       : fg(fg), bg(bg) { }
+
+public:
+       bool HasFG() const { return fg >= 0; }
+       int GetFG() const { return fg; }
+       bool MatchesFG(const Tile &other) const { return fg == other.fg; }
+
+       bool HasBG() const { return bg >= 0; }
+       int GetBG() const { return bg; }
+       bool MatchesBG(const Tile &other) const { return bg == other.bg; }
+
+       bool IsSolid() const { return HasFG(); }
+
+private:
+       int fg;
+       int bg;
+
+};
+
+}
+
+#endif
diff --git a/src/world/TileClass.h b/src/world/TileClass.h
new file mode 100644 (file)
index 0000000..823e76e
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef ORBI_TILECLASS_H_
+#define ORBI_TILECLASS_H_
+
+
+namespace orbi {
+
+class TileClass {
+
+public:
+       explicit TileClass(Vector<int> sb)
+       : spriteBegin(sb) { }
+
+private:
+       Vector<int> spriteBegin;
+
+};
+
+}
+
+#endif
diff --git a/src/world/Tileset.cpp b/src/world/Tileset.cpp
new file mode 100644 (file)
index 0000000..880e1a0
--- /dev/null
@@ -0,0 +1,53 @@
+#include "Tileset.h"
+
+#include "Tile.h"
+#include "World.h"
+#include "../graphics/Canvas.h"
+
+
+namespace orbi {
+
+Tileset::Tileset(Texture &&tex, Vector<int> size)
+: sprite(std::move(tex), size / 2)
+, tileSize(size)
+, subSize(size / 2)
+, subCount(8, 4) {
+
+}
+
+
+void Tileset::DrawFG(Canvas &canv, Vector<int> to, const World &world, Vector<int> at) {
+       const Tile &mid = world.TileAt(at);
+       if (!mid.HasFG()) return;
+
+       const Vector<int> ul = at + Vector<int>(-1, -1);
+       const Vector<int> u = at + Vector<int>(0, -1);
+       const Vector<int> ur = at + Vector<int>(1, -1);
+       const Vector<int> l = at + Vector<int>(-1, 0);
+       const Vector<int> r = at + Vector<int>(1, 0);
+       const Vector<int> dl = at + Vector<int>(-1, 1);
+       const Vector<int> d = at + Vector<int>(0, 1);
+       const Vector<int> dr = at + Vector<int>(1, 1);
+
+       const bool mul = !world.InBounds(ul) || world.TileAt(ul).MatchesFG(mid);
+       const bool mu = !world.InBounds(u) || world.TileAt(u).MatchesFG(mid);
+       const bool mur = !world.InBounds(ur) || world.TileAt(ur).MatchesFG(mid);
+       const bool ml = !world.InBounds(l) || world.TileAt(l).MatchesFG(mid);
+       const bool mr = !world.InBounds(r) || world.TileAt(r).MatchesFG(mid);
+       const bool mdl = !world.InBounds(dl) || world.TileAt(dl).MatchesFG(mid);
+       const bool md = !world.InBounds(d) || world.TileAt(d).MatchesFG(mid);
+       const bool mdr = !world.InBounds(dr) || world.TileAt(dr).MatchesFG(mid);
+
+       const int tl = (ml << 2) | (mul << 1) | (mu);
+       const int tr = (mr << 2) | (mur << 1) | (mu);
+       const int bl = (ml << 2) | (mdl << 1) | (md);
+       const int br = (mr << 2) | (mdr << 1) | (md);
+
+       const Vector<int> off = Vector<int>(0, mid.GetFG() * subCount.y);
+       sprite.Draw(Vector<int>(off.x + tl, off.y), canv, to);
+       sprite.Draw(Vector<int>(off.x + tr, off.y + 1), canv, Vector<int>(to.x + subSize.x, to.y));
+       sprite.Draw(Vector<int>(off.x + bl, off.y + 2), canv, Vector<int>(to.x, to.y + subSize.y));
+       sprite.Draw(Vector<int>(off.x + br, off.y + 3), canv, Vector<int>(to.x + subSize.x, to.y + subSize.y));
+}
+
+}
diff --git a/src/world/Tileset.h b/src/world/Tileset.h
new file mode 100644 (file)
index 0000000..b6d1579
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef ORBI_TILESET_H_
+#define ORBI_TILESET_H_
+
+#include "../graphics/Sprite.h"
+#include "../graphics/Vector.h"
+
+
+namespace orbi {
+
+class Canvas;
+class Texture;
+class Tile;
+class World;
+
+class Tileset {
+
+public:
+       Tileset(Texture &&, Vector<int> size);
+
+       Vector<int> TileSize() const { return tileSize; }
+
+       void DrawFG(Canvas &, Vector<int> to, const World &, Vector<int> at);
+
+private:
+       Sprite sprite;
+       Vector<int> tileSize;
+       Vector<int> subSize;
+       Vector<int> subCount;
+
+};
+
+}
+
+#endif
diff --git a/src/world/World.cpp b/src/world/World.cpp
new file mode 100644 (file)
index 0000000..e390914
--- /dev/null
@@ -0,0 +1,88 @@
+#include "World.h"
+
+#include "../graphics/const.h"
+
+
+namespace orbi {
+
+World::World(Vector<int> size)
+: size(size)
+, count(size.x * size.y)
+, gravity(0, 5)
+, terminal(50, 50)
+, tiles(count) {
+
+}
+
+
+void World::Update(int delta) {
+       for (int i = 0; i < delta; ++i) {
+               const float dt = 1e-3;
+               for (Entity &e : entities) {
+                       e.Update(dt, gravity, terminal);
+
+                       const AABB &b = e.Bounds();
+
+                       // world bounds collision
+                       if (b.Top() < 0) e.Move(Vector<float>(0, -b.Top()));
+                       if (b.Right() > size.x) e.Move(Vector<float>(size.x - b.Right(), 0));
+                       if (b.Bottom() > size.y) e.Move(Vector<float>(0, size.y - b.Bottom()));
+                       if (b.Left() < 0) e.Move(Vector<float>(-b.Left(), 0));
+
+                       const Vector<int> cBegin(b.Left(), b.Top());
+                       const Vector<int> cEnd(b.Right(), b.Bottom());
+
+                       Vector<float> topResponse;
+                       for (Vector<int> pos(cBegin); pos.x < cEnd.x; ++pos.x) {
+                               if (TileAt(pos).IsSolid()) {
+                                       topResponse = Vector<float>(0, pos.y + 1 - b.Top());
+                                       break;
+                               }
+                       }
+                       Vector<float> bottomResponse;
+                       for (Vector<int> pos(cBegin.x, cEnd.y); pos.x < cEnd.x; ++pos.x) {
+                               if (TileAt(pos).IsSolid()) {
+                                       bottomResponse = Vector<float>(0, pos.y - b.Bottom());
+                                       break;
+                               }
+                       }
+                       if (!IsZero(topResponse)) {
+                               if (IsZero(bottomResponse)) {
+                                       e.Move(topResponse);
+                               }
+                       } else if (!IsZero(bottomResponse)) {
+                               e.Move(bottomResponse);
+                       }
+
+                       Vector<float> leftResponse;
+                       for (Vector<int> pos(cBegin); pos.y < cEnd.y; ++pos.y) {
+                               if (TileAt(pos).IsSolid()) {
+                                       leftResponse = Vector<float>(pos.x + 1 - b.Left(), 0);
+                                       break;
+                               }
+                       }
+                       Vector<float> rightResponse;
+                       for (Vector<int> pos(cEnd.x, cBegin.y); pos.y < cEnd.y; ++pos.y) {
+                               if (TileAt(pos).IsSolid()) {
+                                       rightResponse = Vector<float>(pos.x - b.Right(), 0);
+                                       break;
+                               }
+                       }
+                       if (!IsZero(leftResponse)) {
+                               if (IsZero(rightResponse)) {
+                                       e.Move(leftResponse);
+                               }
+                       } else if (!IsZero(rightResponse)) {
+                               e.Move(rightResponse);
+                       }
+               }
+       }
+}
+
+
+Entity &World::AddEntity(const Entity &e) {
+       entities.emplace_back(e);
+       return entities.back();
+}
+
+}
diff --git a/src/world/World.h b/src/world/World.h
new file mode 100644 (file)
index 0000000..3483f66
--- /dev/null
@@ -0,0 +1,51 @@
+#ifndef ORBI_WORLD_H_
+#define ORBI_WORLD_H_
+
+#include "Entity.h"
+#include "Tile.h"
+#include "../graphics/Vector.h"
+
+#include <list>
+#include <vector>
+
+
+namespace orbi {
+
+class World {
+
+public:
+       World(Vector<int> size);
+
+public:
+       Vector<int> Size() const { return size; }
+
+public:
+       void Update(int dt);
+
+       bool InBounds(Vector<int> pos) const
+               { return pos.x > 0 && pos.y > 0 && pos.x < size.x && pos.y < size.y; }
+       int Index(Vector<int> pos) const { return pos.y * size.x + pos.x; }
+
+       Tile &TileAt(Vector<int> pos) { return tiles[Index(pos)]; }
+       const Tile &TileAt(Vector<int> pos) const { return tiles[Index(pos)]; }
+       void SetTile(Vector<int> pos, const Tile &t) { tiles[Index(pos)] = t; }
+
+       const std::list<Entity> &Entities() const { return entities; }
+       Entity &AddEntity(const Entity &);
+
+private:
+       Vector<int> size;
+       int count;
+
+       Vector<float> gravity;
+       Vector<float> terminal;
+
+       std::vector<Tile> tiles;
+
+       std::list<Entity> entities;
+
+};
+
+}
+
+#endif
diff --git a/tests/test-all.cpp b/tests/test-all.cpp
new file mode 100644 (file)
index 0000000..9539c4a
--- /dev/null
@@ -0,0 +1,3 @@
+int main(int argc, const char *argv[]) {
+       return 0;
+}