spacebox/src/Model.hpp

367 lines
15 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 <iostream>
#include <string>
#include <map>
#include <memory>
#include <iterator>
#include <stdexcept>
#include "glm/glm.hpp"
#include "gl.h"
#include "Attributes.hpp"
#include "Texture.hpp"
#include "Carousel.hpp"
#include "VBO.hpp"
namespace sb
{
class Model
{
private:
/* When a texture is copied, it copies its shared pointer to its ID that identifies the texture memory on the GPU. Therefore, when
* this object is copied, its textures are copied with reference to the same GPU memory preserved. */
std::vector<sb::Texture> _textures;
/* The attributes are associated with vertex data copied on the GPU. Shared pointers are used to store the attributes, so that if
* a copy of this object is made, each attributes in the copied object will point to its originally constructed attributes object,
* preserving the association with the vertex data on the GPU. */
std::map<std::string, std::shared_ptr<sb::Attributes>> _attributes;
/* The model keeps a single transformation matrix. Specific types of transformation, like scale, translate, etc, can be combined
* into this transformation. */
glm::mat4 _transformation {1.0f};
public:
/*!
* Construct a sb::Model object with no attributes or textures.
*/
Model() {};
/*!
* Construct a sb::Model, adding sb::Attributes each already wrapped in a shared pointer. The attributes should be passed
* as a map with each key being a name and each value being a shared pointer to attributes.
*
* @param attributes_pack map of names to attribute objects wrapped in shared pointers
*/
Model(const std::map<std::string, std::shared_ptr<sb::Attributes>>& attributes_pack);
/*!
* Construct a sb::Model, adding sb::Attributes, which will each be wrapped in a shared pointer and stored in the created
* object. The attributes should be passed as a map with each key being a name and each value being an attributes object.
*
* @param attributes_pack map of names to attribute objects to be wrapped in a shared pointer and added
*/
Model(const std::map<std::string, sb::Attributes>& attributes_pack);
/*!
* Construct a new model object by passing a list of names which will be used to initialize empty attributes objects
* with the given names.
*
* @param names attributes names
*/
Model(const std::initializer_list<std::string>& names);
/*!
* Get a writable reference to the entire map of attributes, each wrapped in its shared pointer held by this object.
*
* @return writable reference to the entire map of attributes
*/
std::map<std::string, std::shared_ptr<sb::Attributes>>& attributes();
/*!
* Get a constant reference to the entire map of attributes, each wrapped in its shared pointer held by this object.
*
* @return constant reference to the entire map of attributes
*/
const std::map<std::string, std::shared_ptr<sb::Attributes>>& attributes() const;
/*!
* Get a writable reference to the attributes at name, wrapped in the shared pointer held by this object.
*
* This function uses the at method of std::map, so name must refer to attributes already stored in this model. Use this
* function to share ownership of the attributes or to gain access to the public interface of the attributes.
*
* @param name name of the attributes held by this model to retrieve
* @return writable reference to the attributes at name
*/
std::shared_ptr<sb::Attributes>& attributes(const std::string& name);
/*!
* Get a constant reference to the attributes at name, wrapped in the shared pointer held by this object.
*
* @see Model::attributes(const std::string&)
*
* @param name name of the attributes held by this model to retrieve
* @return constant reference to the attributes at name
*/
const std::shared_ptr<sb::Attributes>& attributes(const std::string& name) const;
/*!
* Assign name to attributes, copy and wrap in a shared pointer. The model can share ownership of the created attribute
* memory with callers that request it.
*
* Any existing attributes with the same name will be replaced with the given attributes.
*
* @param attributes attributes object to copy and wrap
* @param name name the model will associate with the attributes
*/
void attributes(const sb::Attributes& attributes, const std::string& name);
/*!
* Assign name to attributes (for example, "position" or "uv") and share ownership.
*
* Any existing attributes with the same name will be replaced with the given attributes.
*
* @param attributes attributes object to share with this model
* @param name name the model will associate with the attributes
*/
void attributes(const std::shared_ptr<sb::Attributes>& attributes, const std::string& name);
/*!
* Get the attributes under name, wrapped in the shared pointer held by this object.
*
* This function uses operator[] or std::map, so this can be used to add new attributes to the object if they are
* wrapped in a shared pointer or the return value is deferenced.
*
* sb::Plane plane;
* sb::Attributes color_attributes;
* color_attributes.extend({255.0f, 0.0f, 0.0f, 255.0f}, plane["position"]->count());
* plane["color"] = std::make_shared<sb::Attributes>(color_attributes);
* // *plane["color"] = color_attributes; // same effect
* // plane.attributes(color_attributes, "color"); // same effect
* sprite = sb::Sprite(plane);
*
* @param name name of the attributes to get
* @return writable reference to the attributes at name
*/
std::shared_ptr<sb::Attributes>& operator[](const std::string& name);
/*!
* Enable all attributes.
*/
void enable() const;
/*!
* Disable all attributes.
*/
void disable() const;
/*!
* @return a constant reference to the texture container
*/
const std::vector<sb::Texture>& textures() const;
/*!
* @return a reference to the texture container
*/
std::vector<sb::Texture>& textures();
/*!
* Get a constant reference to the texture at the given index. If there are no textures, an exception will be thrown. If the
* index is greater than the number of textures or is a negative number, modulus will be be used to wrap the value of the index
* to the size of the texture list.
*
* @param index index of texture to get
*/
const sb::Texture& texture(int index) const;
/*!
* Get the texture at the given index. If there are no textures, an exception will be thrown. If the index is greater than the
* number of textures or is a negative number, modulus will be be used to wrap the value of the index to the size of the texture
* list.
*
* @param index index of texture to get
*/
sb::Texture& texture(int index);
/*!
* Add a copy of the given texture to model's list of textures.
*
* Note: the texture object is copied, but the texture data is not copied. The texture data is stored as a shared pointer in the
* texture object, so only the pointer is copied.
*
* @param texture texture to add to model
*/
void texture(const sb::Texture& texture);
/*!
* Call the load method with no arguments on all textures attached to the model, causing them to load the image data they have
* associated with the paths previously set on them. If no textures are attached, this does nothing.
*
* @see sb::Texture::load()
*/
void 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 model by passing Model::size() to VBO::allocate(GLsizeiptr, GLenum).
*
* The VBO must currently be bound.
*
* @param vbo vertex buffer object that the model's attribute vertices will be added to
*/
void add(sb::VBO& vbo);
/*!
* Bind all of this model's textures by calling each of their bind methods. Textures must already have GL indices set, for
* example by calling Texture::generate() on each.
*/
virtual void bind_textures() const;
/*!
* Bind all of this model's attributes by calling each of their bind methods. Attributes must already have GL indices set, for
* example by calling Attributes::index(GLint) on each.
*/
virtual void bind_attributes() const;
/*!
* Bind all of this model's attributes and textures 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.
*/
virtual void bind() const;
/*!
* @return a constanst reference to the model's transformation matrix
*/
const glm::mat4& transformation() const;
/*!
* Apply the given transformation matrix to the model's current transformation matrix.
*
* @return the model's new transformation matrix
*/
const glm::mat4& transform(const glm::mat4& transformation);
/*!
* Resets the model's transformation to the identity matrix.
*
* @return the model's new transformation matrix, the identity matrix
*/
const glm::mat4& untransform();
/*!
* Specialized version of transform(const glm::mat4&) that builds a scale transformation from an x, y, z vector and
* applies it to the model's transformation matrix.
*
* @param scale x, y, z scale
* @return the model's new transformation matrix
*/
const glm::mat4& scale(const glm::vec3& scale);
/*!
* Specialized version of transform(const glm::mat4&) that builds a rotation matrix from an angle in radians and an axis
* vector and applies it to the model's transformation matrix.
*
* The axis is the vector around which the model will be rotated. For example, to rotate a 2D plane around the origin, use
* the z-axis: `glm::vec3(0.0f, 0.0f, 1.0f)`.
*
* @param angle angle in radians
* @param axis axis to rotate the model around
* @return the model's new transformation matrix
*/
const glm::mat4& rotate(float angle, glm::vec3 axis);
/*!
* Specialized version of transform(const glm::mat4&) that builds a translation matrix from a 3D translation vector
* indicating the distance to translate along each axis and applies it to the model's transformation matrix.
*
* @param translation distance to translate in the x, y, and z dimensions
* @return the model's new transformation matrix
*/
const glm::mat4& translate(glm::vec3 translation);
/*!
* @return total size in bytes of the attributes
*/
std::size_t size() const;
/*!
* @return the transformation matrix
*/
operator glm::mat4() const;
/*!
* Convert to a string with some debugging information about the model.
*/
operator std::string() const;
/*!
* Overload the stream operator to support model objects. Add a string representation of the model to the output stream. Since
* this is defined as a friend function and isn't in the global scope, it should prevent it being looked up with arguments other
* than model objects.
*
* @param out output stream
* @param model model object to print
* @return edited output stream
*/
friend std::ostream& operator<<(std::ostream& out, const Model& model);
};
class Plane : public Model
{
public:
/* A plane in the X and Y dimensions, from (-1.0, -1.0) to (1.0, 1.0) */
inline const static std::shared_ptr<sb::Attributes> position = std::make_shared<sb::Attributes>(sb::Attributes{
{-1.0f, 1.0f}, {1.0f, 1.0f}, {-1.0f, -1.0f},
{1.0f, 1.0f}, {1.0f, -1.0f}, {-1.0f, -1.0f}
});
/* Create texture map that fills the plane */
inline const static std::shared_ptr<sb::Attributes> uv = std::make_shared<sb::Attributes>(sb::Attributes{
{0.0f, 1.0f}, {1.0f, 1.0f}, {0.0f, 0.0f},
{1.0f, 1.0f}, {1.0f, 0.0f}, {0.0f, 0.0f}
});
/* Use transparent black color so it can easily be added to in the shader */
inline const static std::shared_ptr<sb::Attributes> color = std::make_shared<sb::Attributes>(sb::Attributes{
{0.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 0.0f}
});
Plane() : Model(std::map<std::string, std::shared_ptr<sb::Attributes>>({{"position", position}, {"uv", uv}, {"color", color}})) {}
};
/*!
* A version of `Plane` which contains two texture objects, one of which is active at a time. A reference
* to the active `sb::Texture` object is available from `PlaneDoubleBuffer.active`, and the inactive object is
* available from `PlaneDoubleBuffer.inactive`. The buffers can be swapped using `PlaneDoubleBuffer.swap`.
*/
class PlaneDoubleBuffer : public Plane
{
private:
bool swapped = false;
enum { FRONT, BACK };
public:
PlaneDoubleBuffer();
void generate(const glm::vec2&);
sb::Texture& active();
sb::Texture& inactive();
void swap();
};
}