add exception checks to Texture::bind and GLObject::id, make collision info storage a std::optional object

This commit is contained in:
ohsqueezy 2023-06-26 20:49:14 -04:00
parent c0b55752e1
commit 12e5a15d1c
9 changed files with 177 additions and 88 deletions

View File

@ -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<std::reference_wrapper<glm::vec2>> 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<std::reference_wrapper<Box>> 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
{

View File

@ -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 <https://foam.shampoo.ooo> |
| ~~~~~~~~~~~~ | +------------------------------------------------------+
| SPACE ~~~~~ | /
| ~~~~~~~ BOX |/
+-------------*/
#pragma once
#include <iostream>
#include <algorithm>
#include <stdexcept>
#include <functional>
#include <optional>
#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<std::reference_wrapper<glm::vec2>> 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<std::reference_wrapper<Box>> overlap = std::nullopt) const;
std::string string() const;
/*!

View File

@ -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 */

View File

@ -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;

View File

@ -158,7 +158,7 @@ namespace sb
std::vector<sb::Texture>& 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
*/

View File

@ -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<std::reference_wrapper<glm::vec2>> 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;

View File

@ -14,6 +14,8 @@
#include <algorithm>
#include <cmath>
#include <vector>
#include <functional>
#include <optional>
#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<std::reference_wrapper<glm::vec2>> intersection = std::nullopt) const;
float dx() const;
float dy() const;
float length() const;

View File

@ -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");
}
}

View File

@ -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