added opencv camera linux and android demo; added carousel, connection, and model classes; added format parameter to texture.generate; added android opencv lib build instructions

This commit is contained in:
ohsqueezy 2023-05-02 18:43:32 -04:00
parent f793073348
commit a8126605e8
15 changed files with 1253 additions and 31 deletions

View File

@ -25,7 +25,7 @@ Requirements
The repository includes some external libraries in lib/ that the default Makefile included with the demo shows how to compile, but there are other requirements, including external libraries that must be linked to a project in order to compile it.
* libSDL2 (developed against v2.0.22)
* libSDL2 (currently tested against 2.26.3)
* libSDL2-image
* libSDL2-ttf
* libSDL2-mixer
@ -46,15 +46,15 @@ libSDL2, libSDL2-image, libSDL2-ttf, and libSDL2-mixer must be available to link
### libSDL2-image, libSDL2-ttf, libSDL2-mixer
* Download from:
+ https://github.com/libsdl-org/SDL_image
+ https://github.com/libsdl-org/SDL_ttf
+ https://github.com/libsdl-org/SDL_mixer
+ https://github.com/libsdl-org/SDL_image
+ https://github.com/libsdl-org/SDL_ttf
+ https://github.com/libsdl-org/SDL_mixer
* Run `./configure --prefix=[YOUR LIB PATH] --with-sdl-prefix=[YOUR SDL PATH]`
* For example, prefix and SDL prefix might be `$HOME/local/sdl`
### OpenGL/GLES/GLES2
* Install GL/GLES according to your platform and link to it during compilation. GLEW is included in the lib/ folder of this framework and should find GL on your platform if it is installed.
* Install GL/GLES according to your platform and link to it during compilation. GLEW is included in the lib/ folder of this framework and should find GL on your platform (excluding Raspberry Pi and Android) if it is installed.
Builds
------
@ -73,7 +73,7 @@ Exporting a browser build with [Emscripten][] and its built-in version of SDL ha
### Raspberry Pi
Raspberry Pi builds, like Android and Emscripten, require using OpenGL ES. There is currently only an in-progress version of the cube demo to test exporting to Raspberry Pi. The build process is similar to standard desktop Linux with the `-D __PI__` compiler flag used to activate some Pi-specific code in the framework. GLEW is not available for OpenGL ES, so GLES headers are included directly from the expected system directories.
Raspberry Pi builds, like Android and Emscripten, require using OpenGL ES. The build process is similar to standard desktop Linux with the `-D __PI__` compiler flag used to activate some Pi-specific code in the framework. GLEW is not available for OpenGL ES, so GLES headers are included directly from the expected system directories.
#### SDL
@ -168,11 +168,18 @@ The [fill_screen demo][] has a working example of how to build for Android. It m
# Install the APK to the running emulator
$ ~/local/Android/platform-tools/adb -e install -r app/build/outputs/apk/debug/app-debug.apk
# Start the log viewer
$ ~/local/Android/platform-tools/adb [-s device_name] logcat
#### fill_screen demo
The [fill_screen demo][] has a Makefile that should work for building for Android if the paths in the file are adjusted to match the project. Edit the Makefile and run `make build/android/[org.my.app]`. If that isn't working, see below for notes on how the build was originally done manually.
#### Custom assets
Assets can be copied to `app/src/main/assets`. The [box demo](demo/box) has an example of how to include custom assets like config JSON and shaders.
##### Creating the fill_screen Android build
These steps were taken to build the fill_screen demo for Android. The Android SDK is assumed to be installed as explained above in the SDL test example. The instructions are based on SDL 2.24.0. There is also a Makefile target that scripts this process in `[demo/fill_screen/Makefile][]`.
@ -250,33 +257,33 @@ These steps were taken to build the fill_screen demo for Android. The Android SD
### OS X, Windows
Builds for these platforms have only passed the proof of concept phase. An early version of SPACEBOX was compiled for each of them, but none of the demos have been compiled for them in their current form, so there is only some broken code available in the cube demo Makefile.
Builds for these platforms have only passed the proof of concept phase. An early version of SPACEBOX was compiled for each of them, but none of the demos have been compiled for them in their current form, so there is only some broken code available in the box demo Makefile.
Demos
-----
The `demo/` folder contains programs that demonstrate and test the capabilities of the framework. In order to compile each, you should edit the definitions in the Makefile.
### fill_screen
### [Fill screen](demo/fill_screen)
This is intended to be a bare minimum test of the framework which loads the framework and fills the screen with a new color each frame. It currently only builds to Linux but will be ported to every platform in order to test each platform's build process.
This is intended to be a bare minimum test of the framework which loads the framework and fills the screen with a new color each frame. It currently builds to Linux and Android.
### browser webcam
### [Box](demo/box)
Test OpenGL context by drawing a textured, rotating cube. It currently builds to Linux, Android, and web browsers.
### [Browser webcam](demo/browser_webcam_test)
An example for using a C++ program to display a webcam stream in the browser using Emscripten to translate the code from C++ to WebAssembly. Get the frame pixel data from a canvas element, read it into a SPACEBOX object, write the pixel data to an OpenGL texture, and use Emscripten to display the video.
### cube
Switch between GL and SDL contexts by pressing spacebar. The GL context draws a textured, rotating cube, and the SDL context draws basic geometric shapes and a moving 2D sprite. (Note: support for SDL context is being removed in favor of GL context as even SDL recommends for new projects)
### 2d_collision
Test collision detection between a 2D sprite and other 2D sprites and boxes. Per-pixel collision can be tested. (Note: as support for SDL context is being removed in favor of GL context, the Sprite class will be either changing drastically or possibly removed entirely in favor of using a Model class)
### squircle
### [Squircle](demo/squircle)
Map an image from a rectangle to a circle or from a circle to a rectangle using a shader program. Based on a [blog post about elliptical grid mapping equations][].
### [OpenCV camera](demo/camera)
Get camera input using the OpenCV library on Linux or Android.
Other libraries
---------------
@ -284,9 +291,11 @@ These are other libraries that have been used in projects that use this framewor
### OpenCV
Download [a source package](https://opencv.org/releases/) and follow the specific instructions for each platform.
#### Linux
Download from https://opencv.org/releases/ and create a build directory, then configure and make. This example uses a custom installation path:
Create a build directory, then configure and make. This example uses a custom installation path `~/local/opencv`:
$ mkdir build_linux/ && cd build_linux/
$ cmake -DCMAKE_INSTALL_PREFIX=$HOME/local/opencv ..
@ -297,7 +306,7 @@ The `core`, `imgproc`, `videoio`, and `highgui` modules can then be linked to by
-L$(HOME)/local/opencv/lib -Wl,-rpath,$(HOME)/local/opencv/lib -lopencv_videoio -lopencv_core -lopencv_highgui \
-lopencv_imgproc
The specific modules needed may vary depending on the project. There are detailed instructions for building OpenCV on Linux available at [https://docs.opencv.org/4.6.0/d7/d9f/tutorial_linux_install.html]. There are also contributed modules available, which can be downloaded separately and compiled with the core modules. For example, this command builds all the modules in `../../opencv_contrib-4.x/modules`, except `python3`, along with the core modules.
The specific modules needed may vary depending on the project. See [detailed instructions for building OpenCV on Linux](https://docs.opencv.org/4.6.0/d7/d9f/tutorial_linux_install.html) for more advanced installation. There are also contributed modules available, which can be downloaded separately and compiled with the core modules. For example, this command builds all the modules in `../../opencv_contrib-4.x/modules`, except `python3`, along with the core modules.
$ cmake -D CMAKE_INSTALL_PREFIX=$HOME/local/opencv -D OPENCV_EXTRA_MODULES_PATH="../../opencv_contrib-4.x/modules" -D \
BUILD_opencv_python3=OFF ..
@ -317,9 +326,39 @@ To link to the WASM libraries, add the `*.a` files from the build directory to E
$(wildcard $(addprefix $(WASM_BUILD_DIR)/lib/,*.a)) $(wildcard $(addprefix $(WASM_BUILD_DIR)/3rdparty/lib/,*.a))
#### Android
Follow the steps at [Building an SDL example for Linux](#building-an-sdl-example-for-linux) up to the NDK installation steps, using 24.0.8215888 as the NDK version. This version or higher is necessary for using cv::VideoCapture.
$ ~/local/Android/cmdline-tools/bin/sdkmanager --sdk_root=$HOME/local/Android --install "ndk;24.0.8215888"
To build the OpenCV Android SDK, including shared object files, one for each Android ABI, run the `build_sdk.py` script packaged with the OpenCV source.
$ cd [opencv_source]
$ python3 platforms/android/build_sdk.py --sdk_path ~/local/Android/ --ndk_path ~/local/Android/ndk/24.0.8215888/ \
--extra_modules_path ../opencv_contrib-4.7.0-subset/ --shared --no_samples_build \
--config ndk-18-api-level-24.config.py build_android/
$ ls -R build_android/OpenCV-android-sdk/sdk/native/libs/
build_android/OpenCV-android-sdk/sdk/native/libs/:
arm64-v8a armeabi-v7a x86 x86_64
build_android/OpenCV-android-sdk/sdk/native/libs/arm64-v8a:
libopencv_world.so libtbb.so
build_android/OpenCV-android-sdk/sdk/native/libs/armeabi-v7a:
libopencv_world.so libtbb.so
build_android/OpenCV-android-sdk/sdk/native/libs/x86:
libopencv_world.so libtbb.so
build_android/OpenCV-android-sdk/sdk/native/libs/x86_64:
libopencv_world.so libtbb.so
This SDK can be included in an Android NDK project. See the [camera demo](demo/camera) for an example of how to add it to a SPACEBOX project build.
#### Full example
This builds both the local and WASM libraries by downloading OpenCV 4.7.0 and the contributed modules source packages. See [Gunkiss][] for an example of using these libraries in a project.
This builds the local, WASM, and Android libraries by downloading OpenCV 4.7.0 and the contributed modules source packages. See [Gunkiss][] for an example of using these libraries in a project with multiple target platforms.
$ wget https://github.com/opencv/opencv/archive/4.7.0.zip
$ unzip 4.7.0.zip
@ -341,9 +380,13 @@ This builds both the local and WASM libraries by downloading OpenCV 4.7.0 and th
$ source ~/ext/software/emsdk/emsdk_env.sh
$ python3 platforms/js/build_js.py --cmake_option="-DOPENCV_EXTRA_MODULES_PATH=../../opencv_contrib-4.7.0-subset/" \
--emscripten_dir ~/ext/software/emsdk/upstream/emscripten build_wasm_contrib --build_wasm
$ python3 platforms/android/build_sdk.py --sdk_path ~/local/Android/ --ndk_path ~/local/Android/ndk/22.1.7171670/ \
--extra_modules_path ../opencv_contrib-4.7.0-subset/ --shared --no_samples_build build_android/
### ZBar
Note that ZBar has been replaced with OpenCV's barcode contrib module in the original project this was compiled for. The instructions may still work, so they are left in case they are useful for another project, but they have not been used in a while.
#### Linux
Download from http://zbar.sourceforge.net/download.html and configure to only use image processing features (requires the imagemagickwand library, available from, for example `apt get libmagickwand-dev`) and choose your installation directory:

2
demo/camera/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
BPmono.ttf
camera

199
demo/camera/Makefile Normal file
View File

@ -0,0 +1,199 @@
# /\ +------------------------------------------------------------+
# ____/ \____ /| zlib/MIT/Unlicenced game framework licensed to freely use, |
# \ / / | copy, modify and sell without restriction |
# +--\ ^__^ /--+ | |
# | ~/ \~ | | created for <https://foam.shampoo.ooo> |
# | ~~~~~~~~~~~~ | +------------------------------------------------------------+
# | SPACE ~~~~~ | /
# | ~~~~~~~ BOX |/
# +--------------+
#
# Camera input display demo for Linux and Android using OpenCV with SPACEBOX. Linux and Android targets are supported.
# The OpenCV library for the desired target must be installed. The sections marked "Paths", "Linux build", and "Android
# build" each contain paths that may need to be configured.
#########
# Paths #
#########
# Location of project specific source files
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 := $(HOME)/local/sdl/bin/sdl2-config
# Include BPmono.ttf in the project
CREATE_FONT_SYMLINK := ln -nsf $(SB_DIR)"BPmono.ttf" .
#############
# SDL flags #
#############
SDL_CFLAGS = $(shell $(SDLCONFIG) --cflags)
SDL_LFLAGS := $(shell $(SDLCONFIG) --libs)
###########################
# Header and object files #
###########################
SB_H_FILES := $(wildcard $(addprefix $(SB_SRC_DIR),*.hpp))
SB_O_FILES := $(filter-out $(addprefix $(SB_SRC_DIR),filesystem.o Connection.o Carousel.o),$(SB_H_FILES:.hpp=.o))
SRC_H_FILES := $(wildcard $(addprefix $(SRC_DIR),*.hpp))
SRC_O_FILES := camera.o $(SRC_H_FILES:.hpp=.o)
####################################################################
# Targets for building [SPACEBOX], dependencies and project 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)
$(SB_SRC_DIR)Model.o : $(addprefix $(SB_SRC_DIR),extension.hpp Attributes.hpp Texture.hpp utility.hpp Carousel.hpp)
$(SRC_DIR)*.o : $(SRC_H_FILES) $(SB_H_FILES)
%.o : %.cpp %.hpp
$(CXX) $(CXXFLAGS) $< -c -o $@
###############
# Linux build #
###############
# OpenCV Linux headers
OPENCV_INCPATH := $(HOME)/local/opencv/include/opencv4
# OpenCV Linux library (necessary if building for Linux)
OPENCV_LINUX_LDPATH := $(HOME)/local/opencv/lib
camera : CFLAGS = -g -Wall -Wextra -O1 -c -I$(SB_LIB_DIR) -I$(SB_SRC_DIR) $(SDL_CFLAGS) -I $(OPENCV_INCPATH)
camera : CXXFLAGS = $(CFLAGS) --std=c++17
camera : LFLAGS = $(SDL_LFLAGS) -Wl,--enable-new-dtags -lpthread -lGL -lGLESv2 -lSDL2_image -lSDL2_ttf -lSDL2_mixer -lstdc++fs \
-L$(OPENCV_LINUX_LDPATH) -Wl,-rpath,$(OPENCV_LINUX_LDPATH) -lopencv_videoio -lopencv_core -lopencv_highgui -lopencv_imgproc
camera : $(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 camera
#################
# Android build #
#################
# Detailed info on how this build target was originally created for the fill_screen demo is in README.md at the root of the repository. It
# requires the Android SDK and the source packages for SDL. The paths below should be edited to match the local system. Icon creation requires
# Imagemagick's convert tool from <https://imagemagick.org/>. OpenCV Android SDK is also required (see the main SPACEBOX README for how to
# build).
OPENCV_ANDROID_SDK := $(HOME)/ext/software/opencv-4.7.0/build_android/OpenCV-android-sdk/
SDL_SRC := $(HOME)/ext/software/SDL2-2.26.3
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.foam
ANDROID_BUILD_DIR := build/android/$(ANDROID_PACKAGE)
ANDROID_CLASS := Camera
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. Use the SPACEBOX revise skeleton script to edit the SDL android-project parameters. Then add OpenCV to
# the android-project include and library paths.
$(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) "Camera Capture Test" "21" "24.0.8215888" $(SB_SRC_DIR) $(SB_LIB_DIR) $(SRC_DIR)
ln -nsf $(OPENCV_ANDROID_SDK)/sdk/native/libs $(ANDROID_BUILD_DIR)/app/jni/src/opencv
sed -i "2i\include $$\(CLEAR_VARS\)" "$(ANDROID_BUILD_DIR)/$(ANDROID_MK)"
sed -i "3i\LOCAL_MODULE := tbb" "$(ANDROID_BUILD_DIR)/$(ANDROID_MK)"
sed -i "4i\LOCAL_SRC_FILES := opencv/$$\(TARGET_ARCH_ABI\)/libtbb.so" "$(ANDROID_BUILD_DIR)/$(ANDROID_MK)"
sed -i "5i\include $$\(PREBUILT_SHARED_LIBRARY\)" "$(ANDROID_BUILD_DIR)/$(ANDROID_MK)"
sed -i "6i\include $$\(CLEAR_VARS\)" "$(ANDROID_BUILD_DIR)/$(ANDROID_MK)"
sed -i "7i\LOCAL_MODULE := opencv_world" "$(ANDROID_BUILD_DIR)/$(ANDROID_MK)"
sed -i "8i\LOCAL_SRC_FILES := opencv/$$\(TARGET_ARCH_ABI\)/libopencv_world.so" "$(ANDROID_BUILD_DIR)/$(ANDROID_MK)"
sed -i "9i\LOCAL_EXPORT_C_INCLUDES := $(OPENCV_ANDROID_SDK)/sdk/native/jni/include" "$(ANDROID_BUILD_DIR)/$(ANDROID_MK)"
sed -i "10i\include $$\(PREBUILT_SHARED_LIBRARY\)" "$(ANDROID_BUILD_DIR)/$(ANDROID_MK)"
sed -i 's/^LOCAL_SHARED_LIBRARIES.*/& tbb opencv_world/' "$(ANDROID_BUILD_DIR)/$(ANDROID_MK)"
sed -i "s/^APP_CPPFLAGS.*/& -nostartfiles/" "$(ANDROID_BUILD_DIR)/$(ANDROID_APP_MK)"
sed -i "s/org.libsdl.app/$(ANDROID_PACKAGE)/" "$(ANDROID_BUILD_DIR)/app/build.gradle" "$(ANDROID_BUILD_DIR)/$(ANDROID_MANIFEST)"
sed -i '10i\<uses-permission android:name="android.permission.CAMERA" />' "$(ANDROID_BUILD_DIR)/$(ANDROID_MANIFEST)"
# 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 shaders/*
-mkdir -p $(ANDROID_BUILD_DIR)/app/src/main/assets
rsync -ar shaders config.json $(ANDROID_BUILD_DIR)/app/src/main/assets
# Add OpenCV libraries to the gradle project, then 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 up object files #
#########################
clean :
-find $(SRC_DIR) -iname "*.o" -delete
clean-all : clean
-find $(SB_SRC_DIR) -iname "*.o" -delete
-find $(SB_LIB_DIR) -iname "*.o" -delete
#############
# compiledb #
#############
# Generate a clang JSON compilation database file. This can be useful for example for code completion. It requires
# compiledb (https://github.com/nickdiego/compiledb.git). 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)/ext/software/compiledb/bin/compiledb
compiledb :
-$(PATH_TO_COMPILEDB) -n make camera -k

53
demo/camera/README.md Normal file
View File

@ -0,0 +1,53 @@
Camera capture demo using OpenCV
================================
Display camera input using the [OpenCV](https://opencv.org) library.
Requirements
------------
### SPACEBOX
The required libraries for the overall framework must be installed first. Follow the instructions in [Installing Requirements](../../README.md#installing-requirements) in the main README.
### OpenCV
Follow the instructions in the main README to build OpenCV for [Linux](../../README.md/#linux-1), [Android](../../README.md#android-1), or both, depending on which platforms the demo is going to be built for.
Build
-----
### config
In `config.json`, `render driver` should be set to `opengl` on Linux and `opengles2` on Android (in other projects, opengles2 can work on Linux, which is why this is a config value rather than built into the engine).
### Linux
In addition to the usual SPACEBOX paths to edit, there are two paths to configure in the [Makefile](Makefile) under the `Linux build` section. Edit `OPENCV_INCPATH` and `OPENCV_LINUX_LDPATH` to match locations in the OpenCV project compiled earlier. Then the `camera` target should compile.
make camera
After it compiles, run the binary to see the camera input.
./camera
### Android
Compiling with NDK 24 may cause an error `cannot open crtbegin_so.o`. [This thread's](https://stackoverflow.com/q/69795531) suggestion of using the `-nostartfiles` linker flag seems to fix it. It may need to be inserted into the main SDL `Android.mk` file, which is in the root of the SDL source package installed earlier and referenced in this project's Makefile. Line 83 should be changed to:
LOCAL_LDFLAGS := -Wl,--no-undefined -nostartfiles
In the [Makefile](Makefile), edit `OPENCV_ANDROID_SDK` to match the location of the folder built earlier. At this point, the target should be able to build.
make build/android/ooo.shampoo.camera
An APK will be output to `build/android/ooo.shampoo.camera/app-debug.apk` which can be loaded onto a device or emulator.
After loading the APK, make sure to give camera permissions to the app. Then, run the app.
Settings -> Apps & notifications -> All apps -> Camera Capture Test -> Permissions -> Camera
The explanation in the main README for the [fill_screen Android build](../../README.md#fill_screen) applies to this project, with a few additional notes.
* The NDK version is [raised to 24](https://github.com/opencv/opencv/pull/19597), and the minimum SDK version is 21 (this was chosen arbitrarily, but it was failing with version 18).
* There are additional steps added in the Makefile for linking to the pre-built shared libraries for OpenCV and libtbb (which was generated by the OpenCV build process). See [this thread](https://stackoverflow.com/q/9870435) for more info on how to link pre-built libraries per-ABI to an Android build using GNU make.
* There is an extra target for including the config JSON and shaders as assets in the Android project.

274
demo/camera/camera.cpp Normal file
View File

@ -0,0 +1,274 @@
/* /\ +------------------------------------------------------+
* ____/ \____ /| - Open source game framework licensed to freely use, |
* \ / / | copy, modify and sell without restriction |
* +--\ ^__^ /--+ | |
* | ~/ \~ | | - created for <https://foam.shampoo.ooo> |
* | ~~~~~~~~~~~~ | +------------------------------------------------------+
* | SPACE ~~~~~ | /
* | ~~~~~~~ BOX |/
* +--------------+
*
* Camera example by frank at shampoo.ooo
*
* This is an example program that uses the external OpenCV library to display camera input full screen. It can
* be used for testing SPACEBOX with OpenCV.
*/
/* OpenCV */
#include "opencv2/videoio.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
/* SPACEBOX */
#include "Game.hpp"
#include "Model.hpp"
#include "Connection.hpp"
#include "VBO.hpp"
class Camera : public Game
{
public:
cv::VideoCapture capture;
sb::VAO vao;
sb::VBO vbo;
sb::Plane camera_view;
GLuint flat_program;
bool new_frame_available = false;
std::map<std::string, std::map<std::string, GLuint>> uniform;
cv::Mat camera_frame, blurred, low_contrast_mask;
/* Open camera on connection and close on disconnection. */
sb::Connection<> camera_switch {
std::bind(&Camera::open, this),
std::bind(&Camera::close, this)
};
Camera()
{
load_gl_context();
/* Add a texture to the camera Plane for storing frame image data */
camera_view.texture(sb::Texture());
glm::mat4 flip = glm::mat4(1);
flip[1][1] = -1;
camera_view.transformation(flip);
camera_switch.connect();
}
/* Create GL context via super class and load vertices, UV data, and shaders */
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();
/* Load flat shader programs */
GLuint vertex_shader = load_shader("shaders/flat.vert", GL_VERTEX_SHADER);
GLuint fragment_shader = load_shader("shaders/flat.frag", GL_FRAGMENT_SHADER);
flat_program = glCreateProgram();
glAttachShader(flat_program, vertex_shader);
glAttachShader(flat_program, fragment_shader);
sb::Plane::position->bind(0, flat_program, "in_position");
sb::Plane::uv->bind(1, flat_program, "vertex_uv");
sb::Log::gl_errors("after loading shaders");
/* Fill VBO with attribute data */
vbo.allocate(camera_view.size(), GL_STATIC_DRAW);
vbo.add(*camera_view.position);
vbo.add(*camera_view.uv);
sb::Log::gl_errors("after filling VBO");
/* link shaders */
link_shader(flat_program);
glUseProgram(flat_program);
sb::Log::gl_errors("after linking");
/* store uniform locations after linking */
uniform["flat"]["texture"] = glGetUniformLocation(flat_program, "base_texture");
uniform["flat"]["time"] = glGetUniformLocation(flat_program, "time");
uniform["flat"]["scroll"] = glGetUniformLocation(flat_program, "scroll");
uniform["flat"]["blend"] = glGetUniformLocation(flat_program, "blend_min_hsv");
uniform["flat"]["transformation"] = glGetUniformLocation(flat_program, "transformation");
sb::Log::gl_errors("after uniform locations");
/* setup GL parameters that won't change for the duration of the program */
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glActiveTexture(GL_TEXTURE0);
glDisable(GL_DEPTH_TEST);
glClearColor(0, 0, 0, 1);
}
void open()
{
/* Open the OpenCV capture, using device ID #0 to get the default attached camera. */
int device_id = configuration()["scan"]["camera-device-id"];
capture.open(device_id);
std::ostringstream message;
if (capture.isOpened())
{
message << "Opened and initialized " << capture.get(cv::CAP_PROP_FRAME_WIDTH) << "x" <<
capture.get(cv::CAP_PROP_FRAME_HEIGHT) << ", " << capture.get(cv::CAP_PROP_FPS) <<
"fps video capture device ID #" << device_id << " using " << capture.getBackendName();
/* Check config for a requested camera resolution, and if there is one, try applying it to the `cv::VideoCapture`. The
* requested resolution may not be available, and if so, `cv::VideoCapture` will choose a resolution. If the resulting
* resolution is different from the config value, print the resolution the capture device was set to instead. */
if (configuration()["display"].contains("camera-resolution"))
{
capture.set(cv::CAP_PROP_FRAME_WIDTH, configuration()["display"]["camera-resolution"][0]);
capture.set(cv::CAP_PROP_FRAME_HEIGHT, configuration()["display"]["camera-resolution"][1]);
message << std::endl << "Changed resolution to " << configuration()["display"]["camera-resolution"];
if (capture.get(cv::CAP_PROP_FRAME_WIDTH) != configuration()["display"]["camera-resolution"][0] ||
capture.get(cv::CAP_PROP_FRAME_HEIGHT) != configuration()["display"]["camera-resolution"][1])
{
message << " (but got " << capture.get(cv::CAP_PROP_FRAME_WIDTH) << "x" << capture.get(cv::CAP_PROP_FRAME_HEIGHT) << ")";
}
}
/* This is necessary for Android */
capture.set(cv::CAP_PROP_FOURCC, (('R' & 0x000000FF) | (('G' << 8) & 0x0000FF00) | (('B' << 16) & 0x00FF0000) | (('3' << 24) & 0xFF000000)));
/* Generate a texture the size of the camera's resolution. Using GL_RGB8 (instead of GL_RGBA8) seems to be necessary on Android. */
camera_view.texture().generate({capture.get(cv::CAP_PROP_FRAME_WIDTH), capture.get(cv::CAP_PROP_FRAME_HEIGHT)}, GL_RGB8);
/* Create and detach a thread which will read frame data */
std::thread camera_thread(&Camera::capture_frame, this);
camera_thread.detach();
}
else
{
message << "failed to open video capture device ID #" << device_id;
}
sb::Log::log(message);
}
void close()
{
capture.release();
}
/*!
* Read pixels from the camera into a `cv::Mat` and pre-process them if pre-processing methods are enabled.
*
* This function is meant to be launched in a separate thread, where it will run continuously. Set `new_frame_available` to `false` before
* loading camera frame data into the `cv::Mat` object at `camera_frame`, then set it back to `true` to indicate new frame data is available
* in `camera_frame`.
*/
void capture_frame()
{
/* When the camera button is switched off, this thread will automatically finish execution. */
while (camera_switch && capture.isOpened())
{
/* The frame data in the `cv::Mat` at `camera_frame` is about to be modified by the rest of
* this function, so even if there is data stored there that hasn't been read yet, it should not
* be read by the main thread until this is set to `true`at the end of the function. */
new_frame_available = false;
/* Load camera frame data into `cv::Mat` */
capture >> camera_frame;
/* Pre-process image for improved scan results */
if (!camera_frame.empty())
{
if (configuration()["scan"]["sharpen"])
{
/* Sharpen image for barcode detection */
float sigma = 1.0f, threshold = 5.0f, amount = 1.0f;
cv::GaussianBlur(camera_frame, blurred, cv::Size(), sigma, sigma);
low_contrast_mask = cv::abs(camera_frame - blurred) < threshold;
camera_frame = camera_frame * (1.0f + amount) + blurred * (-amount);
camera_frame.copyTo(camera_frame, low_contrast_mask);
}
if (configuration()["scan"]["brighten"])
{
/* Brightness and contrast adjustment, see
* https://docs.opencv.org/2.4.13.7/doc/tutorials/core/basic_linear_transform/basic_linear_transform.html */
int brightness = configuration()["scan"]["brightness-addition"];
float contrast = configuration()["scan"]["contrast-multiplication"];
if (brightness != 0 || contrast != 1.0)
{
camera_frame.convertTo(camera_frame, -1, contrast, brightness);
}
}
/* Finished loading into `cv::Mat`, so it is new data that is safe to read. */
new_frame_available = true;
}
sb::Log::gl_errors("in capture, after capturing frame");
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
/* Update parameters and draw the screen */
void update()
{
sb::Log::gl_errors("at beginning of update");
/* Time in seconds the game has running for */
float time_seconds = SDL_GetTicks() / 1000.0f;
if (new_frame_available)
{
/* Fill camera view texture memory */
camera_view.texture().bind();
/* Pixels from cv::VideoCapture are BGR on Linux, but not on Android (?) */
#if defined(__ANDROID__) || defined(ANDROID)
GLuint pixel_format = GL_RGB;
#else
GLuint pixel_format = GL_BGR;
#endif
camera_view.texture().load(const_cast<cv::Mat&>(camera_frame).ptr(), {camera_frame.cols, camera_frame.rows}, pixel_format, GL_UNSIGNED_BYTE);
/* Frame data has been processed, so there is not a new frame available anymore. */
new_frame_available = false;
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/* flat shader uniforms for BG: time, texture ID, disabled HSV blend, scroll on */
glUniform1f(uniform["flat"]["time"], time_seconds);
glUniform1i(uniform["flat"]["texture"], 0);
glUniform3f(uniform["flat"]["blend"], 0.0f, 0.0f, 1.0f);
glUniform1i(uniform["flat"]["scroll"], true);
glUniformMatrix4fv(uniform["flat"]["transformation"], 1, GL_FALSE, &camera_view.transformation()[0][0]);
/* only draw if camera is enabled and open */
if (camera_switch && capture.isOpened())
{
/* bind texture for drawing */
camera_view.texture().bind();
camera_view.enable();
/* draws rectangle vertices and rectangle texture using UV coords */
glDrawArrays(GL_TRIANGLES, 0, camera_view.attributes("position")->count());
}
SDL_GL_SwapWindow(window());
sb::Log::gl_errors("at end of update");
}
};
/* Launch the mainloop */
int main()
{
Camera camera = Camera();
camera.run();
camera.quit();
return 0;
}

50
demo/camera/config.json Normal file
View File

@ -0,0 +1,50 @@
{
"display":
{
"dimensions": [450, 800],
"framerate": 60,
"title": "Camera Capture Test",
"debug": false,
"render driver": "opengles2",
"show-cursor": true
},
"configuration":
{
"auto-refresh": false
},
"recording":
{
"screenshot-directory": "local/screenshots",
"video-directory": "local/video",
"enabled": false,
"write-mp4": true,
"video-frame-length": 33.333,
"max-video-memory": 2000,
"mp4-pixel-format": "yuv420p"
},
"log":
{
"enabled": false,
"output-directory": "/var/log/sb/",
"debug-to-stdout": false,
"debug-to-file": true,
"info-file-name": "camera_test_info.log",
"debug-file-name": "camera_test_debug.log"
},
"scan":
{
"enabled": false,
"capture-device": "/dev/video0",
"brightness-addition": 20,
"contrast-multiplication": 1.4,
"camera-device-id": 0,
"sharpen": false,
"brighten": false,
"contour-color-decodable": [255, 255, 0],
"contour-color-undecodable": [255, 0, 255]
}
}

View File

@ -0,0 +1,23 @@
#version 300 es
/* +------------------------------------------------------+
____/ \____ /| - Open source game framework licensed to freely use, |
\ / / | copy, modify and sell without restriction |
+--\ ^__^ /--+ | |
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
| ~~~~~~~~~~~~ | +------------------------------------------------------+
| SPACE ~~~~~ | /
| ~~~~~~~ BOX |/
+-------------*/
/* The precision declaration is required by OpenGL ES */
precision mediump float;
in vec2 uv;
uniform sampler2D base_texture;
out vec4 outputColor;
void main(void)
{
outputColor = texture(base_texture, uv);
}

View File

@ -0,0 +1,25 @@
#version 300 es
/* +------------------------------------------------------+
____/ \____ /| - Open source game framework licensed to freely use, |
\ / / | copy, modify and sell without restriction |
+--\ ^__^ /--+ | |
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
| ~~~~~~~~~~~~ | +------------------------------------------------------+
| SPACE ~~~~~ | /
| ~~~~~~~ BOX |/
+-------------*/
/* The precision declaration is required by OpenGL ES */
precision mediump float;
in vec2 in_position;
in vec2 vertex_uv;
out vec2 uv;
void main(void)
{
gl_Position = vec4(in_position, 0.0, 1.0);
uv = vertex_uv;
}

View File

@ -72,7 +72,6 @@ $(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)*.o : $(SRC_H_FILES) $(SB_H_FILES)
%.o : %.cpp %.hpp
$(CXX) $(CXXFLAGS) $< -c -o $@
@ -84,8 +83,7 @@ $(SRC_DIR)*.o : $(SRC_H_FILES) $(SB_H_FILES)
linux : CFLAGS = -Wall -Wextra -O3 -c -I$(SB_LIB_DIR) -I$(SB_SRC_DIR) $(SDL_CFLAGS)
linux : CXXFLAGS = $(CFLAGS) --std=c++17
linux : LFLAGS = $(SDL_LFLAGS) -Wl,--enable-new-dtags -lpthread -lGL -lGLESv2 -lSDL2_image -lSDL2_ttf -lSDL2_mixer -lstdc++fs
linux : $(GLEW_DIR)glew.o $(addprefix $(SDLGFX2_DIR),SDL2_rotozoom.o SDL2_gfxPrimitives.o) \
$(SB_O_FILES) $(SRC_O_FILES)
linux : $(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 fill-screen
@ -122,7 +120,7 @@ $(ANDROID_BUILD_DIR): $(SDL_SRC)/android-project/ $(SB_SRC_DIR)/android/revise_s
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) "Fill Screen Test" "18" "22.1.7171670"
$(ANDROID_APP_NAME) $(ANDROID_MIN_TARGET) $(ANDROID_NDK) "Fill Screen Test" "18" "22.1.7171670" $(SB_SRC_DIR) $(SB_LIB_DIR) $(SRC_DIR)
# Extend the SDLActivity class

90
src/Carousel.hpp Normal file
View File

@ -0,0 +1,90 @@
/* +------------------------------------------------------+
____/ \____ /| - Open source game framework licensed to freely use, |
\ / / | copy, modify and sell without restriction |
+--\ ^__^ /--+ | |
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
| ~~~~~~~~~~~~ | +------------------------------------------------------+
| SPACE ~~~~~ | /
| ~~~~~~~ BOX |/
+-------------*/
#ifndef CAROUSEL_H_
#define CAROUSEL_H_
#include <cstdint>
#include <iterator>
#include "utility.hpp"
namespace sb
{
class Carousel
{
private:
std::uint32_t offset = 0;
public:
template<typename Container>
auto current(Container& container) const
{
auto location = container.begin();
if (location != container.end())
{
std::advance(location, offset);
return location;
}
else
{
throw std::out_of_range("Container is empty");
}
}
template<typename Container>
void next(const Container& container)
{
offset = ++offset % container.size();
}
template<typename Container>
void previous(const Container& container)
{
offset = sb::mod(--offset, container.size());
}
template<typename Container>
void increment(const Container& container, int amount)
{
offset = sb::mod(offset + amount, container.size());
}
void beginning()
{
offset = 0;
}
template<typename Container>
void end(const Container& container)
{
offset = container.size() - 1;
}
/* Return true if the carousel currently points to the location at the beginning of the container. */
bool at_beginning() const
{
return offset == 0;
}
/* Return true if the carousel currently points to the location at the end of the container. */
template<typename Container>
bool at_end(const Container& container)
{
return offset >= container.size() - 1;
}
};
}
#endif

146
src/Connection.hpp Normal file
View File

@ -0,0 +1,146 @@
/* /\ +------------------------------------------------------+
* ____/ \____ /| - Open source game framework licensed to freely use, |
* \ / / | copy, modify and sell without restriction |
* +--\ ^__^ /--+ | |
* | ~/ \~ | | - created for <https://foam.shampoo.ooo> |
* | ~~~~~~~~~~~~ | +------------------------------------------------------+
* | SPACE ~~~~~ | /
* | ~~~~~~~ BOX |/
* +--------------+
*
* Connection
* ==========
*
* A connection is an object containing a binary state of either on (connected) or off (not connected)
* and user supplied functions that run automatically on each state change. The functions each have the
* same return type, number of arguments, and argument types determined by the template arguments.
*
* Original test code:
*
* Connection<> connection_d(std::bind(&Game::print_frame_length_history, this));
* connection_d.toggle();
* Connection<int, int, int> connection_f {
* std::function<int(int, int)>(&sb::mod), std::function<int(int, int)>(&sb::mod) };
* Connection<> connection_g = connection_d;
* connection_g.toggle();
* connection_g.disconnect();
* int result;
* result = connection_f.connect(3, 5);
* std::cout << result << " ";
* std::cout << connection_f.disconnect(20, 6) << " ";
* result = connection_f.toggle(800, 120);
* std::cout << result << std::endl;
* result = connection_f.connect(111, 44);
* std::cout << result << std::endl;
*/
#include <functional>
namespace sb
{
template<typename return_type = void, typename... arguments>
class Connection
{
private:
enum State : bool
{
STATE_OFF,
STATE_ON
};
using callback = std::function<return_type(arguments...)>;
State connection_state = STATE_OFF;
callback on_connect_callback, on_disconnect_callback;
public:
/* Without any arguments, the connection object will be in the disconnected state with empty functions. Otherwise,
* the supplied functions will be added to the connection object. The first function argument will be run on a
* connection, and the second function argument will be run on a disconnection. */
Connection(callback on_connect_callback = callback(), callback on_disconnect_callback = callback())
{
if (on_connect_callback)
{
on_connect(on_connect_callback);
if (on_disconnect_callback)
{
on_disconnect(on_disconnect_callback);
}
}
}
/* Set the function that will run when a connection is made. */
void on_connect(callback on_connect)
{
on_connect_callback = on_connect;
}
/* Set the function that will run when a disconnection happens. */
void on_disconnect(callback on_disconnect)
{
on_disconnect_callback = on_disconnect;
}
/* Set state to Connection::STATE_ON and run response function. If return_type is non-void and the
* connection is already connected, the function will not run and the return value will be a default
* constructed value of the type return_type. Therefore, return_type must be default constructible. */
return_type connect(arguments... args)
{
if (!*this)
{
connection_state = STATE_ON;
if (on_connect_callback)
{
return on_connect_callback(args...);
}
}
return return_type();
}
/* Set state to Connection::STATE_OFF and run response function. If return_type is non-void and the
* connection is already disconnected, the function will not run and the return value will be a default
* constructed value of the type return_type. Therefore, return_type must be default constructible. */
return_type disconnect(arguments... args)
{
if (*this)
{
connection_state = STATE_OFF;
if (on_disconnect_callback)
{
return on_disconnect_callback(args...);
}
}
return return_type();
}
/* Set state to the opposite of current state, causing the appropriate response function to run. */
return_type toggle(arguments... args)
{
if (*this)
{
return disconnect(args...);
}
else
{
return connect(args...);
}
}
/* Return true if state is Connection::STATE_ON, false otherwise. */
bool connected()
{
return connection_state;
}
/* When called as a boolean, return the connection state. */
operator bool()
{
return connected();
}
};
}

197
src/Model.cpp Normal file
View File

@ -0,0 +1,197 @@
/* +------------------------------------------------------+
____/ \____ /| - Open source game framework licensed to freely use, |
\ / / | copy, modify and sell without restriction |
+--\ ^__^ /--+ | |
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
| ~~~~~~~~~~~~ | +------------------------------------------------------+
| SPACE ~~~~~ | /
| ~~~~~~~ BOX |/
+-------------*/
#include "Model.hpp"
/* Default constructor for Model */
sb::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. */
sb::Model::Model(const std::map<std::string, std::shared_ptr<sb::Attributes>>& 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. */
sb::Model::Model(const std::map<std::string, sb::Attributes>& 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 */
sb::Model::Model(const std::initializer_list<std::string>& 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<std::string, std::shared_ptr<sb::Attributes>>& sb::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<sb::Attributes>& sb::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<sb::Attributes>& sb::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 sb::Model::attributes(const sb::Attributes& attributes, const std::string& name)
{
this->attributes(std::make_shared<sb::Attributes>(attributes), name);
}
/* Assign name to attributes and share ownership. */
void sb::Model::attributes(const std::shared_ptr<sb::Attributes>& attributes, const std::string& name)
{
this->attributes()[name] = attributes;
}
/* Enable all attributes. */
void sb::Model::enable()
{
for (const auto& attributes : this->attributes())
{
attributes.second->enable();
}
}
/* Disable all attributes. */
void sb::Model::disable()
{
for (const auto& attributes : this->attributes())
{
attributes.second->disable();
}
}
/* Return a reference to the texture container. */
std::map<std::string, sb::Texture>& sb::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& sb::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 sb::Model::texture(sb::Texture). */
sb::Texture& sb::Model::texture()
{
return texture(DEFAULT_TEXTURE_NAME);
}
/* Assign name to texture and share ownership. */
void sb::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 sb::Model::texture(const sb::Texture& texture)
{
this->texture(texture, DEFAULT_TEXTURE_NAME);
}
/* Get the model's transformation matrix. */
const glm::mat4& sb::Model::transformation() const
{
return model_transformation;
}
/* Set the model's transformation matrix. */
void sb::Model::transformation(const glm::mat4& transformation)
{
model_transformation = transformation;
}
/* Return the size in bytes of the sum of the attributes. */
std::size_t sb::Model::size()
{
std::size_t sum = 0;
for (const auto& attributes : this->attributes())
{
sum += attributes.second->size();
}
return sum;
}
/* Return the transformation matrix. */
sb::Model::operator glm::mat4() const
{
return model_transformation;
}
sb::PlaneDoubleBuffer::PlaneDoubleBuffer() : Plane()
{
texture(sb::Texture(), "front");
texture(sb::Texture(), "back");
}
void sb::PlaneDoubleBuffer::generate(const glm::vec2& size)
{
for (sb::Texture* buffer : {&texture("front"), &texture("back")})
{
buffer->generate(size);
}
}
sb::Texture& sb::PlaneDoubleBuffer::active()
{
return swapped ? texture("back") : texture("front");
}
sb::Texture& sb::PlaneDoubleBuffer::inactive()
{
return swapped ? texture("front") : texture("back");
}
void sb::PlaneDoubleBuffer::swap()
{
swapped = !swapped;
}

113
src/Model.hpp Normal file
View File

@ -0,0 +1,113 @@
/* +------------------------------------------------------+
____/ \____ /| - Open source game framework licensed to freely use, |
\ / / | copy, modify and sell without restriction |
+--\ ^__^ /--+ | |
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
| ~~~~~~~~~~~~ | +------------------------------------------------------+
| SPACE ~~~~~ | /
| ~~~~~~~ BOX |/
+-------------*/
#ifndef MODEL_H_
#define MODEL_H_
/* GL functions */
#if defined(__EMSCRIPTEN__)
#include <GL/glew.h>
#elif defined(__ANDROID__) || defined(ANDROID)
#include <GLES3/gl3.h>
#include <GLES3/gl3ext.h>
#else
#include "glew/glew.h"
#endif
#include <iostream>
#include <string>
#include <map>
#include <memory>
#include <iterator>
#include "glm/glm.hpp"
#include "Attributes.hpp"
#include "Texture.hpp"
#include "Carousel.hpp"
namespace sb
{
class Model
{
private:
inline static const std::string DEFAULT_TEXTURE_NAME = "default";
std::map<std::string, sb::Texture> model_textures;
std::map<std::string, std::shared_ptr<sb::Attributes>> model_attributes;
glm::mat4 model_transformation {1.0f};
public:
Model();
Model(const std::map<std::string, std::shared_ptr<sb::Attributes>>&);
Model(const std::map<std::string, sb::Attributes>&);
Model(const std::initializer_list<std::string>&);
std::map<std::string, std::shared_ptr<sb::Attributes>>& attributes();
std::shared_ptr<sb::Attributes>& attributes(const std::string&);
void attributes(const sb::Attributes&, const std::string&);
void attributes(const std::shared_ptr<sb::Attributes>&, const std::string&);
std::shared_ptr<sb::Attributes>& operator[](const std::string&);
void enable();
void disable();
std::map<std::string, sb::Texture>& 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<sb::Attributes> position = std::make_shared<sb::Attributes>(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<sb::Attributes> uv = std::make_shared<sb::Attributes>(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<std::string, std::shared_ptr<sb::Attributes>>({{"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

View File

@ -27,18 +27,16 @@ void Texture::associate(fs::path path)
this->path = path;
}
/* Forward the GL texture generate function to the base class */
void Texture::generate()
{
GLObject::generate(glGenTextures);
}
/* Generate a GL_TEXTURE_2D texture ID and allocate GL_RGBA8 storage for the given size */
void Texture::generate(glm::vec2 size)
void Texture::generate(glm::vec2 size, GLenum format)
{
generate();
bind();
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, size.x, size.y);
glTexStorage2D(GL_TEXTURE_2D, 1, format, size.x, size.y);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
sb::Log::gl_errors();

View File

@ -50,8 +50,19 @@ namespace sb
Texture();
Texture(fs::path);
void associate(fs::path);
/*!
* Forward the GL texture generate function to the base class
*/
void generate();
void generate(glm::vec2);
/*!
* Generate a GL_TEXTURE_2D texture ID and allocate the specified format storage for the given size.
*
* @param size Width and height of the texture in texels
* @param format Sized internal format to be used to store texture data (for example, GL_RGBA8, GL_RGB8)
*/
void generate(glm::vec2 size, GLenum format = GL_RGBA8);
/*!
* @overload load(fs::path path)