cakefoot/src/Cakefoot.hpp

320 lines
11 KiB
C++

/* >> 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();