separate classes into files
This commit is contained in:
parent
46d0eaec9e
commit
a71455fbd5
2
Makefile
2
Makefile
@ -42,7 +42,7 @@ SB_O_FILES := $(patsubst %.cpp, %.o, $(wildcard $(addprefix $(SB_SRC_DIR),*.cpp)
|
||||
|
||||
# Get all project header and object files
|
||||
SRC_H_FILES := $(wildcard $(addprefix $(SRC_DIR),*.hpp))
|
||||
SRC_O_FILES := $(SRC_H_FILES:.hpp=.o)
|
||||
SRC_O_FILES := $(patsubst %.cpp, %.o, $(wildcard $(addprefix $(SRC_DIR),*.cpp)))
|
||||
|
||||
##################################################################
|
||||
# Object files for [SPACEBOX], its dependencies, and the project #
|
||||
|
123
src/Cakefoot.cpp
123
src/Cakefoot.cpp
@ -81,8 +81,8 @@ Cakefoot::Cakefoot()
|
||||
poke = std::shared_ptr<SDL_Cursor>(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND), SDL_FreeCursor);
|
||||
grab = std::shared_ptr<SDL_Cursor>(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL), SDL_FreeCursor);
|
||||
|
||||
character_sprite = Sprite("resource/cake-frames/cake1.png", glm::vec2{20.0f / 486.0f});
|
||||
|
||||
/* Initialize character sprite and position */
|
||||
character.sprite = Sprite("resource/cake-frames/cake1.png", glm::vec2{20.0f / 486.0f});
|
||||
character.reset(curve());
|
||||
}
|
||||
|
||||
@ -147,11 +147,14 @@ void Cakefoot::load_gl_context()
|
||||
/* Set the active texture and uniform once at context load time because only one texture is used per draw */
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glUniform1i(uniform["model texture"], 0);
|
||||
sb::Log::gl_errors("after uniform locations");
|
||||
|
||||
/* Enable alpha rendering */
|
||||
/* Enable alpha rendering, disable depth test, set clear color */
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glEnable(GL_BLEND);
|
||||
sb::Log::gl_errors("after uniform locations");
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
sb::Log::gl_errors("after GL initialization");
|
||||
}
|
||||
|
||||
Curve& Cakefoot::curve()
|
||||
@ -320,22 +323,18 @@ void Cakefoot::update(float timestamp)
|
||||
{
|
||||
sb::Log::gl_errors("at beginning of update");
|
||||
|
||||
/* Time in seconds the game has been running for */
|
||||
/* Update time in seconds the game has been running for, pass to the shader. */
|
||||
if (!timer)
|
||||
{
|
||||
timer.on();
|
||||
}
|
||||
timer.update(timestamp);
|
||||
set_framerate(configuration()["display"]["framerate"]);
|
||||
|
||||
character.update(curve(), timer);
|
||||
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
glUniform1f(uniform["time"], timer.elapsed());
|
||||
|
||||
/* Update character, along the curve, using the timer to determine movement since last frame. */
|
||||
character.update(curve(), timer);
|
||||
|
||||
/* Transformation for rotating the model space and looking at the center of the field of play from the camera position. */
|
||||
view = glm::lookAt(camera_position, {0.0f, 0.0f, 0.0f}, glm::vec3{0.0f, 1.0f, 0.0f}) *
|
||||
glm::rotate(glm::mat4(1), rotation.x, {0.0f, 1.0f, 0.0f}) * glm::rotate(glm::mat4(1), rotation.y, {1.0f, 0.0f, 0.0f});
|
||||
@ -349,6 +348,9 @@ void Cakefoot::update(float timestamp)
|
||||
/* Plane position vertices will be used for everything before the curve */
|
||||
sb::Plane::position->bind("vertex_position", shader_program);
|
||||
|
||||
/* Clear screen to black */
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
/* Draw playing field */
|
||||
field.attributes("color")->bind("vertex_color", shader_program);
|
||||
// field.draw(uniform["mvp"], uniform["texture enabled"], view, projection);
|
||||
@ -376,9 +378,9 @@ void Cakefoot::update(float timestamp)
|
||||
|
||||
/* Draw cake */
|
||||
sb::Plane::position->bind("vertex_position", shader_program);
|
||||
character_sprite.translate(glm::vec3{cake_translation, 0.0f});
|
||||
character_sprite.bind_texture(uniform["texture enabled"]);
|
||||
character_sprite.draw(uniform["mvp"], view, projection);
|
||||
character.sprite.translate(glm::vec3{cake_translation, 0.0f});
|
||||
character.sprite.bind_texture(uniform["texture enabled"]);
|
||||
character.sprite.draw(uniform["mvp"], view, projection);
|
||||
|
||||
/* Update FPS indicator display to the current FPS count and draw. */
|
||||
if (configuration()["display"]["fps"])
|
||||
@ -406,97 +408,6 @@ void Cakefoot::update(float timestamp)
|
||||
sb::Log::gl_errors("at end of update");
|
||||
}
|
||||
|
||||
int Curve::length() const
|
||||
{
|
||||
return unwrapped.size();
|
||||
}
|
||||
|
||||
const glm::vec2& Curve::front() const
|
||||
{
|
||||
return unwrapped.front();
|
||||
}
|
||||
|
||||
const glm::vec2& Curve::operator[](int index) const
|
||||
{
|
||||
return unwrapped[index];
|
||||
}
|
||||
|
||||
void Character::reset(const Curve& curve)
|
||||
{
|
||||
next_point_index = 0;
|
||||
speed = 0.0f;
|
||||
position = curve.front();
|
||||
accelerating = false;
|
||||
}
|
||||
|
||||
const nlohmann::json& Character::entry(const std::string& suffix) const
|
||||
{
|
||||
return configuration["character"][*profile.current() + "-" + suffix];
|
||||
}
|
||||
|
||||
void Character::update(const Curve& curve, const sb::Timer& timer)
|
||||
{
|
||||
/* Adjust speed based on acceleration state and character profile. */
|
||||
if (accelerating)
|
||||
{
|
||||
/* Apply delta time to the speed increase. */
|
||||
speed += timer.delta(entry("speed-increment").get<float>()) + glm::abs(speed) * entry("increment-mod").get<float>();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Apply delta time to the speed decrease. */
|
||||
speed -= timer.delta(entry("speed-decrement").get<float>()) + glm::abs(speed) * entry("decrement-mod").get<float>();
|
||||
}
|
||||
|
||||
/* Clamp speed, applying delta time to the limits */
|
||||
if (speed > timer.delta(entry("max-speed").get<float>()))
|
||||
{
|
||||
speed = timer.delta(entry("max-speed").get<float>());
|
||||
}
|
||||
else if (speed < timer.delta(entry("min-speed").get<float>()))
|
||||
{
|
||||
speed = timer.delta(entry("min-speed").get<float>());
|
||||
}
|
||||
|
||||
/* Move along unwrapped curve vertices */
|
||||
float distance_remaining = std::abs(speed), distance = 0.0f;
|
||||
glm::vec2 next_point, step;
|
||||
while (distance_remaining)
|
||||
{
|
||||
if (speed < 0.0f && next_point_index == 0)
|
||||
{
|
||||
speed = 0.0f;
|
||||
break;
|
||||
}
|
||||
else if (speed > 0.0f && next_point_index > curve.length() - 1)
|
||||
{
|
||||
speed = 0.0f;
|
||||
break;
|
||||
}
|
||||
if (speed > 0.0f)
|
||||
{
|
||||
next_point = curve[next_point_index];
|
||||
}
|
||||
else
|
||||
{
|
||||
next_point = curve[next_point_index - 1];
|
||||
}
|
||||
distance = glm::distance(position, next_point);
|
||||
if (distance < distance_remaining)
|
||||
{
|
||||
distance_remaining -= distance;
|
||||
position = next_point;
|
||||
next_point_index += speed < 0.0f ? -1 : 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
step = sb::Segment(position, next_point).step(distance_remaining);
|
||||
position += step;
|
||||
distance_remaining = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec4 world_to_clip(glm::vec3 world, glm::mat4 projection, glm::mat4 view)
|
||||
{
|
||||
return projection * (view * glm::vec4{world.x, world.y, world.z, 1.0f});
|
||||
|
516
src/Cakefoot.hpp
516
src/Cakefoot.hpp
@ -43,515 +43,11 @@
|
||||
#include "Timer.hpp"
|
||||
#include "Text.hpp"
|
||||
|
||||
class Sprite : private sb::Plane
|
||||
{
|
||||
|
||||
private:
|
||||
|
||||
/* Keep a reference of the matrix transformations generated when the user applys a transformation, so that each can be reapplied
|
||||
* without having to set all the transformations every time one is changed. */
|
||||
glm::mat4 _scale {1.0f}, _translation {1.0f}, _rotation {1.0f};
|
||||
|
||||
public:
|
||||
|
||||
/*!
|
||||
* Construct a Sprite with an optional scale amount.
|
||||
*
|
||||
* @param scale amount to scale
|
||||
*/
|
||||
Sprite(glm::vec2 scale = glm::vec2{1.0f})
|
||||
{
|
||||
this->scale(scale);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Construct a Sprite from a texture. The texture is the 2D graphic that displays in the Sprite's location.
|
||||
*
|
||||
* @param texture sprite's 2D graphic
|
||||
* @param scale amount to scale
|
||||
*/
|
||||
Sprite(const sb::Texture& texture, glm::vec2 scale = glm::vec2{1.0f}) : Sprite(scale)
|
||||
{
|
||||
this->texture(texture);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Construct a Sprite from a list of textures. Each texture is a frame of the Sprite's animation.
|
||||
*
|
||||
* @param textures list of textures
|
||||
* @param scale amount to scale
|
||||
*/
|
||||
Sprite(const std::vector<sb::Texture>& textures, glm::vec2 scale = glm::vec2{1.0f}) : Sprite(scale)
|
||||
{
|
||||
for (const sb::Texture& texture : textures)
|
||||
{
|
||||
this->texture(texture);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Construct a ::Sprite object from a path to an image file which will converted into a texture.
|
||||
*
|
||||
* The texture is loaded into GPU memory if the GL context is active. Otherwise, the path is just attached to
|
||||
* the texture, and it must be loaded with a call to ::load or Texture::load.
|
||||
*
|
||||
* @param path path to an image
|
||||
* @param scale amount to scale
|
||||
*/
|
||||
Sprite(const fs::path& path, glm::vec2 scale = glm::vec2{1.0f}) : Sprite(scale)
|
||||
{
|
||||
this->texture(path);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Construct a ::Sprite object from a list of paths to image files which will be converted into textures.
|
||||
*
|
||||
* @see ::Sprite(const fs::path, glm::vec3)
|
||||
*
|
||||
* @param paths list of paths to images
|
||||
* @param scale amount to scale
|
||||
*/
|
||||
Sprite(const std::vector<fs::path>& paths, glm::vec2 scale = glm::vec2{1.0f}) : Sprite(scale)
|
||||
{
|
||||
for (const fs::path& path : paths)
|
||||
{
|
||||
this->texture(path);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Add a previously constructed sb::Texture to the ::Sprite object.
|
||||
*
|
||||
* @param texture sb::Texture object to add
|
||||
*/
|
||||
void texture(const sb::Texture& texture)
|
||||
{
|
||||
Model::texture(texture);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Add a new texture to ::Sprite from a path to an image file which will converted into a new texture.
|
||||
*
|
||||
* The texture is loaded into GPU memory if the GL context is active. Otherwise, the path is just attached to
|
||||
* the texture, and it must be loaded with a call to ::load or Texture::load.
|
||||
*
|
||||
* @param path path to an image
|
||||
*/
|
||||
void texture(const fs::path& path)
|
||||
{
|
||||
sb::Texture texture;
|
||||
if (SDL_GL_GetCurrentContext() != nullptr)
|
||||
{
|
||||
texture.load(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
texture.associate(path);
|
||||
}
|
||||
this->texture(texture);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get a constant refernce to the texture at the given index. If no index is given, get the texture at index 0. If
|
||||
* there are no textures, an exception will be thrown.
|
||||
*
|
||||
* @param index index of texture to get
|
||||
*/
|
||||
const sb::Texture& texture(int index = 0) const
|
||||
{
|
||||
return Model::texture(index);
|
||||
}
|
||||
|
||||
/*!
|
||||
*/
|
||||
void translate(const glm::vec3& translation)
|
||||
{
|
||||
untransform();
|
||||
_translation = Model::translate(translation);
|
||||
transform();
|
||||
}
|
||||
|
||||
/*!
|
||||
* @param step amount to move sprite's translation transformation in three dimensions
|
||||
*/
|
||||
void move(const glm::vec3& step)
|
||||
{
|
||||
untransform();
|
||||
_translation += Model::translate(step);
|
||||
transform();
|
||||
}
|
||||
|
||||
/*!
|
||||
*/
|
||||
void scale(const glm::vec2& scale)
|
||||
{
|
||||
untransform();
|
||||
_scale = Model::scale({scale, 1.0f});
|
||||
transform();
|
||||
}
|
||||
|
||||
/*!
|
||||
*/
|
||||
void rotate(float angle, const glm::vec3& axis)
|
||||
{
|
||||
untransform();
|
||||
_rotation = Model::rotate(angle, axis);
|
||||
transform();
|
||||
}
|
||||
|
||||
/*!
|
||||
* The translation, scale, and rotation transformations, if previously set, are applied to the object's transformation
|
||||
* property, along with the optional additional transformation matrix argument.
|
||||
*
|
||||
* The existing transformation property will be reset to the identity matrix before this transformation is applied.
|
||||
*
|
||||
* @warning This function works differently than Model::transform(const glm::mat4&). To apply an arbitrary transformation
|
||||
* without having the non-arbitrary transformations applied as well, the rotate, scale, and translate transformations should
|
||||
* be set to the identity matrix.
|
||||
*
|
||||
* @param transformation optional additional transformation to apply
|
||||
*/
|
||||
void transform(glm::mat4 transformation = glm::mat4{1.0f})
|
||||
{
|
||||
untransform();
|
||||
Model::transform(_translation * _scale * _rotation * transformation);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @return box object that represents the world coordinates of the rectangle surrounding the sprite object
|
||||
*/
|
||||
sb::Box box() const;
|
||||
|
||||
/*!
|
||||
* @param sprite another sprite to check against for a collision
|
||||
* @return true if the two sprite objects collide, false otherwise
|
||||
*/
|
||||
bool collide(const Sprite& sprite) const;
|
||||
|
||||
/*
|
||||
*/
|
||||
void bind_texture() const
|
||||
{
|
||||
if (textures().empty())
|
||||
{
|
||||
throw("Sprite doesn't have any textures to bind");
|
||||
}
|
||||
else
|
||||
{
|
||||
texture().bind();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*/
|
||||
void bind_texture(GLuint texture_flag_uniform) const
|
||||
{
|
||||
glUniform1i(texture_flag_uniform, true);
|
||||
bind_texture();
|
||||
}
|
||||
|
||||
/*
|
||||
*/
|
||||
void draw(GLuint transformation_uniform, const glm::mat4 view = glm::mat4{1.0f}, const glm::mat4 projection = glm::mat4{1.0f}) const
|
||||
{
|
||||
glm::mat4 mvp = projection * view * transformation();
|
||||
glUniformMatrix4fv(transformation_uniform, 1, GL_FALSE, &mvp[0][0]);
|
||||
enable();
|
||||
glDrawArrays(GL_TRIANGLES, 0, attributes("position")->count());
|
||||
disable();
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* 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:
|
||||
|
||||
using Reaction = std::function<return_type(bool, arguments...)>;
|
||||
sb::Switch<return_type, arguments...> connection;
|
||||
|
||||
public:
|
||||
|
||||
Box collision_box;
|
||||
|
||||
/*!
|
||||
* 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)
|
||||
{
|
||||
if (translation != glm::vec2{0.0f, 0.0f})
|
||||
{
|
||||
this->translate(translation);
|
||||
}
|
||||
if (scale != 1.0f || ratio != 1.0f)
|
||||
{
|
||||
this->scale(scale, ratio);
|
||||
}
|
||||
if (rotation)
|
||||
{
|
||||
this->rotate(rotation);
|
||||
}
|
||||
this->on_state_change(on_state_change);
|
||||
collision_box.invert_y(true);
|
||||
collision_box.size({2.0f, 2.0f}, 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);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Rotate the pad around its center by 90 degrees. If a count is given, rotate by 90 degrees that many times, so for example,
|
||||
* a count of 3 will be a 270 degree rotation. If the count is negative, rotate -90 degrees.
|
||||
*
|
||||
* @param count number of 90 degree rotations to make, or use a negative count to rotate -90 degrees
|
||||
*/
|
||||
void rotate(int count = 1)
|
||||
{
|
||||
transform(glm::rotate(count * glm::half_pi<float>(), glm::vec3{0.0f, 0.0f, 1.0f}));
|
||||
}
|
||||
|
||||
/*!
|
||||
* Scale using a factor and ratio that will transform the pad in the X and Y dimensions. The ratio determines 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.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
const glm::mat4& scale(float factor, float ratio = 1.0f)
|
||||
{
|
||||
glm::vec3 scale { factor, factor, 1.0f };
|
||||
if (ratio > 1.0f)
|
||||
{
|
||||
scale.x /= ratio;
|
||||
}
|
||||
else if (ratio < 1.0f)
|
||||
{
|
||||
scale.y *= ratio;
|
||||
}
|
||||
collision_box.scale({scale.x, scale.y}, true);
|
||||
return sb::Plane::scale(scale);
|
||||
}
|
||||
|
||||
const glm::mat4& scale(const glm::vec3& scale)
|
||||
{
|
||||
collision_box.scale({scale.x, scale.y}, true);
|
||||
return sb::Plane::scale(scale);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Move the pad in the X and Y dimension using a 2D vector. The collision box will be moved by the same translation.
|
||||
*
|
||||
* @param translation x, y distance to translate the pad
|
||||
*/
|
||||
void translate(const glm::vec2& translation)
|
||||
{
|
||||
collision_box.move(translation);
|
||||
sb::Plane::translate({translation.x, translation.y, 0.0f});
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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 the transformation uniform, bind the texture, and draw the vertices. Build the full transformation matrix
|
||||
* by combining the Pad object's transformation with the supplied projection and view matrices, and pass it to
|
||||
* the shader.
|
||||
*
|
||||
* The active texture must have been set with its ID passed to the corresponding shader uniform previously.
|
||||
*
|
||||
* @param uniform_id transformation uniform ID
|
||||
* @param texture_flag_uniform_id uniform ID for boolean enabling or disabling texture display
|
||||
* @param view the view matrix for transforming from world space to camera space
|
||||
* @param projection projection matrix for transforming from camera space to clip space
|
||||
*/
|
||||
void draw(GLuint uniform_id, GLuint texture_flag_uniform_id, glm::mat4 view, glm::mat4 projection)
|
||||
{
|
||||
if (!textures().empty())
|
||||
{
|
||||
glUniform1i(texture_flag_uniform_id, true);
|
||||
texture().bind();
|
||||
}
|
||||
else
|
||||
{
|
||||
glUniform1i(texture_flag_uniform_id, false);
|
||||
}
|
||||
glm::mat4 mvp = projection * view * transformation();
|
||||
glUniformMatrix4fv(uniform_id, 1, GL_FALSE, &mvp[0][0]);
|
||||
enable();
|
||||
glDrawArrays(GL_TRIANGLES, 0, attributes("position")->count());
|
||||
disable();
|
||||
}
|
||||
|
||||
const glm::mat4& untransform()
|
||||
{
|
||||
collision_box.size({2.0f, 2.0f}, true);
|
||||
return sb::Plane::untransform();
|
||||
}
|
||||
|
||||
return_type press(arguments... args)
|
||||
{
|
||||
return connection.flip(args...);
|
||||
}
|
||||
};
|
||||
|
||||
class Curve
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
float aspect;
|
||||
std::vector<glm::vec2> unwrapped;
|
||||
std::vector<sb::Attributes> position;
|
||||
sb::Attributes color;
|
||||
|
||||
Curve (float aspect) : aspect(aspect) {}
|
||||
|
||||
void add(const std::vector<glm::vec2>& vertices)
|
||||
{
|
||||
unwrapped = vertices;
|
||||
for (const std::vector<glm::vec2>& wrapped : sb::wrap_curve(vertices, {-aspect, -1.0f}, {aspect, 1.0f}))
|
||||
{
|
||||
position.push_back(sb::Attributes(wrapped));
|
||||
for (std::size_t jj = 0; jj < wrapped.size(); jj++)
|
||||
{
|
||||
color.add(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @return number of vertices in the unwrapped curve
|
||||
*/
|
||||
int length() const;
|
||||
|
||||
/*!
|
||||
* @return the first point of the unwrapped curve
|
||||
*/
|
||||
const glm::vec2& front() const;
|
||||
|
||||
/*!
|
||||
* @return size in bytes of the GL vertex data
|
||||
*/
|
||||
std::size_t size() const
|
||||
{
|
||||
std::size_t byte_count = 0;
|
||||
for (const auto& attr : position)
|
||||
{
|
||||
byte_count += attr.size();
|
||||
}
|
||||
byte_count += color.size();
|
||||
return byte_count;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @param index index of the vertex in the unwrapped curve vertices list
|
||||
* @return vertex in the unwrapped vertices list at index
|
||||
*/
|
||||
const glm::vec2& operator[](int index) const;
|
||||
};
|
||||
|
||||
class Character
|
||||
{
|
||||
|
||||
private:
|
||||
|
||||
inline static const std::vector<std::string> PROFILES = {"cake", "ball", "buffalo"};
|
||||
|
||||
Configuration& configuration;
|
||||
float speed = 0.0f;
|
||||
int next_point_index = 0;
|
||||
|
||||
/*!
|
||||
* @param suffix the character profile config value to get, without the character name
|
||||
* @return character profile config value
|
||||
*/
|
||||
const nlohmann::json& entry(const std::string& suffix) const;
|
||||
|
||||
public:
|
||||
|
||||
sb::Switch<> accelerating = false;
|
||||
sb::Selection<std::vector<std::string>> profile = PROFILES;
|
||||
glm::vec2 position;
|
||||
|
||||
Character(Configuration& configuration) : configuration(configuration) {}
|
||||
|
||||
/*!
|
||||
* @param curve the curve to reset the beginning position to
|
||||
*/
|
||||
void reset(const Curve& curve);
|
||||
|
||||
/*!
|
||||
* Check acceleration state and adjust speed. Move character toward the next point on the curve.
|
||||
*
|
||||
* @param curve the curve to update against
|
||||
*/
|
||||
void update(const Curve& curve, const sb::Timer& timer);
|
||||
};
|
||||
/* Project classes */
|
||||
#include "Character.hpp"
|
||||
#include "Sprite.hpp"
|
||||
#include "Pad.hpp"
|
||||
#include "Curve.hpp"
|
||||
|
||||
/*!
|
||||
* The main game object. There is currently only support for one of these to exist at a time.
|
||||
@ -596,12 +92,12 @@ private:
|
||||
Pad<> field;
|
||||
sb::Timer timer;
|
||||
Character character{_configuration};
|
||||
Sprite character_sprite;
|
||||
glm::vec3 camera_position {0.0f, 0.0f, 2.0f}, subject_position {0.0f, 0.0f, 0.0f};
|
||||
float field_of_view_y = 2 * glm::atan(1.0 / camera_position.z);
|
||||
glm::vec2 rotation = {0.0f, 0.0f};
|
||||
sb::Text fps{font()};
|
||||
std::vector<Curve> curves;
|
||||
std::vector<Sprite> enemies;
|
||||
|
||||
/*!
|
||||
* Create GL context via super class and load vertices, UV data, and shaders.
|
||||
|
77
src/Character.cpp
Normal file
77
src/Character.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
#include "Character.hpp"
|
||||
|
||||
void Character::reset(const Curve& curve)
|
||||
{
|
||||
next_point_index = 0;
|
||||
speed = 0.0f;
|
||||
position = curve.front();
|
||||
accelerating = false;
|
||||
}
|
||||
|
||||
const nlohmann::json& Character::entry(const std::string& suffix) const
|
||||
{
|
||||
return configuration["character"][*profile.current() + "-" + suffix];
|
||||
}
|
||||
|
||||
void Character::update(const Curve& curve, const sb::Timer& timer)
|
||||
{
|
||||
/* Adjust speed based on acceleration state and character profile. */
|
||||
if (accelerating)
|
||||
{
|
||||
/* Apply delta time to the speed increase. */
|
||||
speed += timer.delta(entry("speed-increment").get<float>()) + glm::abs(speed) * entry("increment-mod").get<float>();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Apply delta time to the speed decrease. */
|
||||
speed -= timer.delta(entry("speed-decrement").get<float>()) + glm::abs(speed) * entry("decrement-mod").get<float>();
|
||||
}
|
||||
|
||||
/* Clamp speed, applying delta time to the limits */
|
||||
if (speed > timer.delta(entry("max-speed").get<float>()))
|
||||
{
|
||||
speed = timer.delta(entry("max-speed").get<float>());
|
||||
}
|
||||
else if (speed < timer.delta(entry("min-speed").get<float>()))
|
||||
{
|
||||
speed = timer.delta(entry("min-speed").get<float>());
|
||||
}
|
||||
|
||||
/* Move along unwrapped curve vertices */
|
||||
float distance_remaining = std::abs(speed), distance = 0.0f;
|
||||
glm::vec2 next_point, step;
|
||||
while (distance_remaining)
|
||||
{
|
||||
if (speed < 0.0f && next_point_index == 0)
|
||||
{
|
||||
speed = 0.0f;
|
||||
break;
|
||||
}
|
||||
else if (speed > 0.0f && next_point_index > curve.length() - 1)
|
||||
{
|
||||
speed = 0.0f;
|
||||
break;
|
||||
}
|
||||
if (speed > 0.0f)
|
||||
{
|
||||
next_point = curve[next_point_index];
|
||||
}
|
||||
else
|
||||
{
|
||||
next_point = curve[next_point_index - 1];
|
||||
}
|
||||
distance = glm::distance(position, next_point);
|
||||
if (distance < distance_remaining)
|
||||
{
|
||||
distance_remaining -= distance;
|
||||
position = next_point;
|
||||
next_point_index += speed < 0.0f ? -1 : 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
step = sb::Segment(position, next_point).step(distance_remaining);
|
||||
position += step;
|
||||
distance_remaining = 0;
|
||||
}
|
||||
}
|
||||
}
|
47
src/Character.hpp
Normal file
47
src/Character.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
#include <vector>
|
||||
#include "Configuration.hpp"
|
||||
#include "Switch.hpp"
|
||||
#include "Selection.hpp"
|
||||
#include "Sprite.hpp"
|
||||
#include "Curve.hpp"
|
||||
#include "Segment.hpp"
|
||||
|
||||
class Character
|
||||
{
|
||||
|
||||
private:
|
||||
|
||||
inline static const std::vector<std::string> PROFILES = {"cake", "ball", "buffalo"};
|
||||
|
||||
Configuration& configuration;
|
||||
float speed = 0.0f;
|
||||
int next_point_index = 0;
|
||||
|
||||
/*!
|
||||
* @param suffix the character profile config value to get, without the character name
|
||||
* @return character profile config value
|
||||
*/
|
||||
const nlohmann::json& entry(const std::string& suffix) const;
|
||||
|
||||
public:
|
||||
|
||||
sb::Switch<> accelerating = false;
|
||||
sb::Selection<std::vector<std::string>> profile = PROFILES;
|
||||
glm::vec2 position;
|
||||
Sprite sprite;
|
||||
|
||||
Character(Configuration& configuration) : configuration(configuration) {}
|
||||
|
||||
/*!
|
||||
* @param curve the curve to reset the beginning position to
|
||||
*/
|
||||
void reset(const Curve& curve);
|
||||
|
||||
/*!
|
||||
* Check acceleration state and adjust speed. Move character toward the next point on the curve.
|
||||
*
|
||||
* @param curve the curve to update against
|
||||
* @param timer a timer object that is updated once per frame, so that it provides delta time for movement
|
||||
*/
|
||||
void update(const Curve& curve, const sb::Timer& timer);
|
||||
};
|
16
src/Curve.cpp
Normal file
16
src/Curve.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
#include "Curve.hpp"
|
||||
|
||||
int Curve::length() const
|
||||
{
|
||||
return unwrapped.size();
|
||||
}
|
||||
|
||||
const glm::vec2& Curve::front() const
|
||||
{
|
||||
return unwrapped.front();
|
||||
}
|
||||
|
||||
const glm::vec2& Curve::operator[](int index) const
|
||||
{
|
||||
return unwrapped[index];
|
||||
}
|
62
src/Curve.hpp
Normal file
62
src/Curve.hpp
Normal file
@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "glm/glm.hpp"
|
||||
#include "Attributes.hpp"
|
||||
#include "math.hpp"
|
||||
|
||||
class Curve
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
float aspect;
|
||||
std::vector<glm::vec2> unwrapped;
|
||||
std::vector<sb::Attributes> position;
|
||||
sb::Attributes color;
|
||||
|
||||
Curve (float aspect) : aspect(aspect) {}
|
||||
|
||||
void add(const std::vector<glm::vec2>& vertices)
|
||||
{
|
||||
unwrapped = vertices;
|
||||
for (const std::vector<glm::vec2>& wrapped : sb::wrap_curve(vertices, {-aspect, -1.0f}, {aspect, 1.0f}))
|
||||
{
|
||||
position.push_back(sb::Attributes(wrapped));
|
||||
for (std::size_t jj = 0; jj < wrapped.size(); jj++)
|
||||
{
|
||||
color.add(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* @return number of vertices in the unwrapped curve
|
||||
*/
|
||||
int length() const;
|
||||
|
||||
/*!
|
||||
* @return the first point of the unwrapped curve
|
||||
*/
|
||||
const glm::vec2& front() const;
|
||||
|
||||
/*!
|
||||
* @return size in bytes of the GL vertex data
|
||||
*/
|
||||
std::size_t size() const
|
||||
{
|
||||
std::size_t byte_count = 0;
|
||||
for (const auto& attr : position)
|
||||
{
|
||||
byte_count += attr.size();
|
||||
}
|
||||
byte_count += color.size();
|
||||
return byte_count;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @param index index of the vertex in the unwrapped curve vertices list
|
||||
* @return vertex in the unwrapped vertices list at index
|
||||
*/
|
||||
const glm::vec2& operator[](int index) const;
|
||||
};
|
202
src/Pad.hpp
Normal file
202
src/Pad.hpp
Normal file
@ -0,0 +1,202 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include "Model.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:
|
||||
|
||||
using Reaction = std::function<return_type(bool, arguments...)>;
|
||||
sb::Switch<return_type, arguments...> connection;
|
||||
|
||||
public:
|
||||
|
||||
Box collision_box;
|
||||
|
||||
/*!
|
||||
* 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)
|
||||
{
|
||||
if (translation != glm::vec2{0.0f, 0.0f})
|
||||
{
|
||||
this->translate(translation);
|
||||
}
|
||||
if (scale != 1.0f || ratio != 1.0f)
|
||||
{
|
||||
this->scale(scale, ratio);
|
||||
}
|
||||
if (rotation)
|
||||
{
|
||||
this->rotate(rotation);
|
||||
}
|
||||
this->on_state_change(on_state_change);
|
||||
collision_box.invert_y(true);
|
||||
collision_box.size({2.0f, 2.0f}, 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);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Rotate the pad around its center by 90 degrees. If a count is given, rotate by 90 degrees that many times, so for example,
|
||||
* a count of 3 will be a 270 degree rotation. If the count is negative, rotate -90 degrees.
|
||||
*
|
||||
* @param count number of 90 degree rotations to make, or use a negative count to rotate -90 degrees
|
||||
*/
|
||||
void rotate(int count = 1)
|
||||
{
|
||||
transform(glm::rotate(count * glm::half_pi<float>(), glm::vec3{0.0f, 0.0f, 1.0f}));
|
||||
}
|
||||
|
||||
/*!
|
||||
* Scale using a factor and ratio that will transform the pad in the X and Y dimensions. The ratio determines 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.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
const glm::mat4& scale(float factor, float ratio = 1.0f)
|
||||
{
|
||||
glm::vec3 scale { factor, factor, 1.0f };
|
||||
if (ratio > 1.0f)
|
||||
{
|
||||
scale.x /= ratio;
|
||||
}
|
||||
else if (ratio < 1.0f)
|
||||
{
|
||||
scale.y *= ratio;
|
||||
}
|
||||
collision_box.scale({scale.x, scale.y}, true);
|
||||
return sb::Plane::scale(scale);
|
||||
}
|
||||
|
||||
const glm::mat4& scale(const glm::vec3& scale)
|
||||
{
|
||||
collision_box.scale({scale.x, scale.y}, true);
|
||||
return sb::Plane::scale(scale);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Move the pad in the X and Y dimension using a 2D vector. The collision box will be moved by the same translation.
|
||||
*
|
||||
* @param translation x, y distance to translate the pad
|
||||
*/
|
||||
void translate(const glm::vec2& translation)
|
||||
{
|
||||
collision_box.move(translation);
|
||||
sb::Plane::translate({translation.x, translation.y, 0.0f});
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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 the transformation uniform, bind the texture, and draw the vertices. Build the full transformation matrix
|
||||
* by combining the Pad object's transformation with the supplied projection and view matrices, and pass it to
|
||||
* the shader.
|
||||
*
|
||||
* The active texture must have been set with its ID passed to the corresponding shader uniform previously.
|
||||
*
|
||||
* @param uniform_id transformation uniform ID
|
||||
* @param texture_flag_uniform_id uniform ID for boolean enabling or disabling texture display
|
||||
* @param view the view matrix for transforming from world space to camera space
|
||||
* @param projection projection matrix for transforming from camera space to clip space
|
||||
*/
|
||||
void draw(GLuint uniform_id, GLuint texture_flag_uniform_id, glm::mat4 view, glm::mat4 projection)
|
||||
{
|
||||
if (!textures().empty())
|
||||
{
|
||||
glUniform1i(texture_flag_uniform_id, true);
|
||||
texture().bind();
|
||||
}
|
||||
else
|
||||
{
|
||||
glUniform1i(texture_flag_uniform_id, false);
|
||||
}
|
||||
glm::mat4 mvp = projection * view * transformation();
|
||||
glUniformMatrix4fv(uniform_id, 1, GL_FALSE, &mvp[0][0]);
|
||||
enable();
|
||||
glDrawArrays(GL_TRIANGLES, 0, attributes("position")->count());
|
||||
disable();
|
||||
}
|
||||
|
||||
const glm::mat4& untransform()
|
||||
{
|
||||
collision_box.size({2.0f, 2.0f}, true);
|
||||
return sb::Plane::untransform();
|
||||
}
|
||||
|
||||
return_type press(arguments... args)
|
||||
{
|
||||
return connection.flip(args...);
|
||||
}
|
||||
};
|
223
src/Sprite.hpp
Normal file
223
src/Sprite.hpp
Normal file
@ -0,0 +1,223 @@
|
||||
#pragma once
|
||||
|
||||
#include "glm/glm.hpp"
|
||||
#include "Model.hpp"
|
||||
|
||||
class Sprite : private sb::Plane
|
||||
{
|
||||
|
||||
private:
|
||||
|
||||
/* Keep a reference of the matrix transformations generated when the user applys a transformation, so that each can be reapplied
|
||||
* without having to set all the transformations every time one is changed. */
|
||||
glm::mat4 _scale {1.0f}, _translation {1.0f}, _rotation {1.0f};
|
||||
|
||||
public:
|
||||
|
||||
/*!
|
||||
* Construct a Sprite with an optional scale amount.
|
||||
*
|
||||
* @param scale amount to scale
|
||||
*/
|
||||
Sprite(glm::vec2 scale = glm::vec2{1.0f})
|
||||
{
|
||||
this->scale(scale);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Construct a Sprite from a texture. The texture is the 2D graphic that displays in the Sprite's location.
|
||||
*
|
||||
* @param texture sprite's 2D graphic
|
||||
* @param scale amount to scale
|
||||
*/
|
||||
Sprite(const sb::Texture& texture, glm::vec2 scale = glm::vec2{1.0f}) : Sprite(scale)
|
||||
{
|
||||
this->texture(texture);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Construct a Sprite from a list of textures. Each texture is a frame of the Sprite's animation.
|
||||
*
|
||||
* @param textures list of textures
|
||||
* @param scale amount to scale
|
||||
*/
|
||||
Sprite(const std::vector<sb::Texture>& textures, glm::vec2 scale = glm::vec2{1.0f}) : Sprite(scale)
|
||||
{
|
||||
for (const sb::Texture& texture : textures)
|
||||
{
|
||||
this->texture(texture);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Construct a ::Sprite object from a path to an image file which will converted into a texture.
|
||||
*
|
||||
* The texture is loaded into GPU memory if the GL context is active. Otherwise, the path is just attached to
|
||||
* the texture, and it must be loaded with a call to ::load or Texture::load.
|
||||
*
|
||||
* @param path path to an image
|
||||
* @param scale amount to scale
|
||||
*/
|
||||
Sprite(const fs::path& path, glm::vec2 scale = glm::vec2{1.0f}) : Sprite(scale)
|
||||
{
|
||||
this->texture(path);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Construct a ::Sprite object from a list of paths to image files which will be converted into textures.
|
||||
*
|
||||
* @see ::Sprite(const fs::path, glm::vec3)
|
||||
*
|
||||
* @param paths list of paths to images
|
||||
* @param scale amount to scale
|
||||
*/
|
||||
Sprite(const std::vector<fs::path>& paths, glm::vec2 scale = glm::vec2{1.0f}) : Sprite(scale)
|
||||
{
|
||||
for (const fs::path& path : paths)
|
||||
{
|
||||
this->texture(path);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Add a previously constructed sb::Texture to the ::Sprite object.
|
||||
*
|
||||
* @param texture sb::Texture object to add
|
||||
*/
|
||||
void texture(const sb::Texture& texture)
|
||||
{
|
||||
Model::texture(texture);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Add a new texture to ::Sprite from a path to an image file which will converted into a new texture.
|
||||
*
|
||||
* The texture is loaded into GPU memory if the GL context is active. Otherwise, the path is just attached to
|
||||
* the texture, and it must be loaded with a call to ::load or Texture::load.
|
||||
*
|
||||
* @param path path to an image
|
||||
*/
|
||||
void texture(const fs::path& path)
|
||||
{
|
||||
sb::Texture texture;
|
||||
if (SDL_GL_GetCurrentContext() != nullptr)
|
||||
{
|
||||
texture.load(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
texture.associate(path);
|
||||
}
|
||||
this->texture(texture);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get a constant refernce to the texture at the given index. If no index is given, get the texture at index 0. If
|
||||
* there are no textures, an exception will be thrown.
|
||||
*
|
||||
* @param index index of texture to get
|
||||
*/
|
||||
const sb::Texture& texture(int index = 0) const
|
||||
{
|
||||
return Model::texture(index);
|
||||
}
|
||||
|
||||
/*!
|
||||
*/
|
||||
void translate(const glm::vec3& translation)
|
||||
{
|
||||
untransform();
|
||||
_translation = Model::translate(translation);
|
||||
transform();
|
||||
}
|
||||
|
||||
/*!
|
||||
* @param step amount to move sprite's translation transformation in three dimensions
|
||||
*/
|
||||
void move(const glm::vec3& step)
|
||||
{
|
||||
untransform();
|
||||
_translation += Model::translate(step);
|
||||
transform();
|
||||
}
|
||||
|
||||
/*!
|
||||
*/
|
||||
void scale(const glm::vec2& scale)
|
||||
{
|
||||
untransform();
|
||||
_scale = Model::scale({scale, 1.0f});
|
||||
transform();
|
||||
}
|
||||
|
||||
/*!
|
||||
*/
|
||||
void rotate(float angle, const glm::vec3& axis)
|
||||
{
|
||||
untransform();
|
||||
_rotation = Model::rotate(angle, axis);
|
||||
transform();
|
||||
}
|
||||
|
||||
/*!
|
||||
* The translation, scale, and rotation transformations, if previously set, are applied to the object's transformation
|
||||
* property, along with the optional additional transformation matrix argument.
|
||||
*
|
||||
* The existing transformation property will be reset to the identity matrix before this transformation is applied.
|
||||
*
|
||||
* @warning This function works differently than Model::transform(const glm::mat4&). To apply an arbitrary transformation
|
||||
* without having the non-arbitrary transformations applied as well, the rotate, scale, and translate transformations should
|
||||
* be set to the identity matrix.
|
||||
*
|
||||
* @param transformation optional additional transformation to apply
|
||||
*/
|
||||
void transform(glm::mat4 transformation = glm::mat4{1.0f})
|
||||
{
|
||||
untransform();
|
||||
Model::transform(_translation * _scale * _rotation * transformation);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @return box object that represents the world coordinates of the rectangle surrounding the sprite object
|
||||
*/
|
||||
sb::Box box() const;
|
||||
|
||||
/*!
|
||||
* @param sprite another sprite to check against for a collision
|
||||
* @return true if the two sprite objects collide, false otherwise
|
||||
*/
|
||||
bool collide(const Sprite& sprite) const;
|
||||
|
||||
/*
|
||||
*/
|
||||
void bind_texture() const
|
||||
{
|
||||
if (textures().empty())
|
||||
{
|
||||
throw("Sprite doesn't have any textures to bind");
|
||||
}
|
||||
else
|
||||
{
|
||||
texture().bind();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*/
|
||||
void bind_texture(GLuint texture_flag_uniform) const
|
||||
{
|
||||
glUniform1i(texture_flag_uniform, true);
|
||||
bind_texture();
|
||||
}
|
||||
|
||||
/*
|
||||
*/
|
||||
void draw(GLuint transformation_uniform, const glm::mat4 view = glm::mat4{1.0f}, const glm::mat4 projection = glm::mat4{1.0f}) const
|
||||
{
|
||||
glm::mat4 mvp = projection * view * transformation();
|
||||
glUniformMatrix4fv(transformation_uniform, 1, GL_FALSE, &mvp[0][0]);
|
||||
enable();
|
||||
glDrawArrays(GL_TRIANGLES, 0, attributes("position")->count());
|
||||
disable();
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user