From ae644b7138f43362f6950725d25186ace681a0e0 Mon Sep 17 00:00:00 2001 From: Frank DeMarco Date: Sun, 9 Aug 2020 16:32:32 -0400 Subject: [PATCH] collide box/box, box/line, box/point; sprite/sprite and sprite/box precise alpha collision --- src/Box.cpp | 49 ++++++++++++ src/Box.hpp | 9 +++ src/Delegate.cpp | 2 +- src/Delegate.hpp | 8 +- src/Game.cpp | 6 ++ src/Game.hpp | 1 + src/Node.cpp | 19 ++++- src/Node.hpp | 2 + src/Segment.cpp | 84 +++++++++++++++++-- src/Segment.hpp | 15 +++- src/Sprite.cpp | 200 ++++++++++++++++++++++++++++++++++++++++++++-- src/Sprite.hpp | 17 +++- src/extension.cpp | 64 --------------- src/extension.hpp | 2 - 14 files changed, 386 insertions(+), 92 deletions(-) diff --git a/src/Box.cpp b/src/Box.cpp index 0bcf8c4..320667a 100644 --- a/src/Box.cpp +++ b/src/Box.cpp @@ -271,6 +271,55 @@ void Box::move(const glm::vec2& delta) set_y(get_y() + delta.y); } +bool Box::collide(const glm::vec2& point) const +{ + return point.x >= get_left() && point.x <= get_right() && point.y >= get_top() && point.y <= get_bottom(); +} + +bool Box::collide(const Segment& segment, glm::vec2* intersection) const +{ + if (collide(segment.get_box())) + { + return segment.intersect({get_nw(), get_ne()}, intersection) || + segment.intersect({get_ne(), get_se()}, intersection) || + segment.intersect({get_sw(), get_se()}, intersection) || + segment.intersect({get_nw(), get_sw()}, intersection); + } + else + { + return false; + } +} + +bool Box::collide(const Segment& segment, glm::vec2& intersection) const +{ + return collide(segment, &intersection); +} + +bool Box::collide(const Box& box, Box* overlap) const +{ + float top = std::max(get_top(), box.get_top()); + float right = std::min(get_right(), box.get_right()); + float bottom = std::min(get_bottom(), box.get_bottom()); + float left = std::max(get_left(), box.get_left()); + float w = right - left; + float h = bottom - top; + bool collide = w > 0 && h > 0; + if (collide && overlap != NULL) + { + overlap->set_x(left); + overlap->set_y(top); + overlap->set_w(w); + overlap->set_h(h); + } + return collide; +} + +bool Box::collide(const Box& box, Box& overlap) const +{ + return collide(box, &overlap); +} + std::ostream& Box::to_string (std::ostream& out) const { out << "{(" << rect.x << ", " << rect.y << "), (" << rect.w << ", " << rect.h << ")}"; diff --git a/src/Box.hpp b/src/Box.hpp index 9d6e723..c39636b 100644 --- a/src/Box.hpp +++ b/src/Box.hpp @@ -2,6 +2,7 @@ #define Box_h_ #include +#include #include @@ -9,6 +10,8 @@ #include "glm/common.hpp" #include "glm/vec2.hpp" +struct Segment; + struct Box { SDL_FRect rect; @@ -61,6 +64,11 @@ struct Box void clear(); void scale(float, bool = false); void move(const glm::vec2&); + bool collide(const glm::vec2&) const; + bool collide(const Segment&, glm::vec2* = NULL) const; + bool collide(const Segment&, glm::vec2&) const; + bool collide(const Box&, Box* = NULL) const; + bool collide(const Box&, Box&) const; std::string get_class_name() { return "Box"; } std::ostream& to_string (std::ostream&) const; @@ -69,5 +77,6 @@ struct Box std::ostream& operator<<(std::ostream&, const Box&); #include "extension.hpp" +#include "Segment.hpp" #endif diff --git a/src/Delegate.cpp b/src/Delegate.cpp index 9e20399..3f5c911 100644 --- a/src/Delegate.cpp +++ b/src/Delegate.cpp @@ -20,7 +20,7 @@ void Delegate::dispatch() { for (auto iter = subscribers.begin(); iter != subscribers.end(); iter++) { - if (event.type == iter->first) + if (static_cast(event.type) == iter->first) { cancelling_propagation = false; for (Subscriber s : iter->second) diff --git a/src/Delegate.hpp b/src/Delegate.hpp index 1ef6de1..d3ba54b 100644 --- a/src/Delegate.hpp +++ b/src/Delegate.hpp @@ -45,14 +45,14 @@ struct Delegate : Node template void unsubscribe(T* p) { - const std::type_info& info = typeid(p); for (auto t = subscribers.begin(); t != subscribers.end(); t++) { for (auto s = t->second.begin(); s != t->second.end();) { - std::cout << p << " " << s->o << " " << s->f.target_type().name() << " " << info.name() << " " << - std::type_index(info).name() << " " << - (s->f.target_type() == info) << std::endl; + // const std::type_info& info = typeid(p); + // std::cout << p << " " << s->o << " " << s->f.target_type().name() << " " << info.name() << " " << + // std::type_index(info).name() << " " << + // (s->f.target_type() == info) << std::endl; if (p == s->o) { s = t->second.erase(s); diff --git a/src/Game.cpp b/src/Game.cpp index 3ee5661..c67a72f 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -450,6 +450,11 @@ SDL_Window* Game::get_window() return window; } +const SDL_Renderer* Game::get_renderer() const +{ + return renderer; +} + SDL_Renderer* Game::get_renderer() { return renderer; @@ -505,6 +510,7 @@ void Game::frame(float ticks) framerate_indicator.update(); if (!is_gl_context) { + SDL_SetRenderTarget(renderer, NULL); SDL_RenderPresent(renderer); } } diff --git a/src/Game.hpp b/src/Game.hpp index 947a7dc..c313c1d 100644 --- a/src/Game.hpp +++ b/src/Game.hpp @@ -78,6 +78,7 @@ struct Game : Node void log_surface_format(SDL_Surface*, std::string = "surface"); std::string get_pixel_format_string(Uint32); SDL_Window* get_window(); + const SDL_Renderer* get_renderer() const; SDL_Renderer* get_renderer(); glm::vec2 weight(glm::vec2); void run(); diff --git a/src/Node.cpp b/src/Node.cpp index 3a7c450..8eacf08 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -44,6 +44,11 @@ Display& Node::get_display() return get_root()->display; } +const SDL_Renderer* Node::get_renderer() const +{ + return get_root()->get_renderer(); +} + SDL_Renderer* Node::get_renderer() { return get_root()->get_renderer(); @@ -54,6 +59,16 @@ SDL_Window* Node::get_window() return get_root()->get_window(); } +const Game* Node::get_root() const +{ + const Node* root = this; + while (root->parent != NULL) + { + root = parent; + } + return dynamic_cast(root); +} + void Node::suppress_input_temporarily(int length) { get_root()->input.suppress(); @@ -84,7 +99,7 @@ void Node::print_branch() Node::~Node() { - std::cout << "Destructing "; - print_branch(); + // std::cout << "Destructing "; + // print_branch(); get_delegate().unsubscribe(this); } diff --git a/src/Node.hpp b/src/Node.hpp index 1874289..236e095 100644 --- a/src/Node.hpp +++ b/src/Node.hpp @@ -29,8 +29,10 @@ struct Node nlohmann::json& get_configuration(); Delegate& get_delegate(); Display& get_display(); + const SDL_Renderer* get_renderer() const; SDL_Renderer* get_renderer(); SDL_Window* get_window(); + const Game* get_root() const; void suppress_input_temporarily(int = 0); void print_branch(); virtual std::string get_class_name() { return "Node"; }; diff --git a/src/Segment.cpp b/src/Segment.cpp index a9e59bb..50f8eb4 100644 --- a/src/Segment.cpp +++ b/src/Segment.cpp @@ -26,26 +26,98 @@ void Segment::set_end(const glm::vec2& e) end = e; } -bool Segment::intersect(const Segment& segment) +// taken from http://www.realtimerendering.com/resources/GraphicsGems/gemsii/xlines.c +bool Segment::intersect(const Segment& segment, glm::vec2* intersection) const { - return sfw::segments_intersect(*this, segment); + 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; + + float a1, a2, b1, b2, c1, c2; // Coefficients of line eqns. + float r1, r2, r3, r4; // 'Sign' values + float denom, num; // Intermediate values + + // Compute a1, b1, c1, where line joining points 1 and 2 is "a1 x + b1 y + c1 = 0" + a1 = y2 - y1; + b1 = x1 - x2; + c1 = x2 * y1 - x1 * y2; + + // Compute r3 and r4 + r3 = a1 * x3 + b1 * y3 + c1; + r4 = a1 * x4 + b1 * y4 + c1; + + // Check signs of r3 and r4. If both point 3 and point 4 lie on same side of + // line 1, the line segments do not intersect + if (r3 != 0 && r4 != 0 && std::copysign(1, r3) == std::copysign(1, r4)) + { + return false; + } + + // Compute a2, b2, c2 + a2 = y4 - y3; + b2 = x3 - x4; + c2 = x4 * y3 - x3 * y4; + + // Compute r1 and r2 + r1 = a2 * x1 + b2 * y1 + c2; + r2 = a2 * x2 + b2 * y2 + c2; + + // Check signs of r1 and r2. If both point 1 and point 2 lie on same side + // of second line segment, the line segments do not intersect + if (r1 != 0 && r2 != 0 && std::copysign(1, r1) == std::copysign(1, r2)) + { + return false; + } + + // Line segments intersect: compute intersection point + denom = a1 * b2 - a2 * b1; + if (denom == 0) + { + return false; + } + + num = b1 * c2 - b2 * c1; + if (intersection != NULL) + { + intersection->x = num / denom; + } + num = a2 * c1 - a1 * c2; + if (intersection != NULL) + { + intersection->y = num / denom; + } + + return true; } -bool Segment::intersect(const Segment& segment, glm::vec2& intersection) +bool Segment::intersect(const Segment& segment, glm::vec2& intersection) const { - return sfw::segments_intersect(*this, segment, intersection); + return intersect(segment, &intersection); } -float Segment::get_dx() +float Segment::get_dx() const { return end.x - start.x; } -float Segment::get_length() +float Segment::get_dy() const +{ + return end.y - start.y; +} + +float Segment::get_length() const { return glm::distance(start, end); } +Box Segment::get_box() const +{ + float x = std::min(start.x, end.x); + float y = std::min(start.y, end.y); + float w = std::abs(get_dx()); + float h = std::abs(get_dy()); + return Box({x, y}, {w, h}); +} + void Segment::move(const glm::vec2& delta) { start += delta; diff --git a/src/Segment.hpp b/src/Segment.hpp index b409da9..d95b00b 100644 --- a/src/Segment.hpp +++ b/src/Segment.hpp @@ -2,10 +2,14 @@ #define Segment_h_ #include +#include +#include #include "glm/vec2.hpp" #include "glm/geometric.hpp" +struct Box; + struct Segment { @@ -18,10 +22,12 @@ struct Segment void set_start(const glm::vec2&); glm::vec2 get_end() const; void set_end(const glm::vec2&); - bool intersect(const Segment&); - bool intersect(const Segment&, glm::vec2&); - float get_dx(); - float get_length(); + bool intersect(const Segment&, glm::vec2* = NULL) const; + bool intersect(const Segment&, glm::vec2&) const; + float get_dx() const; + float get_dy() const; + float get_length() const; + Box get_box() const; void move(const glm::vec2&); glm::vec2 get_center(); @@ -30,5 +36,6 @@ struct Segment std::ostream& operator<<(std::ostream&, const Segment&); #include "extension.hpp" +#include "Box.hpp" #endif diff --git a/src/Sprite.cpp b/src/Sprite.cpp index 7d78c39..20b217c 100644 --- a/src/Sprite.cpp +++ b/src/Sprite.cpp @@ -118,17 +118,27 @@ Frameset& Sprite::set_frameset(std::string name) Frameset& Sprite::get_current_frameset() { - return framesets[current_frameset_name]; + return framesets.at(current_frameset_name); } -SDL_Texture* Sprite::get_current_frame() +const Frameset& Sprite::get_current_frameset() const +{ + return framesets.at(current_frameset_name); +} + +SDL_Texture* Sprite::get_current_frame() const { return frames[get_current_frameset().get_current_frame_index()]; } -Box& Sprite::get_box(int index) +const Box& Sprite::get_box(int index) const { - return boxes[index]; + return get_boxes()[index]; +} + +const std::vector& Sprite::get_boxes() const +{ + return boxes; } void Sprite::add_box(glm::vec2 position, bool absolute) @@ -144,10 +154,10 @@ void Sprite::update_size(bool preserve_center) { for (int ii = 0; ii < boxes.size(); ii++) { - get_box(ii).set_size(get_current_frameset().get_size(), preserve_center); + boxes[ii].set_size(get_current_frameset().get_size(), preserve_center); if (scale != 1) { - get_box(ii).scale(scale, preserve_center); + boxes[ii].scale(scale, preserve_center); } } } @@ -227,6 +237,16 @@ Uint8 Sprite::get_alpha_mod() const return alpha_mod; } +void Sprite::set_color_mod(const SDL_Color& color) +{ + color_mod = color; +} + +const SDL_Color& Sprite::get_color_mod() const +{ + return color_mod; +} + float Sprite::get_w() { return get_box().get_w(); @@ -439,6 +459,171 @@ glm::vec2 Sprite::move(glm::vec2 delta, bool weighted) return delta; } +bool Sprite::collide(const glm::vec2& point, bool all) const +{ + if (!all) + { + return get_box().collide(point); + } + else + { + for (const Box& box : boxes) + { + if (box.collide(point)) + { + return true; + } + } + return false; + } +} + +bool Sprite::collide(const Segment& segment, glm::vec2* intersection, bool all) const +{ + if (!all) + { + return get_box().collide(segment, intersection); + } + else + { + for (const Box& box : boxes) + { + if (box.collide(segment, intersection)) + { + return true; + } + } + return false; + } +} + +bool Sprite::collide(const Segment& segment, glm::vec2& intersection, bool all) const +{ + return collide(segment, &intersection, all); +} + +bool Sprite::collide(const Box& box, bool precise, Box* overlap, bool all, SDL_Texture* other_texture) const +{ + if (precise) + { + int texture_access; + SDL_QueryTexture(get_current_frame(), NULL, &texture_access, NULL, NULL); + if (texture_access != SDL_TEXTUREACCESS_TARGET) + { + SDL_LogWarn(SDL_LOG_CATEGORY_ERROR, + "can't do precise collision detection on texture without target access"); + precise = false; + } + else if (other_texture != NULL) + { + SDL_QueryTexture(other_texture, NULL, &texture_access, NULL, NULL); + if (texture_access != SDL_TEXTUREACCESS_TARGET) + { + SDL_LogWarn(SDL_LOG_CATEGORY_ERROR, + "can't use other texture in precise collision detection without target access"); + other_texture = NULL; + } + } + if (precise && overlap == NULL) + { + Box o; + overlap = &o; + } + } + for (int ii = 0; ii < get_boxes().size(); ii++) + { + if (get_box(ii).collide(box, overlap)) + { + if (precise) + { + bool collision_detected = false; + int w = overlap->get_w(), h = overlap->get_h(); + Uint32 format = SDL_PIXELFORMAT_RGBA32; + int bytes_per_pixel = SDL_BYTESPERPIXEL(format); + int bytes_per_row = bytes_per_pixel * w; + int bytes_total = h * bytes_per_row; + SDL_Rect rect; + Uint8* other_region = NULL; + if (other_texture != NULL) + { + rect.x = overlap->get_left() - box.get_left(); + rect.y = overlap->get_top() - box.get_top(); + rect.w = w; + rect.h = h; + other_region = new Uint8[bytes_total]; + SDL_SetRenderTarget(const_cast(get_renderer()), other_texture); + SDL_RenderReadPixels( + const_cast(get_renderer()), &rect, format, other_region, bytes_per_row); + } + rect.x = overlap->get_left() - get_box(ii).get_left(); + rect.y = overlap->get_top() - get_box(ii).get_top(); + rect.w = w; + rect.h = h; + Uint8* region = new Uint8[bytes_total]; + SDL_SetRenderTarget(const_cast(get_renderer()), get_current_frame()); + SDL_RenderReadPixels( + const_cast(get_renderer()), &rect, format, region, bytes_per_row); + for (int byte_index = 3; byte_index < bytes_total; byte_index += 4) + { + if (region[byte_index] > 0 && (other_texture == NULL || other_region[byte_index] > 0)) + { + collision_detected = true; + break; + } + } + delete[] region; + if (other_region != NULL) + { + delete[] other_region; + } + if (collision_detected) + { + return true; + } + } + else + { + return true; + } + } + if (!all) + { + break; + } + } + return false; +} + +bool Sprite::collide(const Box& box, Box& overlap, bool precise, bool all) const +{ + return collide(box, precise, &overlap, all); +} + +bool Sprite::collide(const Sprite& sprite, bool precise, Box* overlap, bool all, bool all_other) const +{ + SDL_Texture* other_sprite_texture = precise ? sprite.get_current_frame() : NULL; + if (all_other) + { + for (const Box& box : sprite.get_boxes()) + { + if (collide(box, precise, overlap, all, other_sprite_texture)) + { + return true; + } + } + return false; + } + else + { + return collide(sprite.get_box(), precise, overlap, all, other_sprite_texture); + } +} + +bool Sprite::collide(const Sprite& sprite, Box& overlap, bool precise, bool all, bool all_other) const +{ + return collide(sprite, precise, &overlap, all, all_other); +} + void Sprite::update() { if (active) @@ -451,6 +636,7 @@ void Sprite::update() SDL_Texture* texture = get_current_frame(); SDL_Renderer* renderer = get_root()->renderer; SDL_SetTextureAlphaMod(texture, alpha_mod); + SDL_SetTextureColorMod(texture, color_mod.r, color_mod.g, color_mod.b); SDL_SetRenderTarget(renderer, NULL); if (wrap.x || wrap.y) { @@ -459,7 +645,7 @@ void Sprite::update() } for (int ii = 0; ii < boxes.size(); ii++) { - SDL_RenderCopyF(renderer, texture, NULL, get_box(ii).get_rect()); + SDL_RenderCopyF(renderer, texture, NULL, boxes[ii].get_rect()); } if (wrap.x || wrap.y) { diff --git a/src/Sprite.hpp b/src/Sprite.hpp index e8ab8f6..aabca2b 100644 --- a/src/Sprite.hpp +++ b/src/Sprite.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -31,6 +32,7 @@ struct Sprite : Node float scale = 1; std::string scale_quality = "nearest"; Uint8 alpha_mod = 255; + SDL_Color color_mod = {255, 255, 255, 255}; std::map framesets; std::string current_frameset_name; glm::bvec2 wrap = {false, false}; @@ -48,8 +50,10 @@ struct Sprite : Node Frameset& add_frameset(std::string); Frameset& set_frameset(std::string); Frameset& get_current_frameset(); - SDL_Texture* get_current_frame(); - Box& get_box(int = 0); + const Frameset& get_current_frameset() const; + SDL_Texture* get_current_frame() const; + const Box& get_box(int = 0) const; + const std::vector& get_boxes() const; void add_box(glm::vec2, bool = false); void update_size(bool = false); void set_scale(float); @@ -65,6 +69,8 @@ struct Sprite : Node void set_step(glm::vec2); void set_alpha_mod(Uint8); Uint8 get_alpha_mod() const; + void set_color_mod(const SDL_Color&); + const SDL_Color& get_color_mod() const; float get_w(); float get_h(); glm::vec2 get_size(); @@ -98,6 +104,13 @@ struct Sprite : Node void add_wrap(bool, bool); void add_wrap(bool, bool, Box); glm::vec2 move(glm::vec2, bool = true); + bool collide(const glm::vec2&, bool = false) const; + bool collide(const Segment&, glm::vec2* = NULL, bool = false) const; + bool collide(const Segment&, glm::vec2&, bool = false) const; + bool collide(const Box&, bool = false, Box* = NULL, bool = false, SDL_Texture* = NULL) const; + bool collide(const Box&, Box&, bool = false, bool = false) const; + bool collide(const Sprite&, bool = false, Box* = NULL, bool = false, bool = false) const; + bool collide(const Sprite&, Box&, bool = false, bool = false, bool = false) const; void update(); std::string get_class_name() { return "Sprite"; } ~Sprite() { unload(); } diff --git a/src/extension.cpp b/src/extension.cpp index 69b8c6c..2b666bd 100644 --- a/src/extension.cpp +++ b/src/extension.cpp @@ -32,70 +32,6 @@ void sfw::set_magnitude(glm::vec2& vector, float magnitude) vector = glm::normalize(vector) * magnitude; } -bool sfw::segments_intersect(const Segment& segment_a, const Segment& segment_b) -{ - glm::vec2 intersection; - return segments_intersect(segment_a, segment_b, intersection); -} - -// --- -// from http://www.realtimerendering.com/resources/GraphicsGems/gemsii/xlines.c -// --- -bool sfw::segments_intersect(const Segment& segment_a, const Segment& segment_b, glm::vec2& intersection) -{ - float x1 = segment_a.start.x, y1 = segment_a.start.y, x2 = segment_a.end.x, - y2 = segment_a.end.y, x3 = segment_b.start.x, y3 = segment_b.start.y, - x4 = segment_b.end.x, y4 = segment_b.end.y; - - float a1, a2, b1, b2, c1, c2; // Coefficients of line eqns. - float r1, r2, r3, r4; // 'Sign' values - float denom, num; // Intermediate values - - // Compute a1, b1, c1, where line joining points 1 and 2 is "a1 x + b1 y + c1 = 0" - a1 = y2 - y1; - b1 = x1 - x2; - c1 = x2 * y1 - x1 * y2; - - // Compute r3 and r4 - r3 = a1 * x3 + b1 * y3 + c1; - r4 = a1 * x4 + b1 * y4 + c1; - - // Check signs of r3 and r4. If both point 3 and point 4 lie on same side of - // line 1, the line segments do not intersect - if (r3 != 0 && r4 != 0 && std::copysign(1, r3) == std::copysign(1, r4)) - { - return false; - } - - // Compute a2, b2, c2 - a2 = y4 - y3; - b2 = x3 - x4; - c2 = x4 * y3 - x3 * y4; - - // Compute r1 and r2 - r1 = a2 * x1 + b2 * y1 + c2; - r2 = a2 * x2 + b2 * y2 + c2; - - // Check signs of r1 and r2. If both point 1 and point 2 lie on same side - // of second line segment, the line segments do not intersect - if (r1 != 0 && r2 != 0 && std::copysign(1, r1) == std::copysign(1, r2)) - { - return false; - } - - // Line segments intersect: compute intersection point - denom = a1 * b2 - a2 * b1; - if (denom == 0) - { - return false; - } - num = b1 * c2 - b2 * c1; - intersection.x = num / denom; - num = a2 * c1 - a1 * c2; - intersection.y = num / denom; - return true; -} - Box sfw::get_texture_box(SDL_Texture* texture) { int w, h; diff --git a/src/extension.hpp b/src/extension.hpp index b3ab5a7..609b3c9 100644 --- a/src/extension.hpp +++ b/src/extension.hpp @@ -33,8 +33,6 @@ namespace sfw glm::vec2 get_step_relative(const Segment&, float); std::vector get_segments(const Segment&, int); void set_magnitude(glm::vec2&, float); - bool segments_intersect(const Segment&, const Segment&); - bool segments_intersect(const Segment&, const Segment&, glm::fvec2&); Box get_texture_box(SDL_Texture*); void populate_pixel_2d_array(SDL_Renderer*, SDL_Texture*, std::vector>&); void populate_pixel_2d_array(SDL_Renderer*, SDL_Texture*, std::vector>&, const Box&);