From f47c080d72e56033f60319ff831014789bd5076a Mon Sep 17 00:00:00 2001 From: frank <420@shampoo.ooo> Date: Tue, 8 Nov 2022 17:16:05 -0500 Subject: [PATCH] box demo android support, android file to string for reading files inside APK --- LICENSE.txt | 2 +- README.md | 22 +- demo/box/.gitignore | 5 + demo/{cube => box}/BPmono.ttf | 0 demo/box/BoxDemo.cpp | 235 +++++++++ demo/box/Makefile | 247 ++++++++++ demo/box/Model.cpp | 196 ++++++++ demo/box/Model.hpp | 105 ++++ demo/{cube => box}/config.json | 13 +- demo/box/index.html | 31 ++ demo/{cube => box}/resource/Ag.ogg | Bin demo/{cube => box}/resource/Field.mp3 | Bin demo/box/resource/tile.png | Bin 0 -> 923 bytes demo/box/shaders/all.frag | 24 + demo/box/shaders/flat.vert | 28 ++ demo/box/shaders/triangle.vert | 30 ++ demo/browser_webcam_test/Makefile | 12 +- .../browser_webcam_test.cpp | 22 +- demo/browser_webcam_test/index.html | 17 +- demo/cube/Cube.cpp | 454 ------------------ demo/cube/Cube.hpp | 65 --- demo/cube/Makefile | 175 ------- demo/cube/resource/Field.png | Bin 5654 -> 0 bytes demo/cube/resource/background.png | Bin 3878 -> 0 bytes demo/cube/resource/shrooms/anxiety.png | Bin 282 -> 0 bytes demo/cube/resource/shrooms/lapis-lazuli.png | Bin 274 -> 0 bytes demo/cube/resource/shrooms/mr-plummy.png | Bin 258 -> 0 bytes demo/cube/resource/shrooms/poison-shibake.png | Bin 282 -> 0 bytes demo/cube/resource/shrooms/shaver-buzz.png | Bin 251 -> 0 bytes demo/cube/resource/shrooms/sombrero.png | Bin 307 -> 0 bytes demo/cube/resource/shrooms/spider-house.png | Bin 289 -> 0 bytes demo/cube/resource/shrooms/teethmouth.png | Bin 281 -> 0 bytes demo/cube/resource/shrooms/terraformer.png | Bin 270 -> 0 bytes demo/cube/resource/tile.png | Bin 703 -> 0 bytes demo/cube/shaders/flat.frag | 10 - demo/cube/shaders/flat.vert | 15 - demo/cube/shaders/triangle.frag | 10 - demo/cube/shaders/triangle.vert | 17 - demo/fill_screen/Makefile | 5 +- demo/fill_screen/fill_screen.cpp | 23 +- icon/static_alt.png | Bin 0 -> 128663 bytes icon/static_solid.png | Bin 0 -> 15771 bytes src/Attributes.cpp | 19 +- src/Attributes.hpp | 236 +++++---- src/Configuration.cpp | 25 +- src/Configuration.hpp | 5 +- src/Game.cpp | 76 ++- src/Game.hpp | 38 +- src/Log.cpp | 18 +- src/Log.hpp | 35 +- src/Texture.cpp | 18 +- src/Texture.hpp | 45 +- src/android/revise_skeleton.sh | 9 +- src/extension.cpp | 91 +++- src/extension.hpp | 33 +- src/filesystem.hpp | 7 - 56 files changed, 1386 insertions(+), 1032 deletions(-) create mode 100644 demo/box/.gitignore rename demo/{cube => box}/BPmono.ttf (100%) create mode 100644 demo/box/BoxDemo.cpp create mode 100644 demo/box/Makefile create mode 100644 demo/box/Model.cpp create mode 100644 demo/box/Model.hpp rename demo/{cube => box}/config.json (71%) create mode 100644 demo/box/index.html rename demo/{cube => box}/resource/Ag.ogg (100%) rename demo/{cube => box}/resource/Field.mp3 (100%) create mode 100644 demo/box/resource/tile.png create mode 100644 demo/box/shaders/all.frag create mode 100644 demo/box/shaders/flat.vert create mode 100644 demo/box/shaders/triangle.vert delete mode 100644 demo/cube/Cube.cpp delete mode 100644 demo/cube/Cube.hpp delete mode 100644 demo/cube/Makefile delete mode 100644 demo/cube/resource/Field.png delete mode 100644 demo/cube/resource/background.png delete mode 100644 demo/cube/resource/shrooms/anxiety.png delete mode 100644 demo/cube/resource/shrooms/lapis-lazuli.png delete mode 100644 demo/cube/resource/shrooms/mr-plummy.png delete mode 100644 demo/cube/resource/shrooms/poison-shibake.png delete mode 100644 demo/cube/resource/shrooms/shaver-buzz.png delete mode 100644 demo/cube/resource/shrooms/sombrero.png delete mode 100644 demo/cube/resource/shrooms/spider-house.png delete mode 100644 demo/cube/resource/shrooms/teethmouth.png delete mode 100644 demo/cube/resource/shrooms/terraformer.png delete mode 100644 demo/cube/resource/tile.png delete mode 100644 demo/cube/shaders/flat.frag delete mode 100644 demo/cube/shaders/flat.vert delete mode 100644 demo/cube/shaders/triangle.frag delete mode 100644 demo/cube/shaders/triangle.vert create mode 100644 icon/static_alt.png create mode 100644 icon/static_solid.png diff --git a/LICENSE.txt b/LICENSE.txt index c422805..48c7e0e 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2021 Nuggets Select <420@shampoo.ooo> +Copyright (c) 2021-22 shampoo.ooo This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of diff --git a/README.md b/README.md index ab98a5a..159232e 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ SPACEBOX ======== -
-         /\         +------------------------------------------------------------+ 
-    ____/  \____   /| zlib/MIT/Unlicenced game framework licensed to freely use, |
-    \          /  / | copy, modify and sell without restriction                  |
-  +--\ ^__^   /--+  |                                                            |
-  | ~/        \~ |  | Learn more about *SPACEBOX* at [shampoo.ooo][]             |
-  | ~~~~~~~~~~~~ |  +------------------------------------------------------------+
-  | SPACE ~~~~~  | /
-  |  ~~~~~~~ BOX |/
-  +--------------+   
- ![logo](icon/static.png) +
+       /\         +------------------------------------------------------+ 
+  ____/  \____   /| - Open source game framework licensed to freely use, |
+  \          /  / |   copy, modify and sell without restriction          |
++--\ ^__^   /--+  |                                                      |
+| ~/        \~ |  | - created for              |
+| ~~~~~~~~~~~~ |  +------------------------------------------------------+
+| SPACE ~~~~~  | /
+|  ~~~~~~~ BOX |/
++--------------+  
+ *SPACEBOX* is a C++ framework that makes creating cross-platform games and other interactive applications easier and faster by providing an added layer of abstraction between SDL + OpenGL and the project. Users can start a project by extending only a single function, the Game class's update function. Using a standard Makefile that can be taken from the examples, the framework can export the same code to multiple platforms like, in order of current stability, Linux, Web, Android, OS X, Windows, and Raspberry Pi. diff --git a/demo/box/.gitignore b/demo/box/.gitignore new file mode 100644 index 0000000..9dc24a1 --- /dev/null +++ b/demo/box/.gitignore @@ -0,0 +1,5 @@ +box +*_log.txt +*.data +box_demo.js +*.wasm diff --git a/demo/cube/BPmono.ttf b/demo/box/BPmono.ttf similarity index 100% rename from demo/cube/BPmono.ttf rename to demo/box/BPmono.ttf diff --git a/demo/box/BoxDemo.cpp b/demo/box/BoxDemo.cpp new file mode 100644 index 0000000..6e58335 --- /dev/null +++ b/demo/box/BoxDemo.cpp @@ -0,0 +1,235 @@ +/*!
+ *        /\         +------------------------------------------------------+ 
+ *   ____/  \____   /| - Open source game framework licensed to freely use, |
+ *   \          /  / |   copy, modify and sell without restriction          |
+ * +--\ ^__^   /--+  |                                                      |
+ * | ~/        \~ |  | - created for              |
+ * | ~~~~~~~~~~~~ |  +------------------------------------------------------+
+ * | SPACE ~~~~~  | /
+ * |  ~~~~~~~ BOX |/
+ * +--------------+                                                    
+ * + * Display a rotating box with a space texture and gradient background using SDL, OpenGL, and Spacebox. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "glm/glm.hpp" +#include "Game.hpp" +#include "GLObject.hpp" +#include "VBO.hpp" +#include "Model.hpp" + +struct BoxDemo : Game +{ + +private: + + Model box; + Plane background; + bool right_active = false, down_active = false, left_active = false, up_active = false, is_writing_audio = true; + SDL_Event event; + sb::Texture fake; + GLuint mvp_uniform_id, flat_program, world_program; + glm::mat4 projection, view, model = glm::mat4(1.0f), mvp; + sb::VAO vao; + sb::VBO vbo; + +public: + + BoxDemo() + { + Mix_Music *music = Mix_LoadMUS("resource/Field.mp3"); + Mix_PlayMusic(music, -1); + load_gl_context(); + delegate.subscribe(&BoxDemo::respond, this); + delegate.subscribe(&BoxDemo::respond, this, SDL_MOUSEBUTTONDOWN); + } + + void load_gl_context() + { + Game::load_gl_context(); + + /* Generate a vertex array object ID, bind it as current (requirement of OpenGL) */ + vao.generate(); + vao.bind(); + + /* Generate ID for the vertex buffer object that will hold all vertex data. Using one buffer for all attributes, data + * will be copied in one after the other. */ + vbo.generate(); + vbo.bind(); + + /* + v0-v1-v2 (front) + v2-v3-v0 + v0-v3-v4 (right) + v4-v5-v0 + v0-v5-v6 (top) + v6-v1-v0 + v1-v6-v7 (left) + v7-v2-v1 + v7-v4-v3 (bottom) + v3-v2-v7 + v4-v7-v6 (back) + v6-v5-v4 + */ + box = Model({ + {"position", { + {1, 1, 1}, {-1, 1, 1}, {-1,-1, 1}, + {-1,-1, 1}, {1,-1, 1}, {1, 1, 1}, + {1, 1, 1}, {1,-1, 1}, {1,-1,-1}, + {1,-1,-1}, {1, 1,-1}, {1, 1, 1}, + {1, 1, 1}, {1, 1,-1}, {-1, 1,-1}, + {-1, 1,-1}, {-1, 1, 1}, {1, 1, 1}, + {-1, 1, 1}, {-1, 1,-1}, {-1,-1,-1}, + {-1,-1,-1}, {-1,-1, 1}, {-1, 1, 1}, + {-1,-1,-1}, {1,-1,-1}, {1,-1, 1}, + {1,-1, 1}, {-1,-1, 1}, {-1,-1,-1}, + {1,-1,-1}, {-1,-1,-1}, {-1, 1,-1}, + {-1, 1,-1}, {1, 1,-1}, {1,-1,-1} + }}, + {"uv", { + {1, 1}, {0, 1}, {0, 0}, + {0, 0}, {1, 0}, {1, 1}, + {0, 1}, {0, 0}, {1, 0}, + {1, 0}, {1, 1}, {0, 1}, + {0, 1}, {0, 0}, {1, 0}, + {1, 0}, {1, 1}, {0, 1}, + {1, 1}, {0, 1}, {0, 0}, + {0, 0}, {1, 0}, {1, 1}, + {0, 0}, {1, 0}, {1, 1}, + {1, 1}, {0, 1}, {0, 0}, + {0, 0}, {1, 0}, {1, 1}, + {1, 1}, {0, 1}, {0, 0} + }} + }); + box.texture(sb::Texture()); + box.texture().load("resource/tile.png"); + + /* Create a 1x1 white texture */ + glm::vec2 fake_size = {1, 1}; + unsigned char fake_color[4] = {255, 255, 255, 255}; + fake.generate(fake_size); + fake.load(fake_color, fake_size); + + /* Add color attributes and a full white texture */ + background.attributes({ + {0.2f, 0.6f, 0.8f}, {0.2f, 0.6f, 0.8f}, {1.0f, 1.0f, 0.0f}, + {0.2f, 0.6f, 0.8f}, {1.0f, 1.0f, 0.0f}, {1.0f, 1.0f, 0.0f} + }, "color"); + background.texture(fake); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + /* Create a projection matrix */ + glm::ivec2 resolution = get_display().window_size(); + float aspect = resolution.x / (float) resolution.y; + projection = glm::perspective(glm::radians(40.0f) / aspect, aspect, 0.1f, 100.0f); + view = glm::lookAt(glm::vec3{4.0f, 3.0f, 3.0f}, glm::vec3{0.0f, 0.65f, 0.0f}, glm::vec3{0.0f, 1.0f, 0.0f}); + + /* Load shader programs, associate model attributes with shader variables, read uniform IDs */ + GLuint triangle_vertex_shader = load_shader("shaders/triangle.vert", GL_VERTEX_SHADER); + GLuint fragment_shader = load_shader("shaders/all.frag", GL_FRAGMENT_SHADER); + GLuint flat_vertex_shader = load_shader("shaders/flat.vert", GL_VERTEX_SHADER); + + world_program = glCreateProgram(); + glAttachShader(world_program, triangle_vertex_shader); + glAttachShader(world_program, fragment_shader); + box.attributes("position")->bind(3, world_program, "in_Position"); + box.attributes("uv")->bind(4, world_program, "vertexUV"); + link_shader(world_program); + + flat_program = glCreateProgram(); + glAttachShader(flat_program, flat_vertex_shader); + glAttachShader(flat_program, fragment_shader); + background.attributes("position")->bind(0, flat_program, "in_Position"); + background.attributes("uv")->bind(1, flat_program, "vertexUV"); + background.attributes("color")->bind(2, flat_program, "in_Color"); + link_shader(flat_program); + + mvp_uniform_id = glGetUniformLocation(world_program, "MVP"); + + /* Fill VBO with attribute data */ + vbo.allocate(background.size() + box.size(), GL_STATIC_DRAW); + vbo.add(*Plane::position); + vbo.add(*Plane::uv); + vbo.add(*background.attributes("color")); + vbo.add(*box.attributes("position")); + vbo.add(*box.attributes("uv")); + sb::Log::gl_errors("after filling VBO"); + + for (GLuint program : {flat_program, world_program}) + { + /* Set the active texture unit to #0, Get the texture uniform from the shader and set it use texture #0. See + * https://www.khronos.org/opengl/wiki/Sampler_(GLSL)#Binding_textures_to_samplers */ + glUseProgram(program); + glActiveTexture(GL_TEXTURE0); + glUniform1i(glGetUniformLocation(program, "myTextureSampler"), 0); + } + + glDepthFunc(GL_LESS); + } + + void respond(SDL_Event& event) + { + if (delegate.compare(event, "play-sound") || event.type == SDL_MOUSEBUTTONDOWN) + { + Mix_Chunk* music = Mix_LoadWAV("resource/Ag.ogg"); + Mix_PlayChannel(-1, music, 0); + } + } + + void update() + { + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glDisable(GL_DEPTH_TEST); + + /* Draw background */ + glUseProgram(flat_program); + box.disable(); + background.enable(); + background.texture().bind(); + glDrawArrays(GL_TRIANGLES, 0, background.attributes("position")->count()); + + /* Rotate projection matrix */ + float rotation = .0005f * get_frame_length(); + model = glm::rotate(model, rotation, glm::vec3(0.0f, 1.0f, 0.0f)); + mvp = projection * view * model; + + glUseProgram(world_program); + + /* Pass the MVP matrix to the triangle vertex shader using the memory address of the top left value */ + glUniformMatrix4fv(mvp_uniform_id, 1, GL_FALSE, &mvp[0][0]); + + /* Set the color attribute to white so the texture will not be modified */ + glVertexAttrib3f(glGetAttribLocation(world_program, "in_Color"), 1, 1, 1); + + /* Draw the box */ + glEnable(GL_DEPTH_TEST); + background.disable(); + box.enable(); + box.texture().bind(); + glDrawArrays(GL_TRIANGLES, 0, box.attributes("position")->count()); + SDL_GL_SwapWindow(window()); + } + +}; + +int main() +{ + BoxDemo demo = BoxDemo(); + demo.run(); + demo.quit(); + return 0; +} diff --git a/demo/box/Makefile b/demo/box/Makefile new file mode 100644 index 0000000..654850b --- /dev/null +++ b/demo/box/Makefile @@ -0,0 +1,247 @@ +# /\ +------------------------------------------------------+ +# ____/ \____ /| - Open source game framework licensed to freely use, | +# \ / / | copy, modify and sell without restriction | +# +--\ ^__^ /--+ | | +# | ~/ \~ | | - created for | +# | ~~~~~~~~~~~~ | +------------------------------------------------------+ +# | SPACE ~~~~~ | / +# | ~~~~~~~ BOX |/ +# +--------------+ +# +# These are build targets for the box demo for Linux, Android, and web. Other platforms haven't been tested in a while +# and won't work without significant editing. Modify variables as necessary to match the system this is running on and +# run `make box`, `make build/android/ooo.shampoo.box/app_debug.apk`, or `make box_demo.js` in the current directory. + +####################### +# Location parameters # +####################### + +# Location of source files for the demo +SRC_DIR := ./ + +# Locations of [SPACEBOX] source and dependencies required to be compiled from source. These locations are configured to match +# the structure of the [SPACEBOX] 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 +CXX := 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 # +############################# + +SDL_CFLAGS = $(shell $(SDLCONFIG) --cflags) +SDL_LFLAGS := $(shell $(SDLCONFIG) --libs) +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 := BoxDemo.o $(SRC_H_FILES:.hpp=.o) + +################################################################# +# Targets for building [SPACEBOX], 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 Log.hpp) +$(SB_SRC_DIR)Node.o: $(addprefix $(SB_SRC_DIR),Game.hpp Configuration.hpp Delegate.hpp Display.hpp Input.hpp Box.hpp Audio.hpp Log.hpp) +$(SB_SRC_DIR)Sprite.o: $(addprefix $(SB_SRC_DIR),Node.hpp Game.hpp Box.hpp Animation.hpp Color.hpp extension.hpp Pixels.hpp Log.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 Log.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 Log.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 Log.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 Log.hpp utility.hpp) +$(SB_SRC_DIR)Audio.o: $(addprefix $(SB_SRC_DIR),Node.hpp Display.hpp Configuration.hpp Box.hpp filesystem.hpp extension.hpp) +$(SB_SRC_DIR)GLObject.o: $(addprefix $(SB_SRC_DIR),Log.hpp) +$(SB_SRC_DIR)Texture.o: $(addprefix $(SB_SRC_DIR),GLObject.hpp filesystem.hpp Log.hpp) +$(SB_SRC_DIR)VBO.o: $(addprefix $(SB_SRC_DIR),Log.hpp GLObject.hpp Attributes.hpp extension.hpp) +$(SB_SRC_DIR)Attributes.o: $(addprefix $(SB_SRC_DIR),Log.hpp extension.hpp) +$(SRC_DIR)Model.o: $(addprefix $(SB_SRC_DIR),extension.hpp Attributes.hpp Texture.hpp utility.hpp) +$(SRC_DIR)BoxDemo.o: $(SRC_H_FILES) $(SB_H_FILES) +%.o: %.cpp %.hpp + $(CXX) $(CXXFLAGS) $< -c -o $@ + +######### +# Linux # +######### + +box: CFLAGS = -Wall -Wextra -g -O3 -c -I$(SB_LIB_DIR) -I$(SB_SRC_DIR) $(SDL_CFLAGS) +box: CXXFLAGS = $(CFLAGS) --std=c++17 +box: LFLAGS = $(SDL_LFLAGS) -Wl,--enable-new-dtags -lpthread -lGL -lGLESv2 -lSDL2_image -lSDL2_ttf -lSDL2_mixer -lstdc++fs +box: $(GLEW_DIR)glew.o $(addprefix $(SDLGFX2_DIR),SDL2_rotozoom.o SDL2_gfxPrimitives.o) $(SB_O_FILES) $(SRC_O_FILES) + $(CREATE_FONT_SYMLINK) + $(CXX) $^ $(LFLAGS) -D__LINUX__ -o box + +####### +# Web # +####### + +# Use Emscripten to output JavaScript and an HTML index page for running in the browser + +EMSCRIPTENHOME = $(HOME)/ext/software/emsdk/upstream/emscripten +EMSCRIPTEN_CFLAGS = -O1 -Wall -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS="['png', 'jpg']" -s USE_SDL_TTF=2 -s USE_SDL_MIXER=2 \ + -I $(SB_LIB_DIR) -I $(SB_SRC_DIR) +EMSCRIPTEN_LFLAGS = -s MIN_WEBGL_VERSION=2 -s EXPORTED_FUNCTIONS="['_main']" -s ALLOW_MEMORY_GROWTH=1 -s FULL_ES3=1 -s LLD_REPORT_UNDEFINED +EMSCRIPTEN_PRELOADS = --preload-file "BPmono.ttf"@/ --preload-file "shaders/"@/"shaders/" --preload-file "config.json"@/ --preload-file "resource/"@/"resource/" + +box_demo.js: CC = $(EMSCRIPTENHOME)/emcc +box_demo.js: CXX = $(EMSCRIPTENHOME)/em++ +box_demo.js: CFLAGS = $(EMSCRIPTEN_CFLAGS) +box_demo.js: CXXFLAGS = $(CFLAGS) --std=c++17 +box_demo.js: $(addprefix $(SDLGFX2_DIR),SDL2_rotozoom.o SDL2_gfxPrimitives.o) $(SB_O_FILES) $(SRC_O_FILES) index.html config.json resource/* shaders/* + $(CREATE_FONT_SYMLINK) + $(CXX) $(filter %.o,$^) $(CXXFLAGS) $(EMSCRIPTEN_LFLAGS) $(EMSCRIPTEN_PRELOADS) -o box_demo.js + +########### +# Android # +########### + +# Detailed info on how this build target was created is in README.md at the root of the repository. It requires the Android SDK and the source packages +# for SDL, SDL_image, SDL_mixer, and SDL_ttf. The paths below should be customized. The icon creation requires Imagemagick's convert tool from +# . The project source files are assumed to be just `$(SRC_DIR)/*.cpp`. If that is not the case, the revise_skeleton.sh script +# must be edited. + +SDL_SRC := $(HOME)/ext/software/SDL2-2.24.0-android +SDL_IMAGE_SRC := $(HOME)/ext/software/SDL2_image-2.6.2-android +SDL_MIXER_SRC := $(HOME)/ext/software/SDL2_mixer-2.6.2-android +SDL_TTF_SRC := $(HOME)/ext/software/SDL2_ttf-2.20.1-android +SDL_ANDROID_PROJECT := $(SDL_SRC)/android-project +ANDROID_MK := app/jni/src/Android.mk +ANDROID_APP_MK := app/jni/Application.mk +ANDROID_MANIFEST := app/src/main/AndroidManifest.xml +ANDROID_SDK := $(HOME)/local/Android +ANDROID_PACKAGE := ooo.shampoo.spacebox +ANDROID_BUILD_DIR := build/android/$(ANDROID_PACKAGE) +ANDROID_CLASS := BoxDemo +ANDROID_CLASS_DIR := app/src/main/java/$(subst .,/,$(ANDROID_PACKAGE)) + +# The skeleton for the Android build is based on the SDL android-project. The libraries are symlinked in. If the script that rewrites the skeleton +# has changed, start with a fresh skeleton. +$(ANDROID_BUILD_DIR): $(SDL_SRC)/android-project/ $(SB_SRC_DIR)/android/revise_skeleton.sh + -mkdir -p $(ANDROID_BUILD_DIR) + rsync -ar $(SDL_SRC)/android-project/ $(ANDROID_BUILD_DIR) + ln -nsf $(SDL_SRC) $(ANDROID_BUILD_DIR)/app/jni/SDL + ln -nsf $(SDL_IMAGE_SRC) $(ANDROID_BUILD_DIR)/app/jni/SDL2_image + ln -nsf $(SDL_MIXER_SRC) $(ANDROID_BUILD_DIR)/app/jni/SDL2_mixer + ln -nsf $(SDL_TTF_SRC) $(ANDROID_BUILD_DIR)/app/jni/SDL2_ttf + $(SB_SRC_DIR)/android/revise_skeleton.sh $(ANDROID_PACKAGE) $(ANDROID_BUILD_DIR) $(ANDROID_MANIFEST) $(ANDROID_APP_MK) $(ANDROID_MK) $(ANDROID_CLASS) \ + $(ANDROID_APP_NAME) $(ANDROID_MIN_TARGET) $(ANDROID_NDK) "Box Demo" "18" "22.1.7171670" $(SB_SRC_DIR) $(SB_LIB_DIR) $(SRC_DIR) + +# Extend the SDLActivity class +$(ANDROID_BUILD_DIR)/$(ANDROID_CLASS_DIR)/$(ANDROID_CLASS).java: $(SB_SRC_DIR)/android/main_class.sh + $(SB_SRC_DIR)/android/main_class.sh $(ANDROID_PACKAGE) $(ANDROID_CLASS) $(ANDROID_BUILD_DIR)/$(ANDROID_CLASS_DIR) + +# Generate icon +$(ANDROID_BUILD_DIR)/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: $(SB_SRC_DIR)/android/generate_icon.sh $(SB_DIR)/icon + $(SB_SRC_DIR)/android/generate_icon.sh $(ANDROID_BUILD_DIR) "../../icon/foreground.png" "../../icon/background.png" + +# Custom assets +$(ANDROID_BUILD_DIR)/app/src/main/assets: config.json resource/* shaders/* + -mkdir -p $(ANDROID_BUILD_DIR)/app/src/main/assets + rsync -ar resource shaders config.json $(ANDROID_BUILD_DIR)/app/src/main/assets + +# Run gradle and generate an APK +$(ANDROID_BUILD_DIR)/app-debug.apk: $(ANDROID_BUILD_DIR) $(ANDROID_BUILD_DIR)/$(ANDROID_CLASS_DIR)/$(ANDROID_CLASS).java \ + $(ANDROID_BUILD_DIR)/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml $(ANDROID_BUILD_DIR)/app/src/main/assets + ANDROID_SDK_ROOT=$(ANDROID_SDK) $(ANDROID_BUILD_DIR)/gradlew -p $(ANDROID_BUILD_DIR) build + ln -nsf app/build/outputs/apk/debug/app-debug.apk $(ANDROID_BUILD_DIR) + ln -nsf app/build/outputs/apk/debug/app-release-unsigned.apk $(ANDROID_BUILD_DIR) + +###################### +# Clean object files # +###################### + +clean: + -find $(SRC_DIR) -iname "*.o" -delete + rm -f BPmono.ttf + rm -f box + +clean-all: clean + -find $(SB_SRC_DIR) -iname "*.o" -delete + -find $(SB_LIB_DIR) -iname "*.o" -delete + +.PHONY = clean clean-all + +############# +# compiledb # +############# + +# Generate a clang JSON compilation database file. This can be useful for example for code completion. It requires a +# compiledb binary (https://pypi.org/project/compiledb/). It should be generated manually every time a file is added, +# renamed, or the compilation flags change. The generated database is based on the Linux build. + +PATH_TO_COMPILEDB = $(HOME)/.local/bin/compiledb +compile_commands.json: + -$(PATH_TO_COMPILEDB) -n make box -k + +############################################################################################ +# WARNING: these targets are out of date and only here for reference until they're updated # +############################################################################################ + +BUILDDIR := build +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) +SDLEMLIBSHOME := $(SDLHOME)/build/em/build/.libs +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 + +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/box/Model.cpp b/demo/box/Model.cpp new file mode 100644 index 0000000..3927d72 --- /dev/null +++ b/demo/box/Model.cpp @@ -0,0 +1,196 @@ +/* _______________ ,----------------------------------------------------------------. + //`````````````\\ \ \ + //~~~~~~~~~~~~~~~\\ \ by @ohsqueezy & @sleepin \ + //=================\\ \ [ohsqueezy.itch.io] [sleepin.itch.io] \ + // \\ \ \ + // \\ \ code released under the zlib license [git.nugget.fun/pudding] \ + // ☆ GUNKISS ☆ \\ \ \ +//_________________________\\ `---------------------------------------------------------------*/ + +#include "Model.hpp" + +/* Default constructor for Model */ +Model::Model() {}; + +/* Construct a Model, adding Attributes each already wrapped in a shared pointer. The attributes should + * be passed as a map with each key being a name and each value being a shared pointer to attributes. */ +Model::Model(const std::map>& attributes_pack) +{ + for (auto attributes : attributes_pack) + { + this->attributes(attributes.second, attributes.first); + } +} + +/* Construct a Model, adding Attributes, which will each be wrapped in a shared pointer and stored in the + * created object. The attributes should be passed as a map with each key being a name and each value being + * an attributes object. */ +Model::Model(const std::map& attributes_pack) +{ + for (auto attributes : attributes_pack) + { + this->attributes(attributes.second, attributes.first); + } +} + +/* Construct a new model object by passing a list of names which will be used to initialize + * empty attributes objects with the given names */ +Model::Model(const std::initializer_list& names) +{ + for (const std::string& name : names) + { + this->attributes(sb::Attributes(), name); + } +} + +/* Get the entire map of attributes, each wrapped in its shared pointer held by this object. + * Can be used to iterate through the attributes. */ +std::map>& Model::attributes() +{ + return model_attributes; +} + +/* Get the attributes under name, wrapped in the shared pointer held by this object. This + * function uses the at method of std::map, so name must refer to attributes already + * stored in this model. Use this function to share ownership of the attributes or to gain + * access to the public interface of the attributes. */ +std::shared_ptr& Model::attributes(const std::string& name) +{ + return attributes().at(name); +} + +/* Get the attributes under name, wrapped in the shared pointer held by this object. This + * function uses operator[] or std::map, so this can be used to add new attributes to the + * object if they are wrapped in a shared pointer. */ +std::shared_ptr& Model::operator[](const std::string& name) +{ + auto element = attributes().find(name); + /* add an empty Attributes at name if it doesn't exist yet */ + if (element == attributes().end()) + { + attributes(sb::Attributes{}, name); + } + return attributes()[name]; +} + +/* Assign name to attributes, copy and wrap in a shared pointer. The model can share + * ownership of the created attribute memory with callers that request it. */ +void Model::attributes(const sb::Attributes& attributes, const std::string& name) +{ + this->attributes(std::make_shared(attributes), name); +} + +/* Assign name to attributes and share ownership. */ +void Model::attributes(const std::shared_ptr& attributes, const std::string& name) +{ + this->attributes()[name] = attributes; +} + +/* Enable all attributes. */ +void Model::enable() +{ + for (const auto& attributes : this->attributes()) + { + attributes.second->enable(); + } +} + +/* Disable all attributes. */ +void Model::disable() +{ + for (const auto& attributes : this->attributes()) + { + attributes.second->disable(); + } +} + +/* Return a reference to the texture container. */ +std::map& Model::textures() +{ + return model_textures; +} + +/* Get the texture at name. This can be used to read the texture memory, share ownership of it, or + * anything else a Texture object can be used for with direct calls to GL functions. */ +sb::Texture& Model::texture(const std::string& name) +{ + return textures().at(name); +} + +/* Get the default texture. The default texture must have previously been set with the default key as + * the name, which can be done using Model::texture(sb::Texture). */ +sb::Texture& Model::texture() +{ + return texture(DEFAULT_TEXTURE_NAME); +} + +/* Assign name to texture and share ownership. */ +void Model::texture(const sb::Texture& texture, const std::string& name) +{ + textures()[name] = texture; +} + +/* If no name is specified, use the default texture. This can be used to conveniently setup a model + * with only one texture. */ +void Model::texture(const sb::Texture& texture) +{ + this->texture(texture, DEFAULT_TEXTURE_NAME); +} + +/* Get the model's transformation matrix. */ +const glm::mat4& Model::transformation() const +{ + return model_transformation; +} + +/* Set the model's transformation matrix. */ +void Model::transformation(const glm::mat4& transformation) +{ + model_transformation = transformation; +} + +/* Return the size in bytes of the sum of the attributes. */ +std::size_t Model::size() +{ + std::size_t sum = 0; + for (const auto& attributes : this->attributes()) + { + sum += attributes.second->size(); + } + return sum; +} + +/* Return the transformation matrix. */ +Model::operator glm::mat4() const +{ + return model_transformation; +} + +PlaneDoubleBuffer::PlaneDoubleBuffer() : Plane() +{ + texture(sb::Texture(), "front"); + texture(sb::Texture(), "back"); +} + +void PlaneDoubleBuffer::generate(const glm::vec2& size) +{ + for (sb::Texture* buffer : {&texture("front"), &texture("back")}) + { + buffer->generate(size); + } +} + +sb::Texture& PlaneDoubleBuffer::active() +{ + return swapped ? texture("back") : texture("front"); +} + +sb::Texture& PlaneDoubleBuffer::inactive() +{ + return swapped ? texture("front") : texture("back"); +} + +void PlaneDoubleBuffer::swap() +{ + swapped = !swapped; +} diff --git a/demo/box/Model.hpp b/demo/box/Model.hpp new file mode 100644 index 0000000..9552fa7 --- /dev/null +++ b/demo/box/Model.hpp @@ -0,0 +1,105 @@ +/* _______________ ,----------------------------------------------------------------. + //`````````````\\ \ \ + //~~~~~~~~~~~~~~~\\ \ by @ohsqueezy & @sleepin \ + //=================\\ \ [ohsqueezy.itch.io] [sleepin.itch.io] \ + // \\ \ \ + // \\ \ code released under the zlib license [git.nugget.fun/pudding] \ + // ☆ GUNKISS ☆ \\ \ \ +//_________________________\\ `---------------------------------------------------------------*/ + +#ifndef MODEL_H_ +#define MODEL_H_ + +/* GL functions */ +#if defined(__ANDROID__) || defined(ANDROID) +#include +#include +#else +#include "glew/glew.h" +#endif + +#include +#include +#include +#include +#include +#include "glm/glm.hpp" +#include "Attributes.hpp" +#include "Texture.hpp" + +class Model +{ + +private: + + inline static const std::string DEFAULT_TEXTURE_NAME = "default"; + std::map model_textures; + std::map> model_attributes; + glm::mat4 model_transformation {1.0f}; + +public: + + Model(); + Model(const std::map>&); + Model(const std::map&); + Model(const std::initializer_list&); + std::map>& attributes(); + std::shared_ptr& attributes(const std::string&); + void attributes(const sb::Attributes&, const std::string&); + void attributes(const std::shared_ptr&, const std::string&); + std::shared_ptr& operator[](const std::string&); + void enable(); + void disable(); + std::map& textures(); + sb::Texture& texture(const std::string&); + sb::Texture& texture(); + void texture(const sb::Texture&, const std::string&); + void texture(const sb::Texture&); + const glm::mat4& transformation() const; + void transformation(const glm::mat4&); + std::size_t size(); + operator glm::mat4() const; + +}; + +class Plane : public Model +{ + +public: + + inline const static std::shared_ptr position = std::make_shared(sb::Attributes{ + {-1.0f, 1.0f}, {1.0f, 1.0f}, {-1.0f, -1.0f}, + {1.0f, 1.0f}, {1.0f, -1.0f}, {-1.0f, -1.0f} + }); + inline const static std::shared_ptr uv = std::make_shared(sb::Attributes{ + {0.0f, 1.0f}, {1.0f, 1.0f}, {0.0f, 0.0f}, + {1.0f, 1.0f}, {1.0f, 0.0f}, {0.0f, 0.0f} + }); + + Plane() : Model(std::map>({{"position", position}, {"uv", uv}})) {} + +}; + +/*! + * A version of `Plane` which contains two texture objects, one of which is active at a time. A reference + * to the active `sb::Texture` object is available from `PlaneDoubleBuffer.active`, and the inactive object is + * available from `PlaneDoubleBuffer.inactive`. The buffers can be swapped using `PlaneDoubleBuffer.swap`. + */ +class PlaneDoubleBuffer : public Plane +{ + +private: + + bool swapped = false; + +public: + + PlaneDoubleBuffer(); + void generate(const glm::vec2&); + sb::Texture& active(); + sb::Texture& inactive(); + void swap(); + +}; + +#endif diff --git a/demo/cube/config.json b/demo/box/config.json similarity index 71% rename from demo/cube/config.json rename to demo/box/config.json index bb174db..4daf856 100644 --- a/demo/cube/config.json +++ b/demo/box/config.json @@ -1,14 +1,14 @@ { "display": { - "dimensions": [864, 486], + "dimensions": [480, 800], "fps": 60 }, "keys": { "context": " ", "print-video-memory-size": "v", - "play-sound": "" + "play-sound": "s" }, "input": { @@ -18,9 +18,14 @@ { "screenshot-directory": "local/screenshots", "video-directory": "local/video", - "enabled": true, - "write-mp4": true, + "enabled": false, + "write-mp4": false, "video-frame-length": 16.667, "max-video-memory": 2000 + }, + "log": + { + "enabled": true, + "debug-to-file": true } } diff --git a/demo/box/index.html b/demo/box/index.html new file mode 100644 index 0000000..68109ad --- /dev/null +++ b/demo/box/index.html @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + diff --git a/demo/cube/resource/Ag.ogg b/demo/box/resource/Ag.ogg similarity index 100% rename from demo/cube/resource/Ag.ogg rename to demo/box/resource/Ag.ogg diff --git a/demo/cube/resource/Field.mp3 b/demo/box/resource/Field.mp3 similarity index 100% rename from demo/cube/resource/Field.mp3 rename to demo/box/resource/Field.mp3 diff --git a/demo/box/resource/tile.png b/demo/box/resource/tile.png new file mode 100644 index 0000000000000000000000000000000000000000..3afb674ce8983cdba39bdf959319703fc912ebf9 GIT binary patch literal 923 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H#=yY1#WFA!$l)yTh%9Dc;1&j9Muu5)Bp4W& zeLY~)y?ZeCwt>j8!d)jgPO90JGVxE~WLiHVx_@UX>tW#%2L6C2QxvQG%L@2g zx!go^Z%)$b`tz4jK1cb~~z$b9%?$-JrB6dz3ifi0FAHRNn z|GMcKiIu+@^8584zWw<07xSBY{M%1AnVhR^D4hLS<7>mjqSfr?XAeDKJ~NMnkt4xF z_Zrtf?gWqh3j{b5K4k`7-v90%f8y?YE${8?@7!zg6-fU3StqJ7O@o1v!-3%|>jx3L zo-Wo4vIiz)AMXxx&=zQ5U@7?cWJ}_jpNaXbdOH`Icrx7nnl{P7=ls{UtJfDEy>c+q zV%wP&oEc0k3LrBYs*W*U7LMo2z0uxYB_l9D=Xy1>qU!{PyJrel+DJ3bn!Mh%CHYU) zUz@-CwoH$65L5wMeEDHv=6x@j-{03oT0gj=G;1GsPFDug$J{F`^VX(mIwXI7w&bz! z{r~@_&ShvjwCmuhi!3ILOhC7NE}XNM?KN9za~9YbkPy@vPMP}i)~A`xuzHzQr*->Z zWX|>UEC*ioo&5ZB^ECFc6+qO-C?AOFzc#a3|8ByoCG+2F7dl+;oL2KG%fYMR`R`B0 z`5J3D;YKn*Om{eXC*^4dhvMnb#gi=*wpy7S4_&;N`@+^Vi*iA?a);`ZIen*7)rA*C zs>)rii{HX<_SS=tYpWyd_P4A#?o$?Op;xi9tZ_0|f{F*wzy+`37@`;?7rF;H!vPxNMJC4aTUsg{Cj*J<|Q2L^taWY$f%Y5ae2=I?g)$LQ9pOS4{d1D za+vex%5xruSeOoj1Fcs+MSP8VVaxl*t48w!xYNKqFMeeZ9@m}{NJcRL4%5ppR zg~<#vSkWv)aJ?VpWil=|dvJTN{J*y`ZHIU+q+dT;v3=Xys>xr|QY|O`<##%#y8q_# z4cWWx)FOUJEb#xd{k3n=>`O7`Yf~GzJA`lh743dA|5vom1%|hOE2eKyG-U4a*dzaO z<-UDC*X8nUZen6&cq-a?Sg{?i{|Ze#Ft^>bP0l+XkK Dn4xwY literal 0 HcmV?d00001 diff --git a/demo/box/shaders/all.frag b/demo/box/shaders/all.frag new file mode 100644 index 0000000..db0311d --- /dev/null +++ b/demo/box/shaders/all.frag @@ -0,0 +1,24 @@ +#version 300 es + + /* +------------------------------------------------------+ + ____/ \____ /| - Open source game framework licensed to freely use, | + \ / / | copy, modify and sell without restriction | ++--\ ^__^ /--+ | | +| ~/ \~ | | - created for | +| ~~~~~~~~~~~~ | +------------------------------------------------------+ +| SPACE ~~~~~ | / +| ~~~~~~~ BOX |/ ++-------------*/ + +/* The precision declaration is required by OpenGL ES */ +precision mediump float; + +in vec4 ex_Color; +in vec2 UV; +uniform sampler2D myTextureSampler; +out vec4 outputColor; + +void main(void) +{ + outputColor = texture(myTextureSampler, UV) * ex_Color; +} diff --git a/demo/box/shaders/flat.vert b/demo/box/shaders/flat.vert new file mode 100644 index 0000000..ab2de99 --- /dev/null +++ b/demo/box/shaders/flat.vert @@ -0,0 +1,28 @@ +#version 300 es + + /* +------------------------------------------------------+ + ____/ \____ /| - Open source game framework licensed to freely use, | + \ / / | copy, modify and sell without restriction | ++--\ ^__^ /--+ | | +| ~/ \~ | | - created for | +| ~~~~~~~~~~~~ | +------------------------------------------------------+ +| SPACE ~~~~~ | / +| ~~~~~~~ BOX |/ ++-------------*/ + +/* The precision declaration is required by OpenGL ES */ +precision mediump float; + +in vec2 in_Position; +in vec2 vertexUV; +in vec3 in_Color; + +out vec2 UV; +out vec4 ex_Color; + +void main(void) +{ + gl_Position = vec4(in_Position, 0.0, 1.0); + UV = vertexUV; + ex_Color = vec4(in_Color, 1.0); +} diff --git a/demo/box/shaders/triangle.vert b/demo/box/shaders/triangle.vert new file mode 100644 index 0000000..cd45194 --- /dev/null +++ b/demo/box/shaders/triangle.vert @@ -0,0 +1,30 @@ +#version 300 es + + /* +------------------------------------------------------+ + ____/ \____ /| - Open source game framework licensed to freely use, | + \ / / | copy, modify and sell without restriction | ++--\ ^__^ /--+ | | +| ~/ \~ | | - created for | +| ~~~~~~~~~~~~ | +------------------------------------------------------+ +| SPACE ~~~~~ | / +| ~~~~~~~ BOX |/ ++-------------*/ + +/* The precision declaration is required by OpenGL ES */ +precision mediump float; + +in vec3 in_Position; +in vec3 in_Color; +in vec2 vertexUV; + +out vec4 ex_Color; +out vec2 UV; + +uniform mat4 MVP; + +void main(void) +{ + gl_Position = MVP * vec4(in_Position, 1); + ex_Color = vec4(in_Color, 1); + UV = vertexUV; +} diff --git a/demo/browser_webcam_test/Makefile b/demo/browser_webcam_test/Makefile index 0fd1bf8..81e3797 100644 --- a/demo/browser_webcam_test/Makefile +++ b/demo/browser_webcam_test/Makefile @@ -1,4 +1,14 @@ -# Browser Webcam Test +# /\ +------------------------------------------------------+ +# ____/ \____ /| - Open source game framework licensed to freely use, | +# \ / / | copy, modify and sell without restriction | +# +--\ ^__^ /--+ | | +# | ~/ \~ | | - created for | +# | ~~~~~~~~~~~~ | +------------------------------------------------------+ +# | SPACE ~~~~~ | / +# | ~~~~~~~ BOX |/ +# +--------------+ +# +# Modify location parameters to match locations for each directory on the system and run `make emscripten` ####################### # Location parameters # diff --git a/demo/browser_webcam_test/browser_webcam_test.cpp b/demo/browser_webcam_test/browser_webcam_test.cpp index a6a0978..7f4a560 100644 --- a/demo/browser_webcam_test/browser_webcam_test.cpp +++ b/demo/browser_webcam_test/browser_webcam_test.cpp @@ -1,12 +1,20 @@ /* - * Browser Webcam Test by frank at shampoo.ooo + * /\ +------------------------------------------------------+ + * ____/ \____ /| - Open source game framework licensed to freely use, | + * \ / / | copy, modify and sell without restriction | + * +--\ ^__^ /--+ | | + * | ~/ \~ | | - created for | + * | ~~~~~~~~~~~~ | +------------------------------------------------------+ + * | SPACE ~~~~~ | / + * | ~~~~~~~ BOX |/ + * +--------------+ * - * Program for testing passing image data from an HTML5 canvas object in JavaScript to an OpenGL context in this C++ program. - * This can be useful, for example, for developing cross-platform applications that use the same codebase to export both desktop - * and web versions, or for using C++ libraries and code to edit and display images on a web page. + * This tests passing image data from an HTML5 canvas object in JavaScript to an OpenGL context. It can be useful, for example, + * for developing cross-platform applications that use the same codebase to export both desktop and web versions, or for using + * C++ libraries to edit and display images on a web page. * - * It uses the [SPACE BOX] engine (https://git.shampoo.ooo/nugget/spacebox) to set up an SDL + GL environment and create a model - * conveniently, but it can be ported to just Emscripten. + * It was written to test transferring webcam image data to OpenCV. The OpenCV dependency was dropped since it isn't necessary just + * for displaying the webcam, but there is an example line in the code where the OpenCV call could be substituted. */ #include @@ -78,7 +86,7 @@ public: /* Set up GL buffers for attributes. Set up shaders and uniforms. Create the texture that will hold the camera pixel data. */ void load_gl_context() { - /* [SPACE BOX] creates an SDL GL context and initializes GLEW. */ + /* [SPACEBOX] uses SDL to create GL context */ Game::load_gl_context(); /* Generate a vertex array object ID, bind it as current (requirement of OpenGL) */ diff --git a/demo/browser_webcam_test/index.html b/demo/browser_webcam_test/index.html index a77004d..21441cf 100644 --- a/demo/browser_webcam_test/index.html +++ b/demo/browser_webcam_test/index.html @@ -1,6 +1,19 @@ - - + + + + + + + + + + diff --git a/demo/cube/Cube.cpp b/demo/cube/Cube.cpp deleted file mode 100644 index 7b9fde1..0000000 --- a/demo/cube/Cube.cpp +++ /dev/null @@ -1,454 +0,0 @@ -/*** - - reset, pause, auto reset, analog d-pad, gamepad config, any key, confirm exit - game, screen + sprite wipes, screen offset, screen scale, networking, post - processing, all objects deactivatable, all objects subscribed to events, all - objects resetable, wrapable & scrollable surface, separate window for - configuration console and variable viewer, text editor for config json, asset - favoriting, audio filters, enforce consistent angle orientation, floating - point coordinates, relative coordinates, relative lengths, relative sizes, - delta time, specify config parameters on command line, effects chain, asset - dict with metadata, move added sprite locations by offset when location is - changed, gradients, level select code input, log to stdout and file, variable - screen resolution, debug display, loading wheel animation, shadowed sprite, - separate update and draw, sprite movement cage, multiple windows, multiple - renderers, node children list, node animations list, copy constructor for all - base classes (esp. sprite, audio, pixels), private and public class members, - pixel class iterator, sprite movement history, input history, use seconds - instead of milliseconds, store a node's animations in list, frame object for - sprite class, inline short functions, add box2d to library, load in separate - thread and display progress, add anchor to box class, add comments to code, - generate documentation from comments, get resource function, automatically - update animations, add arguments list to animation call, queue multiple calls - to animation, print list of chunk and music decoders available, allow nodes - that aren't connected to root, add imagemagick to library, add curl to library, - fail if command line flag is unrecognized - - :) SWEATY HANDS :) OILY SNACKS :) AND BAD HYGIENE :) - -***/ - -#include "Cube.hpp" - -char* file_to_buf(const char *path) -{ - FILE *fp; - long length; - char *buf; - fp = fopen(path, "rb"); - if (not fp) - { - return NULL; - } - fseek(fp, 0, SEEK_END); - length = ftell(fp); - buf = (char*) malloc(length + 1); - fseek(fp, 0, SEEK_SET); - fread(buf, length, 1, fp); - fclose(fp); - buf[length] = 0; - return buf; -} - -GLuint load_shader(const char* path, GLenum type) -{ - char *source = file_to_buf(path); - GLuint shader = glCreateShader(type); - int is_compiled, max_length; - glShaderSource(shader, 1, (const GLchar**) &source, 0); - glCompileShader(shader); - glGetShaderiv(shader, GL_COMPILE_STATUS, &is_compiled); - if(is_compiled == 0) - { - glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &max_length); - char *log = (char *) malloc(max_length); - glGetShaderInfoLog(shader, max_length, &max_length, log); - fprintf(stderr, "%s -- shader compilation failed %s\n", path, log); - free(log); - return -1; - } - return shader; -} - -int link_shader(GLuint program) -{ - glLinkProgram(program); - int is_linked, max_length; - glGetProgramiv(program, GL_LINK_STATUS, (int *) &is_linked); - if(is_linked == 0) - { - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &max_length); - char *log = (char *) malloc(max_length); - glGetProgramInfoLog(program, max_length, &max_length, log); - fprintf(stderr, "shader program %i linking failed %s\n", program, log); - free(log); - return -1; - } - return 0; -} - -GLuint get_gl_texture_from_surface(SDL_Surface *surface, GLint mipmap_filter, bool invert = false) -{ - GLuint id; - glCreateTextures(GL_TEXTURE_2D, 1, &id); - glBindTexture(GL_TEXTURE_2D, id); - GLenum format; -#if SDL_BYTEORDER == SDL_BIG_ENDIAN - format = invert ? GL_RGBA : GL_BGRA; -#else - format = invert ? GL_BGRA : GL_RGBA; -#endif - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surface->w, surface->h, 0, format, - GL_UNSIGNED_BYTE, surface->pixels); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mipmap_filter); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mipmap_filter); - return id; -} - -Mushroom::Mushroom(Node *parent) : Sprite(parent, "resource/shrooms") -{ - set_frame_length(500); - std::cout << "mushroom constructor " << this << std::endl; -} - -void Mushroom::update() -{ - 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, 0}); - } - else - { - move({-x, 0}); - } - } - Sprite::update(); -} - -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(&Cube::respond, this); -} - -void Cube::load_sdl_context() -{ - Game::load_sdl_context(); - grass.load(); - mushroom.load(); -} - -void Cube::load_gl_context() -{ - Game::load_gl_context(); - grass.unload(); - mushroom.unload(); - /* - v0-v1-v2 (front) - v2-v3-v0 - v0-v3-v4 (right) - v4-v5-v0 - v0-v5-v6 (top) - v6-v1-v0 - v1-v6-v7 (left) - v7-v2-v1 - v7-v4-v3 (bottom) - v3-v2-v7 - v4-v7-v6 (back) - v6-v5-v4 - */ - std::array cube = { - { - {1, 1, 1}, {-1, 1, 1}, {-1,-1, 1}, - {-1,-1, 1}, {1,-1, 1}, {1, 1, 1}, - {1, 1, 1}, {1,-1, 1}, {1,-1,-1}, - {1,-1,-1}, {1, 1,-1}, {1, 1, 1}, - {1, 1, 1}, {1, 1,-1}, {-1, 1,-1}, - {-1, 1,-1}, {-1, 1, 1}, {1, 1, 1}, - {-1, 1, 1}, {-1, 1,-1}, {-1,-1,-1}, - {-1,-1,-1}, {-1,-1, 1}, {-1, 1, 1}, - {-1,-1,-1}, {1,-1,-1}, {1,-1, 1}, - {1,-1, 1}, {-1,-1, 1}, {-1,-1,-1}, - {1,-1,-1}, {-1,-1,-1}, {-1, 1,-1}, - {-1, 1,-1}, {1, 1,-1}, {1,-1,-1} - }}; - std::array background_vertices = { - { - {-1, 1, 0}, {1, 1, 0}, {-1, -1, 0}, - {1, 1, 0}, {1, -1, 0}, {-1, -1, 0} - }}; - GLfloat background_colors[40][3] = { - {.2, .6, .8}, {.2, .6, .8}, {1, 1, 0}, - {.2, .6, .8}, {1, 1, 0}, {1, 1, 0} - }; - GLuint background_colors_buffer; - glGenBuffers(1, &background_colors_buffer); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - std::vector vertices; - 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()); - glm::ivec2 resolution = get_display().window_size(); - projection = glm::perspective( - glm::radians(45.0f), resolution.x / (float) resolution.y, 0.1f, - 100.0f); - view = glm::lookAt( - glm::vec3(4, 3, 3), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0)); - SDL_Surface* surface = zoomSurface( - rotateSurface90Degrees(IMG_Load("resource/tile.png"), 2), -1, 1, SMOOTHING_OFF); - SDL_Log("tile.png bytes per pixel %i", surface->format->BytesPerPixel); - space_texture_id = get_gl_texture_from_surface(surface, GL_LINEAR, true); - SDL_FreeSurface(surface); - // std::vector paths = sb::glob("/home/frank/projects/public/games/buddi/local/face03/[0-9]+\\-.+\\.png"); - // for (fs::path path : paths) - // { - // surface = zoomSurface(rotateSurface90Degrees(IMG_Load(path.c_str()), 2), -1, 1, SMOOTHING_OFF); - // SDL_Log("loading %s", path.c_str()); - // face_ids.push_back(get_gl_texture_from_surface(surface, GL_LINEAR)); - // SDL_FreeSurface(surface); - // } - std::array cube_uv = { - { - {1, 1}, {0, 1}, {0, 0}, - {0, 0}, {1, 0}, {1, 1}, - {0, 1}, {0, 0}, {1, 0}, - {1, 0}, {1, 1}, {0, 1}, - {0, 1}, {0, 0}, {1, 0}, - {1, 0}, {1, 1}, {0, 1}, - {1, 1}, {0, 1}, {0, 0}, - {0, 0}, {1, 0}, {1, 1}, - {0, 0}, {1, 0}, {1, 1}, - {1, 1}, {0, 1}, {0, 0}, - {0, 0}, {1, 0}, {1, 1}, - {1, 1}, {0, 1}, {0, 0} - }}; - std::vector uv; - uv.reserve(cube_uv.size() + background_vertices.size()); - std::copy(cube_uv.begin(), cube_uv.end(), uv.begin()); - GLuint uvbuffer; - glGenBuffers(1, &uvbuffer); - unsigned char fake_texture_color[4] = {255, 255, 255, 255}; - glCreateTextures(GL_TEXTURE_2D, 1, &fake_texture_id); - glBindTexture(GL_TEXTURE_2D, fake_texture_id); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, - fake_texture_color); - GLuint vao; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - glGenBuffers(1, &vbo); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(GLfloat) * 3, &vertices.front(), - GL_STATIC_DRAW); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); - glEnableVertexAttribArray(0); - glBindBuffer(GL_ARRAY_BUFFER, uvbuffer); - glBufferData(GL_ARRAY_BUFFER, uv.capacity() * sizeof(GLfloat) * 2, &uv.front(), GL_STATIC_DRAW); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0); - glEnableVertexAttribArray(1); - glBindBuffer(GL_ARRAY_BUFFER, background_colors_buffer); - glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(GLfloat) * 3, 0, GL_STATIC_DRAW); - glBufferSubData(GL_ARRAY_BUFFER, - (cube.size()) * sizeof(GLfloat) * 3, - background_vertices.size() * sizeof(GLfloat) * 3, background_colors); - glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, 0); - glEnableVertexAttribArray(2); - GLuint vertex_shader = load_shader("shaders/triangle.vert", GL_VERTEX_SHADER); - GLuint fragment_shader = load_shader("shaders/triangle.frag", GL_FRAGMENT_SHADER); - GLuint flat_shader = load_shader("shaders/flat.vert", GL_VERTEX_SHADER); - world_program = glCreateProgram(); - glAttachShader(world_program, vertex_shader); - glAttachShader(world_program, fragment_shader); - glBindAttribLocation(world_program, 0, "in_Position"); - glBindAttribLocation(world_program, 1, "vertexUV"); - glBindAttribLocation(world_program, 2, "in_Color"); - glBindAttribLocation(world_program, 3, "pos"); - link_shader(world_program); - flat_program = glCreateProgram(); - glAttachShader(flat_program, flat_shader); - glAttachShader(flat_program, fragment_shader); - glBindAttribLocation(flat_program, 0, "in_Position"); - glBindAttribLocation(flat_program, 1, "vertexUV"); - glBindAttribLocation(flat_program, 2, "in_Color"); - glBindAttribLocation(flat_program, 3, "pos"); - link_shader(flat_program); - mvp_id = glGetUniformLocation(world_program, "MVP"); - // GLuint sampler_uniform_id = glGetUniformLocation(world_program, "myTextureSampler"); - // glActiveTexture(GL_TEXTURE0); - // glBindTexture(GL_TEXTURE_2D, space_texture_id); - // glUniform1i(sampler_uniform_id, 0); - glDepthFunc(GL_LESS); -} - -void Cube::respond(SDL_Event& event) -{ - if (delegate.compare(event, "context")) - { - if (is_gl_context) - { - load_sdl_context(); - } - else - { - load_gl_context(); - } - } - else if (delegate.compare(event, "play-sound")) - { - Mix_Chunk* music = Mix_LoadWAV("resource/Ag.ogg"); - Mix_PlayChannel(-1, music, 0); - } - else if (delegate.compare(event, "fullscreen")) - { - if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) - { - SDL_SetWindowFullscreen(window, 0); - } - else - { - SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN); - } - } -} - -void Cube::update() -{ - // while (SDL_PollEvent(&event)) - // { - // if (event.type == SDL_KEYDOWN) - // { - // if (SDL_GetModState() & KMOD_CTRL) - // { - // if (event.key.keysym.sym == SDLK_f) - // { - // show_framerate = not show_framerate; - // } - // else if (event.key.keysym.sym == SDLK_UP) - // { - // set_framerate(framerate + 1); - // } - // else if (event.key.keysym.sym == SDLK_DOWN) - // { - // set_framerate(framerate - 1); - // } - // } - // else if (event.key.keysym.sym == SDLK_UP) - // { - // up_active = true; - // } - // else if (event.key.keysym.sym == SDLK_RIGHT) - // { - // right_active = true; - // } - // else if (event.key.keysym.sym == SDLK_DOWN) - // { - // down_active = true; - // } - // else if (event.key.keysym.sym == SDLK_LEFT) - // { - // left_active = true; - // } - // } - // else if (event.type == SDL_KEYUP) - // { - // if (event.key.keysym.sym == SDLK_UP) - // { - // up_active = false; - // } - // else if (event.key.keysym.sym == SDLK_RIGHT) - // { - // right_active = false; - // } - // else if (event.key.keysym.sym == SDLK_DOWN) - // { - // down_active = false; - // } - // else if (event.key.keysym.sym == SDLK_LEFT) - // { - // left_active = false; - // } - // } - // } - if (is_gl_context) - { - // glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); - // glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(GLfloat), diamond, GL_STATIC_DRAW); - // glColorMask(false, false, false, true); - // glClearColor(.7, .7, .5, 1); - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glDisable(GL_DEPTH_TEST); - glUseProgram(flat_program); - glUniform1f(glGetUniformLocation(flat_program, "r"), mvp[0][0]); - glBindTexture(GL_TEXTURE_2D, fake_texture_id); - glDisableVertexAttribArray(1); - glEnableVertexAttribArray(2); - glDrawArrays(GL_TRIANGLES, 36, 6); - 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); - glUseProgram(world_program); - glUniformMatrix4fv(mvp_id, 1, GL_FALSE, &mvp[0][0]); - glEnableVertexAttribArray(1); - glDisableVertexAttribArray(2); - glVertexAttrib3f(2, 1, 1, 1); - // for (int ii = 0; ii < 6; ii++) - // { - // glBindTexture(GL_TEXTURE_2D, face_ids[ii]); - // glDrawArrays(GL_TRIANGLES, ii * 6, 6); - // } - glBindTexture(GL_TEXTURE_2D, space_texture_id); - glDrawArrays(GL_TRIANGLES, 0, 36); - //glFlush(); - SDL_GL_SwapWindow(window); - // if (amount_rotated < 3.14f * 2) - // { - // recorder.capture_screen(); - // } - amount_rotated += rotation; - } - else - { - SDL_SetRenderDrawColor(renderer, 0x0f, 0x4f, 0x8f, 0xff); - SDL_RenderClear(renderer); - roundedBoxColor(renderer, 300, 200, 500, 300, 10, 0x8f8fdfff); - aacircleColor(renderer, 300, 200, 30, 0xffef3fff); - int speed = 2; - if (up_active) - { - grass.move_weighted({0, -speed}); - } - if (right_active) - { - grass.move_weighted({speed, 0}); - } - if (down_active) - { - grass.move_weighted({0, speed}); - } - if (left_active) - { - grass.move_weighted({-speed, 0}); - } - grass.update(); - mushroom.update(); - } -} - -int main() -{ - Cube cube = Cube(); - cube.run(); - cube.quit(); - return 0; -} diff --git a/demo/cube/Cube.hpp b/demo/cube/Cube.hpp deleted file mode 100644 index 292fe5f..0000000 --- a/demo/cube/Cube.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "sdl2-gfx/SDL2_gfxPrimitives.h" -#include "sdl2-gfx/SDL2_rotozoom.h" - -#define GLM_ENABLE_EXPERIMENTAL -#include "glm/gtx/string_cast.hpp" -#include "glm/gtx/transform.hpp" -#include "glm/gtc/matrix_transform.hpp" - -#include "filesystem.hpp" -#include "Node.hpp" -#include "Game.hpp" -#include "Box.hpp" -#include "Sprite.hpp" -#include "Input.hpp" -#include "Delegate.hpp" -#include "extension.hpp" - -struct Mushroom : Sprite -{ - - int direction = 1; - - Mushroom(Node*); - void update(); - virtual std::string class_name() const { return "Mushroom"; } - -}; - -struct Cube : Game -{ - - SDL_Texture *grass_texture; - int frame_count = 0, frame_count_timestamp; - bool right_active = false, down_active = false, left_active = false, - up_active = false, is_writing_audio = true; - SDL_Event event; - GLuint vbo, space_texture_id, mvp_id, framerate_texture_id, flat_program, - world_program, fake_texture_id; - glm::mat4 projection, view, model = glm::mat4(1.0f), mvp; - Mushroom mushroom = Mushroom(this); - Sprite grass = Sprite(this, "resource/Field.png"); - std::vector face_ids; - float amount_rotated; - - Cube(); - void load_sdl_context(); - void load_gl_context(); - void respond(SDL_Event&); - void update(); - virtual std::string class_name() const { return "Demo"; } - -}; diff --git a/demo/cube/Makefile b/demo/cube/Makefile deleted file mode 100644 index 34c289c..0000000 --- a/demo/cube/Makefile +++ /dev/null @@ -1,175 +0,0 @@ -# /\ +--------------------------------------------------------------+ -# ____/ \____ /| - 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/cube/resource/Field.png b/demo/cube/resource/Field.png deleted file mode 100644 index d5e74b289e1b7db9c56d99bc68910dab89fec68b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5654 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 V!9xS)W$@w)T%A|(f8~iK{{wGz?T`Qf diff --git a/demo/cube/resource/shrooms/anxiety.png b/demo/cube/resource/shrooms/anxiety.png deleted file mode 100644 index d8907e65e6252a5be5b66ee5468cd2c9a63c03bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmeAS@N?(olHy`uVBq!ia0vp@K+Mg-1|*kkVowB8jKx9jP7LeL$-D$|I14-?i-8J+ zL70(Y)*K0-AbW|YuPgf<4rwtxy^B_6T|l8Vo-U3d8s~c_S@RuM5O8Tfkt)^qP&mTt zEnnxM63gHvB6kjoYF-X}&gBuK#QS$ihtIXXz)eCzyZ1;=>pxT%aOP^+#|_MrU6mu6 zHJ?XiTTXU0jyRma8(AWIP|B-tyT$1l`r#HlJLdW`Fqqpr?2#<3*%a--X~E;m{NP`v ze#BwT=k-y4?5Ag%@wFwI}>VZO2JY5_^G|u-1Z4_)a;Hmj}X;Yhity!S^ zc@9m9M#W3ZlZC~8*aD7fkiA@snjp zklNq(e9m7VrUWl1&zROpOIO}ia@_flXZPN!z7ui{NrAWAH=JFknR(@C*1t2mzGmLw z)P5s)?#q&@e>>%0ue^J^y7p}c@9C>W|9=VEn|*a!I^V+$>rKmHmZWgmtP8_R?Nj2*_DeT!osM1}v7&+N-M_P{K2tY&9shjdW{uEU zzts9Y3>~?wJ0A7i2-_gZT(ZT`MXakM+vDlAP2S!a;)}O%EL18FpDMrh*Uz|BheY`A sJKU3I1c&yJi7U(brPgg&ebxsLQ07R2qlmGw# diff --git a/demo/cube/resource/shrooms/sombrero.png b/demo/cube/resource/shrooms/sombrero.png deleted file mode 100644 index 0e57bc0fd274aaff1024f0c23597a276d4d4975a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 307 zcmeAS@N?(olHy`uVBq!ia0vp@K+Mg-1|*kkVowB8jKx9jP7LeL$-D$|I14-?i-8J+ zL70(Y)*K0-AbW|YuPgf<4rwtp&OI+VLF$frx;TbtoS!?%kh>{B;Q0URSF$cVn$ju2 z>D#ofwzKT|0e|5wyS}SNiOykC7TA2!^~Bbd*(bg4ZEoX}yL0#V-}iqlHOjGmI=oyN-svdI^3C`uqpoV#(7Fws|4TOR&LrN%FFoRh&cnp^P8fH&lXv*C>5D; zCg~slWX8_GlF7|R-k1Xh#j4@IL$1kJ=ggK`!nbgfSoN)AR=elUji3D{?#XwC zuvhC3`idIfUN&c0mx*AlpqJs+GAZ}}e>%iD*Z55ljyh)w^bmumtDnm{r-UW|nM-zJ diff --git a/demo/cube/resource/shrooms/spider-house.png b/demo/cube/resource/shrooms/spider-house.png deleted file mode 100644 index 3176dd555b6253100bd74ffb2c41131315edfa0d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 289 zcmeAS@N?(olHy`uVBq!ia0vp@K+Mg-1|*kkVowB8jKx9jP7LeL$-D$|I14-?i-8J+ zL70(Y)*K0-AbW|YuPgf0CICHS**Cmt8y{;6A~(vCA;+Ui-~+CypFA@WKAYHQgO48$SDqtWNy%|FWoL zQ{LUAgoK12=Z`qBHSGLce_?GjPxkj+&U2oNv4tmW&KA9MtTZbNWK>Vj4JRIVhXh_v zHa51l|FLVO{x4+v3iaERws_yAHE@eU3LW__W78u>0(;|Nr-MDEYWvZn)gr b62!t_Rbj*a;Va``ko!Gd{an^LB{Ts5lv!+I diff --git a/demo/cube/resource/shrooms/teethmouth.png b/demo/cube/resource/shrooms/teethmouth.png deleted file mode 100644 index 4a7b1c4df01183243bd34301cdbea02f1757ab18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 281 zcmeAS@N?(olHy`uVBq!ia0vp@K+Mg-1|*kkVowB8jKx9jP7LeL$-D$|I14-?i-8J+ zL70(Y)*K0-AbW|YuPgf<4ry@);pXX*4M3sQo-U3d8t3N*ZRBk>5ZE*M(q=~K1tB*N z^a!j<;qZT?=)o$iu!k}92wTnsqpQmsEc$Z;pJ@O8|M%|86s7n@_j;zEY0N*Z$|Lu5 zN`k+X&b0+I-M6gcbI(_Pu}Z4s-)cW5fwOmZF)>^?qr5B2cHiymelrdQZY1=?|J%F>N4wvWDio8Ejd@adD3%72%1&IdO=wzq%8 Z9Qe&;okLme8KAQnJYD@<);T3K0RUaAYLox~ diff --git a/demo/cube/resource/shrooms/terraformer.png b/demo/cube/resource/shrooms/terraformer.png deleted file mode 100644 index 0d66af42469706a707ca3b095922b6ccc11fff82..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 270 zcmeAS@N?(olHy`uVBq!ia0vp@K+Mg-1|*kkVowB8jKx9jP7LeL$-D$|I14-?i-8J+ zL70(Y)*K0-AbW|YuPgf<4rwuSw)%ea44}{gPZ!4!jq}MV2l)OeZC6FaMV> zJ@m8SK$GwD#oZSK?lj)*_j&PAZF#+vtHZiKCT*J~PI?M5)Vo=pPEgoe0(2{br>mdK II;Vst0J2MBwEzGB diff --git a/demo/cube/resource/tile.png b/demo/cube/resource/tile.png deleted file mode 100644 index ac71631527bf2fd0227a49610e1e73b924ebbac4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 703 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSY)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmPdHwUvW58tzeUO*w)%#er@=ltB<)VvZPmw~~#C^fMp zHASI3vm`^o-P1Q9MK6_|fq_Zc)5S5Q;?~*xM3Jfd_ z3<3;H3f?#0H=W%S*L?ZFQ3eIYmdHo*>+^o>40re?dq6P&S)~IW&Wr!Qbp;3yRAcOSaL^^U1f+(4iq silt3n!hx5i*`LAT9vc^`|9sy{3)@U*lxRD!fB^_RUHx3vIVCg!0FGPpn*aa+ diff --git a/demo/cube/shaders/flat.frag b/demo/cube/shaders/flat.frag deleted file mode 100644 index 61ec63d..0000000 --- a/demo/cube/shaders/flat.frag +++ /dev/null @@ -1,10 +0,0 @@ -#version 130 - -in vec4 ex_Color; -in vec2 UV; -uniform sampler2D myTextureSampler; - -void main(void) -{ - gl_FragColor = texture(myTextureSampler, UV) * ex_Color; -} diff --git a/demo/cube/shaders/flat.vert b/demo/cube/shaders/flat.vert deleted file mode 100644 index 7ebb009..0000000 --- a/demo/cube/shaders/flat.vert +++ /dev/null @@ -1,15 +0,0 @@ -#version 130 - -in vec3 in_Position; -in vec2 vertexUV; -in vec3 in_Color; - -out vec2 UV; -out vec4 ex_Color; - -void main(void) -{ - gl_Position = vec4(in_Position, 1); - UV = vertexUV; - ex_Color = vec4(in_Color, 1); -} diff --git a/demo/cube/shaders/triangle.frag b/demo/cube/shaders/triangle.frag deleted file mode 100644 index 61ec63d..0000000 --- a/demo/cube/shaders/triangle.frag +++ /dev/null @@ -1,10 +0,0 @@ -#version 130 - -in vec4 ex_Color; -in vec2 UV; -uniform sampler2D myTextureSampler; - -void main(void) -{ - gl_FragColor = texture(myTextureSampler, UV) * ex_Color; -} diff --git a/demo/cube/shaders/triangle.vert b/demo/cube/shaders/triangle.vert deleted file mode 100644 index 0e4da5a..0000000 --- a/demo/cube/shaders/triangle.vert +++ /dev/null @@ -1,17 +0,0 @@ -#version 130 - -in vec3 in_Position; -in vec3 in_Color; -in vec2 vertexUV; - -out vec4 ex_Color; -out vec2 UV; - -uniform mat4 MVP; - -void main(void) -{ - gl_Position = MVP * vec4(in_Position, 1); - ex_Color = vec4(in_Color, 1); - UV = vertexUV; -} diff --git a/demo/fill_screen/Makefile b/demo/fill_screen/Makefile index 781b96b..add1328 100644 --- a/demo/fill_screen/Makefile +++ b/demo/fill_screen/Makefile @@ -57,8 +57,7 @@ $(GLEW_DIR)%.o: $(GLEW_DIR)%.c $(GLEW_DIR)%.h $(SB_SRC_DIR)extension.o : $(addprefix $(SB_SRC_DIR),Box.hpp Segment.hpp Color.hpp filesystem.hpp Pixels.hpp Log.hpp) $(SB_SRC_DIR)Node.o : $(addprefix $(SB_SRC_DIR),Game.hpp Configuration.hpp Delegate.hpp Display.hpp Input.hpp Box.hpp Audio.hpp Log.hpp) $(SB_SRC_DIR)Sprite.o : $(addprefix $(SB_SRC_DIR),Node.hpp Game.hpp Box.hpp Animation.hpp Color.hpp extension.hpp Pixels.hpp Log.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 Log.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 Log.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) @@ -137,7 +136,7 @@ $(ANDROID_BUILD_DIR)/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: $(SB_SR # Run gradle and generate an APK -$(ANDROID_BUILD_DIR)/app-debug.apk: $(ANDROID_BUILD_DIR) $(ANDROID_BUILD_DIR)/$(ANDROID_MK) $(ANDROID_BUILD_DIR)/$(ANDROID_CLASS_DIR)/$(ANDROID_CLASS).java \ +$(ANDROID_BUILD_DIR)/app-debug.apk: $(ANDROID_BUILD_DIR) $(ANDROID_BUILD_DIR)/$(ANDROID_CLASS_DIR)/$(ANDROID_CLASS).java \ $(ANDROID_BUILD_DIR)/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml ANDROID_SDK_ROOT=$(ANDROID_SDK) $(ANDROID_BUILD_DIR)/gradlew -p $(ANDROID_BUILD_DIR) build ln -nsf app/build/outputs/apk/debug/app-debug.apk $(ANDROID_BUILD_DIR) diff --git a/demo/fill_screen/fill_screen.cpp b/demo/fill_screen/fill_screen.cpp index ceea70d..224bd1e 100644 --- a/demo/fill_screen/fill_screen.cpp +++ b/demo/fill_screen/fill_screen.cpp @@ -1,4 +1,14 @@ -/* +/*!
+ *        /\         +------------------------------------------------------+ 
+ *   ____/  \____   /| - Open source game framework licensed to freely use, |
+ *   \          /  / |   copy, modify and sell without restriction          |
+ * +--\ ^__^   /--+  |                                                      |
+ * | ~/        \~ |  | - created for              |
+ * | ~~~~~~~~~~~~ |  +------------------------------------------------------+
+ * | SPACE ~~~~~  | /
+ * |  ~~~~~~~ BOX |/
+ * +--------------+                                                    
+ * * Fill screen example by frank at shampoo.ooo * * This is an example program that fills the screen with a color every frame. It is a minimal example of a SPACEBOX program @@ -28,16 +38,6 @@ public: }; -// #if defined(__ANDROID__) || defined(ANDROID) -// int SDL_main() -// { -// FillScreen fill_screen = FillScreen(); -// fill_screen.load_gl_context(); -// fill_screen.run(); -// fill_screen.quit(); -// return 0; -// } -// #else /* Create a game object, load its GL context, and run the game. */ int main(int argc, char* argv[]) { @@ -47,4 +47,3 @@ int main(int argc, char* argv[]) fill_screen.quit(); return 0; } -// #endif diff --git a/icon/static_alt.png b/icon/static_alt.png new file mode 100644 index 0000000000000000000000000000000000000000..ed8c758a2ec543158e308b2a445bb03fb422aadc GIT binary patch literal 128663 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

+ *        /\         +------------------------------------------------------+ 
+ *   ____/  \____   /| - Open source game framework licensed to freely use, |
+ *   \          /  / |   copy, modify and sell without restriction          |
+ * +--\ ^__^   /--+  |                                                      |
+ * | ~/        \~ |  | - created for              |
+ * | ~~~~~~~~~~~~ |  +------------------------------------------------------+
+ * | SPACE ~~~~~  | /
+ * |  ~~~~~~~ BOX |/
+ * +--------------+                                                  
*/ #include "Attributes.hpp" diff --git a/src/Attributes.hpp b/src/Attributes.hpp index f9be5b0..b9e4d37 100644 --- a/src/Attributes.hpp +++ b/src/Attributes.hpp @@ -1,88 +1,88 @@ -/* /\ +--------------------------------------------------------------+ - ____/ \____ /| - zlib/MIT/Unlicenced game framework licensed to freely use, | - \ / / | copy, modify and sell without restriction | - +--\ ^__^ /--+ | | - | ~/ \~ | | - originally created at [http://nugget.fun] | - | ~~~~~~~~~~~~ | +--------------------------------------------------------------+ - | SPACE ~~~~~ | / - | ~~~~~~~ BOX |/ - +--------------+ - - [Attributes.hpp] - - An Attributes object is a container that acts as a limited, but dynamically typed vector for - vertex properties to be passed to a GL buffer object and described by a vertex array object. - It uses the variant library and type inference to determine the vertex type for an object. The - appropriate variant will be applied based on the first vertices submitted to the object. - - The object can contain float, int, or unsigned int vertices of any dimension from 1D to 4D. It - can also contain bool vertices from 2D to 4D. Vertices can be submitted at initialization through - the constructor or added and appended later using the add function. If the object is initialized - without any vertices, it will be in an empty state indicated by the std::monostate variant - alternative. - - The constructor accepts initializer lists, so unknown types can be submitted as long as a single type - can be inferred. For example, - - Attributes({1.0f, 0.5f, -1.0f}); // glm::vec3 attributes containing one vertex - Attributes({ // glm::vec3 attributes containing two vertices - {1.0f, 0.5f, -1.0f}, - {0.0f, 1.0f, 1.0f} - }); - Attributes({1, 2}); // glm::ivec2 attributes containing one vertex - Attributes(glm::uvec2{1, 2}); // glm::uvec2 attributes containing one vertex - Attributes(1, 2); // same as Attributes({1, 2}), each argument is treated as a - // coordinate - Attributes({ // inferred as glm::uvec3, so the -5, -555, and the - {-5, 55, -555}, // decimal precision are lost - glm::uvec3{8, 88, 888}, - {9.9, 9.99, 9.999} - }); - - The add function also accepts initializer lists and any type that can be converted into the original. - - sb::Attributes attr, attr2, attr3, attr4; - attr.add(420); - attr.add({69, 9000}); - attr.add({}); - attr.add({{1}}); - std::cout << attr << std::endl; - attr2.add(glm::ivec4{1, 1, 2, 3}); - attr2.add({{5.0f, 8.0f, 13.0f, 21.0f}, {34.0f, 55.0f, 89.0f, 144.0f}}); - attr2.add({glm::uvec4{5, 8, 13, 21}, glm::uvec4{34, 55, 89, 144}}); - attr2.add({0.0f, 0.0f, 0.0f, 0.0f}); - std::cout << attr2 << std::endl; - attr3.add(1.1f); - attr3.add(2); - attr3.add(std::vector{3, 4}); - std::cout << attr3 << std::endl; - attr3 = attr2; - attr2 = attr4; - std::cout << attr3 << std::endl; - std::cout << attr2 << std::endl; - attr4 = sb::Attributes({{-5, 55, -555}, glm::ivec3{8, 88, 888}, {9.99, 9.9, 9.0}}); - attr4.add(1000, 10000, 100000); - attr4.add(1, 11); - std::cout << attr4 << std::endl; - attr2 = sb::Attributes(std::vector{5.5, 6.6, 7.7, 8.8}); - attr2.add(glm::vec2{9.9f, 10.10f}); - std::cout << attr2 << std::endl; - - This prints - - warning: was not added to the attributes because its type is incompatible - { 420 69 9000 1 } - warning: { 0 0 0 0 } was not added to the attributes because its type is incompatible - { {1, 1, 2, 3} {5, 8, 13, 21} {34, 55, 89, 144} {5, 8, 13, 21} {34, 55, 89, 144} } - { 1.1 2 3 4 } - { {1, 1, 2, 3} {5, 8, 13, 21} {34, 55, 89, 144} {5, 8, 13, 21} {34, 55, 89, 144} } - - warning: { {1, 11} } was not added to the attributes because its type is incompatible - { {-5, 55, -555} {8, 88, 888} {9, 9, 9} {1000, 10000, 100000} } - warning: { {9.9, 10.1} } was not added to the attributes because its type is incompatible - { 5.5 6.6 7.7 8.8 } - -*/ +/*!
+ *        /\         +------------------------------------------------------+
+ *   ____/  \____   /| - Open source game framework licensed to freely use, |
+ *   \          /  / |   copy, modify and sell without restriction          |
+ * +--\ ^__^   /--+  |                                                      |
+ * | ~/        \~ |  | - created for              |
+ * | ~~~~~~~~~~~~ |  +------------------------------------------------------+
+ * | SPACE ~~~~~  | /
+ * |  ~~~~~~~ BOX |/
+ * +--------------+   
+ * + * Attributes + * ========== + * + * An Attributes object is a container that acts as a limited, but dynamically typed vector for + * vertex properties to be passed to a GL buffer object and described by a vertex array object. + * It uses the variant library and type inference to determine the vertex type for an object. The + * appropriate variant will be applied based on the first vertices submitted to the object. + * + * The object can contain float, int, or unsigned int vertices of any dimension from 1D to 4D. It + * can also contain bool vertices from 2D to 4D. Vertices can be submitted at initialization through + * the constructor or added and appended later using the add function. If the object is initialized + * without any vertices, it will be in an empty state indicated by the std::monostate variant + * alternative. + * + * The constructor accepts initializer lists, so unknown types can be submitted as long as a single type + * can be inferred. For example, + * + * Attributes({1.0f, 0.5f, -1.0f}); // glm::vec3 attributes containing one vertex + * Attributes({ // glm::vec3 attributes containing two vertices + * {1.0f, 0.5f, -1.0f}, + * {0.0f, 1.0f, 1.0f} + * }); + * Attributes({1, 2}); // glm::ivec2 attributes containing one vertex + * Attributes(glm::uvec2{1, 2}); // glm::uvec2 attributes containing one vertex + * Attributes(1, 2); // same as Attributes({1, 2}), each argument is treated as a coordinate + * Attributes({ // inferred as glm::uvec3, so the -5, -555, and the decimal precision are lost + * {-5, 55, -555}, + * glm::uvec3{8, 88, 888}, + * {9.9, 9.99, 9.999} + * }); + * + * The add function also accepts initializer lists and any type that can be converted into the original. + * + * sb::Attributes attr, attr2, attr3, attr4; + * attr.add(420); + * attr.add({69, 9000}); + * attr.add({}); + * attr.add({{1}}); + * std::cout << attr << std::endl; + * attr2.add(glm::ivec4{1, 1, 2, 3}); + * attr2.add({{5.0f, 8.0f, 13.0f, 21.0f}, {34.0f, 55.0f, 89.0f, 144.0f}}); + * attr2.add({glm::uvec4{5, 8, 13, 21}, glm::uvec4{34, 55, 89, 144}}); + * attr2.add({0.0f, 0.0f, 0.0f, 0.0f}); + * std::cout << attr2 << std::endl; + * attr3.add(1.1f); + * attr3.add(2); + * attr3.add(std::vector{3, 4}); + * std::cout << attr3 << std::endl; + * attr3 = attr2; + * attr2 = attr4; + * std::cout << attr3 << std::endl; + * std::cout << attr2 << std::endl; + * attr4 = sb::Attributes({{-5, 55, -555}, glm::ivec3{8, 88, 888}, {9.99, 9.9, 9.0}}); + * attr4.add(1000, 10000, 100000); + * attr4.add(1, 11); + * std::cout << attr4 << std::endl; + * attr2 = sb::Attributes(std::vector{5.5, 6.6, 7.7, 8.8}); + * attr2.add(glm::vec2{9.9f, 10.10f}); + * std::cout << attr2 << std::endl; + * + * This prints + * + * warning: was not added to the attributes because its type is incompatible + * { 420 69 9000 1 } + * warning: { 0 0 0 0 } was not added to the attributes because its type is incompatible + * { {1, 1, 2, 3} {5, 8, 13, 21} {34, 55, 89, 144} {5, 8, 13, 21} {34, 55, 89, 144} } + * { 1.1 2 3 4 } + * { {1, 1, 2, 3} {5, 8, 13, 21} {34, 55, 89, 144} {5, 8, 13, 21} {34, 55, 89, 144} } + * + * warning: { {1, 11} } was not added to the attributes because its type is incompatible + * { {-5, 55, -555} {8, 88, 888} {9, 9, 9} {1000, 10000, 100000} } + * warning: { {9.9, 10.1} } was not added to the attributes because its type is incompatible + * { 5.5 6.6 7.7 8.8 } + */ #ifndef SB_ATTRIBUTES_H_ #define SB_ATTRIBUTES_H_ @@ -135,11 +135,15 @@ namespace sb public: - /* The default constructor creates an empty set of attributes. An empty set of attributes is indicated by - * the class's vertex variant having the std::monostate variant. */ + /*! + * The default constructor creates an empty set of attributes. An empty set of attributes is indicated by + * the class's vertex variant having the std::monostate variant. + */ Attributes() {}; - /* Construct a new Attributes object with vertices set to the vertices contained in this vector */ + /*! + * Construct a new Attributes object with vertices set to the vertices contained in this vector + */ template Attributes(const std::vector& vertices) : vertices(vertices) { @@ -153,26 +157,34 @@ namespace sb sb::Log::log(message, sb::Log::DEBUG); } - /* Add a vertex. The vertex can be any of the variant types. */ + /*! + * Add a vertex. The vertex can be any of the variant types. + */ template Attributes(const Type& vertex) : Attributes({vertex}) {} - /* Add a vertex by specifying each coordinate as a separate argument. All arguments must have idential type. */ + /*! + * Add a vertex by specifying each coordinate as a separate argument. All arguments must have idential type. + */ template), std::nullptr_t> = nullptr> Attributes(const XType& coordinate_0, const CoordinateTypes&... coordinates) : Attributes({std::initializer_list({coordinate_0, coordinates ...})}) {} - /* Add vertices by passing an uninitialized list of vertices. This template applies to a list of + /*! + * Add vertices by passing an uninitialized list of vertices. This template applies to a list of * vertices where the list type is undeclared but the containing vertices have been initialized - * or the type can be inferred because the vertices are 1D scalars */ + * or the type can be inferred because the vertices are 1D scalars. + */ template Attributes(const std::initializer_list& vertices) : Attributes(std::vector(vertices.begin(), vertices.end())) {} - /* Add vertices by passing a two-dimensional initializer list, a list of uninitialized vertices. + /*! + * Add vertices by passing a two-dimensional initializer list, a list of uninitialized vertices. * The appropriate glm vertex size is applied by looking at the length of the first uninitialized - * vertex in the initializer list. */ + * vertex in the initializer list. + */ template Attributes(const std::initializer_list>& vertices) { @@ -200,8 +212,15 @@ namespace sb void add(const Attributes&); - /* Add a 2D, 3D or 4D vertex by specifying each coordinate as a separate argument. All arguments must have - * identical type. */ + /*! + * Add a 2D, 3D or 4D vertex by specifying each coordinate as a separate argument. All arguments must have + * identical type. + * + * @param coordinate_0 x-coordinate + * @param coordinate_1 y-coordinate + * @param coordinate_2 z-coordinate (optional) + * @param coordinate_3 w-coordinate (optional) + */ template && (... && std::is_same_v), std::nullptr_t> = nullptr> @@ -213,23 +232,38 @@ namespace sb add({std::initializer_list({coordinate_0, coordinate_1, coordinates ...})}); } - /* Return a const reference to the vertex at the specified index in the attributes vector. */ + /*! + * Return a const reference to the vertex at the specified index in the attributes vector. + * + * @param index vertex index + * @return std::vector of type VertexType + */ template const VertexType& read(std::size_t index) const { return std::get>(vertices)[index]; } - /* 2D lookup of a value in the attributes. Return a const reference to the coordinate value at inner index - * within a vertex at outer index in the attributes vector. */ + /*! + * 2D lookup of a value in the attributes. Return a const reference to the coordinate value at inner index + * within a vertex at outer index in the attributes vector. + * + * @param outer vertex index + * @param inner coordinate index within vertex + * @return value of type CoordinateType + */ template const CoordinateType& read(std::size_t outer, std::size_t inner) { return std::get>(vertices)[outer][inner]; } - /* Return the attributes as a reference to a typed vector. If the type is not the alternative in use by the - * attributes, std::bad_variant_access will be thrown. */ + /*! + * Return the attributes as a reference to a typed vector. If the type is not the alternative in use by the + * attributes, std::bad_variant_access will be thrown. + * + * @return std::vector of type VertexType + */ template operator const std::vector&() const { diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 1c439c1..279bd36 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -48,10 +48,9 @@ void Configuration::set_defaults() {"ignore-repeat-keypress", true} }; sys_config["display"] = { - // {"dimensions", {960, 540}}, - {"dimensions", {480, 800}}, + {"dimensions", {960, 540}}, {"framerate", 60}, - {"title", "[SPACE BOX]"}, + {"title", "[SPACEBOX]"}, {"debug", false}, {"show-cursor", false}, {"render-test-spacing", 2}, @@ -99,9 +98,10 @@ void Configuration::set_defaults() {"enabled", false}, {"debug-to-stdout", false}, {"debug-to-file", false}, - {"ouput-directory", "."}, + {"output-directory", "."}, {"info-file-name", "space_box_log.txt"}, - {"debug-file-name", "space_box_debug_log.txt"} + {"debug-file-name", "space_box_debug_log.txt"}, + {"short-name", "spacebox"} }; sys_config["configuration"] = { {"auto-refresh", false}, @@ -114,15 +114,10 @@ void Configuration::set_defaults() 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(); - } + user_config = nlohmann::json::parse(sb::file_to_string(path)); + + /* merge into the full config JSON dict */ + merge(); } /* Load the configuration file at Configuration::config_path */ @@ -135,7 +130,7 @@ void Configuration::load() * dict (loaded from disk by the load function) */ void Configuration::merge() { - if (not user_config.empty()) + if (!user_config.empty()) { /* loop over first level key/value pairs */ for (auto& item: user_config.items()) diff --git a/src/Configuration.hpp b/src/Configuration.hpp index bf305ba..53cf907 100644 --- a/src/Configuration.hpp +++ b/src/Configuration.hpp @@ -1,4 +1,4 @@ -/* /\ +--------------------------------------------------------------+ + /* +--------------------------------------------------------------+ ____/ \____ /| - zlib/MIT/Unlicenced game framework licensed to freely use, | \ / / | copy, modify and sell without restriction | +--\ ^__^ /--+ | | @@ -19,6 +19,7 @@ #include "Node.hpp" #include "Animation.hpp" #include "Log.hpp" +#include "extension.hpp" class Configuration : public Node { @@ -70,8 +71,6 @@ namespace glm /* Extend std::filesystem so nlohmann::json can read and write std::filesystem::path */ #if defined(__MINGW32__) namespace std::experimental::filesystem -// #elif defined(__ANDROID__) || defined(ANDROID) -// namespace std::__fs::filesystem #else namespace std::filesystem #endif diff --git a/src/Game.cpp b/src/Game.cpp index 380f4a8..d299f47 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -1,3 +1,13 @@ + /* +------------------------------------------------------+ + ____/ \____ /| - Open source game framework licensed to freely use, | + \ / / | copy, modify and sell without restriction | ++--\ ^__^ /--+ | | +| ~/ \~ | | - created for | +| ~~~~~~~~~~~~ | +------------------------------------------------------+ +| SPACE ~~~~~ | / +| ~~~~~~~ BOX |/ ++-------------*/ + #include "Game.hpp" Game::Game() @@ -14,21 +24,43 @@ Game::Game() default_log_category_priority = SDL_LOG_PRIORITY_INFO; } SDL_LogSetPriority(sb::Log::DEFAULT_CATEGORY, default_log_category_priority); - /* set custom log function that prints to stdout/stderr and to file if enabled */ + + /* Set custom log function that prints to stdout/stderr and to file if enabled */ SDL_LogSetOutputFunction(&Game::sdl_log_override, this); - /* pretty print config to debug log */ + + /* Log the current working directory as seen by std::filesystem */ std::ostringstream log_message; + log_message << "Current path is " << std::filesystem::current_path(); + sb::Log::log(log_message); + + /* Log Android storage paths as determined by SDL */ +#if defined(__ANDROID__) || defined(ANDROID) + log_message = std::ostringstream(); + log_message << "SDL_AndroidGetInternalStoragePath() is " << SDL_AndroidGetInternalStoragePath() << std::endl; + log_message << "SDL_AndroidGetExternalStorageState() is " << SDL_AndroidGetExternalStorageState() << std::endl; + log_message << "SDL_AndroidGetExternalStoragePath() is " << SDL_AndroidGetExternalStoragePath(); + sb::Log::log(log_message); +#endif + + /* Pretty print config JSON to debug log */ + log_message = std::ostringstream(); log_message << std::setw(4) << configuration() << std::endl; sb::Log::log(log_message, sb::Log::DEBUG); - /* tell SDL which render driver you will be requesting when calling SDL_CreateRenderer */ + + /* Tell SDL which render driver you will be requesting when calling SDL_CreateRenderer */ SDL_SetHint(SDL_HINT_RENDER_DRIVER, configuration()["display"]["render driver"].get().c_str()); - /* initialize the buffer of frame lengths which will be used to calculate FPS */ + + /* Initialize the buffer of frame lengths which will be used to calculate FPS */ frame_length_history.reserve(5000); set_framerate(configuration()["display"]["framerate"]); + + /* Subscribe to SDL's quit event */ delegate.subscribe(&Game::handle_quit_event, this, SDL_QUIT); + /* Needed for displaying fullscreen correctly on Linux (?) Also might need SDL_VIDEO_CENTERED (?) */ std::string fullscreen_env_assigment = "SDL_VIDEO_X11_LEGACY_FULLSCREEN=0"; putenv(const_cast(fullscreen_env_assigment.c_str())); + /* log compiled and linked SDL versions */ SDL_version version; log_message = std::ostringstream(); @@ -223,19 +255,29 @@ void Game::load_gl_context() log_display_mode(); } -/* Overrides SDL's default log function to log a message to stdout/stderr and, if log is enabled in the - * global configuration, to a file. Debug level statements may be suppressed, printed to stdout, or printed to - * both stdout and file, depending on the global configuration. */ void Game::sdl_log_override(void* userdata, int category, SDL_LogPriority priority, const char* message) { Game* game = static_cast(userdata); std::ostream& out = (priority > SDL_LOG_PRIORITY_WARN) ? std::cerr : std::cout; - /* print to stdout/stderr if priority is higher than debug or debug statements are enabled */ + + /* Print to stdout/stderr if priority is higher than debug or debug statements are enabled */ if (priority > SDL_LOG_PRIORITY_DEBUG || game->configuration()["log"]["debug-to-stdout"]) { out << message << std::endl; + + /* If printing to stdout, print to Android log as well */ +#if defined(__ANDROID__) || defined(ANDROID) + __android_log_print(ANDROID_LOG_VERBOSE, game->configuration()["log"]["short-name"].get_ref().c_str(), "%s", message); +#endif + } - /* handle writing to log file */ + + /* Create a date + time timestamp */ + std::ostringstream timestamp; + std::time_t now = std::time(nullptr); + timestamp << std::put_time(std::localtime(&now), "%F %T "); + + /* Handle writing to log file */ if (game->configuration()["log"]["enabled"]) { fs::path path = game->configuration()["log"]["output-directory"]; @@ -243,18 +285,20 @@ void Game::sdl_log_override(void* userdata, int category, SDL_LogPriority priori { fs::create_directories(path); } - /* prepend a timestamp to the message */ - std::time_t now = std::time(nullptr); + + /* Prepend timestamp to the message */ std::stringstream stamped_message; - stamped_message << std::put_time(std::localtime(&now), "%F %T ") << message; - /* if debug is enabled, append message to debug log file */ + stamped_message << timestamp.str() << message; + + /* If debug is enabled, append message to debug log file */ if (game->configuration()["log"]["debug-to-file"]) { fs::path debug_path = path / game->configuration()["log"]["debug-file-name"]; std::ofstream debug_stream(debug_path, std::ios_base::app); debug_stream << stamped_message.str() << std::endl; } - /* only append messages to the info log that are higher than debug priority */ + + /* Only append messages to the info log that are higher than debug priority */ if (priority > SDL_LOG_PRIORITY_DEBUG) { fs::path info_path = path / game->configuration()["log"]["info-file-name"]; @@ -277,10 +321,10 @@ void Game::print_frame_length_history() GLuint Game::load_shader(const fs::path& path, GLenum type) const { GLuint shader = glCreateShader(type); - std::fstream file = std::fstream(path); std::ostringstream message; std::string contents = sb::file_to_string(path); - glShaderSource(shader, 1, reinterpret_cast(&contents), 0); + const char *c_str = contents.c_str(); + glShaderSource(shader, 1, &c_str, 0); glCompileShader(shader); GLint is_compiled; glGetShaderiv(shader, GL_COMPILE_STATUS, &is_compiled); diff --git a/src/Game.hpp b/src/Game.hpp index 2166718..8fd486e 100644 --- a/src/Game.hpp +++ b/src/Game.hpp @@ -1,3 +1,13 @@ + /* +------------------------------------------------------+ + ____/ \____ /| - Open source game framework licensed to freely use, | + \ / / | copy, modify and sell without restriction | ++--\ ^__^ /--+ | | +| ~/ \~ | | - created for | +| ~~~~~~~~~~~~ | +------------------------------------------------------+ +| SPACE ~~~~~ | / +| ~~~~~~~ BOX |/ ++-------------*/ + #pragma once #include @@ -26,6 +36,10 @@ #include "glew/glew.h" #endif +#if defined(__ANDROID__) || defined(ANDROID) +#include +#endif + #include "Node.hpp" #include "Input.hpp" #include "Display.hpp" @@ -60,7 +74,20 @@ private: Configuration _configuration {this}; SDL_Window* _window; - static void sdl_log_override(void*, int, SDL_LogPriority, const char*); + /*! + * Overrides SDL's default log function to log a message to stdout/stderr and, if log is enabled in the + * global configuration, to a file. Debug level statements may be suppressed, printed to stdout, or printed to + * both stdout and file, depending on the global configuration. This shouldn't be called directly. Use + * `sb::Log::log` instead. + * + * @see sb::Log::log(const std::string&) + * + * @param userdata must be a pointer to Game + * @param category SDL log category. It is not used by SPACEBOX, so it should be sb::Log::DEFAULT_CATEGORY + * @param priority SDL log priority, which is equivalent to the values in sb::Log::Level + * @param message message as a C-style string + */ + static void sdl_log_override(void* userdata, int category, SDL_LogPriority priority, const char* message); public: @@ -131,9 +158,14 @@ public: virtual std::string class_name() const { return "Game"; } ~Game(); - /* Applies delta timing to a value: returns the value as weighted by the amount of time passed since the + /*! + * Applies delta timing to a value: returns the value as weighted by the amount of time passed since the * last frame update, allowing for values to change the same amount over time independent of the frame rate. - * The amount is how much the value should change per second. */ + * The amount is how much the value should change per second. + * + * @param amount any scalar value to be weighted + * @return weighted value + */ template T weight(T amount) { diff --git a/src/Log.cpp b/src/Log.cpp index ae64d52..2067fd9 100644 --- a/src/Log.cpp +++ b/src/Log.cpp @@ -1,12 +1,12 @@ -/* /\ +--------------------------------------------------------------+ - ____/ \____ /| - zlib/MIT/Unlicenced game framework licensed to freely use, | - \ / / | copy, modify and sell without restriction | - +--\ ^__^ /--+ | | - | ~/ \~ | | - originally created at [http://nugget.fun] | - | ~~~~~~~~~~~~ | +--------------------------------------------------------------+ - | SPACE ~~~~~ | / - | ~~~~~~~ BOX |/ - +-------------*/ + /* +------------------------------------------------------+ + ____/ \____ /| - Open source game framework licensed to freely use, | + \ / / | copy, modify and sell without restriction | ++--\ ^__^ /--+ | | +| ~/ \~ | | - created for | +| ~~~~~~~~~~~~ | +------------------------------------------------------+ +| SPACE ~~~~~ | / +| ~~~~~~~ BOX |/ ++-------------*/ #include "Log.hpp" diff --git a/src/Log.hpp b/src/Log.hpp index 770994c..0bedd4f 100644 --- a/src/Log.hpp +++ b/src/Log.hpp @@ -1,20 +1,21 @@ -/* /\ +--------------------------------------------------------------+ - ____/ \____ /| - zlib/MIT/Unlicenced game framework licensed to freely use, | - \ / / | copy, modify and sell without restriction | - +--\ ^__^ /--+ | | - | ~/ \~ | | - originally created at [http://nugget.fun] | - | ~~~~~~~~~~~~ | +--------------------------------------------------------------+ - | SPACE ~~~~~ | / - | ~~~~~~~ BOX |/ - +--------------+ - - [Log.hpp] - - Log messages of specified priority through the SDL logging method, which is overridden in - the Game class to write to stdout, file, or both, depending on the user's configuration - settings. - -*/ +/*!
+ *        /\         +------------------------------------------------------+
+ *   ____/  \____   /| - Open source game framework licensed to freely use, |
+ *   \          /  / |   copy, modify and sell without restriction          |
+ * +--\ ^__^   /--+  |                                                      |
+ * | ~/        \~ |  | - created for              |
+ * | ~~~~~~~~~~~~ |  +------------------------------------------------------+
+ * | SPACE ~~~~~  | /
+ * |  ~~~~~~~ BOX |/
+ * +--------------+                                                    
+ * + * Log + * === + * + * Log messages of specified priority through the SDL logging method, which is overridden in + * the Game class to write to stdout, file, or both, depending on the user's configuration + * settings. + */ #ifndef SB_LOG_H_ #define SB_LOG_H_ diff --git a/src/Texture.cpp b/src/Texture.cpp index 1274ff2..11b0c1a 100644 --- a/src/Texture.cpp +++ b/src/Texture.cpp @@ -1,12 +1,12 @@ -/* /\ +--------------------------------------------------------------+ - ____/ \____ /| - zlib/MIT/Unlicenced game framework licensed to freely use, | - \ / / | copy, modify and sell without restriction | - +--\ ^__^ /--+ | | - | ~/ \~ | | - originally created at [http://nugget.fun] | - | ~~~~~~~~~~~~ | +--------------------------------------------------------------+ - | SPACE ~~~~~ | / - | ~~~~~~~ BOX |/ - +-------------*/ + /* +------------------------------------------------------+ + ____/ \____ /| - Open source game framework licensed to freely use, | + \ / / | copy, modify and sell without restriction | ++--\ ^__^ /--+ | | +| ~/ \~ | | - created for | +| ~~~~~~~~~~~~ | +------------------------------------------------------+ +| SPACE ~~~~~ | / +| ~~~~~~~ BOX |/ ++-------------*/ #include "Texture.hpp" using namespace sb; diff --git a/src/Texture.hpp b/src/Texture.hpp index a43903c..82f6f74 100644 --- a/src/Texture.hpp +++ b/src/Texture.hpp @@ -1,23 +1,24 @@ -/* /\ +--------------------------------------------------------------+ - ____/ \____ /| - zlib/MIT/Unlicenced game framework licensed to freely use, | - \ / / | copy, modify and sell without restriction | - +--\ ^__^ /--+ | | - | ~/ \~ | | - originally created at [http://nugget.fun] | - | ~~~~~~~~~~~~ | +--------------------------------------------------------------+ - | SPACE ~~~~~ | / - | ~~~~~~~ BOX |/ - +--------------+ - - [Texture.hpp] - - The Texture class abstracts the file opening, data loading and binding steps of Open GL - texture creation. Currently it only supports loading GL_TEXTURE_2D with GL_RGB8 pixels - and automatic GL_NEAREST mipmapping. Support may be added for users to pass in their own - pixel data, to customize mipmapping, and more. The class can also be used in a custom - way by passing the Texture ID to GL functions instead of using the class's member - functions. - -*/ +/*!
+ *        /\         +------------------------------------------------------+ 
+ *   ____/  \____   /| - Open source game framework licensed to freely use, |
+ *   \          /  / |   copy, modify and sell without restriction          |
+ * +--\ ^__^   /--+  |                                                      |
+ * | ~/        \~ |  | - created for              |
+ * | ~~~~~~~~~~~~ |  +------------------------------------------------------+
+ * | SPACE ~~~~~  | /
+ * |  ~~~~~~~ BOX |/
+ * +--------------+                                                    
+ * + * Texture + * ======= + * + * The Texture class abstracts the file opening, data loading and binding steps of Open GL + * texture creation. Currently it only supports loading GL_TEXTURE_2D with GL_RGB8 pixels + * and automatic GL_NEAREST mipmapping. Support may be added for users to pass in their own + * pixel data, to customize mipmapping, and more. The class can also be used in a custom + * way by passing the Texture ID to GL functions instead of using the class's member + * functions. + */ #ifndef SB_TEXTURE_H_ #define SB_TEXTURE_H_ @@ -104,7 +105,7 @@ namespace sb * * The texture must have been previously generated with a size to use this generic pixel data load function. The size is * determined with `glGetTexLevelParameter`, which is only available to OpenGL ES 3.1+, so this overload is not available - * for Emscripten builds. + * for Emscripten or Android builds. * * see OpenGL's `glTexSubImage2D` * @@ -117,7 +118,7 @@ namespace sb /*! * Return the size in pixels of mipmap level 0 (the only mipmap level supported by this class). If the texture hasn't been, * generated, return {0, 0}. `glGetTexLevelParameter` is only available in OpenGL ES 3.1+, so this function is removed from - * Emscripten builds. + * Emscripten and Android builds. * * @return glm::vec2 A vector consisting of {TEXTURE_MIPMAP_WIDTH, TEXTURE_MIPMAP_HEIGHT} */ diff --git a/src/android/revise_skeleton.sh b/src/android/revise_skeleton.sh index 1f5cbbf..550221d 100755 --- a/src/android/revise_skeleton.sh +++ b/src/android/revise_skeleton.sh @@ -21,11 +21,14 @@ ANDROID_CLASS=$6 ANDROID_APP_NAME=$7 ANDROID_MIN_TARGET=$8 ANDROID_NDK=$9 +SB_SRC="${10}" +SB_LIB_SRC="${11}" +SRC="${12}" sed -i "s/org.libsdl.app/$ANDROID_PACKAGE/" "$ANDROID_BUILD_DIR/app/build.gradle" "$ANDROID_BUILD_DIR/$ANDROID_MANIFEST" sed -i "s/^#.*\(APP_STL\)/\1/" "$ANDROID_BUILD_DIR/$ANDROID_APP_MK" echo "APP_CPPFLAGS := -std=c++17 -fexceptions -frtti" >> "$ANDROID_BUILD_DIR/$ANDROID_APP_MK" -sed -i -e 's/^LOCAL_LDLIBS.*/& -lGLESv3/' "$ANDROID_BUILD_DIR/$ANDROID_MK" +sed -i -e 's/^LOCAL_LDLIBS.*/& -lGLESv3 -llog/' "$ANDROID_BUILD_DIR/$ANDROID_MK" sed -i 's/0x0002/0x0003/' "$ANDROID_BUILD_DIR/$ANDROID_MANIFEST" sed -i "s/\(minSdkVersion\).*16/\1 $ANDROID_MIN_TARGET/" "$ANDROID_BUILD_DIR/app/build.gradle" "$ANDROID_BUILD_DIR/$ANDROID_APP_MK" sed -i "s/\(android\)-16/\1-$ANDROID_MIN_TARGET/" "$ANDROID_BUILD_DIR/app/build.gradle" "$ANDROID_BUILD_DIR/$ANDROID_APP_MK" @@ -33,9 +36,9 @@ sed -i "11i\ ndkVersion \"$ANDROID_NDK\"" "$ANDROID_BUILD_DIR/app/build.gradl sed -i 's/1536m/4096m/' "$ANDROID_BUILD_DIR/gradle.properties" sed -i "s/^#.*\(org.gradle.parallel\)/\1/" "$ANDROID_BUILD_DIR/gradle.properties" sed -i 's/^LOCAL_SHARED_LIBRARIES.*/& SDL2_image SDL2_mixer SDL2_ttf/' "$ANDROID_BUILD_DIR/$ANDROID_MK" -sed -i 's#^LOCAL_C_INCLUDES.*#& $(LOCAL_PATH)/../../../../../../../../lib $(LOCAL_PATH)/../../../../../../../../src#' \ +sed -i "s#^LOCAL_C_INCLUDES.*#& \$(LOCAL_PATH)/../../../../../../$SB_LIB_SRC \$(LOCAL_PATH)/../../../../../../$SB_SRC#" \ "$ANDROID_BUILD_DIR/$ANDROID_MK" -sed -i 's#YourSourceHere.c#$(LOCAL_PATH)/../../../../../../fill_screen.cpp#' "$ANDROID_BUILD_DIR/$ANDROID_MK" +sed -i "s#YourSourceHere.c#\$(wildcard \$(LOCAL_PATH)/../../../../../../*.cpp)#" "$ANDROID_BUILD_DIR/$ANDROID_MK" sed -i 's#^LOCAL_SRC_FILES.*#& $(wildcard $(LOCAL_PATH)/../../../../../../../../src/*.cpp)#' "$ANDROID_BUILD_DIR/$ANDROID_MK" sed -i 's#^LOCAL_SRC_FILES.*#& $(wildcard $(LOCAL_PATH)/../../../../../../../../lib/sdl2-gfx/*.c)#' "$ANDROID_BUILD_DIR/$ANDROID_MK" sed -i "s/\(name=\)\"SDLActivity\"/\1\"$ANDROID_CLASS\"/" "$ANDROID_BUILD_DIR/$ANDROID_MANIFEST" diff --git a/src/extension.cpp b/src/extension.cpp index 47dc08a..54363ad 100644 --- a/src/extension.cpp +++ b/src/extension.cpp @@ -1,3 +1,13 @@ + /* +------------------------------------------------------+ + ____/ \____ /| - Open source game framework licensed to freely use, | + \ / / | copy, modify and sell without restriction | ++--\ ^__^ /--+ | | +| ~/ \~ | | - created for | +| ~~~~~~~~~~~~ | +------------------------------------------------------+ +| SPACE ~~~~~ | / +| ~~~~~~~ BOX |/ ++-------------*/ + #include "Pixels.hpp" #include "extension.hpp" @@ -593,35 +603,72 @@ fs::path sb::get_next_file_name(fs::path directory, int zfill, std::string prefi return path; } -/* Read the file at path into a string and return the string */ std::string sb::file_to_string(const fs::path& path) { - std::fstream file; - file.open(path); - std::ostringstream message; - std::string m_str; - if (!file.is_open()) + std::string contents = ""; + +#if !defined(__ANDROID__) && !defined(ANDROID) + + std::ostringstream log_message; + if (!fs::exists(path)) { - message << "failed to open " << path; - message.str(); - SDL_Log("%s", m_str.c_str()); - return ""; + log_message << "No file found at " << path; + sb::Log::log(log_message, sb::Log::Level::WARN); } else { - message << "opened file " << path; - m_str = message.str(); - SDL_Log("%s", m_str.c_str()); - file.seekg(0, std::ios::end); - size_t size = file.tellg(); - std::string contents; - contents.resize(size + 1, '\0'); - file.seekg(0, std::ios::beg); - file.read(contents.data(), size); - contents[size] = '\0'; - SDL_LogDebug(SDL_LOG_CATEGORY_CUSTOM, "%s", contents.c_str()); - return contents; + std::ifstream file; + file.open(path); + if (!file.is_open()) + { + log_message << "Failed to open " << path; + sb::Log::log(log_message, sb::Log::Level::WARN); + } + else + { + /* Read file using std::string's range constructor, from the beginning of the file stream to end of stream (represented + * by {} (?)) */ + contents = std::string(std::istreambuf_iterator(file), {}); + std::size_t size = file.tellg(); + log_message << "Opened file " << path << " (" << (size / 1000.0f) << "KB)"; + sb::Log::log(log_message); + sb::Log::log(contents, sb::Log::Level::DEBUG); + } } + +#else + + std::unique_ptr sdl_rw(SDL_RWFromFile(path.c_str(), "r"), SDL_RWclose); + if (sdl_rw.get() == nullptr) + { + __android_log_print(ANDROID_LOG_VERBOSE, "spacebox", "Unable to open file %s", SDL_GetError()); + } + else + { + int byte_count = SDL_RWsize(sdl_rw.get()); + __android_log_print(ANDROID_LOG_VERBOSE, "spacebox", "File at %s is %fKB", path.c_str(), (byte_count / 1000.0f)); + + contents.resize(byte_count); + + int nb_read_total = 0, nb_read = 1; + while (nb_read_total < byte_count && nb_read != 0) + { + nb_read = SDL_RWread(sdl_rw.get(), &contents[nb_read_total], 1, (byte_count - nb_read_total)); + nb_read_total += nb_read; + } + if (nb_read_total != byte_count) + { + __android_log_print(ANDROID_LOG_VERBOSE, "spacebox", "File could not be read because of a mismatch in file size and number of bytes read"); + } + else + { + __android_log_print(ANDROID_LOG_VERBOSE, "spacebox", "%s", contents.c_str()); + } + } + +#endif + + return contents; } int SDL_SetRenderDrawColor(SDL_Renderer* renderer, const Color& color) diff --git a/src/extension.hpp b/src/extension.hpp index e07be89..7f49ddf 100644 --- a/src/extension.hpp +++ b/src/extension.hpp @@ -1,16 +1,21 @@ -/* /\ +--------------------------------------------------------------+ - ____/ \____ /| - zlib/MIT/Unlicenced game framework licensed to freely use, | - \ / / | copy, modify and sell without restriction | - +--\ ^__^ /--+ | | - | ~/ \~ | | - originally created at [http://nugget.fun] | - | ~~~~~~~~~~~~ | +--------------------------------------------------------------+ - | SPACE ~~~~~ | / - | ~~~~~~~ BOX |/ - +-------------*/ + /* +------------------------------------------------------+ + ____/ \____ /| - Open source game framework licensed to freely use, | + \ / / | copy, modify and sell without restriction | ++--\ ^__^ /--+ | | +| ~/ \~ | | - created for | +| ~~~~~~~~~~~~ | +------------------------------------------------------+ +| SPACE ~~~~~ | / +| ~~~~~~~ BOX |/ ++-------------*/ #ifndef SB_EXTENSION_H_ #define SB_EXTENSION_H_ +/* For logging pre-SPACEBOX messages in sb::file_to_string */ +#if defined(__ANDROID__) || defined(ANDROID) +#include +#endif + #include #include #include @@ -75,6 +80,16 @@ namespace sb SDL_Surface* get_surface_from_pixels(Pixels&); std::vector glob(fs::path); fs::path get_next_file_name(fs::path, int = 0, std::string = "", std::string = ""); + + /*! + * Read the file at path into a string and return the string. + * + * Android builds use SDL_RWops to extract the file from an APK. Otherwise, the file will be read using C++ STL. + * + * @param path Path to file, relative to working directory, or on Android, relative to app/src/main/assets/ in + * the build directory. + * @return std::string containing file contents + */ std::string file_to_string(const fs::path&); /* Returns an unsorted vector of keys from the passed map */ diff --git a/src/filesystem.hpp b/src/filesystem.hpp index de191ec..529f640 100644 --- a/src/filesystem.hpp +++ b/src/filesystem.hpp @@ -4,12 +4,5 @@ namespace fs = std::experimental::filesystem; #else #include - -/* Android uses a different path to the filesystem namespace */ -// #if defined(__ANDROID__) || defined(ANDROID) -// namespace fs = std::__fs::filesystem; -// #else namespace fs = std::filesystem; -// #endif - #endif

1s+Li&%(ygOu_RP)Nu-dxo1rCS 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 P00000NkvXXu0mjf3ajB3 literal 0 HcmV?d00001 diff --git a/icon/static_solid.png b/icon/static_solid.png new file mode 100644 index 0000000000000000000000000000000000000000..7045800f78f08b8d94ff3903d0e2bc2d2cacba3f GIT binary patch literal 15771 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 literal 0 HcmV?d00001 diff --git a/src/Attributes.cpp b/src/Attributes.cpp index d4c4e76..35f402b 100644 --- a/src/Attributes.cpp +++ b/src/Attributes.cpp @@ -1,12 +1,13 @@ -/* /\ +--------------------------------------------------------------+ - ____/ \____ /| - zlib/MIT/Unlicenced game framework licensed to freely use, | - \ / / | copy, modify and sell without restriction | - +--\ ^__^ /--+ | | - | ~/ \~ | | - originally created at [http://nugget.fun] | - | ~~~~~~~~~~~~ | +--------------------------------------------------------------+ - | SPACE ~~~~~ | / - | ~~~~~~~ BOX |/ - +-------------*/ +/*!