/* +------------------------------------------------------+ ____/ \____ /| - Open source game framework licensed to freely use, | \ / / | copy, modify and sell without restriction | +--\ ^__^ /--+ | | | ~/ \~ | | - created for | | ~~~~~~~~~~~~ | +------------------------------------------------------+ | SPACE ~~~~~ | / | ~~~~~~~ BOX |/ +-------------*/ #pragma once #include #include #include #include #include #include #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 _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> _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>& 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& 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& 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>& 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>& 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& 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& 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& 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(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& 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& textures() const; /*! * @return a reference to the texture container */ std::vector& 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 position = std::make_shared(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 uv = std::make_shared(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 color = std::make_shared(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>({{"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(); }; }