Cocktail Frank
6965d7255f
Update sb::Text blending so that no blending is used when the background is fully transparent, leaving the text surface intact. Fix a bug in Progress::stat_default so that it calculates a sum stat instead of just skipping it. Add overload to Color::percent for setting the color with a 3- or 4-dimensional vector. Add hinting and alignment parameters to Game::font. Add overload to Sprite::translate for setting translation with a 2D vector. Add range-based for loop to stats and achievements lists.
406 lines
16 KiB
C++
406 lines
16 KiB
C++
/* +=======================================================+
|
|
____/ \____ /: Open source game framework licensed to freely use, :
|
|
\ / / : copy, and modify - created for dank.game :
|
|
+==\ ^__^ /==+ : :
|
|
: ~/ \~ : : Download at https://open.shampoo.ooo/shampoo/spacebox :
|
|
: ~~~~~~~~~~~~ : +=======================================================+
|
|
: SPACE ~~~~~ : /
|
|
: ~~~~~~~ BOX :/
|
|
+=============*/
|
|
|
|
#pragma once
|
|
|
|
#include <vector>
|
|
#include <optional>
|
|
#include "glm/glm.hpp"
|
|
#include "filesystem.hpp"
|
|
#include "Model.hpp"
|
|
#include "Animation.hpp"
|
|
|
|
namespace sb
|
|
{
|
|
/*!
|
|
* A Sprite is a wrapper around an sb::Plane object that resets and stores scale, translation, and rotation matrices every time
|
|
* they are set and combines them automatically to get the full transformation. This allows those transformations to be set
|
|
* repeatedly without having to call sb::Plane::untransform() after each set. In the case of translation, there is also a move
|
|
* function that allows the translation to be modified without resetting, so the sprite can move relative amounts.
|
|
*/
|
|
class Sprite
|
|
{
|
|
|
|
private:
|
|
|
|
/* The plane is a class member rather than the class's inherited type, allowing the user to define a custom plane. When the
|
|
* sprite is copied, the plane is copied with its references to GPU memory preserved. */
|
|
sb::Plane plane;
|
|
|
|
/* Keep a copy of the matrix transformations generated when the user applies a transformation, so that each can be reapplied
|
|
* without having to set all the transformations every time one is changed. When the sprite is copied, the transformation
|
|
* values are copied to the new object, so the new sprite can alter the transformations independently. */
|
|
glm::mat4 _scale {1.0f}, _translation {1.0f}, _rotation {1.0f};
|
|
|
|
/* A sprite by definition has only one texture per draw, so keep an index to the currently active texture. */
|
|
int _texture_index = 0;
|
|
|
|
void frame_by_frame()
|
|
{
|
|
if (static_cast<std::size_t>(++_texture_index) >= plane.textures().size())
|
|
{
|
|
_texture_index = 0;
|
|
}
|
|
}
|
|
|
|
public:
|
|
|
|
sb::Animation frames;
|
|
|
|
/*!
|
|
* Construct an instance of Sprite using an existing plane object. The plane will be copied into the Sprite, so further edits
|
|
* must be made using the Sprite class.
|
|
*
|
|
* @param plane flat model of the sprite
|
|
*/
|
|
Sprite(const sb::Plane& plane) : plane(plane) {};
|
|
|
|
/*!
|
|
* Construct a Sprite with a default constructed sb::Plane and optional scale amount.
|
|
*
|
|
* @param scale amount to scale
|
|
*/
|
|
Sprite(glm::vec2 scale = glm::vec2{1.0f}) : Sprite(sb::Plane())
|
|
{
|
|
this->scale(scale);
|
|
}
|
|
|
|
/*!
|
|
* Construct a Sprite with a default constructed sb::Plane and attach a list of textures to the plane. Each texture is a
|
|
* frame of the sprite's animation. The texture is the 2D graphic drawn at the sprite's location.
|
|
*
|
|
* @param textures list of textures
|
|
* @param scale amount to scale
|
|
*/
|
|
Sprite(std::initializer_list<sb::Texture> textures, glm::vec2 scale = glm::vec2{1.0f}) : Sprite(scale)
|
|
{
|
|
for (const sb::Texture& texture : textures)
|
|
{
|
|
this->texture(texture);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Construct a Sprite with a default constructed sb::Plane and give the plane a texture. The texture is the 2D graphic drawn
|
|
* at 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({texture}, scale) {};
|
|
|
|
/*!
|
|
* Construct a ::Sprite object from a list of paths to image files which will be converted into textures.
|
|
*
|
|
* The textures are loaded into GPU memory if the GL context is active. Otherwise, the path is just attached to
|
|
* each texture, and they must be loaded with a call to Sprite::load after the GL context is active.
|
|
*
|
|
* @see sb::Texture::load()
|
|
*
|
|
* @param paths List of paths to images
|
|
* @param scale Amount to scale
|
|
* @param filter Resize filter to use when rendering textures
|
|
*/
|
|
Sprite(std::initializer_list<fs::path> paths, glm::vec2 scale = glm::vec2{1.0f},
|
|
std::optional<GLint> filter = std::nullopt) : Sprite(scale)
|
|
{
|
|
for (const fs::path& path : paths)
|
|
{
|
|
this->texture(path, filter);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Construct a ::Sprite object from a path to an image file which will be converted into a texture.
|
|
*
|
|
* @see ::Sprite(std::initializer_list<fs::path>, glm::vec2)
|
|
*
|
|
* @param path Path to an image
|
|
* @param scale Amount to scale
|
|
* @param filter Resize filter to use when rendering textures
|
|
*/
|
|
Sprite(const fs::path& path, glm::vec2 scale = glm::vec2{1.0f}, std::optional<GLint> filter = std::nullopt) :
|
|
Sprite({path}, scale, filter) {};
|
|
|
|
/*!
|
|
* Add a previously constructed sb::Texture to the sprite's plane object.
|
|
*
|
|
* @param texture sb::Texture object to add
|
|
*/
|
|
void texture(const sb::Texture& texture)
|
|
{
|
|
plane.texture(texture);
|
|
}
|
|
|
|
/*!
|
|
* Add a new texture to the sprite's plane object from a path to an image file which will be 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 Sprite::load.
|
|
*
|
|
* @param path Path to an image
|
|
* @param filter Resize filter to use when rendering the texture
|
|
*/
|
|
void texture(const fs::path& path, std::optional<GLint> filter = std::nullopt)
|
|
{
|
|
sb::Texture texture;
|
|
if (filter.has_value())
|
|
{
|
|
texture.filter(filter.value());
|
|
}
|
|
if (SDL_GL_GetCurrentContext() != nullptr)
|
|
{
|
|
texture.load(path);
|
|
}
|
|
else
|
|
{
|
|
texture.associate(path);
|
|
}
|
|
this->texture(texture);
|
|
}
|
|
|
|
/*!
|
|
* Get a constant reference to the texture attached to the sprite's plane object at the object's current texture index.
|
|
*/
|
|
const sb::Texture& texture() const
|
|
{
|
|
return plane.texture(_texture_index);
|
|
}
|
|
|
|
/*!
|
|
* Remove all textures from the sprite object's plane.
|
|
*/
|
|
void clear_textures()
|
|
{
|
|
plane.textures() = {};
|
|
}
|
|
|
|
/*!
|
|
* @param index set the object's texture index
|
|
*/
|
|
void texture_index(int index)
|
|
{
|
|
_texture_index = index;
|
|
}
|
|
|
|
/*!
|
|
* @return the object's current texture index value
|
|
*/
|
|
int texture_index() const
|
|
{
|
|
return _texture_index;
|
|
}
|
|
|
|
/*!
|
|
* Increment the texture index the given number of times. Defaults to 1. It will wrap around at the end. Negative increment
|
|
* can be used.
|
|
*
|
|
* @param increment amount to increment the texture index
|
|
*/
|
|
void texture_increment(int increment = 1)
|
|
{
|
|
/* Add and wrap (even though model wraps as well) */
|
|
_texture_index = glm::mod(_texture_index + increment, static_cast<int>(plane.textures().size()));
|
|
}
|
|
|
|
/*!
|
|
* If the GL context is active, this can be called to load image paths previously associated with textures attached to the
|
|
* sprite's plane object.
|
|
*/
|
|
void load()
|
|
{
|
|
plane.load();
|
|
}
|
|
|
|
/*!
|
|
* Add all attributes to the given vertex buffer object. The buffer object should have been previously allocated to at least
|
|
* the size of this sprite by passing Sprite::size() to VBO::allocate(GLsizeiptr, GLenum).
|
|
*
|
|
* The VBO must currently be bound.
|
|
*
|
|
* @param vbo vertex buffer object that the sprite's attribute vertices will be added to
|
|
*/
|
|
void add(sb::VBO& vbo)
|
|
{
|
|
plane.add(vbo);
|
|
}
|
|
|
|
/*!
|
|
* Bind all of this sprite's attributes and its active texture by calling each of their bind methods. Textures and
|
|
* attributes all must already have GL indices set, for example by calling Texture::generate() and
|
|
* Attributes::index(GLint) on each.
|
|
*/
|
|
void bind()
|
|
{
|
|
plane.bind_attributes();
|
|
texture().bind();
|
|
}
|
|
|
|
/*!
|
|
* Get a reference to the plane object's shared pointer to the attributes with the given name. The underlying attributes
|
|
* object is fully exposed, meaning it can be edited, and both its const and non-const methods can be used.
|
|
*
|
|
* @param name name of the attributes, see Model::attributes(const sb::Attributes&, const std::string&)
|
|
* @return const reference to a shared pointer held by the plane object that points to the attributes with the given
|
|
* name
|
|
*/
|
|
const std::shared_ptr<sb::Attributes>& attributes(const std::string name) const
|
|
{
|
|
return plane.attributes(name);
|
|
}
|
|
|
|
/*!
|
|
* Set the sprite plane's translation transformation using an x, y, and z offset. Any previous translation will be reset. Can
|
|
* be used to move the sprite relative to the origin.
|
|
*
|
|
* @param translation transformation along the x, y, and z axes
|
|
*/
|
|
void translate(const glm::vec3& translation)
|
|
{
|
|
plane.untransform();
|
|
_translation = plane.translate(translation);
|
|
transform();
|
|
}
|
|
|
|
/*!
|
|
* 2D translation in the X and Y planes
|
|
*
|
|
* @overload translate(const glm::vec3&)
|
|
*/
|
|
void translate(const glm::vec2& translation)
|
|
{
|
|
translate({translation.x, translation.y, 0.0f});
|
|
}
|
|
|
|
/*!
|
|
* Add to the sprite's current translation transformation. Can be used to move the sprite relative to its current position.
|
|
*
|
|
* @param step amount to move sprite's translation transformation in three dimensions
|
|
*/
|
|
void move(const glm::vec3& step)
|
|
{
|
|
plane.untransform();
|
|
_translation += plane.translate(step);
|
|
transform();
|
|
}
|
|
|
|
/*!
|
|
* Set the sprite plane's scale transformation in the x and y dimensions. Any previous scale will be reset.
|
|
*
|
|
* @param scale sprite plane's new scale transformation
|
|
*/
|
|
void scale(const glm::vec2& scale)
|
|
{
|
|
plane.untransform();
|
|
_scale = plane.scale({scale, 1.0f});
|
|
transform();
|
|
}
|
|
|
|
/*!
|
|
* Set the sprite plane's rotation transformation to a rotation around the given axis by a given angle. Any previous
|
|
* rotation will be reset. This does not rotate the sprite relative to its current rotation, it rotates relative to
|
|
* the initial rotation of zero.
|
|
*
|
|
* @param angle angle in radians amount to rotate
|
|
* @param axis three dimensional axis around which to rotate the sprite
|
|
*/
|
|
void rotate(float angle, const glm::vec3& axis)
|
|
{
|
|
plane.untransform();
|
|
_rotation = plane.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})
|
|
{
|
|
plane.untransform();
|
|
plane.transform(_translation * _scale * _rotation * transformation);
|
|
}
|
|
|
|
void update(const sb::Timer& timer)
|
|
{
|
|
/* Update animation */
|
|
if (frames.update(timer.stamp()))
|
|
{
|
|
frame_by_frame();
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Get the sprite's transformation from Sprite::transform(glm::mat4), which combines the translation, scale,
|
|
* rotation, and an optional arbitrary transformation, apply the given view and projection transformations, and
|
|
* pass the transformation to the shader at the given transformation uniform. The uniform is not checked for
|
|
* existence, so it must be present in the shader.
|
|
*
|
|
* Then enable the plane's attributes, and draw the amount of vertices in the plane's position attributes using
|
|
* `glDrawArrays`. Disable the plane's attributes after the draw.
|
|
*
|
|
* The optional texture flag uniform can be passed to automatically set that uniform to true if there are
|
|
* textures attached to this sprite, and false if not. The currently bound shader should be written to use that
|
|
* flag. For example, the shader could use the flag to choose whether to use the UV or the color attributes.
|
|
*
|
|
* The vertex data is expected to be bound before this function is called.
|
|
*
|
|
* @param transformation_uniform transformation uniform ID
|
|
* @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
|
|
* @param texture_flag_uniform uniform ID for boolean enabling or disabling texture display
|
|
*/
|
|
void draw(GLuint transformation_uniform, const glm::mat4 view = glm::mat4{1.0f}, const glm::mat4 projection = glm::mat4{1.0f},
|
|
std::optional<GLuint> texture_flag_uniform = std::nullopt) const
|
|
{
|
|
if (!plane.textures().empty())
|
|
{
|
|
if (texture_flag_uniform.has_value())
|
|
{
|
|
glUniform1i(texture_flag_uniform.value(), true);
|
|
}
|
|
texture().bind();
|
|
}
|
|
else if (texture_flag_uniform.has_value())
|
|
{
|
|
glUniform1i(texture_flag_uniform.value(), false);
|
|
}
|
|
glm::mat4 mvp = projection * view * plane.transformation();
|
|
|
|
/* It's possible to use glGetActiveUniformName to test for existence of the given uniform index before proceeding, but the
|
|
* test is left out to optimize speed since the draw call is expected to be used every frame.
|
|
*
|
|
* If the index is -1, the check could be skipped since -1 is a special case where the uniform is not expected to exist.
|
|
*/
|
|
|
|
glUniformMatrix4fv(transformation_uniform, 1, GL_FALSE, &mvp[0][0]);
|
|
plane.enable();
|
|
glDrawArrays(GL_TRIANGLES, 0, plane.attributes("position")->count());
|
|
plane.disable();
|
|
}
|
|
|
|
/*!
|
|
* @return size in bytes of the sprite's plane object
|
|
*/
|
|
std::size_t size() const
|
|
{
|
|
return plane.size();
|
|
}
|
|
};
|
|
}
|