From 4a51a83bdff30d1e25a5867cfb19936adc0034b1 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Mon, 7 Apr 2014 09:42:42 +0200 Subject: [PATCH 1/1] initial collision tests --- .gitignore | 10 ++ build/base.mk | 15 +++ build/collision.mk | 20 +++ build/config.mk | 32 +++++ build/debug/Makefile | 13 ++ build/orbi.mk | 25 ++++ build/release/Makefile | 14 +++ build/rules.mk | 49 ++++++++ build/targets.mk | 12 ++ build/test.mk | 27 ++++ src/app/Application.cpp | 183 +++++++++++++++++++++++++++ src/app/Application.h | 57 +++++++++ src/app/IMG.cpp | 20 +++ src/app/IMG.h | 20 +++ src/app/SDL.cpp | 19 +++ src/app/SDL.h | 22 ++++ src/collision.cpp | 230 ++++++++++++++++++++++++++++++++++ src/graphics/Camera.cpp | 19 +++ src/graphics/Camera.h | 52 ++++++++ src/graphics/Canvas.cpp | 264 +++++++++++++++++++++++++++++++++++++++ src/graphics/Canvas.h | 67 ++++++++++ src/graphics/Color.h | 25 ++++ src/graphics/Moveable.h | 44 +++++++ src/graphics/Rect.h | 50 ++++++++ src/graphics/Sprite.cpp | 19 +++ src/graphics/Sprite.h | 30 +++++ src/graphics/Texture.cpp | 92 ++++++++++++++ src/graphics/Texture.h | 70 +++++++++++ src/graphics/Vector.h | 251 +++++++++++++++++++++++++++++++++++++ src/graphics/Window.cpp | 58 +++++++++ src/graphics/Window.h | 41 ++++++ src/graphics/const.h | 20 +++ src/orbi.cpp | 59 +++++++++ src/world/AABB.cpp | 42 +++++++ src/world/AABB.h | 43 +++++++ src/world/Entity.cpp | 13 ++ src/world/Entity.h | 35 ++++++ src/world/Tile.h | 31 +++++ src/world/TileClass.h | 20 +++ src/world/Tileset.cpp | 53 ++++++++ src/world/Tileset.h | 34 +++++ src/world/World.cpp | 88 +++++++++++++ src/world/World.h | 51 ++++++++ tests/test-all.cpp | 3 + 44 files changed, 2342 insertions(+) create mode 100644 .gitignore create mode 100644 build/base.mk create mode 100644 build/collision.mk create mode 100644 build/config.mk create mode 100644 build/debug/Makefile create mode 100644 build/orbi.mk create mode 100644 build/release/Makefile create mode 100644 build/rules.mk create mode 100644 build/targets.mk create mode 100644 build/test.mk create mode 100644 src/app/Application.cpp create mode 100644 src/app/Application.h create mode 100644 src/app/IMG.cpp create mode 100644 src/app/IMG.h create mode 100644 src/app/SDL.cpp create mode 100644 src/app/SDL.h create mode 100644 src/collision.cpp create mode 100644 src/graphics/Camera.cpp create mode 100644 src/graphics/Camera.h create mode 100644 src/graphics/Canvas.cpp create mode 100644 src/graphics/Canvas.h create mode 100644 src/graphics/Color.h create mode 100644 src/graphics/Moveable.h create mode 100644 src/graphics/Rect.h create mode 100644 src/graphics/Sprite.cpp create mode 100644 src/graphics/Sprite.h create mode 100644 src/graphics/Texture.cpp create mode 100644 src/graphics/Texture.h create mode 100644 src/graphics/Vector.h create mode 100644 src/graphics/Window.cpp create mode 100644 src/graphics/Window.h create mode 100644 src/graphics/const.h create mode 100644 src/orbi.cpp create mode 100644 src/world/AABB.cpp create mode 100644 src/world/AABB.h create mode 100644 src/world/Entity.cpp create mode 100644 src/world/Entity.h create mode 100644 src/world/Tile.h create mode 100644 src/world/TileClass.h create mode 100644 src/world/Tileset.cpp create mode 100644 src/world/Tileset.h create mode 100644 src/world/World.cpp create mode 100644 src/world/World.h create mode 100644 tests/test-all.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..310f039 --- /dev/null +++ b/.gitignore @@ -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 index 0000000..027286e --- /dev/null +++ b/build/base.mk @@ -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 index 0000000..4f6df22 --- /dev/null +++ b/build/collision.mk @@ -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 index 0000000..e80cd03 --- /dev/null +++ b/build/config.mk @@ -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 index 0000000..f5dbeda --- /dev/null +++ b/build/debug/Makefile @@ -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 index 0000000..1366554 --- /dev/null +++ b/build/orbi.mk @@ -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 index 0000000..a52cce5 --- /dev/null +++ b/build/release/Makefile @@ -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 index 0000000..f0026a6 --- /dev/null +++ b/build/rules.mk @@ -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 index 0000000..6cc7daa --- /dev/null +++ b/build/targets.mk @@ -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 index 0000000..ed2793c --- /dev/null +++ b/build/test.mk @@ -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 index 0000000..01568d9 --- /dev/null +++ b/src/app/Application.cpp @@ -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(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 begin(0, 0); + const Vector end(world.Size()); + + for (Vector 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 pos(e.Bounds().Left(), e.Bounds().Top()); + const Vector 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(0, 0)), cam.ToScale(world.Size()), cam.ToScale(Vector(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 index 0000000..2b0b361 --- /dev/null +++ b/src/app/Application.h @@ -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 + + +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 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 index 0000000..b5e9cea --- /dev/null +++ b/src/app/IMG.cpp @@ -0,0 +1,20 @@ +#include "IMG.h" + +#include +#include +#include + + +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 index 0000000..f8133b9 --- /dev/null +++ b/src/app/IMG.h @@ -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 index 0000000..a7e2dab --- /dev/null +++ b/src/app/SDL.cpp @@ -0,0 +1,19 @@ +#include "SDL.h" + +#include +#include + + +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 index 0000000..ce116d5 --- /dev/null +++ b/src/app/SDL.h @@ -0,0 +1,22 @@ +#ifndef ORBI_SDL_H_ +#define ORBI_SDL_H_ + +#include + + +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 index 0000000..af4234a --- /dev/null +++ b/src/collision.cpp @@ -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 +#include + +using namespace orbi; +using namespace std; + + +namespace { + +struct World { + + bool alive; + + Vector focus; + Camera cam; + + Vector move; + + AABB controlled; + vector stationary; + + struct Collision { + Vector pos; + Vector norm; + Vector depth; + }; + vector coll; + + World() + : alive(true) + , focus() + , cam(Vector(), 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 speed { 5, 5 }; + + const float delta = dt / 1e3; + + world.controlled.Move(Vector(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(0, 0)), + world.cam.ToScale(Vector(10, 10)), + world.cam.ToScale(Vector(1, 1))); + + canvas.SetColor(entityColor); + for (const AABB &e : world.stationary) { + canvas.OutlineRect( + world.cam.ToScreen(Vector(e.Left(), e.Top())), + world.cam.ToScale(e.Size())); + } + + canvas.SetColor(controlledColor); + canvas.OutlineRect( + world.cam.ToScreen(Vector(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(c.norm * 25.0f)); + } +} + +void run(Canvas &canvas) { + World world; + world.cam.SetScale(Vector(32, 32)); + world.controlled.Resize(Vector(2, 3)); + world.controlled.Move(Vector(1, 1.5)); + + AABB e; + e.Resize(Vector(2, 2)); + e.Move(Vector(5, 5)); + world.stationary.push_back(e); + e.Move(Vector(0, 2)); + world.stationary.push_back(e); + e.Move(Vector(-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(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 index 0000000..5c519a4 --- /dev/null +++ b/src/graphics/Camera.cpp @@ -0,0 +1,19 @@ +#include "Camera.h" + + +namespace orbi { + +Camera::Camera(Vector s, const Vector &t) +: target(&t) +, size(s) +, offset(size / 2) { + +} + + +void Camera::Resize(Vector s) { + size = s; + offset = size / 2; +} + +} diff --git a/src/graphics/Camera.h b/src/graphics/Camera.h new file mode 100644 index 0000000..7e8ba2f --- /dev/null +++ b/src/graphics/Camera.h @@ -0,0 +1,52 @@ +#ifndef ORBI_CAMERA_H_ +#define ORBI_CAMERA_H_ + +#include "Vector.h" + + +namespace orbi { + +class Camera { + +public: + Camera(Vector size, const Vector &); + +public: + void SetTarget(const Vector &t) { target = &t; } + void SetScale(Vector s) { scale = s; } + + Vector ScreenSize() const { return size; } + + void Resize(int w, int h) { Resize(Vector(w, h)); } + void Resize(Vector); + void Update(float deltaT) { } // unused + + /// transform v from world coords to screen coords + Vector ToScreen(Vector v) const { + return ToScale(v - *target) + offset; + } + /// scale v from world to screen + Vector ToScale(Vector v) const { + return Vector(v * scale); + } + + /// transform v from screen coords to world coords + Vector FromScreen(Vector v) const { + return FromScale(v - offset) + *target; + } + /// scale v from screen to world + Vector FromScale(Vector v) const { + return Vector(v) / scale; + } + +private: + const Vector *target; + Vector scale; + Vector size; + Vector offset; + +}; + +} + +#endif diff --git a/src/graphics/Canvas.cpp b/src/graphics/Canvas.cpp new file mode 100644 index 0000000..3f1c3d1 --- /dev/null +++ b/src/graphics/Canvas.cpp @@ -0,0 +1,264 @@ +#include "Canvas.h" + +#include +#include +#include +#include + +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 Canvas::Size() const { + assert(canv); + Vector size; + SDL_GetRendererOutputSize(canv, &size.x, &size.y); + return size; +} + + +Texture Canvas::CreateStaticTexture(Vector 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 to) { + tex.Copy(canv, to); +} + +void Canvas::Copy(Texture &tex, Rect clip, Vector 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 from, Vector to) { + SDL_RenderDrawLine(canv, from.x, from.y, to.x, to.y); +} + +void Canvas::FillRect(Vector pos, Vector 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 pos, Vector 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 pos) { + SDL_RenderDrawPoint(canv, pos.x, pos.y); +} + +void Canvas::Cross(Vector pos, int extent) { + Line( + Vector(pos.x - extent, pos.y), + Vector(pos.x + extent, pos.y)); + Line( + Vector(pos.x, pos.y - extent), + Vector(pos.x, pos.y + extent)); +} + +void Canvas::Arrow(Vector from, Vector to) { + Line(from, to); + Vector delta(to - from); + delta = delta / Length(delta); + + Line(to, to + Vector(Rotate90(delta) * 5.0f - (delta * 5.0f))); + Line(to, to + Vector(Rotate270(delta) * 5.0f - (delta * 5.0f))); +} + +void Canvas::Triangle(Vector v1, Vector v2, Vector v3) { + SDL_Point points[4] = { v1, v2, v3, v1 }; + SDL_RenderDrawLines(canv, points, 4); +} + +void Canvas::Quad(Vector v1, Vector v2, Vector v3, Vector v4) { + SDL_Point points[5] = { v1, v2, v3, v4, v1 }; + SDL_RenderDrawLines(canv, points, 5); +} + + +namespace { + +template +void GridImpl( + Canvas &canv, + Vector pos, + Vector size, + Vector step) { + Vector from(pos); + Vector to(pos + size); + Vector 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 pos(from); pos.x <= to.x; pos.x += step.x) { + canv.Line(pos, Vector(pos.x, pos.y + height)); + } + for (Vector pos(from); pos.y <= to.y; pos.y += step.y) { + canv.Line(pos, Vector(pos.x + width, pos.y)); + } +} + +template +void Grid2Impl( + Canvas &canv, + Vector pos, + Vector size, + Vector step, + Vector n, + Color c1, + Color c2) { + Vector from(pos); + Vector to(pos + size); + Vector 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(n)); + return; + } + + Vector 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 pos(from); pos.x <= to.x; pos.x += step.x) { + canv.SetColor((i.x++ % n.x) ? c1 : c2); + canv.Line(pos, Vector(pos.x, pos.y + height)); + } + for (Vector pos(from); pos.y <= to.y; pos.y += step.y) { + canv.SetColor((i.y++ % n.y) ? c1 : c2); + canv.Line(pos, Vector(pos.x + width, pos.y)); + } +} + +} + +void Canvas::Grid(Vector pos, Vector size, Vector step) { + GridImpl(*this, pos, size, step); +} + +void Canvas::Grid(Vector pos, Vector size, Vector step) { + GridImpl(*this, pos, size, step); +} + +void Canvas::Grid2( + Vector pos, Vector size, Vector step, + Vector n, Color c1, Color c2) { + Grid2Impl(*this, pos, size, step, n, c1, c2); +} + +void Canvas::Grid2( + Vector pos, Vector size, Vector step, + Vector 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 index 0000000..f399b36 --- /dev/null +++ b/src/graphics/Canvas.h @@ -0,0 +1,67 @@ +#ifndef ORBI_CANVAS_H_ +#define ORBI_CANVAS_H_ + +#include "Color.h" +#include "Texture.h" +#include "Vector.h" + +#include + + +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 Size() const; + + void Present(); + + Texture CreateStaticTexture(Vector size); + Texture LoadTexture(const char *file); + + void Copy(Texture &, Vector to); + void Copy(Texture &, Rect clip, Vector to); + + void SetColor(Color); + + void Fill(); + void Outline(); + + void Line(Vector from, Vector to); + void FillRect(Vector pos, Vector size); + void OutlineRect(Vector pos, Vector size); + + void Dot(Vector pos); + void Cross(Vector pos, int extent); + void Arrow(Vector from, Vector to); + void Triangle(Vector, Vector, Vector); + void Quad(Vector, Vector, Vector, Vector); + + void Grid(Vector pos, Vector size, Vector step); + void Grid(Vector pos, Vector size, Vector step); + + void Grid2(Vector pos, Vector size, Vector step, + Vector n, Color, Color); + void Grid2(Vector pos, Vector size, Vector step, + Vector n, Color, Color); + +private: + SDL_Renderer *canv; + +}; + +} + +#endif diff --git a/src/graphics/Color.h b/src/graphics/Color.h new file mode 100644 index 0000000..8cc7bf2 --- /dev/null +++ b/src/graphics/Color.h @@ -0,0 +1,25 @@ +#ifndef ORBI_COLOR_H_ +#define ORBI_COLOR_H_ + +#include + + +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 index 0000000..b99dc11 --- /dev/null +++ b/src/graphics/Moveable.h @@ -0,0 +1,44 @@ +#ifndef ORBI_MOVEABLE_H_ +#define ORBI_MOVEABLE_H_ + +#include "Vector.h" + + +namespace orbi { + +template +class Moveable { + +public: + Moveable(Vector pos, Scalar speed) + : dir(0, 0), pos(pos), speed(speed) { } + +public: + const Vector &Pos() const { return pos; } + Vector Vel() const { return Vector(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 dir; + Vector pos; + Scalar speed; + +}; + +} + +#endif diff --git a/src/graphics/Rect.h b/src/graphics/Rect.h new file mode 100644 index 0000000..4601253 --- /dev/null +++ b/src/graphics/Rect.h @@ -0,0 +1,50 @@ +#ifndef ORBI_RECT_H_ +#define ORBI_RECT_H_ + +#include "Vector.h" + +#include + + +namespace orbi { + +template +class Rect { + +public: + constexpr Rect() : x(0), y(0), w(0), h(0) { } + constexpr Rect(Vector pos, Vector size) + : x(pos.x), y(pos.y), w(size.x), h(size.y) { } + +public: + constexpr Vector Pos() const { return Vector(x, y); } + constexpr Vector Size() const { return Vector(w, h); } + +public: + Scalar x; + Scalar y; + Scalar w; + Scalar h; + +}; + + +/// specialization with same layout as SDL_Rect +template<> +class Rect +: public SDL_Rect { + +public: + constexpr Rect() : SDL_Rect({0, 0, 0, 0}) { } + constexpr Rect(Vector pos, Vector size) + : SDL_Rect({pos.x, pos.y, size.x, size.y}) { } + +public: + constexpr Vector Pos() const { return Vector(x, y); } + constexpr Vector Size() const { return Vector(w, h); } + +}; + +} + +#endif diff --git a/src/graphics/Sprite.cpp b/src/graphics/Sprite.cpp new file mode 100644 index 0000000..137b75e --- /dev/null +++ b/src/graphics/Sprite.cpp @@ -0,0 +1,19 @@ +#include "Sprite.h" + +#include "Canvas.h" + + +namespace orbi { + +Sprite::Sprite(Texture &&tex, Vector size) +: tex(std::move(tex)) +, size(size) { + +} + + +void Sprite::Draw(Vector index, Canvas &canv, Vector at) { + canv.Copy(tex, Rect(index * size, size), at); +} + +} diff --git a/src/graphics/Sprite.h b/src/graphics/Sprite.h new file mode 100644 index 0000000..3f8f1bb --- /dev/null +++ b/src/graphics/Sprite.h @@ -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 size); + +public: + Vector Size() const { return size; } + + void Draw(Vector index, Canvas &to, Vector at); + +private: + Texture tex; + Vector size; + +}; + +} + +#endif diff --git a/src/graphics/Texture.cpp b/src/graphics/Texture.cpp new file mode 100644 index 0000000..f05f4b6 --- /dev/null +++ b/src/graphics/Texture.cpp @@ -0,0 +1,92 @@ +#include "Texture.h" + +#include + + +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 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 clip) { + SDL_RenderCopy(canv, tex, &clip, nullptr); +} + +void Texture::Copy(SDL_Renderer *canv, Vector to) { + Copy(canv, Rect(to, Size())); +} + +void Texture::Copy(SDL_Renderer *canv, Rect to) { + SDL_RenderCopy(canv, tex, nullptr, &to); +} + +void Texture::Copy(SDL_Renderer *canv, Rect clip, Vector to) { + Copy(canv, clip, Rect(to, clip.Size())); +} + +void Texture::Copy(SDL_Renderer *canv, Rect clip, Rect to) { + SDL_RenderCopy(canv, tex, &clip, &to); +} + +} diff --git a/src/graphics/Texture.h b/src/graphics/Texture.h new file mode 100644 index 0000000..e3fe38a --- /dev/null +++ b/src/graphics/Texture.h @@ -0,0 +1,70 @@ +#ifndef ORBI_TEXTURE_H_ +#define ORBI_TEXTURE_H_ + +#include "Color.h" +#include "Rect.h" +#include "Vector.h" + +#include +#include + + +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 size); + Texture(SDL_Renderer *, const char *file); + + void Swap(Texture &); + +public: + Vector 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 clip); + /// copy entire texture as is to given coordinates + void Copy(SDL_Renderer *, Vector to); + /// copy entire texture stretched to given rect + void Copy(SDL_Renderer *, Rect to); + /// copy given clip to given coordinates + void Copy(SDL_Renderer *, Rect, Vector to); + /// copy given clip stretched to given rect + void Copy(SDL_Renderer *, Rect clip, Rect 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 size; + +}; + +} + + +namespace std { + +template<> +inline void swap(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 index 0000000..c355091 --- /dev/null +++ b/src/graphics/Vector.h @@ -0,0 +1,251 @@ +#ifndef ORBI_VECTOR_H_ +#define ORBI_VECTOR_H_ + +#include +#include +#include +#include +#include + + +namespace orbi { + +template +class Vector { + +public: + constexpr Vector() : x(0), y(0) { } + constexpr Vector(Scalar x, Scalar y) : x(x), y(y) { } + + template + constexpr Vector(Vector other) : x(other.x), y(other.y) { } + + static Vector FromPolar(Scalar rad, Scalar az) { + return Vector(rad * std::cos(az), rad * std::sin(az)); + } + + static constexpr Vector unit45 = Vector(-0.7071, 0.7071); + +public: + Vector &operator +=(Vector other) { + x += other.x; + y += other.y; + return *this; + } + Vector &operator -=(Vector other) { + x -= other.x; + y -= other.y; + return *this; + } + Vector &operator *=(Scalar factor) { + x *= factor; + y *= factor; + return *this; + } + Vector &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 +constexpr Vector Vector::unit45; + +/// specialization with same layout as SDL_Point +template<> +class Vector +: public SDL_Point { + +public: + constexpr Vector() : SDL_Point({0, 0}) { } + constexpr Vector(int x, int y) : SDL_Point({x, y}) { } + + template + constexpr Vector(Vector other) + : SDL_Point({int(other.x), int(other.y)}) { } + +public: + Vector &operator +=(Vector other) { + x += other.x; + y += other.y; + return *this; + } + Vector &operator -=(Vector other) { + x -= other.x; + y -= other.y; + return *this; + } + + SDL_Point ToPoint() const { + return *this; + } + +}; + + +template +constexpr Vector operator -(Vector v) { + return Vector(-v.x, -v.y); +} + + +template +constexpr Vector operator +(Vector lhs, Vector rhs) { + return Vector(lhs.x + rhs.x, lhs.y + rhs.y); +} + +template +constexpr Vector operator -(Vector lhs, Vector rhs) { + return Vector(lhs.x - rhs.x, lhs.y - rhs.y); +} + + +template +constexpr Vector operator *(Vector lhs, Scalar rhs) { + return Vector(lhs.x * rhs, lhs.y * rhs); +} + +template +constexpr Vector operator *(Scalar lhs, Vector rhs) { + return rhs * lhs; +} +template +constexpr Vector operator *(Vector lhs, Vector rhs) { + return Vector(lhs.x * rhs.x, lhs.y * rhs.y); +} + + +template +constexpr Vector operator /(Vector lhs, Scalar rhs) { + return Vector(lhs.x / rhs, lhs.y / rhs); +} + +template +constexpr Vector operator /(Scalar lhs, Vector rhs) { + return rhs / lhs; +} +template +constexpr Vector operator /(Vector lhs, Vector rhs) { + return Vector(lhs.x / rhs.x, lhs.y / rhs.y); +} + + +template +constexpr bool operator ==(Vector lhs, Vector rhs) { + return lhs.x == rhs.x && lhs.y == rhs.y; +} +template +constexpr bool operator !=(Vector lhs, Vector rhs) { + return lhs.x != rhs.x && lhs.y != rhs.y; +} + + +template +constexpr bool IsZero(Vector v) { + return std::abs(v.x) < std::numeric_limits::epsilon() + && std::abs(v.y) < std::numeric_limits::epsilon(); +} + +template +constexpr Vector abs(Vector v) { + return Vector(std::abs(v.x), std::abs(v.y)); +} + + +template +constexpr Scalar Cross2D(Vector lhs, Vector rhs) { + return (lhs.x * rhs.y) - (lhs.y * rhs.x); +} +template +constexpr Scalar Dot(Vector lhs, Vector rhs) { + return (lhs.x * rhs.x) + (lhs.y * rhs.y); +} +template +constexpr Scalar Length(Vector v) { + return std::sqrt(Dot(v, v)); +} +template +constexpr Vector Norm(Vector v) { + return v / Length(v); +} + +template +constexpr Vector Rotate90(Vector v) { + return Vector(-v.y, v.x); +} +template +constexpr Vector Rotate180(Vector v) { + return -v; +} +template +constexpr Vector Rotate270(Vector v) { + return Vector(v.y, -v.x); +} +template +inline Vector Rotate(Vector v, Float by) { + Float sine(std::sin(by)); + Float cosine(std::cos(by)); + return Vector(v.x * cosine - v.y * sine, v.x * sine + v.y * cosine); +} + +/// reflect v along normalized n +template +inline Vector Reflect(Vector v, Vector n) { + const Scalar dd = Scalar(2) * Dot(v, n); + return Vector(v.x - (dd * n.x), v.y - (dd * n.y)); +} +/// project v onto normalized n +template +inline Vector Project(Vector v, Vector n) { + return Dot(v, n) * n; +} + + +template +inline std::ostream &operator <<(std::ostream &out, Vector v) { + return out << '<' << v.x << ',' << v.y << '>'; +} + +} + + +namespace std { + +template +constexpr orbi::Vector min( + orbi::Vector lhs, + orbi::Vector rhs +) { + return orbi::Vector( + min(lhs.x, rhs.x), + min(lhs.y, rhs.y) + ); +} + +template +constexpr orbi::Vector max( + orbi::Vector lhs, + orbi::Vector rhs +) { + return orbi::Vector( + 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 index 0000000..5aa76bb --- /dev/null +++ b/src/graphics/Window.cpp @@ -0,0 +1,58 @@ +#include "Window.h" + +#include +#include +#include +#include + +using std::runtime_error; + + +namespace orbi { + +const Vector Window::POS_CENTER( + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); +const Vector Window::POS_UNDEF( + SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED); + + +Window::Window( + const char *title, + Vector pos, + Vector 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 Window::Size() const { + assert(win); + Vector 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 index 0000000..23d44ca --- /dev/null +++ b/src/graphics/Window.h @@ -0,0 +1,41 @@ +#ifndef ORBI_WINDOW_H_ +#define ORBI_WINDOW_H + +#include "Canvas.h" +#include "Vector.h" + +#include + + +namespace orbi { + +class Window { + +public: + static const Vector POS_CENTER; + static const Vector POS_UNDEF; + +public: + Window() : win(nullptr) { } + Window(const char *title, Vector pos, Vector size, Uint32 flags); + ~Window(); + + Window(Window &&); + Window &operator =(Window &&); + + Window(const Window &) = delete; + Window &operator =(const Window &) = delete; + +public: + Vector 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 index 0000000..eaddf65 --- /dev/null +++ b/src/graphics/const.h @@ -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 +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 index 0000000..f410a40 --- /dev/null +++ b/src/orbi.cpp @@ -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 +#include + +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(800, 600), + SDL_WINDOW_RESIZABLE + ); + Canvas canv(win.CreateCanvas( + 0 + )); + + Tileset tiles( + canv.LoadTexture("/home/holy/projects/orbi/data/test-tile.png"), + Vector(32, 32) + ); + + World world(Vector(10, 10)); + world.SetTile(Vector(5, 5), Tile(0)); + world.SetTile(Vector(5, 6), Tile(0)); + world.SetTile(Vector(6, 5), Tile(0)); + world.SetTile(Vector(6, 6), Tile(0)); + world.SetTile(Vector(6, 7), Tile(0)); + world.SetTile(Vector(7, 4), Tile(0)); + world.SetTile(Vector(7, 5), Tile(0)); + world.SetTile(Vector(8, 5), Tile(0)); + world.SetTile(Vector(9, 4), Tile(0)); + world.SetTile(Vector(9, 5), Tile(0)); + world.SetTile(Vector(9, 6), Tile(0)); + + Entity e; + e.Bounds() = AABB(Vector(5, 2), Vector(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 index 0000000..c54537e --- /dev/null +++ b/src/world/AABB.cpp @@ -0,0 +1,42 @@ +#include "AABB.h" + +namespace orbi { + +bool AABB::Intersects(const AABB &other, Vector &p, Vector &n, Vector &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 sdiff = diff.Size(); + + if (sdiff.x < sdiff.y) { + if (Center().x < other.Center().x) { + p = Vector(Right(), ((Top() + Bottom()) / 2 + (other.Top() + other.Bottom()) / 2) / 2); + n = Vector(-1, 0); + d = Vector(other.Left() - Right(), 0); + } else { + p = Vector(Left(), ((Top() + Bottom()) / 2 + (other.Top() + other.Bottom()) / 2) / 2); + n = Vector(1, 0); + d = Vector(other.Right() - Left(), 0); + } + } else { + if (Center().y < other.Center().y) { + p = Vector(((Left() + Right()) / 2 + (other.Left() + other.Right()) / 2) / 2, Bottom()); + n = Vector(0, -1); + d = Vector(0, other.Top() - Bottom()); + } else { + p = Vector(((Left() + Right()) / 2 + (other.Left() + other.Right()) / 2) / 2, Top()); + n = Vector(0, 1); + d = Vector(0, other.Bottom() - Top()); + } + } + return true; +} + +} diff --git a/src/world/AABB.h b/src/world/AABB.h new file mode 100644 index 0000000..20580ca --- /dev/null +++ b/src/world/AABB.h @@ -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 pos, Vector 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 Position() const { return lt; } + Vector Center() const { return lt + HalfSize(); } + Vector HalfSize() const { return Size() / 2.0f; } + Vector Size() const { return rb - lt; } + +public: + void Move(Vector delta) { lt += delta; rb += delta; } + void Resize(Vector size) { *this = AABB(lt, size); } + + bool Intersects(const AABB &other, Vector &p, Vector &n, Vector &d) const; + +private: + Vector lt; + Vector rb; + +}; + +} + +#endif diff --git a/src/world/Entity.cpp b/src/world/Entity.cpp new file mode 100644 index 0000000..86d3a45 --- /dev/null +++ b/src/world/Entity.cpp @@ -0,0 +1,13 @@ +#include "Entity.h" + +namespace orbi { + +void Entity::Update(float dt, Vector extAcc, Vector tv) { + const Vector 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 index 0000000..f9671ca --- /dev/null +++ b/src/world/Entity.h @@ -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 extAcc, Vector tv); + void Move(Vector delta) { bounds.Move(delta); } + +public: + AABB &Bounds() { return bounds; } + const AABB &Bounds() const { return bounds; } + +private: + AABB bounds; + Vector vel; + Vector 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 index 0000000..1a86db6 --- /dev/null +++ b/src/world/Tile.h @@ -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 index 0000000..823e76e --- /dev/null +++ b/src/world/TileClass.h @@ -0,0 +1,20 @@ +#ifndef ORBI_TILECLASS_H_ +#define ORBI_TILECLASS_H_ + + +namespace orbi { + +class TileClass { + +public: + explicit TileClass(Vector sb) + : spriteBegin(sb) { } + +private: + Vector spriteBegin; + +}; + +} + +#endif diff --git a/src/world/Tileset.cpp b/src/world/Tileset.cpp new file mode 100644 index 0000000..880e1a0 --- /dev/null +++ b/src/world/Tileset.cpp @@ -0,0 +1,53 @@ +#include "Tileset.h" + +#include "Tile.h" +#include "World.h" +#include "../graphics/Canvas.h" + + +namespace orbi { + +Tileset::Tileset(Texture &&tex, Vector size) +: sprite(std::move(tex), size / 2) +, tileSize(size) +, subSize(size / 2) +, subCount(8, 4) { + +} + + +void Tileset::DrawFG(Canvas &canv, Vector to, const World &world, Vector at) { + const Tile &mid = world.TileAt(at); + if (!mid.HasFG()) return; + + const Vector ul = at + Vector(-1, -1); + const Vector u = at + Vector(0, -1); + const Vector ur = at + Vector(1, -1); + const Vector l = at + Vector(-1, 0); + const Vector r = at + Vector(1, 0); + const Vector dl = at + Vector(-1, 1); + const Vector d = at + Vector(0, 1); + const Vector dr = at + Vector(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 off = Vector(0, mid.GetFG() * subCount.y); + sprite.Draw(Vector(off.x + tl, off.y), canv, to); + sprite.Draw(Vector(off.x + tr, off.y + 1), canv, Vector(to.x + subSize.x, to.y)); + sprite.Draw(Vector(off.x + bl, off.y + 2), canv, Vector(to.x, to.y + subSize.y)); + sprite.Draw(Vector(off.x + br, off.y + 3), canv, Vector(to.x + subSize.x, to.y + subSize.y)); +} + +} diff --git a/src/world/Tileset.h b/src/world/Tileset.h new file mode 100644 index 0000000..b6d1579 --- /dev/null +++ b/src/world/Tileset.h @@ -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 size); + + Vector TileSize() const { return tileSize; } + + void DrawFG(Canvas &, Vector to, const World &, Vector at); + +private: + Sprite sprite; + Vector tileSize; + Vector subSize; + Vector subCount; + +}; + +} + +#endif diff --git a/src/world/World.cpp b/src/world/World.cpp new file mode 100644 index 0000000..e390914 --- /dev/null +++ b/src/world/World.cpp @@ -0,0 +1,88 @@ +#include "World.h" + +#include "../graphics/const.h" + + +namespace orbi { + +World::World(Vector 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(0, -b.Top())); + if (b.Right() > size.x) e.Move(Vector(size.x - b.Right(), 0)); + if (b.Bottom() > size.y) e.Move(Vector(0, size.y - b.Bottom())); + if (b.Left() < 0) e.Move(Vector(-b.Left(), 0)); + + const Vector cBegin(b.Left(), b.Top()); + const Vector cEnd(b.Right(), b.Bottom()); + + Vector topResponse; + for (Vector pos(cBegin); pos.x < cEnd.x; ++pos.x) { + if (TileAt(pos).IsSolid()) { + topResponse = Vector(0, pos.y + 1 - b.Top()); + break; + } + } + Vector bottomResponse; + for (Vector pos(cBegin.x, cEnd.y); pos.x < cEnd.x; ++pos.x) { + if (TileAt(pos).IsSolid()) { + bottomResponse = Vector(0, pos.y - b.Bottom()); + break; + } + } + if (!IsZero(topResponse)) { + if (IsZero(bottomResponse)) { + e.Move(topResponse); + } + } else if (!IsZero(bottomResponse)) { + e.Move(bottomResponse); + } + + Vector leftResponse; + for (Vector pos(cBegin); pos.y < cEnd.y; ++pos.y) { + if (TileAt(pos).IsSolid()) { + leftResponse = Vector(pos.x + 1 - b.Left(), 0); + break; + } + } + Vector rightResponse; + for (Vector pos(cEnd.x, cBegin.y); pos.y < cEnd.y; ++pos.y) { + if (TileAt(pos).IsSolid()) { + rightResponse = Vector(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 index 0000000..3483f66 --- /dev/null +++ b/src/world/World.h @@ -0,0 +1,51 @@ +#ifndef ORBI_WORLD_H_ +#define ORBI_WORLD_H_ + +#include "Entity.h" +#include "Tile.h" +#include "../graphics/Vector.h" + +#include +#include + + +namespace orbi { + +class World { + +public: + World(Vector size); + +public: + Vector Size() const { return size; } + +public: + void Update(int dt); + + bool InBounds(Vector pos) const + { return pos.x > 0 && pos.y > 0 && pos.x < size.x && pos.y < size.y; } + int Index(Vector pos) const { return pos.y * size.x + pos.x; } + + Tile &TileAt(Vector pos) { return tiles[Index(pos)]; } + const Tile &TileAt(Vector pos) const { return tiles[Index(pos)]; } + void SetTile(Vector pos, const Tile &t) { tiles[Index(pos)] = t; } + + const std::list &Entities() const { return entities; } + Entity &AddEntity(const Entity &); + +private: + Vector size; + int count; + + Vector gravity; + Vector terminal; + + std::vector tiles; + + std::list entities; + +}; + +} + +#endif diff --git a/tests/test-all.cpp b/tests/test-all.cpp new file mode 100644 index 0000000..9539c4a --- /dev/null +++ b/tests/test-all.cpp @@ -0,0 +1,3 @@ +int main(int argc, const char *argv[]) { + return 0; +} -- 2.39.2