390 lines
16 KiB
C++
390 lines
16 KiB
C++
/* +------------------------------------------------------+
|
|
____/ \____ /| - Open source game framework licensed to freely use, |
|
|
\ / / | copy, modify and sell without restriction |
|
|
+--\ ^__^ /--+ | |
|
|
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
|
|
| ~~~~~~~~~~~~ | +------------------------------------------------------+
|
|
| 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 when getting 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();
|
|
}
|
|
|
|
/*!
|
|
* 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, it 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();
|
|
}
|
|
};
|
|
}
|