From 12e5a15d1ced403cedf23752bea487a06e37d4f4 Mon Sep 17 00:00:00 2001 From: frank Date: Mon, 26 Jun 2023 20:49:14 -0400 Subject: [PATCH] add exception checks to Texture::bind and GLObject::id, make collision info storage a std::optional object --- src/Box.cpp | 57 ++++++---------------- src/Box.hpp | 120 ++++++++++++++++++++++++++++++++++++++--------- src/GLObject.cpp | 9 +++- src/Model.cpp | 8 ++++ src/Model.hpp | 10 +++- src/Segment.cpp | 16 ++----- src/Segment.hpp | 17 ++++++- src/Texture.cpp | 17 ++++--- src/Texture.hpp | 11 ++++- 9 files changed, 177 insertions(+), 88 deletions(-) diff --git a/src/Box.cpp b/src/Box.cpp index fa515a5..b30d2f7 100644 --- a/src/Box.cpp +++ b/src/Box.cpp @@ -10,9 +10,6 @@ #include "Box.hpp" -/* Construct a Box by giving a corner coordinate (top left if Y-axis is not inverted, bottom left otherwise) and - * size (as width, height). The invert_y argument indicates that the Y-coordinate increases from the bottom of - * the screen to the top rather than the default case where it increases from the top to the bottom. */ Box::Box(const glm::vec2& corner, const glm::vec2& size, bool invert_y) { x = corner.x; @@ -22,11 +19,8 @@ Box::Box(const glm::vec2& corner, const glm::vec2& size, bool invert_y) this->invert_y(invert_y); } -/* Construct a Box from float arguments: x, y, width, height. If invert_y is set, the Y-axis increases from bottom to - * top instead of top to bottom. */ Box::Box(float x, float y, float width, float height, bool invert_y) : Box({x, y}, {width, height}, invert_y) {} -/* Construct a Box by passing an SDL_Rect struct, which is of the form {x, y, w, h} and limited to int arguments. */ Box::Box(const SDL_Rect& rect, bool invert_y) : Box({rect.x, rect.y}, {rect.w, rect.h}, invert_y) {} /* Set inverted Y mode to true, meaning the Y-coordinate increases from the bottom to the top, or false, meaning the @@ -67,13 +61,11 @@ void Box::height(float height) h = std::max(height, 0.0f); } -/* Return the size as a vector {width, height} */ glm::vec2 Box::size() const { return {width(), height()}; } -/* Set the size. Negative values will be clamped to zero. */ void Box::size(const glm::vec2& size, bool preserve_center) { glm::vec2 center = this->center(); @@ -514,36 +506,28 @@ void Box::crop(const Box& area) } } -/* Returns true if point is inside the box, false otherwise. Includes the edges, so points equal to the edges - * will return true. */ bool Box::collide(const glm::vec2& point) const { return point.x >= left() && point.x <= right() && point.y >= (inverted_y() ? bottom() : top()) && point.y <= (inverted_y() ? top() : bottom()); } -/* Returns true if the line segment intersects the box, false otherwise. If intersection is passed and there is a - * collision, intersection will be filled with the coordinates of the first intersection found unless the segment - * is fully within the box not touching any edges. */ -bool Box::collide(const Segment& segment, glm::vec2* intersection) const +bool Box::collide(const Segment& segment, std::optional> intersection) const { - /* do the faster check of whether the square represented by the segment diagonal collides to determine - * if we should look more closely */ + /* Do the faster check of whether the square represented by the segment diagonal collides to determine whether to look more closely. */ if (collide(segment.box())) { - /* check if segment intersects any edges, storing the intersection point if so */ - if (segment.intersect({nw(), ne()}, intersection) || - segment.intersect({ne(), se()}, intersection) || - segment.intersect({sw(), se()}, intersection) || - segment.intersect({nw(), sw()}, intersection)) + /* Check if segment intersects any edges, storing the intersection point if so. */ + if (segment.intersect({nw(), ne()}, intersection) || segment.intersect({ne(), se()}, intersection) || + segment.intersect({sw(), se()}, intersection) || segment.intersect({nw(), sw()}, intersection)) { return true; } - /* check if segment is fully inside the box */ + /* Check if segment is fully inside the box. */ else if (collide(segment.start()) && collide(segment.end())) { return true; } - /* otherwise, segment must be outside the box even though its box has collided */ + /* Otherwise, segment must be outside the box even though its box has collided. */ else { return false; @@ -555,15 +539,7 @@ bool Box::collide(const Segment& segment, glm::vec2* intersection) const } } -/* Do segment collision with intersection specified by reference rather than pointer */ -bool Box::collide(const Segment& segment, glm::vec2& intersection) const -{ - return collide(segment, &intersection); -} - -/* Return true if box collides with the passed box, false otherwise. If overlap is passed, it is set to - * the box representing the area where the two boxes overlap. */ -bool Box::collide(const Box& box, Box* overlap) const +bool Box::collide(const Box& box, std::optional> overlap) const { float top, bottom, h; if (inverted_y()) @@ -582,22 +558,17 @@ bool Box::collide(const Box& box, Box* overlap) const float left = std::max(this->left(), box.left()); float w = right - left; bool collide = w > 0 && h > 0; - if (collide && overlap != nullptr) + if (collide && overlap.has_value()) { - overlap->left(left); - overlap->top(top); - overlap->width(w); - overlap->height(h); + Box& overlap_box = overlap.value(); + overlap_box.left(left); + overlap_box.top(top); + overlap_box.width(w); + overlap_box.height(h); } return collide; } -/* Do a box to box collision test with overlap passed by reference instead of pointer */ -bool Box::collide(const Box& box, Box& overlap) const -{ - return collide(box, &overlap); -} - /* Return the string representation of a Box "{left, top, width, height}" */ std::string Box::string() const { diff --git a/src/Box.hpp b/src/Box.hpp index 06728e6..cfd3262 100644 --- a/src/Box.hpp +++ b/src/Box.hpp @@ -1,18 +1,20 @@ -/* /\ +--------------------------------------------------------------+ - ____/ \____ /| - zlib/MIT/Unlicenced game framework licensed to freely use, | - \ / / | copy, and modify without restriction | - +--\ ^__^ /--+ | | - | ~/ \~ | | - originally created at [http://nugget.fun] | - | ~~~~~~~~~~~~ | +--------------------------------------------------------------+ - | SPACE ~~~~~ | / - | ~~~~~~~ BOX |/ - +-------------*/ + /* +------------------------------------------------------+ + ____/ \____ /| - 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 "SDL.h" @@ -22,6 +24,12 @@ class Segment; +/*! + * A ::Box object represents a rectangle, a two-dimensional four-sided polygon with two parallel sides. + * + * Rotating by three dimensions into the Z-dimension is not currently supported and must be implemented by the user, + * but 3D transformation is planned to be added in a future update. + */ class Box : public SDL_FRect { @@ -31,21 +39,66 @@ private: public: - Box(const glm::vec2& = {0, 0}, const glm::vec2& = {0, 0}, bool = false); - Box(float, float, float, float, bool = false); - Box(const SDL_Rect&, bool = false); + /*! + * Construct a Box object by giving a corner coordinate (top left if Y-axis is not inverted, bottom left otherwise) and + * size (as width, height). The invert_y argument indicates that the Y-coordinate increases from the bottom of the screen + * to the top rather than the default case where it increases from the top to the bottom. + * + * @param corner top left coordinate in the x, y axis + * @param size size of the box in width and height + * @param invert_y if true, y-coordinates increase as they move up on the screen, rather than decrease + */ + Box(const glm::vec2& corner = {0, 0}, const glm::vec2& size = {0, 0}, bool invert_y = false); + + /*! + * Construct a Box object lfrom float arguments: x, y, width, height. If invert_y is set, the Y-axis increases from bottom to + * top instead of top to bottom. + * + * @see Box::Box(const glm::vec2&, const glm::vec2&, bool) + * + * @param x + * @param y + * @param width size of the x-dimension + * @param height size of the y-dimension + * @param invert_y if true, y-coordinates increase as they move up on the screen, rather than decrease + */ + Box(float x, float y, float width, float height, bool invert_y = false); + + /*! + * Construct a ::Box object by passing an SDL_Rect struct, which is of the form {x, y, w, h} and limited to int arguments. + * + * @param rect see SDL's rect documentation http://doc.here/sdlwiki/SDL_FRect.html + * @param invert_y if true, y-coordinates increase as they move up on the screen, rather than decrease + */ + Box(const SDL_Rect& rect, bool invert_y = false); + void invert_y(bool); bool inverted_y() const; float width() const; void width(float); float height() const; void height(float); - glm::vec2 size() const; - void size(const glm::vec2&, bool = false); - /* If the flag is not set, this will return width divided by height. Otherwise, it will check which side is longer and return the longer side - * divided by the shorter side. */ - float aspect(bool = false) const; + /*! + * @return the size as a vector `{width, height}` + */ + glm::vec2 size() const; + + /*! + * Set the size. Negative values will be clamped to zero. + * + * @param size new size in width, height + * @param preserve_center if true, expand or contract the box from the center, leaving its center value unchanged + */ + void size(const glm::vec2& size, bool preserve_center = false); + + /*! + * If the flag is not set, this will return width divided by height. Otherwise, it will check which side is longer and return the longer side + * divided by the shorter side. + * + * @param larger_ratio if true, divide by the shorter side, causing the result to always be 1.0 or greater + */ + float aspect(bool larger_ratio = false) const; float area() const; float top() const; @@ -92,12 +145,33 @@ public: Box stamp(const glm::vec2&) const; bool fits(const Box&) const; void crop(const Box&); - bool collide(const glm::vec2&) const; - bool collide(const Segment&, glm::vec2* = nullptr) const; - bool collide(const Segment&, glm::vec2&) const; - bool collide(const Box&, Box* = nullptr) const; - bool collide(const Box&, Box&) const; - virtual std::string class_name() const { return "Box"; } + + /*! + * Collide a 2D point with the box. Includes the edges, so points on the edges will return `true`. + * + * @return `true` if point is inside the box, `false` otherwise + */ + bool collide(const glm::vec2& point) const; + + /*! + * Collide a line segment with the box. If intersection is passed and there is a collision, intersection will be filled with + * the coordinates of the first intersection found unless the segment is fully within the box not touching any edges. + * + * @param segment line segment to collide with the box + * @param intersection optional reference to 2D vector to fill with the first intersection point + * @return `true` if segment collides with the box, `false` otherwies + */ + bool collide(const Segment& segment, std::optional> intersection = std::nullopt) const; + + /*! + * Collide with another box object. If overlap is passed, it is set to the box representing the area where the two boxes overlap. + * + * @param box another box object to collide with + * @param overlap optional reference to a box object to be filled with the overlapping area + * @return `true` if box collides with the passed box, `false` otherwise + */ + bool collide(const Box& box, std::optional> overlap = std::nullopt) const; + std::string string() const; /*! diff --git a/src/GLObject.cpp b/src/GLObject.cpp index 33d65c4..7e57106 100644 --- a/src/GLObject.cpp +++ b/src/GLObject.cpp @@ -61,7 +61,14 @@ void GLObject::id(GLuint id) /* Return the GL ID that was set for this object */ GLuint GLObject::id() const { - return *object_id; + if (generated()) + { + return *object_id; + } + else + { + throw std::runtime_error("Cannot get ID for GL object that has no ID set yet"); + } } /* Returns true if an ID has been generated */ diff --git a/src/Model.cpp b/src/Model.cpp index b27b22c..1196d1f 100644 --- a/src/Model.cpp +++ b/src/Model.cpp @@ -129,6 +129,14 @@ void sb::Model::texture(const sb::Texture& texture) textures().push_back(texture); } +void sb::Model::load() +{ + for (sb::Texture& texture : textures()) + { + texture.load(); + } +} + const glm::mat4& sb::Model::transformation() const { return _transformation; diff --git a/src/Model.hpp b/src/Model.hpp index bcda1b7..7196d6a 100644 --- a/src/Model.hpp +++ b/src/Model.hpp @@ -158,7 +158,7 @@ namespace sb std::vector& textures(); /*! - * Get a constant refernce to the texture at the given index. If no index is given, get the texture at index 0. If + * Get a constant reference to the texture 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 @@ -180,6 +180,14 @@ namespace sb */ 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(); + /*! * @return a constanst reference to the model's transformation matrix */ diff --git a/src/Segment.cpp b/src/Segment.cpp index 7065c8d..b1e617d 100644 --- a/src/Segment.cpp +++ b/src/Segment.cpp @@ -35,8 +35,7 @@ void Segment::end(const glm::vec2& end) end_ = end; } -// taken from http://www.realtimerendering.com/resources/GraphicsGems/gemsii/xlines.c -bool Segment::intersect(const Segment& segment, glm::vec2* intersection) const +bool Segment::intersect(const Segment& segment, std::optional> intersection) const { float x1 = start_.x, y1 = start_.y, x2 = end_.x, y2 = end_.y, x3 = segment.start_.x, y3 = segment.start_.y, x4 = segment.end_.x, y4 = segment.end_.y; @@ -85,24 +84,19 @@ bool Segment::intersect(const Segment& segment, glm::vec2* intersection) const } num = b1 * c2 - b2 * c1; - if (intersection != NULL) + if (intersection.has_value()) { - intersection->x = num / denom; + intersection.value().get().x = num / denom; } num = a2 * c1 - a1 * c2; - if (intersection != NULL) + if (intersection.has_value()) { - intersection->y = num / denom; + intersection.value().get().y = num / denom; } return true; } -bool Segment::intersect(const Segment& segment, glm::vec2& intersection) const -{ - return intersect(segment, &intersection); -} - float Segment::dx() const { return end_.x - start_.x; diff --git a/src/Segment.hpp b/src/Segment.hpp index 2b8df80..ffff2e7 100644 --- a/src/Segment.hpp +++ b/src/Segment.hpp @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include "glm/vec2.hpp" @@ -36,8 +38,19 @@ public: void start(const glm::vec2&); glm::vec2 end() const; void end(const glm::vec2&); - bool intersect(const Segment&, glm::vec2* = nullptr) const; - bool intersect(const Segment&, glm::vec2&) const; + + /*! + * Check for an intersection with another segment object. If the intersection argument is given a reference to a 2D vector, + * it will be filled with the point of intersection. + * + * Original source: http://www.realtimerendering.com/resources/GraphicsGems/gemsii/xlines.c + * + * @param segment segment to collide with + * @param intersection optional reference to a 2D vector to fill with the intersection point + * @return `true` if the two segments intersect, `false` otherwise + */ + bool intersect(const Segment& segment, std::optional> intersection = std::nullopt) const; + float dx() const; float dy() const; float length() const; diff --git a/src/Texture.cpp b/src/Texture.cpp index cd6c3af..d64d23c 100644 --- a/src/Texture.cpp +++ b/src/Texture.cpp @@ -11,10 +11,8 @@ #include "Texture.hpp" using namespace sb; -/* Have to pass our deleter to abstract base class at instantiation */ Texture::Texture() : GLObject(texture_deleter) {} -/* If an image path is passed at creation, the path will be stored in the class for later loading */ Texture::Texture(fs::path path) : Texture() { associate(path); @@ -127,20 +125,27 @@ void Texture::load(void* pixels, glm::vec2 size, GLenum format, GLenum type) void Texture::bind() const { - glBindTexture(GL_TEXTURE_2D, this->id()); + if (generated()) + { + glBindTexture(GL_TEXTURE_2D, this->id()); + } + else + { + throw std::runtime_error("Cannot bind texture that has not been generated yet."); + } } /* glGetTexlevelparameteriv is not available in OpenGL ES 3.0 */ #if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(ANDROID) void Texture::load(void* pixels, GLenum format, GLenum type) { - if (!generated()) + if (generated()) { - throw std::invalid_argument("This texture has not been generated and has no size property, so a size must be provided"); + load(pixels, size(), format, type); } else { - load(pixels, size(), format, type); + throw std::invalid_argument("This texture has not been generated and has no size property, so a size must be provided"); } } diff --git a/src/Texture.hpp b/src/Texture.hpp index 6b1a976..539fe2a 100644 --- a/src/Texture.hpp +++ b/src/Texture.hpp @@ -46,8 +46,17 @@ namespace sb public: + /*! + * Construct an empty Texture. The deleter will be passed to the base sb::GLObject class. + */ Texture(); - Texture(fs::path); + + /*! + * If an image path is passed at creation, the path will be stored in the class for later loading. + * + * @param path path to an image to be used as the default texture, which will be loaded later with Texture::load() + */ + Texture(fs::path path); /*! * Store an image path as a member variable for loading later. Each texture should have one image