This commit is contained in:
ohsqueezy 2023-05-23 15:35:29 -04:00
commit 70d1d18615
14 changed files with 1120 additions and 0 deletions

12
.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
# Compilation
*.o
*.data
*.wasm
build/
Cakefoot.x86_64
# Auto complete
compile_commands.json
# SPACEBOX
BPmono.ttf

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "lib/sb"]
path = lib/sb
url = https://open.shampoo.ooo/shampoo/spacebox

188
Makefile Normal file
View File

@ -0,0 +1,188 @@
# >> Cakefoot <<
#
# This Makefile is used to create a Linux, web, or Android build.
#
# The [SPACEBOX] game framework source code from <https://open.shampoo.ooo/shampoo/spacebox> is required.
#########
# Paths #
#########
# Location of project specific source files
SRC_DIR := src/
# Locations of [SPACEBOX] source and its packaged dependencies
SB_DIR := lib/sb/
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. See README.md for how to compile the SDL library and this utility.
SDLCONFIG := $(HOME)/local/sdl/bin/sdl2-config
# Include BPmono.ttf in the project
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 Connection.o Switch.o Carousel.o time_it.o),$(SB_H_FILES:.hpp=.o))
SRC_H_FILES := $(wildcard $(addprefix $(SRC_DIR),*.hpp))
SRC_O_FILES := $(SRC_H_FILES:.hpp=.o)
##################################################################
# Object files for [SPACEBOX], its dependencies, and the project #
##################################################################
$(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)Cakefoot.o : $(SRC_H_FILES) $(SB_H_FILES)
%.o : %.cpp %.hpp
$(CXX) $(CXXFLAGS) $< -c -o $@
###############
# Linux build #
###############
Cakefoot.x86_64 : CFLAGS = -g -Wall -Wextra -O1 -c -I$(SB_LIB_DIR) -I$(SB_SRC_DIR) $(SDL_CFLAGS) \
-I $(HOME)/ext/software/emsdk/upstream/emscripten/system/include
Cakefoot.x86_64 : CXXFLAGS = $(CFLAGS) --std=c++17
Cakefoot.x86_64 : LFLAGS = $(SDL_LFLAGS) -Wl,--enable-new-dtags -lpthread -lGL -lGLESv2 -lSDL2_image -lSDL2_ttf -lSDL2_mixer -lstdc++fs
Cakefoot.x86_64 : $(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 Cakefoot.x86_64
#############
# Web build #
#############
# Use Emscripten to output JavaScript
EMSCRIPTENHOME = $(HOME)/ext/software/emsdk/upstream/emscripten
EMSCRIPTEN_CFLAGS = -O0 -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 \
--no-heap-copy -I $(SB_LIB_DIR) -I $(SB_SRC_DIR)
EMSCRIPTEN_LFLAGS = -s MIN_WEBGL_VERSION=2 -s EXPORTED_FUNCTIONS="['_main', '_malloc']" -s ALLOW_MEMORY_GROWTH=1 -s FULL_ES3=1 \
-sLLD_REPORT_UNDEFINED -sNO_DISABLE_EXCEPTION_CATCHING
EMSCRIPTEN_PRELOADS = --preload-file "BPmono.ttf"@/ --preload-file "config.json"@/ --preload-file "resource/"@/"resource/" \
--preload-file "src/shaders/"@/"src/shaders/"
cakefoot.js : CC = $(EMSCRIPTENHOME)/emcc
cakefoot.js : CXX = $(EMSCRIPTENHOME)/em++
cakefoot.js : CFLAGS = $(EMSCRIPTEN_CFLAGS)
cakefoot.js : CXXFLAGS = $(CFLAGS) --std=c++17
cakefoot.js : $(addprefix $(SDLGFX2_DIR),SDL2_rotozoom.o SDL2_gfxPrimitives.o) $(SB_O_FILES) $(SRC_O_FILES)
$(CREATE_FONT_SYMLINK)
$(CXX) $^ $(CXXFLAGS) $(EMSCRIPTEN_LFLAGS) $(EMSCRIPTEN_PRELOADS) -o cakefoot.js
#################
# 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/>.
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 := Cakefoot
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.
$(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) "Cakefoot" "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
ln -nsf $(CURL_ANDROID)/jni/build/curl $(ANDROID_BUILD_DIR)/app/jni/src/curl
ln -nsf $(CURL_ANDROID)/jni/build/openssl $(ANDROID_BUILD_DIR)/app/jni/src/openssl
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)"
# 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) "$(SB_DIR)/icon/foreground.png" "$(SB_DIR)/icon/background.png"
# Custom assets
$(ANDROID_BUILD_DIR)/app/src/main/assets: config*.json $(shell find resource/) $(shell find src/shaders)
-mkdir -p $(ANDROID_BUILD_DIR)/app/src/main/assets
rsync -ar --relative config*.json resource src/shaders $(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 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 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)/ext/software/compiledb/bin/compiledb
compiledb :
-$(PATH_TO_COMPILEDB) -n make Cakefoot.x86_64 -k

42
config.json Normal file
View File

@ -0,0 +1,42 @@
{
"display":
{
"dimensions": [864, 486],
"framerate": 60,
"title": "Cakefoot",
"debug": false,
"render driver": "opengl",
"show-cursor": true
},
"configuration":
{
"auto-refresh": true
},
"recording":
{
"screenshot-directory": "local/screenshots",
"video-directory": "local/video",
"enabled": true,
"write-mp4": true,
"video-frame-length": 33.333,
"max-video-memory": 2000,
"mp4-pixel-format": "yuv420p"
},
"input":
{
"suppress-any-key-on-mods": true
},
"log":
{
"enabled": true,
"output-directory": "/var/log/sb/",
"debug-to-stdout": true,
"debug-to-file": true,
"info-file-name": "cakefoot_info.log",
"debug-file-name": "cakefoot_debug.log"
}
}

16
config_android.json Normal file
View File

@ -0,0 +1,16 @@
{
"display":
{
"render driver": "opengles2"
},
"recording":
{
"enabled": false
},
"log":
{
"enabled": false
}
}

1
lib/sb Submodule

@ -0,0 +1 @@
Subproject commit 3ba3be449618ef2a7a5a46cef61492d57d04ed82

183
src/Cakefoot.cpp Normal file
View File

@ -0,0 +1,183 @@
/* >> Cakefoot << */
#if defined(__ANDROID__) || defined(ANDROID)
#include <android/asset_manager_jni.h>
#endif
#include "Cakefoot.hpp"
Cakefoot::Cakefoot()
{
#ifdef __ANDROID__
SDL_SetHint(SDL_HINT_ORIENTATIONS, "Portrait");
#endif
start_button.scale(0.5f);
start_button.on_state_change([&](bool state, int count){
if (state)
{
std::ostringstream message;
message << "Hello, " << state << " World! " << count;
sb::Log::log(message);
start_button.press(1);
}
});
/* subscribe to command events */
delegate().subscribe(&Cakefoot::respond, this);
delegate().subscribe(&Cakefoot::respond, this, SDL_MOUSEMOTION);
delegate().subscribe(&Cakefoot::respond, this, SDL_MOUSEBUTTONDOWN);
/* loading GL context for 3D */
load_gl_context();
/* Load a pointer cursor from the system library that will be freed automatically */
poke = std::shared_ptr<SDL_Cursor>(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND), SDL_FreeCursor);
}
void Cakefoot::load_gl_context()
{
super::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 shader program */
GLuint vertex_shader = load_shader("src/shaders/shader.vert", GL_VERTEX_SHADER);
GLuint fragment_shader = load_shader("src/shaders/shader.frag", GL_FRAGMENT_SHADER);
shader_program = glCreateProgram();
glAttachShader(shader_program, vertex_shader);
glAttachShader(shader_program, fragment_shader);
sb::Plane::position->bind(0, shader_program, "vertex_position");
sb::Plane::uv->bind(1, shader_program, "vertex_uv");
sb::Plane::color->bind(2, shader_program, "vertex_color");
sb::Log::gl_errors("after loading shaders");
/* Fill VBO with attribute data */
vbo.allocate(start_button.size(), GL_STATIC_DRAW);
vbo.add(*sb::Plane::position);
vbo.add(*sb::Plane::uv);
vbo.add(*sb::Plane::color);
sb::Log::gl_errors("after filling VBO");
/* link shaders */
link_shader(shader_program);
glUseProgram(shader_program);
sb::Log::gl_errors("after linking");
/* store uniform locations after linking */
uniform["mvp"] = glGetUniformLocation(shader_program, "mvp");
uniform["time"] = glGetUniformLocation(shader_program, "time");
uniform["effect"] = glGetUniformLocation(shader_program, "effect");
uniform["uv transformation"] = glGetUniformLocation(shader_program, "uv_transformation");
uniform["coordinate bound"] = glGetUniformLocation(shader_program, "coordinate_bound");
uniform["model texture"] = glGetUniformLocation(shader_program, "model_texture");
uniform["texture enabled"] = glGetUniformLocation(shader_program, "texture_enabled");
/* enable alpha rendering */
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
sb::Log::gl_errors("after uniform locations");
}
void Cakefoot::respond(SDL_Event& event)
{
/* Mouse interface */
if (event.type == SDL_MOUSEMOTION || event.type == SDL_MOUSEBUTTONDOWN)
{
/* Get mouse coordinates in pixel resolution and NDC */
glm::vec2 mouse_pixel = event.type == SDL_MOUSEBUTTONDOWN ? glm::vec2{event.button.x, event.button.y} :
glm::vec2{event.motion.x, event.motion.y};
glm::vec2 mouse_ndc {
float(mouse_pixel.x) / window_box().width() * 2.0f - 1.0f, (1.0f - float(mouse_pixel.y) / window_box().height()) * 2.0f - 1.0f
};
/* Collide with start button */
bool over_start_button = start_button.collide(mouse_ndc);
/* Check for press */
if (over_start_button)
{
/* Set cursor to pokey finger */
if (SDL_GetCursor() != poke.get())
{
SDL_SetCursor(poke.get());
}
/* Respond to a click */
if (event.type == SDL_MOUSEBUTTONDOWN)
{
/* Reset cursor to default arrow */
// SDL_SetCursor(SDL_GetDefaultCursor());
start_button.press(0);
}
}
else if (SDL_GetCursor() == poke.get())
{
SDL_SetCursor(SDL_GetDefaultCursor());
}
}
else if (sb::Delegate::compare(event, "reset"))
{
sb::Log::log("Goodbye, World!");
}
}
void Cakefoot::destroy_texture(GLuint* texture_id)
{
/* not sure why SDL_Log works here but SDL_LogDebug and SDL_LogInfo don't */
std::ostringstream message;
message << "destroying texture ID " << *texture_id;
sb::Log::log(message);
glDeleteTextures(1, texture_id);
}
void Cakefoot::update()
{
sb::Log::gl_errors("at beginning of update");
/* Time in seconds the game has running for */
float time_seconds = SDL_GetTicks() / 1000.0f;
glDisable(GL_DEPTH_TEST);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/* flat shader uniforms: time, texture ID, disabled HSV blend, scroll on */
glActiveTexture(GL_TEXTURE0);
glUniform1f(uniform["time"], time_seconds);
glUniformMatrix4fv(uniform["mvp"], 1, GL_FALSE, &glm::mat4(1)[0][0]);
/* calculate the transformation matrix for displaying pudding in viewport */
model = glm::rotate(model, 0.0f, Y_UNIT_NORMAL_3D);
projection = glm::perspective(glm::radians(40.0f * 1 / window_box().aspect()), window_box().aspect(), 0.1f, 100.0f);
mvp = projection * VIEW_MATRIX * model;
/* uniforms */
// glUniformMatrix4fv(uniform["mvp"], 1, GL_FALSE, &mvp[0][0]);
/* draw pudding model */
// glEnable(GL_DEPTH_TEST);
/* draws bg vertices and texture */
// cake_model.enable();
start_button.draw(uniform["mvp"], uniform["texture enabled"]);
// glDrawArrays(GL_TRIANGLES, 0, cake_model.attributes("position")->count());
/* Display */
SDL_GL_SwapWindow(window());
sb::Log::gl_errors("at end of update");
}
int main()
{
Cakefoot game = Cakefoot();
game.run();
game.quit();
return 0;
}

319
src/Cakefoot.hpp Normal file
View File

@ -0,0 +1,319 @@
/* >> Cakefoot << */
#pragma once
/* Needed for functions in glm/gtx/ */
#define GLM_ENABLE_EXPERIMENTAL
/* Standard library includes */
#include <stdlib.h>
#include <string>
#include <iostream>
#include <map>
// #include <algorithm>
// #include <thread>
// #include <mutex>
#include <memory>
// #include <stdexcept>
#include <functional>
// #include <chrono>
/* Include Game.hpp before any other SDL-related headers because it defines SDL_MAIN_HANDLED */
#include "Game.hpp"
/* SPACEBOX external libraries included in source package */
#include "sdl2-gfx/SDL2_gfxPrimitives.h"
#include "json/json.hpp"
#include "glm/glm.hpp"
#include "glm/gtx/matrix_decompose.hpp"
#include "glm/gtc/matrix_access.hpp"
/* SPACEBOX classes and functions */
#include "Color.hpp"
#include "extension.hpp"
#include "filesystem.hpp"
#include "Animation.hpp"
#include "Texture.hpp"
#include "GLObject.hpp"
#include "Log.hpp"
#include "Attributes.hpp"
#include "VBO.hpp"
#include "Model.hpp"
#include "utility.hpp"
#include "Box.hpp"
#include "Switch.hpp"
/*!
* A Pad is a Plane which can be clicked to launch an arbitrary user function. It can be sized and placed by setting its
* translation and scale values.
*
* Each instance:
*
* - Shares vertices and UV in VBO
* - Has its own Texture representing the button on-screen
* - Has its own response to click
* - Shares mouse collision code
* - Has its own translate + scale transformation
*
* Example:
*
* glm::vec3 w = glm::mat3({{1, 0, 0}, {0, 1, 0}, {-0.6739, -0.74, 1}}) * glm::mat3({{.1, 0, 0}, {0, .1 * (460.0 / 768.0), 0}, {0, 0, 1}}) *
* glm::vec3({-1, -1, 1});
* std::cout << w << std::endl << glm::translate(glm::vec3{-0.6739, -0.74, 0}) *
* glm::scale(glm::vec3{.1, .1 * (460.0 / 768.0), 1}) * glm::vec4{-1, -1, 0, 1} << std::endl;
* Pad p {background.current(), {-0.6739f, -0.74f}, 0.1f, get_display().window_box().aspect(), std::function<void()>()};
* const std::vector<glm::vec2>& p_position = *p.attributes("position");
* glm::vec4 final_position = p.transformation() * glm::vec4{p_position[2].x, p_position[2].y, 0, 1};
* std::cout << p.transformation() << std::endl << final_position << std::endl;
* assert(final_position == glm::vec4({w.x, w.y, 0, 1}));
*/
template<typename return_type = void, typename... arguments>
class Pad : public sb::Plane
{
private:
inline static const glm::vec3 ROTATION_AXIS {0.0f, 0.0f, 1.0f};
using Reaction = std::function<return_type(bool, arguments...)>;
sb::Switch<return_type, arguments...> connection;
Box collision_box;
float rotation_angle = 0.0f, scale_factor = 1.0f, scale_ratio = 1.0f;
glm::vec2 translation_vector {0.0f, 0.0f};
/*!
* Set the transformation matrix for the pad object by applying the scale to the translation and the rotation to the
* resulting matrix, meaning the transformations will be applied to the pad in the order of: translate, scale, and
* rotate. The collision box will be scaled and moved to fit around the position coordinates that would result from
* applying this transformation to the position coordinates.
*/
void transform()
{
glm::vec3 scale { scale_factor, scale_factor, 1.0f };
if (scale_ratio > 1.0f)
{
scale.x /= scale_ratio;
}
else if (scale_ratio < 1.0f)
{
scale.y *= scale_ratio;
}
collision_box.size(2.0f * glm::vec2{scale.x, scale.y}, true);
collision_box.center(translation_vector);
sb::Model::transformation(glm::translate(glm::vec3{translation_vector.x, translation_vector.y, 0.0f}) *
glm::scale(scale) * glm::rotate(rotation_angle, ROTATION_AXIS));
}
public:
/*!
* Construct a Pad object without a texture.
*
* @overload Pad(sb::Texture, glm::vec2, flat, float, Reaction, float)
*/
Pad(glm::vec2 translation = {0.0f, 0.0f}, float scale = 1.0f, float ratio = 1.0f, Reaction on_state_change = Reaction(), float rotation = 0.0f)
{
this->translation(translation);
this->scale(scale, ratio);
if (rotation)
{
this->rotation(rotation);
}
this->on_state_change(on_state_change);
collision_box.invert_y(true);
}
/*!
* Construct a Pad from a texture, a translation amount, a scale factor, and a reaction function. The translation is relative
* to (0.0, 0.0), and the scale is relative to the superclass Plane object, which has opposite corners at (-1.0, -1.0) and
* (1.0, 1.0). The texture is the graphic that displays in the Pad location. The reaction must accept a boolean as its first
* argument, which will be the state of the contained Switch object.
*
* @param texture pad display graphic
* @param translation x, y amount to translate the pad position
* @param scale amount to scale both x and y
* @param ratio ratio to adjust scale of x and y
* @param on_state_change reaction function which accepts a boolean as its first argument
* @param rotation angle in radians to rotate the pad
*/
Pad(sb::Texture texture, glm::vec2 translation = {0.0f, 0.0f}, float scale = 1.0f, float ratio = 1.0f,
Reaction on_state_change = Reaction(), float rotation = 0.0f) : Pad(translation, scale, ratio, on_state_change, rotation)
{
this->texture(texture);
}
/*!
* Set angle in radians the pad will be rotated. The pad will be rotated around its center. The collision box will not
* change, so the box will not contain the entire pad if the angle is not a multiple of pi/2. The pad's transformation
* matrix will automatically be set to incorporate this rotation transformation.
*
* @param angle angle in radians to rotate pad
*/
void rotation(float angle)
{
rotation_angle = angle;
transform();
}
/*!
* Set the scale using a factor and ratio that will transform the pad in the X and Y dimensions. The ratio will determine
* how much each axis is scaled. If the ratio is above one, the X-axis's scale will be divided by the ratio. If the ratio
* is below one, the Y-axis's scale will be multiplied by the aspect ratio. If the aspect ratio of the window is given,
* this will force the pad to display as a square, and the ratio will be relative to the shorter axis. The collision box
* will be scaled by the same factors. The pad's transformation matrix will automatically be set to incorporate this
* scale transformation.
*
* @param factor amount to scale in both x and y directions
* @param ratio amount to adjust scaling, set to the window aspect ratio to make the pad appear square
*/
void scale(float factor, float ratio = 1.0f)
{
scale_factor = factor;
scale_ratio = ratio;
transform();
}
/*!
* Set a translation for the pad object in the X and Y dimension using a 2d vector. The collision box will be moved by the
* same translation. The pad's transformation matrix will automatically be set to incorporate this translation
* transformation.
*
* @param translation x, y distance to translate the pad
*/
void translation(const glm::vec2& translation)
{
translation_vector = translation;
transform();
}
/*!
* Set the function that will run when a pad object is clicked.
*
* @param on_state_change reaction function which accepts a boolean as its first argument
*/
void on_state_change(Reaction reaction)
{
connection.on_state_change(reaction);
}
/*!
* Returns true if the point at given position collides with the pad's collision box.
*
* @param position x, y coordinate to check for collision with Pad::collision_box
* @return true if the point is inside Pad::collision_box, false otherwise
*/
bool collide(const glm::vec2& position) const
{
return collision_box.collide(position);
}
/*!
* Set transformation uniform, bind texture, and draw vertices.
*
* @param uniform_id transformation uniform ID
* @param texture_flag_uniform_id uniform ID for boolean enabling or disabling texture display
*/
void draw(GLuint uniform_id, GLuint texture_flag_uniform_id)
{
glUniformMatrix4fv(uniform_id, 1, GL_FALSE, &transformation()[0][0]);
if (!textures().empty())
{
glUniform1i(texture_flag_uniform_id, true);
texture().bind();
}
else
{
glUniform1i(texture_flag_uniform_id, false);
}
enable();
glDrawArrays(GL_TRIANGLES, 0, attributes("position")->count());
disable();
}
return_type press(arguments... args)
{
return connection.flip(args...);
}
};
/*!
* The main game object. There is currently only support for one of these to exist at a time.
*/
class Cakefoot : public Game
{
private:
/* Defines for effect IDs that will be passed to the shader program. Since EFFECT_COUNT is last and every value
* is the default integer, it will be set to the number of effects available. */
enum Effect
{
EFFECT_NONE,
EFFECT_COUNT
};
/* Defines for UV transformations available in the fragment shader program */
enum UVTransformation
{
UV_NONE,
UV_SQUIRCLE
};
/* Convention for calling parent class in a consistent way across classes */
typedef Game super;
/* Constants */
inline static const glm::vec3 ZERO_VECTOR_3D {0, 0, 0};
inline static const glm::vec3 Y_UNIT_NORMAL_3D {0, 1, 0};
inline static const glm::mat4 VIEW_MATRIX = glm::lookAt({4.0f, 2.0f, 1.0f}, {0.0f, -0.325f, 0.0f}, Y_UNIT_NORMAL_3D);
/* Member variables */
std::shared_ptr<SDL_Cursor> poke;
int effect_id = EFFECT_NONE;
std::map<std::string, GLuint> uniform;
GLuint shader_program;
glm::mat4 projection, model {1.0f}, mvp;
sb::VAO vao;
sb::VBO vbo;
Pad<void, int> start_button;
sb::Plane cake_model;
/*!
* Create GL context via super class and load vertices, UV data, and shaders.
*/
void load_gl_context();
/*!
* Call GL's delete texture function, and print a debug statement for testing. This is defined as a static member
* function and uses the SDL logging function instead of the inherited logging functions from Node since the object
* may not be allocated at destruction time (?)
*
* @param texture_id GL texture ID being destroyed
*/
static void destroy_texture(GLuint* texture_id);
public:
/*!
* Initialize a Cakefoot instance
*/
Cakefoot();
/*!
* Respond to command events
*/
void respond(SDL_Event&);
/*!
* Update parameters and draw the screen
*/
void update();
};
/*!
* Create a Cakewalk instance and launch its mainloop.
*
* @return Always returns 0
*/
int main();

44
src/shaders/flat.frag Normal file
View File

@ -0,0 +1,44 @@
#version 300 es
/* _______________ ,--------------------------------------------------------.
//`````````````\\ \ \
//~~~~~~~~~~~~~~~\\ \ by @ohsqueezy & @sleepin \
//=================\\ \ [ohsqueezy.itch.io] [sleepin.itch.io] \
// \\ \ \
// \\ \ zlib licensed code at [git.nugget.fun/nugget/gunkiss] \
// ☆ GUNKISS ☆ \\ \ \
//_________________________\\ `--------------------------------------------------------*/
/* The precision declaration is required by OpenGL ES */
precision mediump float;
in vec2 uv;
uniform sampler2D base_texture;
uniform vec3 blend_min_hsv;
uniform float time;
uniform bool scroll;
out vec4 outputColor;
/* from http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl, licensed under WTFPL */
vec3 hsv2rgb(vec3 c)
{
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
void main(void)
{
if (scroll)
{
ivec2 texture_size = textureSize(base_texture, 0);
float speed = time * 35.0;
outputColor = texelFetch(base_texture, ivec2(mod(vec2(gl_FragCoord.x + speed, gl_FragCoord.y - speed), vec2(texture_size))), 0);
}
else
{
outputColor = texture(base_texture, uv);
}
/* apply blending, leaving alpha unchanged */
outputColor.xyz = min(outputColor.xyz, hsv2rgb(blend_min_hsv));
}

24
src/shaders/flat.vert Normal file
View File

@ -0,0 +1,24 @@
#version 300 es
/* _______________ ,--------------------------------------------------------.
//`````````````\\ \ \
//~~~~~~~~~~~~~~~\\ \ by @ohsqueezy & @sleepin \
//=================\\ \ [ohsqueezy.itch.io] [sleepin.itch.io] \
// \\ \ \
// \\ \ zlib licensed code at [git.nugget.fun/nugget/gunkiss] \
// ☆ GUNKISS ☆ \\ \ \
//_________________________\\ `--------------------------------------------------------*/
/* The precision declaration is required by OpenGL ES */
precision mediump float;
in vec2 in_position;
in vec2 vertex_uv;
uniform mat4 transformation;
out vec2 uv;
void main(void)
{
gl_Position = transformation * vec4(in_position, 0, 1);
uv = vertex_uv;
}

76
src/shaders/mvp.frag Normal file
View File

@ -0,0 +1,76 @@
#version 300 es
/* >> Cakefoot by https://foam.shampoo.ooo/ << */
/* The precision declaration is required by OpenGL ES */
precision mediump float;
#define TRANSFORMATION_NONE 0
#define TRANSFORMATION_SQUIRCLE 1
in vec2 fragment_uv;
in vec3 ex_color;
in float x_center_proximity;
in vec3 original_coordinates;
in vec3 clip_coordinates;
uniform sampler2D pudding_texture;
uniform int uv_transformation;
uniform float coordinate_bound;
out vec4 output_color;
/* [-coordinate_bound, coordinate_bound] arbitrary box coordinates to [-1, 1] normalized coordinates */
vec2 normalize_coordinates(vec2 coordinates)
{
return coordinates / coordinate_bound;
}
/* [-1, 1] box coordinates to [0, 1] UV coordinates */
vec2 coordinates_to_uv(vec2 coordinates)
{
return (1.0 + coordinates) / 2.0;
}
/* coordinates in circle with radius <= 1 to box coordinates in [-1, 1] */
vec2 circle_to_box(vec2 circle)
{
float u = circle.x;
float v = circle.y;
float u_sq = pow(u, 2.0);
float v_sq = pow(v, 2.0);
float rt_2 = sqrt(2.0);
float x = 0.5 * sqrt(2.0 + 2.0 * u * rt_2 + u_sq - v_sq) - 0.5 * sqrt(2.0 - 2.0 * u * rt_2 + u_sq - v_sq);
float y = 0.5 * sqrt(2.0 + 2.0 * v * rt_2 - u_sq + v_sq) - 0.5 * sqrt(2.0 - 2.0 * v * rt_2 - u_sq + v_sq);
return vec2(x, y);
}
/* Apply color passed in from the vertex shader, compressing to one of 16 colors. Add retro effect
* by alternately darkening and lightening 2x2 pixel areas in a checker pattern. Add shadowing by
* brightening the color based on how near it is to the center in the X-dimension */
void retro()
{
vec3 shadowed = min(ex_color, 1.0);
float dx = abs(floor(gl_FragCoord[0]) - 480.0) / 480.0;
if (int(floor(gl_FragCoord[0] / 2.0) + floor(gl_FragCoord[1]) / 2.0) % 2 == 0)
{
output_color = vec4(shadowed * 1.2, 1);
}
else
{
output_color = vec4(shadowed * 0.7, 1);
}
output_color[0] = float(int(output_color[0] * 4.0)) / 4.0;
output_color[1] = float(int(output_color[1] * 4.0)) / 4.0;
output_color[2] = float(int(output_color[2] * 4.0)) / 4.0;
output_color *= x_center_proximity;
}
void main()
{
vec2 uv = fragment_uv;
// if (uv_transformation == TRANSFORMATION_SQUIRCLE)
// {
// vec2 normalized_circle_coordinates = normalize_coordinates(vec2(original_coordinates.x, original_coordinates.z));
// uv = coordinates_to_uv(circle_to_box(normalized_circle_coordinates));
// }
output_color = texture(pudding_texture, uv);
}

67
src/shaders/mvp.vert Normal file
View File

@ -0,0 +1,67 @@
#version 300 es
/* >> Cakefoot by https://foam.shampoo.ooo/ << */
/* The precision declaration is required by OpenGL ES */
precision mediump float;
#define PI 3.1415926535897932384626433832795
#define AMPLITUDE 0.2
#define PERIOD .5
#define WAVELENGTH 2.5
#define EFFECT_NONE 0
#define EFFECT_SNAKE 1
#define EFFECT_WOBBLE 2
in vec3 vertex_position;
in vec3 vertex_color;
in vec2 vertex_uv;
uniform mat4 mvp;
uniform float time;
uniform int effect;
out vec3 ex_color;
out float x_center_proximity;
out vec2 fragment_uv;
out vec3 original_coordinates;
out vec3 clip_coordinates;
/* Offset X-coordinate according to the time step and Y-coordinate to create a wobble effect when run on
* flattened coordinates (after projection matrix has been applied) */
void wobble()
{
gl_Position.x += sin(time * 10.0) * vertex_position.y / 2.0;
}
/* Contort the X-coordinate according the time step and Y-coordinate using the sine function. This contorts
* the model into a sine wave along the Y-axis. It also moves the sine wave along the Y-axis using the time
* step uniform. The shape can be edited by changing the defintions for amplitude (maximum distance from
* Y-axis), wavelength (length in GL model coordinates between peaks), and period (amount of time in
* seconds it takes to loop through one wavelength cycle). */
void snake()
{
gl_Position = vec4(
vertex_position.x + AMPLITUDE * sin(2.0 * PI / PERIOD * (time + vertex_position.y * PERIOD / WAVELENGTH)), vertex_position.yz, 1);
}
void main()
{
// if (effect == EFFECT_SNAKE)
// {
// snake();
// }
// else
// {
gl_Position = vec4(vertex_position, 1);
// }
gl_Position = mvp * gl_Position;
// if (effect == EFFECT_WOBBLE)
// {
// wobble();
// }
/* passing to fragment program */
ex_color = vertex_color;
x_center_proximity = 1.8 - abs(vertex_position[0]);
fragment_uv = vertex_uv;
original_coordinates = vertex_position;
clip_coordinates = gl_Position.xyz;
}

78
src/shaders/shader.frag Normal file
View File

@ -0,0 +1,78 @@
#version 300 es
/* >> Cakefoot by https://foam.shampoo.ooo/ << */
/* The precision declaration is required by OpenGL ES */
precision mediump float;
#define TRANSFORMATION_NONE 0
#define TRANSFORMATION_SQUIRCLE 1
in vec2 fragment_uv;
in vec3 ex_color;
in float x_center_proximity;
in vec3 original_coordinates;
in vec3 clip_coordinates;
uniform sampler2D model_texture;
uniform int uv_transformation;
uniform float coordinate_bound;
uniform bool texture_enabled;
out vec4 output_color;
/* [-coordinate_bound, coordinate_bound] arbitrary box coordinates to [-1, 1] normalized coordinates */
vec2 normalize_coordinates(vec2 coordinates)
{
return coordinates / coordinate_bound;
}
/* [-1, 1] box coordinates to [0, 1] UV coordinates */
vec2 coordinates_to_uv(vec2 coordinates)
{
return (1.0 + coordinates) / 2.0;
}
/* coordinates in circle with radius <= 1 to box coordinates in [-1, 1] */
vec2 circle_to_box(vec2 circle)
{
float u = circle.x;
float v = circle.y;
float u_sq = pow(u, 2.0);
float v_sq = pow(v, 2.0);
float rt_2 = sqrt(2.0);
float x = 0.5 * sqrt(2.0 + 2.0 * u * rt_2 + u_sq - v_sq) - 0.5 * sqrt(2.0 - 2.0 * u * rt_2 + u_sq - v_sq);
float y = 0.5 * sqrt(2.0 + 2.0 * v * rt_2 - u_sq + v_sq) - 0.5 * sqrt(2.0 - 2.0 * v * rt_2 - u_sq + v_sq);
return vec2(x, y);
}
/* Apply color passed in from the vertex shader, compressing to one of 16 colors. Add retro effect
* by alternately darkening and lightening 2x2 pixel areas in a checker pattern. Add shadowing by
* brightening the color based on how near it is to the center in the X-dimension */
void retro()
{
vec3 shadowed = min(ex_color, 1.0);
float dx = abs(floor(gl_FragCoord[0]) - 480.0) / 480.0;
if (int(floor(gl_FragCoord[0] / 2.0) + floor(gl_FragCoord[1]) / 2.0) % 2 == 0)
{
output_color = vec4(shadowed * 1.2, 1);
}
else
{
output_color = vec4(shadowed * 0.7, 1);
}
output_color[0] = float(int(output_color[0] * 4.0)) / 4.0;
output_color[1] = float(int(output_color[1] * 4.0)) / 4.0;
output_color[2] = float(int(output_color[2] * 4.0)) / 4.0;
output_color *= x_center_proximity;
}
void main()
{
// if (uv_transformation == TRANSFORMATION_SQUIRCLE)
// {
// vec2 normalized_circle_coordinates = normalize_coordinates(vec2(original_coordinates.x, original_coordinates.z));
// fragment_uv = coordinates_to_uv(circle_to_box(normalized_circle_coordinates));
// }
output_color = mix(vec4(ex_color, 1), texture(model_texture, fragment_uv) * vec4(ex_color, 1), float(texture_enabled));
// output_color = float(texture_enabled) * texture(model_texture, fragment_uv) * vec4(1, 0, 0, 0) + (1.0 - float(texture_enabled)) * vec4(1, 0, 0, 0);
// output_color = (1.0 - float(texture_enabled)) * vec4(ex_color, 1);
}

67
src/shaders/shader.vert Normal file
View File

@ -0,0 +1,67 @@
#version 300 es
/* >> Cakefoot by https://foam.shampoo.ooo/ << */
/* The precision declaration is required by OpenGL ES */
precision mediump float;
#define PI 3.1415926535897932384626433832795
#define AMPLITUDE 0.2
#define PERIOD .5
#define WAVELENGTH 2.5
#define EFFECT_NONE 0
#define EFFECT_SNAKE 1
#define EFFECT_WOBBLE 2
in vec3 vertex_position;
in vec3 vertex_color;
in vec2 vertex_uv;
uniform mat4 mvp;
uniform float time;
uniform int effect;
out vec3 ex_color;
out float x_center_proximity;
out vec2 fragment_uv;
out vec3 original_coordinates;
out vec3 clip_coordinates;
/* Offset X-coordinate according to the time step and Y-coordinate to create a wobble effect when run on
* flattened coordinates (after projection matrix has been applied) */
void wobble()
{
gl_Position.x += sin(time * 10.0) * vertex_position.y / 2.0;
}
/* Contort the X-coordinate according the time step and Y-coordinate using the sine function. This contorts
* the model into a sine wave along the Y-axis. It also moves the sine wave along the Y-axis using the time
* step uniform. The shape can be edited by changing the defintions for amplitude (maximum distance from
* Y-axis), wavelength (length in GL model coordinates between peaks), and period (amount of time in
* seconds it takes to loop through one wavelength cycle). */
void snake()
{
gl_Position = vec4(
vertex_position.x + AMPLITUDE * sin(2.0 * PI / PERIOD * (time + vertex_position.y * PERIOD / WAVELENGTH)), vertex_position.yz, 1);
}
void main()
{
// if (effect == EFFECT_SNAKE)
// {
// snake();
// }
// else
// {
gl_Position = vec4(vertex_position, 1);
// }
gl_Position = mvp * gl_Position;
// if (effect == EFFECT_WOBBLE)
// {
// wobble();
// }
/* passing to fragment program */
ex_color = vertex_color;
x_center_proximity = 1.8 - abs(vertex_position[0]);
fragment_uv = vertex_uv;
original_coordinates = vertex_position;
clip_coordinates = gl_Position.xyz;
}