320 lines
11 KiB
C++
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();
|