From c8bc86cab729a26cc7563c7177c42f919b47df07 Mon Sep 17 00:00:00 2001 From: frank <420@shampoo.ooo> Date: Tue, 31 Aug 2021 23:55:38 -0400 Subject: [PATCH] squircle and 2d collision demo; config auto refresh --- .gitignore | 4 +- README | 162 ++++++++++------ demo/2d_collision/BPmono.ttf | 1 + demo/2d_collision/CollisionTest.cpp | 173 +++++++++++++++++ demo/2d_collision/CollisionTest.hpp | 42 ++++ demo/2d_collision/Makefile | 101 ++++++++++ demo/2d_collision/config.json | 8 + demo/2d_collision/wooper.png | Bin 0 -> 425 bytes demo/Makefile | 120 ------------ demo/cube/BPmono.ttf | 1 + demo/{Demo.cpp => cube/Cube.cpp} | 112 +++-------- demo/{Demo.hpp => cube/Cube.hpp} | 15 +- demo/cube/Makefile | 175 +++++++++++++++++ demo/{ => cube}/config.json | 6 +- demo/{ => cube}/resource/Ag.ogg | Bin demo/{ => cube}/resource/Field.mp3 | Bin demo/{ => cube}/resource/Field.png | Bin demo/{ => cube}/resource/background.png | Bin demo/{ => cube}/resource/shrooms/anxiety.png | Bin .../resource/shrooms/lapis-lazuli.png | Bin .../{ => cube}/resource/shrooms/mr-plummy.png | Bin .../resource/shrooms/poison-shibake.png | Bin .../resource/shrooms/shaver-buzz.png | Bin demo/{ => cube}/resource/shrooms/sombrero.png | Bin .../resource/shrooms/spider-house.png | Bin .../resource/shrooms/teethmouth.png | Bin .../resource/shrooms/terraformer.png | Bin demo/{ => cube}/resource/tile.png | Bin demo/{ => cube}/shaders/flat.frag | 0 demo/{ => cube}/shaders/flat.vert | 0 demo/{ => cube}/shaders/triangle.frag | 0 demo/{ => cube}/shaders/triangle.vert | 0 demo/resource/SourceCodePro-Regular.otf | Bin 81384 -> 0 bytes demo/squircle/BPmono.ttf | 1 + demo/squircle/Makefile | 101 ++++++++++ demo/squircle/Squircle.cpp | 182 ++++++++++++++++++ demo/squircle/Squircle.hpp | 47 +++++ demo/squircle/config.json | 27 +++ demo/squircle/flat.frag | 51 +++++ demo/squircle/flat.vert | 21 ++ demo/squircle/images/2Ddim-L1norm-10site.png | Bin 0 -> 9956 bytes src/Animation.cpp | 2 +- src/Box.cpp | 7 - src/Box.hpp | 1 - src/Configuration.cpp | 96 ++++++--- src/Configuration.hpp | 30 ++- src/Game.cpp | 10 +- src/Game.hpp | 8 +- src/Segment.cpp | 2 +- src/Segment.hpp | 2 +- src/extension.cpp | 1 + src/extension.hpp | 1 - 52 files changed, 1177 insertions(+), 333 deletions(-) create mode 120000 demo/2d_collision/BPmono.ttf create mode 100644 demo/2d_collision/CollisionTest.cpp create mode 100644 demo/2d_collision/CollisionTest.hpp create mode 100644 demo/2d_collision/Makefile create mode 100644 demo/2d_collision/config.json create mode 100644 demo/2d_collision/wooper.png delete mode 100644 demo/Makefile create mode 120000 demo/cube/BPmono.ttf rename demo/{Demo.cpp => cube/Cube.cpp} (81%) rename demo/{Demo.hpp => cube/Cube.hpp} (82%) create mode 100644 demo/cube/Makefile rename demo/{ => cube}/config.json (78%) rename demo/{ => cube}/resource/Ag.ogg (100%) rename demo/{ => cube}/resource/Field.mp3 (100%) rename demo/{ => cube}/resource/Field.png (100%) rename demo/{ => cube}/resource/background.png (100%) rename demo/{ => cube}/resource/shrooms/anxiety.png (100%) rename demo/{ => cube}/resource/shrooms/lapis-lazuli.png (100%) rename demo/{ => cube}/resource/shrooms/mr-plummy.png (100%) rename demo/{ => cube}/resource/shrooms/poison-shibake.png (100%) rename demo/{ => cube}/resource/shrooms/shaver-buzz.png (100%) rename demo/{ => cube}/resource/shrooms/sombrero.png (100%) rename demo/{ => cube}/resource/shrooms/spider-house.png (100%) rename demo/{ => cube}/resource/shrooms/teethmouth.png (100%) rename demo/{ => cube}/resource/shrooms/terraformer.png (100%) rename demo/{ => cube}/resource/tile.png (100%) rename demo/{ => cube}/shaders/flat.frag (100%) rename demo/{ => cube}/shaders/flat.vert (100%) rename demo/{ => cube}/shaders/triangle.frag (100%) rename demo/{ => cube}/shaders/triangle.vert (100%) delete mode 100755 demo/resource/SourceCodePro-Regular.otf create mode 120000 demo/squircle/BPmono.ttf create mode 100644 demo/squircle/Makefile create mode 100644 demo/squircle/Squircle.cpp create mode 100644 demo/squircle/Squircle.hpp create mode 100644 demo/squircle/config.json create mode 100644 demo/squircle/flat.frag create mode 100644 demo/squircle/flat.vert create mode 100755 demo/squircle/images/2Ddim-L1norm-10site.png diff --git a/.gitignore b/.gitignore index dd86103..12b1da2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ *.o local/ build/ -demo/demo +demo/2d_collision/2d_collision +demo/squircle/squircle +demo/cube/cube diff --git a/README b/README index f1d010c..fc4f2e8 100644 --- a/README +++ b/README @@ -1,26 +1,37 @@ -++~~~~~~~~~~~~~~~~~~~~~~~++ -++~~~~~~~~~~~~~~~~~~~~~~~++ -:: :: -:: SFW (SDL Framework) :: -:: :: -++~~~~~~~~~~~~~~~~~~~~~~~++ -++~~~~~~~~~~~~~~~~~~~~~~~++ + /\ +--------------------------------------------------------------+ + ____/ \____ /| - zlib/MIT/Unlicenced game framework licensed to freely use, | + \ / / | copy, modify and sell without restriction | + +--\ ^__^ /--+ | | + | ~/ \~ | | - originally created at [http://nugget.fun] | + | ~~~~~~~~~~~~ | +--------------------------------------------------------------+ + | SPACE ~~~~~ | / + | ~~~~~~~ BOX |/ + +--------------+ + +[SPACE BOX] is a C++ framework that facilitates the creation of SDL + OpenGL projects +through the use of generic objects that can be used and extended by the programmer. It +focuses on game projects but its usefulness is not limited to games. -SFW is a C++ framework that facilitates the creation of SDL projects. It -provides generic game objects that can be used, extended or overwritten. Games -written using this framework can be exported to PC, Mac, Linux, Web GL, -Raspberry Pi, and Android. +The main intention for the project is to make running cross-platform applications easier +and more immediate by providing a universal tool set that exists as a hidden, extensible +layer between SDL + OpenGL and the project. -It is in an early untested state. It comes with a simple program that -demonstrates how to use it for a project that can switch between SDL and OpenGL -contexts. +An important quality of the framework is it should allow the programmer to start a project +by extending only a single function, the Game class's update function. Additionally, the +framework should by default create applications that can be easily exported to many +platforms (PC, OS/X, Linux, Web GL, Raspberry Pi, and Android) by using a standard +Makefile. -Requirements -```````````` -The SFW source includes some external libraries in lib/ that the default -Makefile included with the demo shows how to compile, but there are other -libraries that must also be present in order to compile a project which uses the -framework +It is in an early, untested state. It comes with a few simple examples that demonstrate +how in can be used to create interactive graphics programs. + +################ +# Requirements # +################ + +The repository includes some external libraries in lib/ that the default Makefile included +with the demo shows how to compile, but there are other requirements, including external +libraries that must be linked to a project in order to compile it. * libSDL2 (developed against v2.0.14) * libSDL2-image @@ -29,22 +40,23 @@ framework * OpenGL/GLES/GLES2 * compiler that supports C++17 -Installing Requirements -``````````````````````` -libSDL2, libSDL2-image, libSDL2-ttf, and libSDL2-mixer must be available to -link with your project, so you can try your package manager's libSDL2 dev -packages or build from source. The included sdl2-config utility program can be -used to generate flags for linking to SDL2 when it is installed outside of -your platform's usual library location. +########################### +# Installing Requirements # +########################### -libSDL2: +libSDL2, libSDL2-image, libSDL2-ttf, and libSDL2-mixer must be available to link with your +project, so you can try your package manager's libSDL2 dev packages or build from source. +The included sdl2-config utility program can be used to generate flags for linking to SDL2 +when it is installed outside of your platform's usual library location. +libSDL2 +``````` - Download from http://libsdl.org/download-2.0.php - Run ./configure --prefix=[YOUR LIBRARIES PATH] (I'm using $HOME/local/sdl) - Run make && make install -libSDL2-image, libSDL2-ttf, libSDL2-mixer: - +libSDL2-image, libSDL2-ttf, libSDL2-mixer +````````````````````````````````````````` - Download from: - https://www.libsdl.org/projects/SDL_image/ - https://www.libsdl.org/projects/SDL_ttf/ @@ -52,48 +64,80 @@ libSDL2-image, libSDL2-ttf, libSDL2-mixer: - Run ./configure --prefix=[YOUR LIB PATH] --with-sdl-prefix=[YOUR SDL PATH] - In my case, prefix and SDL prefix are both $HOME/local/sdl -OpenGL/GLES/GLES2: +OpenGL/GLES/GLES2 +````````````````` +- Install GL/GLES according to your platform and link to it during compilation. GLEW is + included in the lib/ folder of this framework and should find GL on your platform if it + is installed. -- Install GL/GLES according to your platform and link to it during compilation. - GLEW is included in the lib/ folder of this framework and should find GL on - your platform if it is installed. +######### +# Demos # +######### -Demo +The `demo/` folder contains programs that demonstrate and test the capabilities of the +framework. In order to compile each, you should edit the definitions in the Makefile. + +cube ```` -The `demo/` folder contains a simple cube demo. The main purpose of the demo is -to demonstrate switching between 3D and 2D contexts. In order to compile the -demo, you will have to edit the paths in the Makefile to point to the locations -of the necessary libraries on your system. +Switch between GL and SDL contexts by pressing spacebar. The GL context draws a textured, +rotating cube, and the SDL context draws basic geometric shapes and a moving 2D sprite. -Other libraries -``````````````` -These are other libraries that have been used in projects that use this -framework but aren't required by the framework +2d_collision +```````````` +Test collision detection between a 2D sprite and other 2D sprites and boxes. Per-pixel +collision can be tested. -opencv: +squircle +```````` +Map an image from a rectangle to a circle or from a circle to a rectangle using a shader +program. Based on the elliptical grid mapping equations at: + - http://squircular.blogspot.com/2015/09/mapping-circle-to-square.html + +################### +# Other libraries # +################### + +These are other libraries that have been used in projects that use this framework but +aren't required by the framework + +opencv +`````` - Download from https://opencv.org/releases/ - configure (with custom installation path) cmake -DCMAKE_INSTALL_PREFIX=$HOME/local/opencv .. - make && make install -zbar: - +zbar +```` - Download from http://zbar.sourceforge.net/download.html - configure to only use image processing features (requires imagemagick) and choose your installation directory ./configure --without-gtk --without-python --without-qt --disable-video --prefix=$HOME/local/zbar - make && make install -License -``````` -The original code for this framework is licensed to freely use, copy, modify and -sell, without restriction under the zlib license. See LICENSE.txt for details. +######## +# Font # +######## + +When initializing a Game object, the framework will attempt to load the font file +"BPmono.ttf" from the project root (where the compiled executable is located). If this +file isn't found, the program can still run successfully, but the framerate indicator +(mapped to CTRL+f by default) will be disabled. The repository contains "BPmono.ttf", so +you can create a symlink to the file in the project root if you want to use the framerate +indicator. + +########### +# License # +########### + +The original code for this framework is licensed to freely use, copy, modify and sell, +without restriction under the zlib license. See LICENSE.txt for details. Included libraries are included under various permissive licenses: -- BPmono.ttf is licensed under the Creative Commons Attribution - No Derivative - Works 3.0 license. See LICENSE_BPmono.txt for details. +- BPmono.ttf is licensed under the Creative Commons Attribution - No Derivative Works 3.0 + license. See LICENSE_BPmono.txt for details. - gif-h is unlicensed, public domain code released under the The Unlicense. See lib/gif-h/LICENSE @@ -102,14 +146,14 @@ Included libraries are included under various permissive licenses: - GLM is included under the MIT license in lib/glm/LICENSE -- nlohmann's json library is included under the MIT license in - lib/json/LICENSE.MIT +- nlohmann's json library is included under the MIT license in lib/json/LICENSE.MIT - SDL2 GFX is included under a permissive license in lib/sdl2-gfx/LICENSE -- superxbr.cpp is included under a permissive license at the top of - lib/superxbr.cpp +- superxbr.cpp is included under the permissive license at the top of lib/superxbr.cpp -Business -```````` -420@shampoo.ooo +############ +# Business # +############ + +egg@shampoo.ooo diff --git a/demo/2d_collision/BPmono.ttf b/demo/2d_collision/BPmono.ttf new file mode 120000 index 0000000..80db3f8 --- /dev/null +++ b/demo/2d_collision/BPmono.ttf @@ -0,0 +1 @@ +../../BPmono.ttf \ No newline at end of file diff --git a/demo/2d_collision/CollisionTest.cpp b/demo/2d_collision/CollisionTest.cpp new file mode 100644 index 0000000..d84d847 --- /dev/null +++ b/demo/2d_collision/CollisionTest.cpp @@ -0,0 +1,173 @@ +#include "CollisionTest.hpp" + +CollisionTest::CollisionTest() : Game() +{ + get_delegate().subscribe(&CollisionTest::respond, this); + load_sdl_context(); + enemy.set_color_mod({255, 128, 128, 255}); + wooper.load(); + enemy.load(); + wooper.add_box({80, 200}); + enemy.add_box({90, 100}); + wooper.add_box({300, 10}); + enemy.add_box({90, 200}); + wooper.add_box({180, 20}); + enemy.add_box({180, 50}); + enemy.move({50, 50}); + Pixels wooper_pixels = Pixels(wooper); + for (int x = 0; x < wooper.get_w(); x++) + { + wooper_pixels.set(Color(128, 255, 64), x, 12); + } + wooper_pixels.apply(); + Box box = {{5, 10}, {20, 4}}; + Pixels enemy_pixels = Pixels(enemy, box); + for (int x = -1, y = -1; x > -box.width(); x--, y--) + { + enemy_pixels.set(Color(-1.3, 68.9, 800.8), x, y); + } + enemy_pixels.apply(); + canvas = SDL_CreateTexture(get_renderer(), SDL_PIXELFORMAT_RGBA4444, SDL_TEXTUREACCESS_STREAMING, 200, 100); + Pixels canvas_pixels = Pixels(get_renderer(), canvas); + for (int x = 0, y = 0; x < 288; x++, y += x) + { + canvas_pixels.set(Color(x, y, x + y), x, y); + } + canvas_pixels.apply(); + Box subsection = Box({5, 10}, {60, 20}); + Pixels sub_canvas_pixels = Pixels(get_renderer(), canvas, subsection); + for (int x = 0; x < subsection.width(); x++) + { + for (int y = 0; y < subsection.height(); y++) + { + // *canvas_pixels.operator()(x, y) = 0xfb60; + *canvas_pixels.operator()(x, y) = Color(255, 200, 100); + } + } + SDL_Color color = {0, 1, 2, 3}; + std::cout << std::boolalpha << (Color(255, 255, 255) == Color(255, 255, 255)) << " " << + (Color(122.1, 853.8, -1.3) == Color(122, 86, 255)) << " " << (Color(1, 2, 3, 4) == ((SDL_Color){1, 2, 3, 4})) << " " << + (Color(256, 257, 258, 259) != color) << std::endl; + sub_canvas_pixels.apply(); + test_crop( + { + Box({-5, -2}, {10, 5}), + Box({window_box().right() - 30, 10}, {100, 1000}), + Box(window_box().se() - glm::vec2(10, 15), {789, 123}), + Box(window_box().sw() - glm::vec2(1, 1), {5, 5}) + }); + // std::cout << "out of bounds pixel is " << screen_subsection_pixels.get(0, 0) << + // " out of bounds pixel is " << screen_subsection_pixels.get(-1, 0) << + // " in bounds pixel is " << screen_subsection_pixels.get(0, -1) << + // " out of bounds pixel is " << screen_subsection_pixels.get(-1, -1) << std::endl; + // screen_subsection_pixels.set(Color(255, 0, 0), 0, -1); + // screen_subsection_pixels.set(Color(255, 255, 255), 0, 0); + // screen_subsection_pixels.apply(); +} + +void CollisionTest::test_crop(const std::vector& boxes) +{ + for (const Box& box : boxes) + { + Pixels screen_subsection_pixels = Pixels(get_renderer(), nullptr, box); + std::cout << box << " cropped by screen to " << screen_subsection_pixels.rect << std::endl; + } +} + +void CollisionTest::respond(SDL_Event& event) +{ + if (get_delegate().compare(event, "up")) + { + wooper.move_weighted({0, -2}); + } + else if (get_delegate().compare(event, "left")) + { + wooper.move_weighted({-2, 0}); + } + else if (get_delegate().compare(event, "right")) + { + wooper.move_weighted({2, 0}); + } + else if (get_delegate().compare(event, "down")) + { + wooper.move_weighted({0, 2}); + } + else if (get_delegate().compare(event, "toggle-collide-all")) + { + collide_all = !collide_all; + } + else if (get_delegate().compare(event, "toggle-collide-all-other")) + { + collide_all_other = !collide_all_other; + } + else if (get_delegate().compare(event, "toggle-precise-collision")) + { + precise = !precise; + } +} + +void CollisionTest::update() +{ + SDL_SetRenderTarget(get_renderer(), NULL); + SDL_SetRenderDrawColor(get_renderer(), 64, 128, 64, 255); + SDL_RenderClear(get_renderer()); + wooper.update(); + enemy.update(); + SDL_Color white = {255, 255, 255, 255}, red = {255, 0, 0, 255}, color; + Box overlap; + SDL_Rect rect; + for (const Box& box : boxes) + { + color = wooper.collide(box, overlap, precise, collide_all) ? red : white; + SDL_SetRenderTarget(get_renderer(), NULL); + SDL_SetRenderDrawColor(get_renderer(), color.r, color.g, color.b, color.a); + rect = box; + SDL_RenderDrawRect(get_renderer(), &rect); + rect = overlap; + SDL_SetRenderDrawColor(get_renderer(), 255, 255, 0, 255); + SDL_RenderDrawRect(get_renderer(), &rect); + } + glm::vec2 intersection; + for (const Segment& segment : segments) + { + color = wooper.collide(segment, intersection, collide_all) ? red : white; + glm::vec2 start = segment.start(), end = segment.end(); + SDL_SetRenderDrawColor(get_renderer(), color.r, color.g, color.b, color.a); + SDL_RenderDrawLine(get_renderer(), start.x, start.y, end.x, end.y); + SDL_SetRenderDrawColor(get_renderer(), 255, 255, 0, 255); + SDL_RenderDrawPoint(get_renderer(), intersection.x, intersection.y); + } + for (const glm::vec2& point : points) + { + color = wooper.collide(point, collide_all) ? red : white; + SDL_SetRenderDrawColor(get_renderer(), color.r, color.g, color.b, color.a); + SDL_RenderDrawPoint(get_renderer(), point.x, point.y); + } + if (wooper.collide(enemy, overlap, precise, collide_all, collide_all_other)) + { + SDL_SetRenderTarget(get_renderer(), nullptr); + SDL_SetRenderDrawColor(get_renderer(), 0, 255, 0, 255); + rect = overlap; + SDL_RenderDrawRect(get_renderer(), &rect); + } + Box screen_subsection_box = Box({0, 0}, {30, 60}); + screen_subsection_box.sw(window_box().sw()); + Pixels screen_subsection_pixels = Pixels(get_renderer(), nullptr, screen_subsection_box); + for (int x = 0; x < screen_subsection_pixels.rect.w; x++) + { + screen_subsection_pixels.set(Color(255, 0, 255), x, 5); + } + screen_subsection_pixels.apply(); + SDL_SetRenderTarget(get_renderer(), nullptr); + Box canvas_box = sfw::get_texture_box(canvas); + canvas_box.nw({340, 200}); + SDL_RenderCopyF(get_renderer(), canvas, nullptr, &canvas_box); +} + +int main() +{ + CollisionTest collision_test = CollisionTest(); + collision_test.run(); + collision_test.quit(); + return 0; +} diff --git a/demo/2d_collision/CollisionTest.hpp b/demo/2d_collision/CollisionTest.hpp new file mode 100644 index 0000000..4d255ff --- /dev/null +++ b/demo/2d_collision/CollisionTest.hpp @@ -0,0 +1,42 @@ +#ifndef CollisionTest_h_ +#define CollisionTest_h_ + +#include "Game.hpp" +#include "Sprite.hpp" +#include "Pixels.hpp" +#include "Color.hpp" +#include "extension.hpp" + +class CollisionTest : public Game +{ + +private: + + Sprite wooper = Sprite(this, "wooper.png"); + Sprite enemy = Sprite(this, "wooper.png"); + SDL_Texture* canvas; + std::vector boxes = { + {{50, 20}, {20, 10}}, + {{300, 100}, {50, 60}}, + {{160, 300}, {30, 10}}, + }; + std::vector segments = { + {{5, 50}, {15, 90}}, + {{250, 250}, {200, 240}} + }; + std::vector points = {{100, 300}, {205, 20}, {450, 300}}; + bool collide_all = false; + bool collide_all_other = false; + bool precise = false; + + void test_crop(const std::vector&); + void respond(SDL_Event&); + void update(); + +public: + + CollisionTest(); + +}; + +#endif diff --git a/demo/2d_collision/Makefile b/demo/2d_collision/Makefile new file mode 100644 index 0000000..5d0ed98 --- /dev/null +++ b/demo/2d_collision/Makefile @@ -0,0 +1,101 @@ +# /\ +--------------------------------------------------------------+ +# ____/ \____ /| - zlib/MIT/Unlicenced game framework licensed to freely use, | +# \ / / | copy, modify and sell without restriction | +# +--\ ^__^ /--+ | | +# | ~/ \~ | | - originally created at [http://nugget.fun] | +# | ~~~~~~~~~~~~ | +--------------------------------------------------------------+ +# | SPACE ~~~~~ | / +# | ~~~~~~~ BOX |/ +# +--------------+ +# +# [ Makefile for 2D collision demo ] +# +# This should build the 2D collision demo for Linux. Compilation to other platforms +# hasn't been attempted yet. +# +# Edit the parameters as necessary and run `make linux` in the current directory. +# + +####################### +# Location parameters # +####################### + +# Location of source files for the demo +SRC_DIR := ./ + +# Locations of [SPACE BOX] source and dependencies required to be compiled from source. These +# locations are configured to match the structure of the [SPACE BOX] repository but can be +# modified as necessary. +SB_DIR := ../../ +SB_SRC_DIR := $(SB_DIR)src/ +SB_LIB_DIR := $(SB_DIR)lib/ +SDLGFX2_DIR := $(SB_LIB_DIR)sdl2-gfx/ +GLEW_DIR := $(SB_LIB_DIR)glew/ + +# C and C++ compiler commands +CC := clang +CPPC := clang++ + +# Location of SDL config program +SDLCONFIG := ~/local/sdl/bin/sdl2-config + +# Edit to point to the location of BPmono.ttf +CREATE_FONT_SYMLINK := ln -nsf $(SB_DIR)"BPmono.ttf" . + +############################# +# Based on above parameters # +############################# + +CFLAGS := -Wall -O0 -c -I$(SB_LIB_DIR) -I$(SB_SRC_DIR) -g $(shell $(SDLCONFIG) --cflags) +CPP_FLAGS := $(CFLAGS) --std=c++17 +LFLAGS := $(shell $(SDLCONFIG) --libs) -lpthread +SB_H_FILES := $(wildcard $(addprefix $(SB_SRC_DIR),*.hpp)) +SB_O_FILES := $(filter-out $(addprefix $(SB_SRC_DIR),filesystem.o),$(SB_H_FILES:.hpp=.o)) +SRC_H_FILES := $(wildcard $(addprefix $(SRC_DIR),*.hpp)) +SRC_O_FILES := $(SRC_H_FILES:.hpp=.o) + +################################################################## +# Targets for building [SPACE BOX], dependencies and demo source # +################################################################## + +$(SDLGFX2_DIR)%.o: $(SDLGFX2_DIR)%.c $(SDLGFX2_DIR)%.h +$(GLEW_DIR)%.o: $(GLEW_DIR)%.c $(GLEW_DIR)%.h + $(CC) $(CFLAGS) $< -c -o $@ + +$(SB_SRC_DIR)extension.o : $(addprefix $(SB_SRC_DIR),Box.hpp Segment.hpp Color.hpp filesystem.hpp Pixels.hpp) +$(SB_SRC_DIR)Node.o : $(addprefix $(SB_SRC_DIR),Game.hpp Configuration.hpp Delegate.hpp Display.hpp Input.hpp Box.hpp Audio.hpp) +$(SB_SRC_DIR)Sprite.o : $(addprefix $(SB_SRC_DIR),Node.hpp Game.hpp Box.hpp Animation.hpp Color.hpp extension.hpp Pixels.hpp) +$(SB_SRC_DIR)Game.o : $(addprefix $(SB_SRC_DIR),extension.hpp Node.hpp Sprite.hpp Recorder.hpp Input.hpp Configuration.hpp \ + Delegate.hpp Audio.hpp) +$(SB_SRC_DIR)Animation.o : $(addprefix $(SB_SRC_DIR),Node.hpp Timer.hpp) +$(SB_SRC_DIR)Recorder.o : $(addprefix $(SB_SRC_DIR),Node.hpp Game.hpp Configuration.hpp Delegate.hpp Animation.hpp extension.hpp) +$(SB_SRC_DIR)Input.o : $(addprefix $(SB_SRC_DIR),Node.hpp Animation.hpp Configuration.hpp Delegate.hpp) +$(SB_SRC_DIR)Configuration.o : $(addprefix $(SB_SRC_DIR),Node.hpp Animation.hpp) +$(SB_SRC_DIR)Delegate.o : $(addprefix $(SB_SRC_DIR),Node.hpp Game.hpp Input.hpp) +$(SB_SRC_DIR)Display.o : $(addprefix $(SB_SRC_DIR),Node.hpp Game.hpp Box.hpp Configuration.hpp Delegate.hpp) +$(SB_SRC_DIR)Box.o : $(addprefix $(SB_SRC_DIR),extension.hpp Segment.hpp) +$(SB_SRC_DIR)Segment.o : $(addprefix $(SB_SRC_DIR),extension.hpp Box.hpp) +$(SB_SRC_DIR)Pixels.o : $(addprefix $(SB_SRC_DIR),Box.hpp extension.hpp) +$(SB_SRC_DIR)Audio.o : $(addprefix $(SB_SRC_DIR),Node.hpp Display.hpp Configuration.hpp Box.hpp filesystem.hpp extension.hpp) +$(SRC_DIR)CollisionTest.o : $(SB_H_FILES) +%.o : %.cpp %.hpp + $(CPPC) $(CPP_FLAGS) $< -c -o $@ + +########################## +# Target for Linux build # +########################## + +linux : $(GLEW_DIR)glew.o $(addprefix $(SDLGFX2_DIR),SDL2_rotozoom.o SDL2_gfxPrimitives.o) \ + $(SB_O_FILES) CollisionTest.o + $(CREATE_FONT_SYMLINK) + $(CPPC) $(LFLAGS) -D__LINUX__ -lGL -lSDL2_image -lSDL2_ttf -lSDL2_mixer -lstdc++fs $^ -o 2d_collision + +####################################### +# Target for cleaning up object files # +####################################### + +clean : + - rm *.o + - rm $(SB_SRC_DIR)*.o + - rm $(GLEW_DIR)*.o + - rm $(SDLGFX2_DIR)*.o diff --git a/demo/2d_collision/config.json b/demo/2d_collision/config.json new file mode 100644 index 0000000..ec129ec --- /dev/null +++ b/demo/2d_collision/config.json @@ -0,0 +1,8 @@ +{ + "keys": + { + "toggle-collide-all": ["CTRL", "c"], + "toggle-collide-all-other": ["CTRL", "o"], + "toggle-precise-collision": ["CTRL", "p"] + } +} diff --git a/demo/2d_collision/wooper.png b/demo/2d_collision/wooper.png new file mode 100644 index 0000000000000000000000000000000000000000..1d70c2e2ec1eaee3e428aae03b85d940b2599739 GIT binary patch literal 425 zcmV;a0apHrP)X1^@s6wkGG*00009a7bBm000XU z000XU0RWnu7ytkPTS-JgR7l6gS3$OdFbtgZaUnU597UFR6g&0~+?5+r5@01y!a05cN~48fI%;HMmj1(JBa-YmbD@;sdAg*$yfgJ|W{XFTpK1hWd~ zh_G0jVHhgx&H3)vf_ksC*IbzU>6*8hUh+(_OZZ7L)G TiJ4bT00000NkvXXu0mjfUj(;< literal 0 HcmV?d00001 diff --git a/demo/Makefile b/demo/Makefile deleted file mode 100644 index 3257271..0000000 --- a/demo/Makefile +++ /dev/null @@ -1,120 +0,0 @@ -SFW_DIR = ../ -SFW_SRC_DIR = $(SFW_DIR)src/ -SFW_LIB_DIR = $(SFW_DIR)lib/ -SDLGFX2_DIR = $(SFW_LIB_DIR)sdl2-gfx/ -GLEW_DIR = $(SFW_LIB_DIR)glew/ -CC_LINUX = clang-7 -CPPC_LINUX = clang++-7 -SDLCONFIG = /home/frank/local/sdl/bin/sdl2-config -CFLAGS = -Wall -O0 -c -I$(SFW_LIB_DIR) -I$(SFW_SRC_DIR) -g -CPP_FLAGS = $(CFLAGS) --std=c++17 -SDL_FLAGS = $(shell $(SDLCONFIG) --cflags) -LFLAGS = $(shell $(SDLCONFIG) --libs) -lpthread -export ANDROID_HOME = /home/frank/ext/software/android-sdk -export ANDROID_NDK_HOME = /home/frank/ext/software/android-ndk-r8d -BUILDDIR = build -ANDROIDPROJECT = com.tarecontrol.demo -SOFTWARE_ROOT = /home/frank/ext/software -SDLHOME = $(SOFTWARE_ROOT)/SDL2-2.0.9 -SDL_IMG_HOME = $(SOFTWARE_ROOT)/SDL2_image-2.0.4 -SDL_TTF_HOME = $(SOFTWARE_ROOT)/SDL2_ttf-2.0.14 -GLEW_WIN32_HOME = $(SOFTWARE_ROOT)/glew-2.1.0-win32 -PROJECTHOME = $(shell pwd) -EMSCRIPTENHOME = /home/frank/ext/software/emsdk/emscripten/tag-1.38.12 -SDLEMLIBSHOME = $(SDLHOME)/build/em/build/.libs -EMBUILDDIR = em -WINBUILDDIR = win -SDLMINGWHOME = $(SDLHOME)/i686-w64-mingw32 -SDL_IMG_MINGW_HOME = $(SDL_IMG_HOME)/i686-w64-mingw32 -SDL_TTF_MINGW_HOME = $(SDL_TTF_HOME)/i686-w64-mingw32 -APPDIR = Main.app/Contents -SYSFWPATH = /Library/Frameworks - -$(SDLGFX2_DIR)%.o: $(SDLGFX2_DIR)%.c $(SDLGFX2_DIR)%.h - $(CC_LINUX) $(CFLAGS) $(SDL_FLAGS) $< -o $@ - -$(GLEW_DIR)%.o: $(GLEW_DIR)%.c $(GLEW_DIR)%.h - $(CC_LINUX) $(CFLAGS) $< -o $@ - -$(SFW_SRC_DIR)Sprite.o: $(addprefix $(SFW_SRC_DIR),Game.*pp Box.*pp Node.*pp Animation.*pp) -$(SFW_SRC_DIR)Game.o: $(addprefix $(SFW_SRC_DIR),Sprite.*pp Configuration.*pp Delegate.*pp Display.*pp \ - Recorder.*pp Node.*pp Input.*pp) -$(SFW_SRC_DIR)Node.o: $(addprefix $(SFW_SRC_DIR),Game.*pp Configuration.*pp Delegate.*pp) -$(SFW_SRC_DIR)Animation.o: $(addprefix $(SFW_SRC_DIR),Node.*pp Timer.*pp) -$(SFW_SRC_DIR)Recorder.o: $(addprefix $(SFW_SRC_DIR),extension.*pp Node.*pp Delegate.*pp Animation.*pp Display.*pp) -$(SFW_SRC_DIR)Input.o: $(addprefix $(SFW_SRC_DIR),Delegate.*pp Node.*pp) -$(SFW_SRC_DIR)Configuration.o: $(addprefix $(SFW_SRC_DIR),Node.*pp) -$(SFW_SRC_DIR)Delegate.o: $(addprefix $(SFW_SRC_DIR),Node.*pp) -$(SFW_SRC_DIR)Display.o: $(addprefix $(SFW_SRC_DIR),Node.*pp) -$(SFW_SRC_DIR)%.o: $(addprefix $(SFW_SRC_DIR),%.cpp %.hpp) - $(CPPC_LINUX) $(CPP_FLAGS) $(SDL_FLAGS) $< -o $@ - -Demo.o: Demo.cpp Demo.hpp $(addprefix $(SFW_SRC_DIR),Sprite.*pp Node.*pp Game.*pp Box.*pp Input.*pp \ - Recorder.*pp Timer.*pp Animation.*pp extension.*pp) - $(CPPC_LINUX) $(CPP_FLAGS) $(SDL_FLAGS) $< -o $@ - -linux: Demo.o $(addprefix $(SFW_SRC_DIR),Sprite.o Node.o Game.o Box.o Configuration.o Input.o Delegate.o \ - Display.o Recorder.o Timer.o Animation.o extension.o) \ - $(GLEW_DIR)glew.o $(addprefix $(SDLGFX2_DIR),SDL2_rotozoom.o SDL2_gfxPrimitives.o) - $(CPPC_LINUX) $(LFLAGS) -D__LINUX__ $^ -lGL -lSDL2_image -lSDL2_ttf -lSDL2_mixer -lstdc++fs -o demo - -clean: - rm *.o - rm $(SFW_SRC_DIR)*.o - rm $(GLEW_DIR)*.o - rm $(SDLGFX2_DIR)*.o - -android : - if [ ! -d $(BUILDDIR) ]; then mkdir $(BUILDDIR); fi; - cd $(SDLHOME)/build-scripts/ && \ - ./androidbuild.sh $(ANDROIDPROJECT) $(PROJECTHOME)/main.cpp - cp -r $(SDLHOME)/build/$(ANDROIDPROJECT) $(BUILDDIR) - sed -i s/2\.3\.3/2\.2\.3/g $(BUILDDIR)/$(ANDROIDPROJECT)/build.gradle - sed -i s/26\.0\.1/23\.0\.1/g $(BUILDDIR)/$(ANDROIDPROJECT)/app/build.gradle - cd $(BUILDDIR)/$(ANDROIDPROJECT) && ./gradlew assembleDebug - -emscripten : - if [ ! -d $(BUILDDIR)/$(EMBUILDDIR) ]; then mkdir -p $(BUILDDIR)/$(EMBUILDDIR); fi; - cd $(BUILDDIR)/$(EMBUILDDIR) && \ - $(EMSCRIPTENHOME)/em++ -O2 $(PROJECTHOME)/main.cpp -I$(SDLHOME)/include \ - -Wall -s USE_SDL=2 -o main.html - -mingw : - if [ ! -d $(BUILDDIR)/$(WINBUILDDIR) ]; then mkdir -p $(BUILDDIR)/$(WINBUILDDIR); fi; - cd $(BUILDDIR)/$(WINBUILDDIR) && \ - i686-w64-mingw32-g++ -std=c++17 $(PROJECTHOME)/main.cpp -I$(SDLMINGWHOME)/include/SDL2 \ - -I$(SDL_IMG_MINGW_HOME)/include/SDL2 -I$(SDL_TTF_MINGW_HOME)/include/SDL2 $(INC) \ - $(PROJECTHOME)/sdl2-gfx/SDL2_gfxPrimitives.c $(PROJECTHOME)/sdl2-gfx/SDL2_rotozoom.c $(PROJECTHOME)/glew/glew.c \ - -L$(SDLMINGWHOME)/lib -L$(SDL_IMG_MINGW_HOME)/lib -L$(SDL_TTF_MINGW_HOME)/lib \ - -lmingw32 -lSDL2_image \ - -lSDL2_ttf -lstdc++fs \ - -lSDL2main -lSDL2 -lopengl32 -Wall -O2 -o main.exe && \ - cp $(SDLMINGWHOME)/bin/SDL2.dll $(SDL_IMG_MINGW_HOME)/bin/SDL2_image.dll \ - $(SDL_TTF_MINGW_HOME)/bin/SDL2_ttf.dll . - -osx : - g++ -I $(SYSFWPATH)/SDL2.framework/Headers $(INC) \ - -I $(SYSFWPATH)/SDL2_image.framework/Headers -Wl,-rpath,$(SYSFWPATH) \ - -framework SDL2 -framework SDL2_image -framework OpenGL main.cpp sdl2-gfx/SDL2_rotozoom.c \ - -o main - -osx-bundle : - if [ ! -d "$(APPDIR)" ]; then mkdir -p $(APPDIR); fi; - if [ ! -d "$(APPDIR)" ]; then mkdir $(APPDIR); fi; - if [ ! -d "$(APPDIR)/MacOS" ]; then mkdir $(APPDIR)/MacOS; fi; - if [ ! -d "$(APPDIR)/Frameworks" ]; then mkdir $(APPDIR)/Frameworks; fi; - if [ ! -d "$(APPDIR)/Resources" ]; then mkdir $(APPDIR)/Resources; fi; - touch $(APPDIR)/Info.plist - cp -r $(SYSFWPATH)/SDL2.framework $(APPDIR)/Frameworks - cp -r $(SYSFWPATH)/SDL2_image.framework $(APPDIR)/Frameworks - g++ -I $(SYSFWPATH)/SDL2.framework/Headers -I $(SYSFWPATH)/SDL2_image.framework/Headers \ - -Wl,-rpath,@executable_path/../Frameworks -Wl,-rpath,$(SYSFWPATH) \ - -framework SDL2 -framework SDL2_image -framework OpenGL main.cpp -o $(APPDIR)/MacOS/main - -cross : linux android emscripten mingw - -# main : main.o -# g++ -o main $(LFLAGS) -lGL main.o - -# main.o : main.cpp -# g++ -c $(CFLAGS) -DLINUX main.cpp diff --git a/demo/cube/BPmono.ttf b/demo/cube/BPmono.ttf new file mode 120000 index 0000000..80db3f8 --- /dev/null +++ b/demo/cube/BPmono.ttf @@ -0,0 +1 @@ +../../BPmono.ttf \ No newline at end of file diff --git a/demo/Demo.cpp b/demo/cube/Cube.cpp similarity index 81% rename from demo/Demo.cpp rename to demo/cube/Cube.cpp index 72ff9fd..6075e33 100644 --- a/demo/Demo.cpp +++ b/demo/cube/Cube.cpp @@ -27,7 +27,7 @@ ***/ -#include "Demo.hpp" +#include "Cube.hpp" char* file_to_buf(const char *path) { @@ -104,36 +104,6 @@ GLuint get_gl_texture_from_surface(SDL_Surface *surface, GLint mipmap_filter, bo return id; } -SDL_Surface* get_framerate_indicator_surface(int frame_count) -{ - TTF_Font* font = TTF_OpenFont("resource/SourceCodePro-Regular.otf", 14); - std::string padded = sfw::pad(frame_count, 2); - SDL_Surface* shaded = TTF_RenderText_Shaded( - font, padded.c_str(), {0, 0, 0}, {255, 255, 255}); - SDL_Surface* converted = SDL_ConvertSurfaceFormat( - shaded, SDL_PIXELFORMAT_ARGB8888, 0); - SDL_Surface* flipped = zoomSurface(converted, 1, -1, SMOOTHING_OFF); - SDL_FreeSurface(shaded); - SDL_FreeSurface(converted); - if (not flipped) - { - fprintf(stderr, "Could not create text %s\n", SDL_GetError()); - } - TTF_CloseFont(font); - return flipped; -} - -void set_framerate_indicator(int frame_count, GLuint id) -{ - SDL_Surface* message = get_framerate_indicator_surface(frame_count); - glBindTexture(GL_TEXTURE_2D, id); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, message->w, message->h, GL_BGRA, - GL_UNSIGNED_BYTE, message->pixels); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - SDL_FreeSurface(message); -} - Mushroom::Mushroom(Node *parent) : Sprite(parent, "resource/shrooms") { set_frame_length(500); @@ -142,41 +112,41 @@ Mushroom::Mushroom(Node *parent) : Sprite(parent, "resource/shrooms") void Mushroom::update() { - move(direction); - int x = box.left(); + move_weighted({direction, 0}); + int x = get_left(); glm::ivec2 resolution = get_display().window_size(); if (x > resolution.x or x < 0) { direction = -direction; if (x > resolution.x) { - move(resolution.x - x); + move({resolution.x - x, 0}); } else { - move(-x); + move({-x, 0}); } } Sprite::update(); } -Demo::Demo() +Cube::Cube() { Mix_Music *music = Mix_LoadMUS("resource/Field.mp3"); // Mix_Music *music = Mix_LoadMUS("/home/frank/WATERMELON-clean.mp3"); Mix_PlayMusic(music, -1); load_gl_context(); - delegate.subscribe(&Demo::respond, this); + delegate.subscribe(&Cube::respond, this); } -void Demo::load_sdl_context() +void Cube::load_sdl_context() { Game::load_sdl_context(); grass.load(); mushroom.load(); } -void Demo::load_gl_context() +void Cube::load_gl_context() { Game::load_gl_context(); grass.unload(); @@ -221,19 +191,12 @@ void Demo::load_gl_context() }; GLuint background_colors_buffer; glGenBuffers(1, &background_colors_buffer); - std::array framerate_indicator_vertices = { - { - {.9, 1, 0}, {1, 1, 0}, {.9, .9, 0}, - {1, 1, 0}, {1, .9, 0}, {.9, .9, 0} - }}; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); std::vector vertices; - vertices.reserve(cube.size() + background_vertices.size() + framerate_indicator_vertices.size()); + vertices.reserve(cube.size() + background_vertices.size()); vertices.insert(vertices.begin(), cube.begin(), cube.end()); vertices.insert(vertices.end(), background_vertices.begin(), background_vertices.end()); - vertices.insert(vertices.end(), framerate_indicator_vertices.begin(), - framerate_indicator_vertices.end()); glm::ivec2 resolution = get_display().window_size(); projection = glm::perspective( glm::radians(45.0f), resolution.x / (float) resolution.y, 0.1f, @@ -253,11 +216,6 @@ void Demo::load_gl_context() // face_ids.push_back(get_gl_texture_from_surface(surface, GL_LINEAR)); // SDL_FreeSurface(surface); // } - std::array framerate_indicator_uv = { - { - {0, 1}, {1, 1}, {0, 0}, - {1, 1}, {1, 0}, {0, 0} - }}; std::array cube_uv = { { {1, 1}, {0, 1}, {0, 0}, @@ -274,10 +232,8 @@ void Demo::load_gl_context() {1, 1}, {0, 1}, {0, 0} }}; std::vector uv; - uv.reserve(cube_uv.size() + background_vertices.size() + framerate_indicator_uv.size()); + uv.reserve(cube_uv.size() + background_vertices.size()); std::copy(cube_uv.begin(), cube_uv.end(), uv.begin()); - std::copy(framerate_indicator_uv.begin(), framerate_indicator_uv.end(), - uv.begin() + cube_uv.size() + background_vertices.size()); GLuint uvbuffer; glGenBuffers(1, &uvbuffer); unsigned char fake_texture_color[4] = {255, 255, 255, 255}; @@ -330,13 +286,9 @@ void Demo::load_gl_context() // glBindTexture(GL_TEXTURE_2D, space_texture_id); // glUniform1i(sampler_uniform_id, 0); glDepthFunc(GL_LESS); - frame_count_timestamp = SDL_GetTicks(); - SDL_Surface* fps_surface = get_framerate_indicator_surface(frame_count); - framerate_texture_id = get_gl_texture_from_surface(fps_surface, GL_LINEAR); - SDL_FreeSurface(fps_surface); } -void Demo::respond(SDL_Event& event) +void Cube::respond(SDL_Event& event) { if (delegate.compare(event, "context")) { @@ -367,7 +319,7 @@ void Demo::respond(SDL_Event& event) } } -void Demo::update() +void Cube::update() { // while (SDL_PollEvent(&event)) // { @@ -440,16 +392,7 @@ void Demo::update() glDisableVertexAttribArray(1); glEnableVertexAttribArray(2); glDrawArrays(GL_TRIANGLES, 36, 6); - if (show_framerate) - { - glBindTexture(GL_TEXTURE_2D, framerate_texture_id); - glDisableVertexAttribArray(2); - glEnableVertexAttribArray(1); - glVertexAttrib3f(2, 1, 1, 1); - glDrawArrays(GL_TRIANGLES, 42, 6); - } - // printf("%s\n", glm::to_string(model).c_str()); - float rotation = .0005f * frame_length; + float rotation = .0005f * get_frame_length(); model = glm::rotate(model, rotation, glm::vec3(0.0f, 1.0f, 0.0f)); mvp = projection * view * model; glEnable(GL_DEPTH_TEST); @@ -482,40 +425,29 @@ void Demo::update() int speed = 2; if (up_active) { - grass.move(0, -speed); + grass.move_weighted({0, -speed}); } if (right_active) { - grass.move(speed); + grass.move_weighted({speed, 0}); } if (down_active) { - grass.move(0, speed); + grass.move_weighted({0, speed}); } if (left_active) { - grass.move(-speed, 0); + grass.move_weighted({-speed, 0}); } grass.update(); mushroom.update(); - SDL_RenderPresent(renderer); - } - frame_count++; - if (ticks - frame_count_timestamp >= 1000) - { - frame_count_timestamp = ticks; - if (is_gl_context and show_framerate) - { - set_framerate_indicator(frame_count, framerate_texture_id); - } - frame_count = 0; } } -int main(int argc, char *argv[]) +int main() { - Demo demo = Demo(); - demo.run(); - demo.quit(); + Cube cube = Cube(); + cube.run(); + cube.quit(); return 0; } diff --git a/demo/Demo.hpp b/demo/cube/Cube.hpp similarity index 82% rename from demo/Demo.hpp rename to demo/cube/Cube.hpp index d464855..292fe5f 100644 --- a/demo/Demo.hpp +++ b/demo/cube/Cube.hpp @@ -14,17 +14,6 @@ #include "sdl2-gfx/SDL2_gfxPrimitives.h" #include "sdl2-gfx/SDL2_rotozoom.h" -// #if defined(__LINUX__) or defined(__MINGW32__) -// #define GL_GLEXT_PROTOTYPES -// #include -// #elif defined(__ANDROID__) -// #include -// #elif defined(__EMSCRIPTEN__) -// #include -// #elif defined(__APPLE__) -// #include -// #endif - #define GLM_ENABLE_EXPERIMENTAL #include "glm/gtx/string_cast.hpp" #include "glm/gtx/transform.hpp" @@ -50,7 +39,7 @@ struct Mushroom : Sprite }; -struct Demo : Game +struct Cube : Game { SDL_Texture *grass_texture; @@ -66,7 +55,7 @@ struct Demo : Game std::vector face_ids; float amount_rotated; - Demo(); + Cube(); void load_sdl_context(); void load_gl_context(); void respond(SDL_Event&); diff --git a/demo/cube/Makefile b/demo/cube/Makefile new file mode 100644 index 0000000..34c289c --- /dev/null +++ b/demo/cube/Makefile @@ -0,0 +1,175 @@ +# /\ +--------------------------------------------------------------+ +# ____/ \____ /| - zlib/MIT/Unlicenced game framework licensed to freely use, | +# \ / / | copy, modify and sell without restriction | +# +--\ ^__^ /--+ | | +# | ~/ \~ | | - originally created at [http://nugget.fun] | +# | ~~~~~~~~~~~~ | +--------------------------------------------------------------+ +# | SPACE ~~~~~ | / +# | ~~~~~~~ BOX |/ +# +--------------+ +# +# [ Makefile for cube demo ] +# +# This should build the cube demo for Linux. Other platforms haven't been tested in +# a while and won't work without significant editing. +# +# Edit the parameters as necessary and run `make linux` in the current directory. +# + +####################### +# Location parameters # +####################### + +# Location of source files for the demo +SRC_DIR := ./ + +# Locations of [SPACE BOX] source and dependencies required to be compiled from source. These +# locations are configured to match the structure of the [SPACE BOX] repository but can be +# modified as necessary. +SB_DIR := ../../ +SB_SRC_DIR := $(SB_DIR)src/ +SB_LIB_DIR := $(SB_DIR)lib/ +SDLGFX2_DIR := $(SB_LIB_DIR)sdl2-gfx/ +GLEW_DIR := $(SB_LIB_DIR)glew/ + +# C and C++ compiler commands +CC_LINUX := clang +CPPC_LINUX := clang++ + +# Location of SDL config program +SDLCONFIG := ~/local/sdl/bin/sdl2-config + +# Edit to point to the location of BPmono.ttf +CREATE_FONT_SYMLINK := ln -nsf $(SB_DIR)"BPmono.ttf" . + +############################# +# Based on above parameters # +############################# + +CFLAGS := -Wall -O0 -c -I$(SB_LIB_DIR) -I$(SB_SRC_DIR) -g $(shell $(SDLCONFIG) --cflags) +CPP_FLAGS := $(CFLAGS) --std=c++17 +LFLAGS := $(shell $(SDLCONFIG) --libs) -lpthread +SFW_H_FILES := $(wildcard $(addprefix $(SB_SRC_DIR),*.hpp)) +SFW_O_FILES := $(filter-out $(addprefix $(SB_SRC_DIR),filesystem.o),$(SFW_H_FILES:.hpp=.o)) +SRC_H_FILES := $(wildcard $(addprefix $(SRC_DIR),*.hpp)) +SRC_O_FILES := $(SRC_H_FILES:.hpp=.o) + +################################################################## +# Targets for building [SPACE BOX], dependencies and demo source # +################################################################## + +$(SDLGFX2_DIR)%.o: $(SDLGFX2_DIR)%.c $(SDLGFX2_DIR)%.h +$(GLEW_DIR)%.o: $(GLEW_DIR)%.c $(GLEW_DIR)%.h + $(CC_LINUX) $(CFLAGS) $< -c -o $@ + +$(SB_SRC_DIR)extension.o : $(addprefix $(SB_SRC_DIR),Box.hpp Segment.hpp Color.hpp filesystem.hpp Pixels.hpp) +$(SB_SRC_DIR)Node.o : $(addprefix $(SB_SRC_DIR),Game.hpp Configuration.hpp Delegate.hpp Display.hpp Input.hpp Box.hpp Audio.hpp) +$(SB_SRC_DIR)Sprite.o : $(addprefix $(SB_SRC_DIR),Node.hpp Game.hpp Box.hpp Animation.hpp Color.hpp extension.hpp Pixels.hpp) +$(SB_SRC_DIR)Game.o : $(addprefix $(SB_SRC_DIR),extension.hpp Node.hpp Sprite.hpp Recorder.hpp Input.hpp Configuration.hpp \ + Delegate.hpp Audio.hpp) +$(SB_SRC_DIR)Animation.o : $(addprefix $(SB_SRC_DIR),Node.hpp Timer.hpp) +$(SB_SRC_DIR)Recorder.o : $(addprefix $(SB_SRC_DIR),Node.hpp Game.hpp Configuration.hpp Delegate.hpp Animation.hpp extension.hpp) +$(SB_SRC_DIR)Input.o : $(addprefix $(SB_SRC_DIR),Node.hpp Animation.hpp Configuration.hpp Delegate.hpp) +$(SB_SRC_DIR)Configuration.o : $(addprefix $(SB_SRC_DIR),Node.hpp Animation.hpp) +$(SB_SRC_DIR)Delegate.o : $(addprefix $(SB_SRC_DIR),Node.hpp Game.hpp Input.hpp) +$(SB_SRC_DIR)Display.o : $(addprefix $(SB_SRC_DIR),Node.hpp Game.hpp Box.hpp Configuration.hpp Delegate.hpp) +$(SB_SRC_DIR)Box.o : $(addprefix $(SB_SRC_DIR),extension.hpp Segment.hpp) +$(SB_SRC_DIR)Segment.o : $(addprefix $(SB_SRC_DIR),extension.hpp Box.hpp) +$(SB_SRC_DIR)Pixels.o : $(addprefix $(SB_SRC_DIR),Box.hpp extension.hpp) +$(SB_SRC_DIR)Audio.o : $(addprefix $(SB_SRC_DIR),Node.hpp Display.hpp Configuration.hpp Box.hpp filesystem.hpp extension.hpp) +$(SRC_DIR)Cube.o : $(SFW_H_FILES) +%.o : %.cpp %.hpp + $(CPPC_LINUX) $(CPP_FLAGS) $< -c -o $@ + +########################## +# Target for Linux build # +########################## + +linux : $(GLEW_DIR)glew.o $(addprefix $(SDLGFX2_DIR),SDL2_rotozoom.o SDL2_gfxPrimitives.o) \ + $(SRC_O_FILES) $(SFW_O_FILES) + $(CREATE_FONT_SYMLINK) + $(CPPC_LINUX) $(LFLAGS) -D__LINUX__ $^ -lGL -lSDL2_image -lSDL2_ttf -lSDL2_mixer -lstdc++fs -o cube + +####################################### +# Target for cleaning up object files # +####################################### + +clean : + - rm *.o + - rm $(SB_SRC_DIR)*.o + - rm $(GLEW_DIR)*.o + - rm $(SDLGFX2_DIR)*.o + +# +# These assignments are necessary for cross-compiling to Android, web (via emscripten), Windows (via mingw), +# and OS/X. Cross-compilation targets have not been tested in a while and won't compile without significant changes. +# + +export ANDROID_HOME = /home/frank/ext/software/android-sdk +export ANDROID_NDK_HOME = /home/frank/ext/software/android-ndk-r8d +BUILDDIR := build +ANDROIDPROJECT := com.nuggetsselect.demo +SOFTWARE_ROOT := /home/frank/ext/software +SDLHOME := $(SOFTWARE_ROOT)/SDL2-2.0.14 +SDL_IMG_HOME := $(SOFTWARE_ROOT)/SDL2_image-2.0.5 +SDL_TTF_HOME := $(SOFTWARE_ROOT)/SDL2_ttf-2.0.15 +GLEW_WIN32_HOME := $(SOFTWARE_ROOT)/glew-2.1.0-win32 +PROJECTHOME := $(shell pwd) +EMSCRIPTENHOME := /home/frank/ext/software/emsdk/emscripten/tag-1.38.12 +SDLEMLIBSHOME := $(SDLHOME)/build/em/build/.libs +EMBUILDDIR := em +WINBUILDDIR := win +SDLMINGWHOME := $(SDLHOME)/i686-w64-mingw32 +SDL_IMG_MINGW_HOME := $(SDL_IMG_HOME)/i686-w64-mingw32 +SDL_TTF_MINGW_HOME := $(SDL_TTF_HOME)/i686-w64-mingw32 +APPDIR := Main.app/Contents +SYSFWPATH := /Library/Frameworks + +android : + if [ ! -d $(BUILDDIR) ]; then mkdir $(BUILDDIR); fi; + cd $(SDLHOME)/build-scripts/ && \ + ./androidbuild.sh $(ANDROIDPROJECT) $(PROJECTHOME)/main.cpp + cp -r $(SDLHOME)/build/$(ANDROIDPROJECT) $(BUILDDIR) + sed -i s/2\.3\.3/2\.2\.3/g $(BUILDDIR)/$(ANDROIDPROJECT)/build.gradle + sed -i s/26\.0\.1/23\.0\.1/g $(BUILDDIR)/$(ANDROIDPROJECT)/app/build.gradle + cd $(BUILDDIR)/$(ANDROIDPROJECT) && ./gradlew assembleDebug + +emscripten : + if [ ! -d $(BUILDDIR)/$(EMBUILDDIR) ]; then mkdir -p $(BUILDDIR)/$(EMBUILDDIR); fi; + cd $(BUILDDIR)/$(EMBUILDDIR) && \ + $(EMSCRIPTENHOME)/em++ -O2 $(PROJECTHOME)/main.cpp -I$(SDLHOME)/include \ + -Wall -s USE_SDL=2 -o main.html + +mingw : + if [ ! -d $(BUILDDIR)/$(WINBUILDDIR) ]; then mkdir -p $(BUILDDIR)/$(WINBUILDDIR); fi; + cd $(BUILDDIR)/$(WINBUILDDIR) && \ + i686-w64-mingw32-g++ -std=c++17 $(PROJECTHOME)/main.cpp -I$(SDLMINGWHOME)/include/SDL2 \ + -I$(SDL_IMG_MINGW_HOME)/include/SDL2 -I$(SDL_TTF_MINGW_HOME)/include/SDL2 $(INC) \ + $(PROJECTHOME)/sdl2-gfx/SDL2_gfxPrimitives.c $(PROJECTHOME)/sdl2-gfx/SDL2_rotozoom.c $(PROJECTHOME)/glew/glew.c \ + -L$(SDLMINGWHOME)/lib -L$(SDL_IMG_MINGW_HOME)/lib -L$(SDL_TTF_MINGW_HOME)/lib \ + -lmingw32 -lSDL2_image \ + -lSDL2_ttf -lstdc++fs \ + -lSDL2main -lSDL2 -lopengl32 -Wall -O2 -o main.exe && \ + cp $(SDLMINGWHOME)/bin/SDL2.dll $(SDL_IMG_MINGW_HOME)/bin/SDL2_image.dll \ + $(SDL_TTF_MINGW_HOME)/bin/SDL2_ttf.dll . + +osx : + g++ -I $(SYSFWPATH)/SDL2.framework/Headers $(INC) \ + -I $(SYSFWPATH)/SDL2_image.framework/Headers -Wl,-rpath,$(SYSFWPATH) \ + -framework SDL2 -framework SDL2_image -framework OpenGL main.cpp sdl2-gfx/SDL2_rotozoom.c \ + -o main + +osx-bundle : + if [ ! -d "$(APPDIR)" ]; then mkdir -p $(APPDIR); fi; + if [ ! -d "$(APPDIR)" ]; then mkdir $(APPDIR); fi; + if [ ! -d "$(APPDIR)/MacOS" ]; then mkdir $(APPDIR)/MacOS; fi; + if [ ! -d "$(APPDIR)/Frameworks" ]; then mkdir $(APPDIR)/Frameworks; fi; + if [ ! -d "$(APPDIR)/Resources" ]; then mkdir $(APPDIR)/Resources; fi; + touch $(APPDIR)/Info.plist + cp -r $(SYSFWPATH)/SDL2.framework $(APPDIR)/Frameworks + cp -r $(SYSFWPATH)/SDL2_image.framework $(APPDIR)/Frameworks + g++ -I $(SYSFWPATH)/SDL2.framework/Headers -I $(SYSFWPATH)/SDL2_image.framework/Headers \ + -Wl,-rpath,@executable_path/../Frameworks -Wl,-rpath,$(SYSFWPATH) \ + -framework SDL2 -framework SDL2_image -framework OpenGL main.cpp -o $(APPDIR)/MacOS/main + +cross : linux android emscripten mingw diff --git a/demo/config.json b/demo/cube/config.json similarity index 78% rename from demo/config.json rename to demo/cube/config.json index ec5d21a..bb174db 100644 --- a/demo/config.json +++ b/demo/cube/config.json @@ -10,10 +10,14 @@ "print-video-memory-size": "v", "play-sound": "" }, + "input": + { + "suppress-any-key-on-mods": true + }, "recording": { "screenshot-directory": "local/screenshots", - "video-directory": "local/video" + "video-directory": "local/video", "enabled": true, "write-mp4": true, "video-frame-length": 16.667, diff --git a/demo/resource/Ag.ogg b/demo/cube/resource/Ag.ogg similarity index 100% rename from demo/resource/Ag.ogg rename to demo/cube/resource/Ag.ogg diff --git a/demo/resource/Field.mp3 b/demo/cube/resource/Field.mp3 similarity index 100% rename from demo/resource/Field.mp3 rename to demo/cube/resource/Field.mp3 diff --git a/demo/resource/Field.png b/demo/cube/resource/Field.png similarity index 100% rename from demo/resource/Field.png rename to demo/cube/resource/Field.png diff --git a/demo/resource/background.png b/demo/cube/resource/background.png similarity index 100% rename from demo/resource/background.png rename to demo/cube/resource/background.png diff --git a/demo/resource/shrooms/anxiety.png b/demo/cube/resource/shrooms/anxiety.png similarity index 100% rename from demo/resource/shrooms/anxiety.png rename to demo/cube/resource/shrooms/anxiety.png diff --git a/demo/resource/shrooms/lapis-lazuli.png b/demo/cube/resource/shrooms/lapis-lazuli.png similarity index 100% rename from demo/resource/shrooms/lapis-lazuli.png rename to demo/cube/resource/shrooms/lapis-lazuli.png diff --git a/demo/resource/shrooms/mr-plummy.png b/demo/cube/resource/shrooms/mr-plummy.png similarity index 100% rename from demo/resource/shrooms/mr-plummy.png rename to demo/cube/resource/shrooms/mr-plummy.png diff --git a/demo/resource/shrooms/poison-shibake.png b/demo/cube/resource/shrooms/poison-shibake.png similarity index 100% rename from demo/resource/shrooms/poison-shibake.png rename to demo/cube/resource/shrooms/poison-shibake.png diff --git a/demo/resource/shrooms/shaver-buzz.png b/demo/cube/resource/shrooms/shaver-buzz.png similarity index 100% rename from demo/resource/shrooms/shaver-buzz.png rename to demo/cube/resource/shrooms/shaver-buzz.png diff --git a/demo/resource/shrooms/sombrero.png b/demo/cube/resource/shrooms/sombrero.png similarity index 100% rename from demo/resource/shrooms/sombrero.png rename to demo/cube/resource/shrooms/sombrero.png diff --git a/demo/resource/shrooms/spider-house.png b/demo/cube/resource/shrooms/spider-house.png similarity index 100% rename from demo/resource/shrooms/spider-house.png rename to demo/cube/resource/shrooms/spider-house.png diff --git a/demo/resource/shrooms/teethmouth.png b/demo/cube/resource/shrooms/teethmouth.png similarity index 100% rename from demo/resource/shrooms/teethmouth.png rename to demo/cube/resource/shrooms/teethmouth.png diff --git a/demo/resource/shrooms/terraformer.png b/demo/cube/resource/shrooms/terraformer.png similarity index 100% rename from demo/resource/shrooms/terraformer.png rename to demo/cube/resource/shrooms/terraformer.png diff --git a/demo/resource/tile.png b/demo/cube/resource/tile.png similarity index 100% rename from demo/resource/tile.png rename to demo/cube/resource/tile.png diff --git a/demo/shaders/flat.frag b/demo/cube/shaders/flat.frag similarity index 100% rename from demo/shaders/flat.frag rename to demo/cube/shaders/flat.frag diff --git a/demo/shaders/flat.vert b/demo/cube/shaders/flat.vert similarity index 100% rename from demo/shaders/flat.vert rename to demo/cube/shaders/flat.vert diff --git a/demo/shaders/triangle.frag b/demo/cube/shaders/triangle.frag similarity index 100% rename from demo/shaders/triangle.frag rename to demo/cube/shaders/triangle.frag diff --git a/demo/shaders/triangle.vert b/demo/cube/shaders/triangle.vert similarity index 100% rename from demo/shaders/triangle.vert rename to demo/cube/shaders/triangle.vert diff --git a/demo/resource/SourceCodePro-Regular.otf b/demo/resource/SourceCodePro-Regular.otf deleted file mode 100755 index bcaa0a9924f0b6c3535f4c0d495ebf93098908c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81384 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

;@n+binWGQ&;ZYJJm<> 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 plane_vertices = {{ + {-1.0f, 1.0f}, {1.0f, 1.0f}, {-1.0f, -1.0f}, + {1.0f, 1.0f}, {1.0f, -1.0f}, {-1.0f, -1.0f} + }}; + std::vector circle_vertices = sfw::points_on_circle(get_configuration()["circle-resolution"]); + circle_vertices.insert(circle_vertices.begin(), {0.0f, 0.0f}); + circle_vertices.insert(circle_vertices.end(), circle_vertices[1]); + circle_vertices_count = circle_vertices.size(); + /* Generate vertex buffer object to hold both mapped and unmapped data */ + GLuint vbo; + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + /* Allocate space for plane and circle and copy plane vertices in VBO */ + GLsizeiptr vbo_size = (plane_vertices.size() + circle_vertices.size()) * sizeof(glm::vec2); + glBufferData(GL_ARRAY_BUFFER, vbo_size, plane_vertices.data(), GL_STATIC_DRAW); + /* Allocate VAO for the plane vertices and connect attributes to the VBO */ + glGenVertexArrays(1, &unmapped_vao); + glBindVertexArray(unmapped_vao); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); + glEnableVertexAttribArray(0); + /* Copy circle vertices into VBO */ + GLintptr offset = plane_vertices.size() * sizeof(glm::vec2); + glBufferSubData(GL_ARRAY_BUFFER, offset, circle_vertices.size() * sizeof(glm::vec2), circle_vertices.data()); + /* Allocate VAO for the circle vertices and connect attributes to the VBO */ + glGenVertexArrays(1, &mapped_vao); + glBindVertexArray(mapped_vao); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, reinterpret_cast(offset)); + glEnableVertexAttribArray(0); + /* Load flat shader program */ + GLuint vertex_shader = load_shader("flat.vert", GL_VERTEX_SHADER); + GLuint fragment_shader = load_shader("flat.frag", GL_FRAGMENT_SHADER); + flat_program = glCreateProgram(); + glBindAttribLocation(flat_program, 0, "position"); + glAttachShader(flat_program, vertex_shader); + glAttachShader(flat_program, fragment_shader); + link_shader(flat_program); + glUseProgram(flat_program); + /* load image */ + load_image_index(0); + base_texture_shader_location = glGetUniformLocation(flat_program, "base_texture"); + mode_uniform_location = glGetUniformLocation(flat_program, "mode"); + transformation_uniform_location = glGetUniformLocation(flat_program, "transformation"); + log_gl_errors(); +} + +/* Load image at path as an SDL surface, generate texture to load pixel data into, allocate storage, and bind + * and edit texture properties. Returns the ID of the generated texture. */ +GLuint Squircle::load_file_into_texture(fs::path path) const +{ + GLuint texture_id; + std::unique_ptr surface(IMG_Load(path.c_str()), SDL_FreeSurface); + std::unique_ptr flipped_surface(rotozoomSurfaceXY(surface.get(), 0, 1, -1, 0), SDL_FreeSurface); + glGenTextures(1, &texture_id); + glBindTexture(GL_TEXTURE_2D, texture_id); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGB8, flipped_surface->w, flipped_surface->h); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, flipped_surface->w, flipped_surface->h, GL_RGBA, GL_UNSIGNED_BYTE, flipped_surface->pixels); + std::ostringstream message; + message << "loaded image into texture id #" << texture_id << " " << flipped_surface->w << " x " << flipped_surface->h; + log(message.str()); + return texture_id; +} + +void Squircle::load_image_index(int index) +{ + std::vector paths = sfw::glob("images/.*"); + fs::path path = paths[index % paths.size()]; + std::ostringstream message; + message << "loading " << path; + log(message.str()); + image_index = index; + base_texture_id = load_file_into_texture(path); +} + +void Squircle::respond(SDL_Event& event) +{ + if (get_delegate().compare(event, "next")) + { + load_image_index(image_index + 1); + } + else if (get_delegate().compare(event, "mode")) + { + mode = mode == Mode::SQUIRCLE ? Mode::UNSQUIRCLE : Mode::SQUIRCLE; + } + else if (get_delegate().compare(event, "left")) + { + spin_z = Spin::NEGATIVE; + } + else if (get_delegate().compare(event, "right")) + { + spin_z = Spin::POSITIVE; + } + else if (get_delegate().compare(event, {std::string("left"), std::string("right")}, false, true)) + { + spin_z = Spin::NONE; + } + else if (get_delegate().compare(event, "up")) + { + spin_x = Spin::NEGATIVE; + } + else if (get_delegate().compare(event, "down")) + { + spin_x = Spin::POSITIVE; + } + else if (get_delegate().compare(event, {std::string("up"), std::string("down")}, false, true)) + { + spin_x = Spin::NONE; + } +} + +void Squircle::update() +{ + /* apply rotation to transformation matrix */ + float rotation_speed = get_configuration()["rotation-speed"]; + transformation = glm::rotate(transformation, spin_z * rotation_speed, {0, 0, 1}); + transformation = glm::rotate(transformation, spin_x * rotation_speed, {1, 0, 0}); + glUniformMatrix4fv(transformation_uniform_location, 1, false, &transformation[0][0]); + /* viewport box will be used to tell GL where to draw */ + Box viewport_box = window_box(); + glDisable(GL_DEPTH_TEST); + /* paint the screen black */ + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + /* set uniform and activate texture */ + glUniform1i(base_texture_shader_location, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, base_texture_id); + /* set viewport to left side of screen for unmapped plane */ + glViewport(viewport_box.left(), viewport_box.top(), viewport_box.width() / 2, viewport_box.height()); + /* draws plane vertices and plane UV */ + glBindVertexArray(unmapped_vao); + glUniform1i(mode_uniform_location, mode == Mode::UNSQUIRCLE ? 2 : 0); + glDrawArrays(GL_TRIANGLES, 0, 6); + /* set viewport to right side of screen for mapped plane */ + glUseProgram(flat_program); + glViewport(viewport_box.cx(), viewport_box.top(), viewport_box.width() / 2, viewport_box.height()); + /* draws mapped plane vertices and plane UV */ + glBindVertexArray(mapped_vao); + glUniform1i(mode_uniform_location, mode == Mode::SQUIRCLE ? 1 : 0); + glDrawArrays(GL_TRIANGLE_FAN, 0, circle_vertices_count); + SDL_GL_SwapWindow(get_window()); +} diff --git a/demo/squircle/Squircle.hpp b/demo/squircle/Squircle.hpp new file mode 100644 index 0000000..678ab0a --- /dev/null +++ b/demo/squircle/Squircle.hpp @@ -0,0 +1,47 @@ +#ifndef Squircle_h_ +#define Squircle_h_ + +#include "glm/glm.hpp" +#include "SDL.h" +#include "Game.hpp" +#include "filesystem.hpp" + +class Squircle : public Game +{ + +private: + + enum Mode : bool + { + SQUIRCLE, + UNSQUIRCLE + }; + + enum Spin + { + NEGATIVE = -1, + NONE, + POSITIVE = 1 + }; + + GLuint base_texture_shader_location = 0, base_texture_id = 0, flat_program = 0, unmapped_vao = 0, mapped_vao = 0, + mode_uniform_location = 0, transformation_uniform_location = 0; + int circle_vertices_count = 0, image_index = 0; + Mode mode = Mode::SQUIRCLE; + glm::mat4 transformation = glm::mat4(1.0f); + Spin spin_z = Spin::NONE, spin_x = Spin::NONE; + + typedef Game super; + void load_gl_context(); + GLuint load_file_into_texture(fs::path) const; + void load_image_index(int); + void update(); + +public: + + Squircle(); + void respond(SDL_Event&); + +}; + +#endif diff --git a/demo/squircle/config.json b/demo/squircle/config.json new file mode 100644 index 0000000..65e5b99 --- /dev/null +++ b/demo/squircle/config.json @@ -0,0 +1,27 @@ +{ + "circle-resolution": 48, + "rotation-speed": 0.1, + "recording": + { + "screenshot-directory": "local/screenshots", + "video-directory": "local/video", + "enabled": true, + "write-mp4": true, + "video-frame-length": 33.333, + "max-video-memory": 2000, + "mp4-pixel-format": "yuv420p" + }, + "log": + { + "debug-to-stdout": false + }, + "configuration": + { + "auto-refresh": true + }, + "keys": + { + "next": "space", + "mode": "enter" + } +} diff --git a/demo/squircle/flat.frag b/demo/squircle/flat.frag new file mode 100644 index 0000000..2245f82 --- /dev/null +++ b/demo/squircle/flat.frag @@ -0,0 +1,51 @@ +#version 130 + +in vec2 frag_uv; +in vec2 original_coordinates; +in vec4 clip_coordinates; +uniform sampler2D base_texture; +uniform int mode; + +/* [-1, 1] normalized device coordinates to [0, 1] UV coordinates */ +vec2 ndc_to_uv(vec2 coordinates) +{ + return (coordinates + 1) / 2; +} + +/* coordinates in circle with radius <= 1 to box coordinates in [-1, 1] */ +vec2 circle_to_box(vec2 circle) +{ + float u = circle.x; + float v = circle.y; + float u_sq = pow(u, 2); + float v_sq = pow(v, 2); + float rt_2 = sqrt(2); + float x = .5 * sqrt(2 + 2 * u * rt_2 + u_sq - v_sq) - .5 * sqrt(2 - 2 * u * rt_2 + u_sq - v_sq); + float y = .5 * sqrt(2 + 2 * v * rt_2 - u_sq + v_sq) - .5 * sqrt(2 - 2 * v * rt_2 - u_sq + v_sq); + return vec2(x, y); +} + +/* box coordinates in [-1, 1] to coordinates in circle with radius <= 1 */ +vec2 box_to_circle(vec2 box) +{ + float u = box.x * sqrt(1 - .5 * pow(box.y, 2)); + float v = box.y * sqrt(1 - .5 * pow(box.x, 2)); + return vec2(u, v); +} + +void main() +{ + /* normalized device coordinates (coordinates in [-1, 1] space within viewport) */ + vec2 ndc = original_coordinates; + /* map to circle if requested */ + if (mode == 1) + { + ndc = circle_to_box(ndc); + } + else if (mode == 2) + { + ndc = box_to_circle(ndc); + } + /* translate ndc to uv space and get texel */ + gl_FragColor = texture(base_texture, ndc_to_uv(ndc)); +} diff --git a/demo/squircle/flat.vert b/demo/squircle/flat.vert new file mode 100644 index 0000000..bf1f6eb --- /dev/null +++ b/demo/squircle/flat.vert @@ -0,0 +1,21 @@ +#version 130 + +in vec2 position; +in vec2 vertex_uv; +uniform mat4 transformation; +uniform int mode; +out vec2 original_coordinates; +out vec4 clip_coordinates; +out vec2 frag_uv; + +void main(void) +{ + gl_Position = vec4(position, 0, 1); + if (mode > 0) + { + gl_Position *= transformation; + } + original_coordinates = position; + clip_coordinates = gl_Position; + frag_uv = vertex_uv; +} diff --git a/demo/squircle/images/2Ddim-L1norm-10site.png b/demo/squircle/images/2Ddim-L1norm-10site.png new file mode 100755 index 0000000000000000000000000000000000000000..1be50488c69d6929c8be856283c7c94d2faa663e GIT binary patch literal 9956 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 H@&5k+%&3yH literal 0 HcmV?d00001 diff --git a/src/Animation.cpp b/src/Animation.cpp index 45f57f9..8eb93b1 100644 --- a/src/Animation.cpp +++ b/src/Animation.cpp @@ -52,7 +52,7 @@ bool Animation::is_playing(bool include_delay) void Animation::update() { timer.update(); - if (playing and not paused and containing_object->is_active()) + if (playing && !paused && containing_object->is_active()) { if (delay > 0) { diff --git a/src/Box.cpp b/src/Box.cpp index d4c4312..d5cbb07 100644 --- a/src/Box.cpp +++ b/src/Box.cpp @@ -515,13 +515,6 @@ bool Box::collide(const Box& box, Box& overlap) const return collide(box, &overlap); } -/* Map any point in the bounds of a square with x and y between [-, radius] to a circle with specified radius. If - * center is specified, both the square and circle are centered at specified point. The default radius is 1, and the - * default center is (0, 0). Formula taken from http://squircular.blogspot.com/2015/09/mapping-circle-to-square.html */ -glm::vec2 Box::map_point_to_circle(const glm::vec2& point, float radius, const glm::vec2& center) const -{ -} - /* Return the string representation of a Box "{left, top, width, height}" */ std::string Box::string() const { diff --git a/src/Box.hpp b/src/Box.hpp index 406571e..53afbe6 100644 --- a/src/Box.hpp +++ b/src/Box.hpp @@ -77,7 +77,6 @@ public: bool collide(const Segment&, glm::vec2&) const; bool collide(const Box&, Box* = nullptr) const; bool collide(const Box&, Box&) const; - glm::vec2 map_point_to_circle(const glm::vec2&, float, const glm::vec2&) const; virtual std::string class_name() const { return "Box"; } std::string string() const; diff --git a/src/Configuration.cpp b/src/Configuration.cpp index c6d3e49..29429d0 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -4,17 +4,20 @@ #include "Configuration.hpp" -Configuration::Configuration(Node *parent) : Configuration(parent, "config.json") {} - +/* Initialize a Configuration object. The path argument is the location where the config file is stored. + * If there is no file located at the path, it will be created if the write method is called. System level + * default assignments defined in this file can be added to and overwritten by user supplied JSON file at + * the specified path or at a path passed to the load function. */ Configuration::Configuration(Node *parent, fs::path path) : Node(parent) { config_path = path; set_defaults(); load(); - merge(); + auto_refresher.set_frame_length(config["configuration"]["auto-refresh-interval"].get()); + auto_refresh(config["configuration"]["auto-refresh"]); } -/* Fill the system level config JSON with default values set by the framework */ +/* Fill the system level config JSON dict with default values set by the framework */ void Configuration::set_defaults() { sys_config["keys"] = { @@ -34,14 +37,14 @@ void Configuration::set_defaults() sys_config["input"] = { {"suppress-any-key-on-mods", true}, {"system-any-key-ignore-commands", {"fullscreen", "screenshot", "toggle-framerate", "record", "quit"}}, - {"any-key-ignore-commands", {}}, + {"any-key-ignore-commands", nlohmann::json::array()}, {"default-unsuppress-delay", 700}, {"ignore-repeat-keypress", true} }; sys_config["display"] = { {"dimensions", {640, 480}}, {"framerate", 60}, - {"title", "sfw"}, + {"title", "[SPACE BOX]"}, {"debug", false}, {"show-cursor", false}, {"render-test-spacing", 2}, @@ -83,41 +86,90 @@ void Configuration::set_defaults() {"info-file-name", "log.txt"}, {"debug-file-name", "debug_log.txt"} }; + sys_config["configuration"] = { + {"auto-refresh", false}, + {"auto-refresh-interval", 1000} + }; + config = sys_config; } +/* Load the configuration file at path */ +void Configuration::load(fs::path path) +{ + /* read contents of path into the game level config JSON dict */ + if (fs::exists(path)) + { + std::ifstream contents(path); + contents >> user_config; + /* store modification time */ + config_file_modification_time = fs::last_write_time(path); + /* merge into the full config JSON dict */ + merge(); + } +} + +/* Load the configuration file at Configuration::config_path */ void Configuration::load() { load(config_path); } -void Configuration::load(fs::path path) -{ - if (fs::exists(path)) - { - std::ifstream contents(path); - contents >> game_config; - } -} - +/* Merge the system level config JSON dict (hard-coded in this file) with the user level config JSON + * dict (loaded from disk by the load function) */ void Configuration::merge() { - config = sys_config; - if (not game_config.empty()) + if (not user_config.empty()) { - for (auto& section: game_config.items()) + /* loop over first level key/value pairs */ + for (auto& item: user_config.items()) { - config[section.key()].update(game_config[section.key()]); + /* if the value is an object (dict), merge it into the config, overwriting keys already in the config */ + if (item.value().is_object()) + { + config[item.key()].update(item.value()); + } + /* otherwise just assign config key to this value */ + else + { + config[item.key()] = item.value(); + } } } } +/* Set auto refresh to on or off */ +void Configuration::auto_refresh(bool on) +{ + on ? auto_refresher.play() : auto_refresher.pause(); +} + +/* Refresh the config contents by calling the default load function */ +void Configuration::refresh() +{ + if (fs::exists(config_path) && fs::last_write_time(config_path) > config_file_modification_time) + { + std::ostringstream message; + message << "config file modified, reloading " << config_path; + debug(message.str()); + load(); + } +} + +/* Write configuration to specified path in JSON format */ +void Configuration::write(fs::path path) +{ + std::ofstream output(path); + output << std::setw(tab_width) << user_config << std::endl; +} + +/* Write configuration to config_path (set at initialization) */ void Configuration::write() { write(config_path); } -void Configuration::write(fs::path path) +/* Updates the auto refresher */ +void Configuration::update() { - std::ofstream output(path); - output << std::setw(tab_width) << game_config << std::endl; + auto_refresher.update(); } diff --git a/src/Configuration.hpp b/src/Configuration.hpp index 833927f..fc43b43 100644 --- a/src/Configuration.hpp +++ b/src/Configuration.hpp @@ -2,28 +2,41 @@ #define Configuration_h_ #include "json/json.hpp" - #include "filesystem.hpp" #include "Node.hpp" +#include "Animation.hpp" -struct Configuration : Node +class Configuration : public Node { - nlohmann::json sys_config, game_config, config; + +private: + + nlohmann::json sys_config, user_config; fs::path config_path; int tab_width = 4; + Animation auto_refresher = Animation(&Configuration::refresh, this); + fs::file_time_type config_file_modification_time; - Configuration(Node*); - Configuration(Node*, fs::path); void set_defaults(); - void load(); - void load(fs::path path); void merge(); - void write(); + +public: + + nlohmann::json config; + + Configuration(Node*, fs::path = "config.json"); + void load(fs::path path); + void load(); + void auto_refresh(bool); + void refresh(); void write(fs::path path); + void write(); + void update(); virtual std::string class_name() const { return "Configuration"; } }; +/* Extend GLM so nlohmann::json can read and write glm::vec2 */ namespace glm { template @@ -40,6 +53,7 @@ namespace glm } } +/* Extend std::filesystem so nlohmann::json can read and write std::filesystem::path */ #if defined(__MINGW32__) namespace std::experimental::filesystem #else diff --git a/src/Game.cpp b/src/Game.cpp index b9d882e..b2e4084 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -28,7 +28,7 @@ SDL_Surface* FramerateIndicator::get_surface() void FramerateIndicator::refresh() { - if (!is_hidden() && get_root()->bp_mono_font != NULL) + if (!is_hidden() && get_root()->bp_mono_font != nullptr) { unload(); SDL_Surface* surface = get_surface(); @@ -133,7 +133,7 @@ Game::Game() SDL_Log("initialized SDL ttf %d.%d.%d", SDL_TTF_MAJOR_VERSION, SDL_TTF_MINOR_VERSION, SDL_TTF_PATCHLEVEL); } - if ((bp_mono_font = TTF_OpenFont("BPmono.ttf", 14)) == NULL) + if ((bp_mono_font = TTF_OpenFont("BPmono.ttf", 14)) == nullptr) { print_error("Could not load BPmono.ttf"); } @@ -671,15 +671,13 @@ void Game::frame(float ticks) // std::cout << ", last_frame_length: " << last_frame_length << " [rendering frame]"; if (last_frame_length < 1000) { - // if (!is_gl_context) - // { - recorder.update(); - // } + recorder.update(); delegate.dispatch(); audio.update(); input.unsuppress_animation.update(); update(); framerate_indicator.update(); + configuration.update(); if (!is_gl_context) { SDL_SetRenderTarget(renderer, nullptr); diff --git a/src/Game.hpp b/src/Game.hpp index dd1fff9..99acf5b 100644 --- a/src/Game.hpp +++ b/src/Game.hpp @@ -57,6 +57,12 @@ private: public: + /* two-state enum equivalent to a boolean that can improve readability depending on the context */ + enum class Flip { + OFF, + ON + }; + /* Prevent an instance of this class from being copied or moved */ Game(const Game&) = delete; Game& operator=(const Game&) = delete; @@ -77,7 +83,7 @@ public: Input input = Input(this); Audio audio = Audio(this); std::vector frame_length_history; - TTF_Font* bp_mono_font = NULL; + TTF_Font* bp_mono_font = nullptr; FramerateIndicator framerate_indicator = FramerateIndicator(this); Game(); diff --git a/src/Segment.cpp b/src/Segment.cpp index 5fbc7ef..02fe6c7 100644 --- a/src/Segment.cpp +++ b/src/Segment.cpp @@ -15,7 +15,7 @@ Segment::Segment(const glm::vec2& location) : Segment(location, location) {}; Segment::Segment(const Box& start, const Box& end) : Segment(start.center(), end.center()) {}; -inline glm::vec2 Segment::start() const +glm::vec2 Segment::start() const { return start_; } diff --git a/src/Segment.hpp b/src/Segment.hpp index 10de75f..9c24cea 100644 --- a/src/Segment.hpp +++ b/src/Segment.hpp @@ -24,7 +24,7 @@ public: Segment(const glm::vec2&); Segment(const Box&, const Box&); Segment(const Sprite&, const Sprite&); - inline glm::vec2 start() const; + glm::vec2 start() const; void start(const glm::vec2&); glm::vec2 end() const; void end(const glm::vec2&); diff --git a/src/extension.cpp b/src/extension.cpp index 07683a1..fb02688 100644 --- a/src/extension.cpp +++ b/src/extension.cpp @@ -1,6 +1,7 @@ #include "Pixels.hpp" #include "extension.hpp" +/* Edit a vector in place, giving it the specified magnitude while maintaining the direction */ void sfw::set_magnitude(glm::vec2& vector, float magnitude) { vector = glm::normalize(vector) * magnitude; diff --git a/src/extension.hpp b/src/extension.hpp index c7d9de5..2e8aef8 100644 --- a/src/extension.hpp +++ b/src/extension.hpp @@ -40,7 +40,6 @@ namespace sfw glm::vec2 point_on_circle(float, float = 1.0f); void points_on_circle(std::vector&, int, float = 1.0f, const glm::vec2& = {0, 0}, float = 0.0f); std::vector points_on_circle(int, float = 1.0f, const glm::vec2& = {0, 0}, float = 0.0f); - glm::vec2 map_rectangle_xy_to_circle(const glm::vec2&, float = 1.0f, const glm::vec2& = {0, 0}); Box get_texture_box(SDL_Texture*); glm::vec2 fit_and_preserve_aspect(const glm::vec2&, const glm::vec2&); std::vector> get_blinds_boxes(glm::vec2, float = 0.05f, int = 4);