move sprite and pad into sb namespace
This commit is contained in:
parent
7bbc0f851d
commit
d1fe6b927c
423
src/Pad.hpp
423
src/Pad.hpp
|
@ -16,226 +16,229 @@
|
|||
#include "Switch.hpp"
|
||||
#include "math.hpp"
|
||||
|
||||
/*!
|
||||
* A Pad is an object containing an sb::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:
|
||||
*
|
||||
* - Has an sb::Plane or derivative (either a custom one provided, or a default constructed one automatically)
|
||||
* - Has an arbitrary response function for when pressed
|
||||
* - Can collide with a point (for example, mouse click)
|
||||
*
|
||||
* 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 ReturnType = void, typename... Arguments>
|
||||
class Pad
|
||||
namespace sb
|
||||
{
|
||||
|
||||
private:
|
||||
|
||||
using Reaction = std::function<ReturnType(bool, Arguments...)>;
|
||||
sb::Switch<ReturnType, Arguments...> connection;
|
||||
sb::Plane plane;
|
||||
Box box;
|
||||
|
||||
public:
|
||||
|
||||
/*!
|
||||
* Construct a Pad object with a default constructed sb::Plane.
|
||||
* A Pad is an object containing an sb::Plane which can be clicked to launch an arbitrary user function. It can be sized and placed by setting its
|
||||
* translation and scale values.
|
||||
*
|
||||
* @see Pad(sb::Plane, const glm::vec2&, float, float, Reaction, float)
|
||||
* Each instance:
|
||||
*
|
||||
* - Has an sb::Plane or derivative (either a custom one provided, or a default constructed one automatically)
|
||||
* - Has an arbitrary response function for when pressed
|
||||
* - Can collide with a point (for example, mouse click)
|
||||
*
|
||||
* 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}));
|
||||
*/
|
||||
Pad(const 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(sb::Plane(), translation, scale, ratio, on_state_change, rotation) {}
|
||||
|
||||
/*!
|
||||
* Construct a pad object from an sb::Plane, 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 plane object.
|
||||
*
|
||||
* The reaction function must accept a boolean as its first argument, which will be given the state of the pad object's switch.
|
||||
*
|
||||
* The plane object will be copied into the pad, so any edits must be made before constructing the pad, unless there is a Pad function
|
||||
* that applies the edit, such as Pad::scale(float, float).
|
||||
*
|
||||
* @param plane plane or plane derivative to represent the pad visually
|
||||
* @param translation x, y amount to translate the 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(const sb::Plane& plane, glm::vec2 translation = {0.0f, 0.0f}, float scale = 1.0f, float ratio = 1.0f,
|
||||
Reaction on_state_change = Reaction(), float rotation = 0.0f) : plane(plane)
|
||||
template<typename ReturnType = void, typename... Arguments>
|
||||
class Pad
|
||||
{
|
||||
box.size({2.0f, 2.0f}, true);
|
||||
if (translation != glm::vec2{0.0f, 0.0f})
|
||||
|
||||
private:
|
||||
|
||||
using Reaction = std::function<ReturnType(bool, Arguments...)>;
|
||||
sb::Switch<ReturnType, Arguments...> connection;
|
||||
sb::Plane plane;
|
||||
Box box;
|
||||
|
||||
public:
|
||||
|
||||
/*!
|
||||
* Construct a Pad object with a default constructed sb::Plane.
|
||||
*
|
||||
* @see Pad(sb::Plane, const glm::vec2&, float, float, Reaction, float)
|
||||
*/
|
||||
Pad(const 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(sb::Plane(), translation, scale, ratio, on_state_change, rotation) {}
|
||||
|
||||
/*!
|
||||
* Construct a pad object from an sb::Plane, 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 plane object.
|
||||
*
|
||||
* The reaction function must accept a boolean as its first argument, which will be given the state of the pad object's switch.
|
||||
*
|
||||
* The plane object will be copied into the pad, so any edits must be made before constructing the pad, unless there is a Pad function
|
||||
* that applies the edit, such as Pad::scale(float, float).
|
||||
*
|
||||
* @param plane plane or plane derivative to represent the pad visually
|
||||
* @param translation x, y amount to translate the 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(const sb::Plane& plane, glm::vec2 translation = {0.0f, 0.0f}, float scale = 1.0f, float ratio = 1.0f,
|
||||
Reaction on_state_change = Reaction(), float rotation = 0.0f) : plane(plane)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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)
|
||||
{
|
||||
plane.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;
|
||||
}
|
||||
box.scale({scale.x, scale.y}, true);
|
||||
return plane.scale(scale);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Set the pad's location 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
|
||||
*/
|
||||
const glm::mat4& translate(const glm::vec2& translation)
|
||||
{
|
||||
box.move(translation);
|
||||
return plane.translate({translation.x, translation.y, 0.0f});
|
||||
}
|
||||
|
||||
/*!
|
||||
* Set the function that will run when a pad object is clicked.
|
||||
*
|
||||
* Example, always keep state true and print "Hello, World!" whenever the pad is clicked,
|
||||
*
|
||||
* start_button.on_state_change([&](bool state, int count){
|
||||
* if (state) {
|
||||
* std::ostringstream message;
|
||||
* message << "Hello, " << state << " World! " << count;
|
||||
* sb::Log::log(message);
|
||||
* start_button.press(1);
|
||||
* }});
|
||||
*
|
||||
* @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 NDC coordinates collides with the pad's collision box. This only works properly if the
|
||||
* Pad is flat in the z-dimension.
|
||||
*
|
||||
* @param position x, y NDC coordinates to check for collision
|
||||
* @return true if the point is inside Pad::box, false otherwise
|
||||
*/
|
||||
bool collide(const glm::vec2& position, const glm::mat4& view, const glm::mat4& projection) const
|
||||
{
|
||||
/* Corners of the box */
|
||||
std::vector<glm::vec3> corners;
|
||||
|
||||
/* Transform each of the corners into NDC coordinates */
|
||||
for (const glm::vec2& vertex : {box.sw(), box.nw(), box.ne(), box.se()})
|
||||
{
|
||||
corners.push_back(sb::world_to_ndc(vertex, projection * view));
|
||||
}
|
||||
|
||||
/* Create a new box using the NDC bottom-left corner */
|
||||
glm::vec2 control = corners[0];
|
||||
float width = glm::distance(corners[3], corners[0]);
|
||||
float height = glm::distance(corners[1], corners[0]);
|
||||
sb::Box transformed {control, {width, height}};
|
||||
|
||||
/* Collide the NDC box with the caller's coordinates */
|
||||
return transformed.collide(position);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Set the transformation uniform, bind a texture if any are attached, 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.
|
||||
*
|
||||
* @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, glm::mat4 view, glm::mat4 projection, std::optional<GLuint> texture_flag_uniform = std::nullopt)
|
||||
{
|
||||
if (!plane.textures().empty())
|
||||
{
|
||||
if (texture_flag_uniform.has_value())
|
||||
box.size({2.0f, 2.0f}, true);
|
||||
if (translation != glm::vec2{0.0f, 0.0f})
|
||||
{
|
||||
glUniform1i(texture_flag_uniform.value(), true);
|
||||
this->translate(translation);
|
||||
}
|
||||
plane.texture().bind();
|
||||
if (scale != 1.0f || ratio != 1.0f)
|
||||
{
|
||||
this->scale(scale, ratio);
|
||||
}
|
||||
if (rotation)
|
||||
{
|
||||
this->rotate(rotation);
|
||||
}
|
||||
this->on_state_change(on_state_change);
|
||||
}
|
||||
else if (texture_flag_uniform.has_value())
|
||||
|
||||
/*!
|
||||
* 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)
|
||||
{
|
||||
glUniform1i(texture_flag_uniform.value(), false);
|
||||
plane.transform(glm::rotate(count * glm::half_pi<float>(), glm::vec3{0.0f, 0.0f, 1.0f}));
|
||||
}
|
||||
glm::mat4 mvp = projection * view * plane.transformation();
|
||||
glUniformMatrix4fv(transformation_uniform, 1, GL_FALSE, &mvp[0][0]);
|
||||
plane.enable();
|
||||
glDrawArrays(GL_TRIANGLES, 0, plane.attributes("position")->count());
|
||||
plane.disable();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Run the reaction function.
|
||||
*
|
||||
* @param args arguments to pass to the reaction function
|
||||
* @return result of the reaction function if it returns a value, or void otherwise
|
||||
*/
|
||||
ReturnType press(Arguments... args)
|
||||
{
|
||||
return connection.flip(args...);
|
||||
}
|
||||
/*!
|
||||
* 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;
|
||||
}
|
||||
box.scale({scale.x, scale.y}, true);
|
||||
return plane.scale(scale);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @return size in bytes of the pad object's plane object
|
||||
*/
|
||||
std::size_t size() const
|
||||
{
|
||||
return plane.size();
|
||||
}
|
||||
};
|
||||
/*!
|
||||
* Set the pad's location 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
|
||||
*/
|
||||
const glm::mat4& translate(const glm::vec2& translation)
|
||||
{
|
||||
box.move(translation);
|
||||
return plane.translate({translation.x, translation.y, 0.0f});
|
||||
}
|
||||
|
||||
/*!
|
||||
* Set the function that will run when a pad object is clicked.
|
||||
*
|
||||
* Example, always keep state true and print "Hello, World!" whenever the pad is clicked,
|
||||
*
|
||||
* start_button.on_state_change([&](bool state, int count){
|
||||
* if (state) {
|
||||
* std::ostringstream message;
|
||||
* message << "Hello, " << state << " World! " << count;
|
||||
* sb::Log::log(message);
|
||||
* start_button.press(1);
|
||||
* }});
|
||||
*
|
||||
* @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 NDC coordinates collides with the pad's collision box. This only works properly if the
|
||||
* Pad is flat in the z-dimension.
|
||||
*
|
||||
* @param position x, y NDC coordinates to check for collision
|
||||
* @return true if the point is inside Pad::box, false otherwise
|
||||
*/
|
||||
bool collide(const glm::vec2& position, const glm::mat4& view, const glm::mat4& projection) const
|
||||
{
|
||||
/* Corners of the box */
|
||||
std::vector<glm::vec3> corners;
|
||||
|
||||
/* Transform each of the corners into NDC coordinates */
|
||||
for (const glm::vec2& vertex : {box.sw(), box.nw(), box.ne(), box.se()})
|
||||
{
|
||||
corners.push_back(sb::world_to_ndc(vertex, projection * view));
|
||||
}
|
||||
|
||||
/* Create a new box using the NDC bottom-left corner */
|
||||
glm::vec2 control = corners[0];
|
||||
float width = glm::distance(corners[3], corners[0]);
|
||||
float height = glm::distance(corners[1], corners[0]);
|
||||
sb::Box transformed {control, {width, height}};
|
||||
|
||||
/* Collide the NDC box with the caller's coordinates */
|
||||
return transformed.collide(position);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Set the transformation uniform, bind a texture if any are attached, 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.
|
||||
*
|
||||
* @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, glm::mat4 view, glm::mat4 projection, std::optional<GLuint> texture_flag_uniform = std::nullopt)
|
||||
{
|
||||
if (!plane.textures().empty())
|
||||
{
|
||||
if (texture_flag_uniform.has_value())
|
||||
{
|
||||
glUniform1i(texture_flag_uniform.value(), true);
|
||||
}
|
||||
plane.texture().bind();
|
||||
}
|
||||
else if (texture_flag_uniform.has_value())
|
||||
{
|
||||
glUniform1i(texture_flag_uniform.value(), false);
|
||||
}
|
||||
glm::mat4 mvp = projection * view * plane.transformation();
|
||||
glUniformMatrix4fv(transformation_uniform, 1, GL_FALSE, &mvp[0][0]);
|
||||
plane.enable();
|
||||
glDrawArrays(GL_TRIANGLES, 0, plane.attributes("position")->count());
|
||||
plane.disable();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Run the reaction function.
|
||||
*
|
||||
* @param args arguments to pass to the reaction function
|
||||
* @return result of the reaction function if it returns a value, or void otherwise
|
||||
*/
|
||||
ReturnType press(Arguments... args)
|
||||
{
|
||||
return connection.flip(args...);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @return size in bytes of the pad object's plane object
|
||||
*/
|
||||
std::size_t size() const
|
||||
{
|
||||
return plane.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
533
src/Sprite.hpp
533
src/Sprite.hpp
|
@ -16,285 +16,288 @@
|
|||
#include "filesystem.hpp"
|
||||
#include "Model.hpp"
|
||||
|
||||
/*!
|
||||
* A Sprite is a container for 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
|
||||
namespace sb
|
||||
{
|
||||
|
||||
private:
|
||||
|
||||
/* The plane is included using composition instead of inheritance, allowing the user to define a custom plane. */
|
||||
sb::Plane plane;
|
||||
|
||||
/* Keep a reference 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. */
|
||||
glm::mat4 _scale {1.0f}, _translation {1.0f}, _rotation {1.0f};
|
||||
|
||||
public:
|
||||
|
||||
/*!
|
||||
* Construct an instance of Sprite using an existing plane object. The plane will be copied into the Sprite, so further edits will
|
||||
* need to go through the Sprite class.
|
||||
*
|
||||
* @param plane flat model of the sprite
|
||||
* A Sprite is a container for 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.
|
||||
*/
|
||||
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())
|
||||
class Sprite
|
||||
{
|
||||
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 that displays in the sprite object'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)
|
||||
private:
|
||||
|
||||
/* The plane is included using composition instead of inheritance, allowing the user to define a custom plane. */
|
||||
sb::Plane plane;
|
||||
|
||||
/* Keep a reference 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. */
|
||||
glm::mat4 _scale {1.0f}, _translation {1.0f}, _rotation {1.0f};
|
||||
|
||||
public:
|
||||
|
||||
/*!
|
||||
* Construct an instance of Sprite using an existing plane object. The plane will be copied into the Sprite, so further edits will
|
||||
* need to go through 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->texture(texture);
|
||||
this->scale(scale);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Construct a Sprite with a default constructed sb::Plane and give the plane a texture. The texture is the 2D graphic that displays
|
||||
* in the sprite object'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 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 that displays in the sprite object'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 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
|
||||
*/
|
||||
Sprite(std::initializer_list<fs::path> paths, glm::vec2 scale = glm::vec2{1.0f}) : Sprite(scale)
|
||||
{
|
||||
for (const fs::path& path : paths)
|
||||
/*!
|
||||
* Construct a Sprite with a default constructed sb::Plane and give the plane a texture. The texture is the 2D graphic that displays
|
||||
* in the sprite object'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
|
||||
*/
|
||||
Sprite(std::initializer_list<fs::path> paths, glm::vec2 scale = glm::vec2{1.0f}) : Sprite(scale)
|
||||
{
|
||||
for (const fs::path& path : paths)
|
||||
{
|
||||
this->texture(path);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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
|
||||
*/
|
||||
Sprite(const fs::path& path, glm::vec2 scale = glm::vec2{1.0f}) : Sprite({path}, scale)
|
||||
{
|
||||
this->texture(path);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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
|
||||
*/
|
||||
Sprite(const fs::path& path, glm::vec2 scale = glm::vec2{1.0f}) : Sprite({path}, scale)
|
||||
{
|
||||
this->texture(path);
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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 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
|
||||
*/
|
||||
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 reference to the texture attached to the sprite's plane object 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 plane.texture(index);
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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();
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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);
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
plane.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.
|
||||
/*!
|
||||
* Add a previously constructed sb::Texture to the sprite's plane object.
|
||||
*
|
||||
* If the index is -1, the check could be skipped since -1 is a special case where the uniform is not expected to exist.
|
||||
* @param texture sb::Texture object to add
|
||||
*/
|
||||
void texture(const sb::Texture& texture)
|
||||
{
|
||||
plane.texture(texture);
|
||||
}
|
||||
|
||||
glUniformMatrix4fv(transformation_uniform, 1, GL_FALSE, &mvp[0][0]);
|
||||
plane.enable();
|
||||
glDrawArrays(GL_TRIANGLES, 0, plane.attributes("position")->count());
|
||||
plane.disable();
|
||||
}
|
||||
/*!
|
||||
* Add a new texture to the sprite's plane object 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 Sprite::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);
|
||||
}
|
||||
|
||||
/*!
|
||||
* @return size in bytes of the sprite's plane object
|
||||
*/
|
||||
std::size_t size() const
|
||||
{
|
||||
return plane.size();
|
||||
}
|
||||
};
|
||||
/*!
|
||||
* Get a constant reference to the texture attached to the sprite's plane object 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 plane.texture(index);
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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();
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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);
|
||||
}
|
||||
|
||||
/*!
|
||||
* 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.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
plane.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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue