#include "Game.hpp" #include "Sprite.hpp" Sprite::Sprite() : Sprite(nullptr) {} Sprite::Sprite(Node* parent) : Node(parent), current_frameset_name(configuration()["animation"]["all-frames-frameset-name"]) { add_frameset(current_frameset_name); frame_animation.play(); } Sprite::Sprite(Node* parent, std::string path) : Sprite(parent) { associate(path); } void Sprite::reset() { Node::reset(); activate(); wipe_animation.reset(); unhide(); } void Sprite::associate(std::string path) { if (fs::is_regular_file(path)) { frame_paths.push_back(fs::path(path)); } else if (fs::is_directory(path)) { fs::directory_iterator directory(path); std::vector paths; std::copy(directory, fs::directory_iterator(), std::back_inserter(paths)); std::sort(paths.begin(), paths.end()); for (const fs::path& name : paths) { frame_paths.push_back(name); } } else { std::ostringstream message; message << "invalid path " << path; sb::Log::log(message, sb::Log::Level::ERROR); } } void Sprite::load() { for (const fs::path &path : frame_paths) { load_file(path); } for (Child& child : children) { child.sprite.load(); } } void Sprite::load_file(fs::path path) { Game *game = get_root(); const char* previous_scale_quality = SDL_GetHint(SDL_HINT_RENDER_SCALE_QUALITY); SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, scale_quality.c_str()); SDL_Texture *base = IMG_LoadTexture(game->renderer, path.string().c_str()), *texture; if (texture_access == SDL_TEXTUREACCESS_TARGET) { texture = sb::duplicate_texture(get_renderer(), base); SDL_DestroyTexture(base); } else { texture = base; } SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, previous_scale_quality); if (not texture) { sb::Log::sdl_error("Could not load image"); } else { add_frames(texture); } } void Sprite::add_frames(SDL_Texture* frame) { bool preserve_center = frames.size() > 0; frames.push_back(frame); Frameset& all_frames_frameset = get_all_frames_frameset(); all_frames_frameset.clear(); for (std::size_t ii = 0; ii < frames.size(); ii++) { all_frames_frameset.add_frame_indicies(ii); } for (auto& [name, frameset] : framesets) { frameset.set_size(); } update_size(preserve_center); } void Sprite::add_frames(const std::vector& frames) { for (SDL_Texture* frame : frames) { add_frames(frame); } } const std::vector& Sprite::get_frames() const { return frames; } Sprite::Frameset& Sprite::get_all_frames_frameset() { return framesets[configuration()["animation"]["all-frames-frameset-name"]]; } Sprite::Frameset& Sprite::add_frameset(std::string name) { if (framesets.find(name) == framesets.end()) { framesets[name] = Frameset(this); } return framesets[name]; } Sprite::Frameset& Sprite::set_frameset(std::string name) { current_frameset_name = name; frame_animation.set_frame_length(get_current_frameset().get_frame_length()); if (is_loaded()) { update_size(true); } return get_current_frameset(); } Sprite::Frameset& Sprite::get_current_frameset() { return framesets.at(current_frameset_name); } const Sprite::Frameset& Sprite::get_current_frameset() const { return framesets.at(current_frameset_name); } void Sprite::set_frame_length(float length) { get_current_frameset().set_frame_length(length); } SDL_Texture* Sprite::get_current_frame() const { if (frames.size() == 0) { return nullptr; } else { return frames[get_current_frameset().get_current_frame_index()]; } } const Box& Sprite::get_box(int index) const { return get_boxes()[index]; } const std::vector& Sprite::get_boxes() const { return boxes; } void Sprite::add_box(glm::vec2 position, bool absolute) { if (!absolute) { position += get_nw(); } boxes.emplace_back(glm::vec2(position.x, position.y), glm::vec2(get_w(), get_h())); } void Sprite::update_size(bool preserve_center) { for (std::size_t ii = 0; ii < boxes.size(); ii++) { boxes[ii].size(get_current_frameset().get_size(), preserve_center); if (scale != 1) { boxes[ii].scale(scale, preserve_center); } } } void Sprite::set_scale(float s) { scale = s; update_size(true); } void Sprite::set_scale_quality(const std::string& quality) { scale_quality = quality; } float Sprite::get_scale() const { return scale; } bool Sprite::is_loaded() const { return !frames.empty(); } void Sprite::unload() { while (!frames.empty()) { if (!leave_memory_allocated) { SDL_DestroyTexture(frames.back()); } frames.pop_back(); } boxes = {{{0, 0}, {0, 0}}}; } void Sprite::advance_frame() { get_current_frameset().step(); } void Sprite::hide() { hidden = true; for (Child& child : children) { child.sprite.hide(); } } void Sprite::unhide() { hidden = false; for (Child& child : children) { child.sprite.unhide(); } } void Sprite::toggle_hidden() { if (is_hidden()) { unhide(); } else { hide(); } } bool Sprite::is_hidden() const { return hidden; } void Sprite::set_step(glm::vec2 s) { step.x = s.x; step.y = s.y; } void Sprite::set_alpha_mod(Uint8 mod) { alpha_mod = mod; } 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() const { return get_box().width(); } float Sprite::get_h() const { return get_box().height(); } glm::vec2 Sprite::get_size() const { return get_box().size(); } float Sprite::get_top(int index) const { return get_box(index).top(); } float Sprite::get_right(int index) const { return get_box(index).right(); } float Sprite::get_bottom(int index) const { return get_box(index).bottom(); } float Sprite::get_left(int index) const { return get_box(index).left(); } float Sprite::get_center_x(int index) const { return get_box(index).cx(); } float Sprite::get_center_y(int index) const { return get_box(index).cy(); } glm::vec2 Sprite::get_nw(int index) const { return get_box(index).nw(); } glm::vec2 Sprite::get_north(int index) const { return get_box(index).north(); } glm::vec2 Sprite::get_ne(int index) const { return get_box(index).ne(); } glm::vec2 Sprite::get_east(int index) const { return get_box(index).east(); } glm::vec2 Sprite::get_se(int index) const { return get_box(index).se(); } glm::vec2 Sprite::get_south(int index) const { return get_box(index).south(); } glm::vec2 Sprite::get_sw(int index) const { return get_box(index).sw(); } glm::vec2 Sprite::get_west(int index) const { return get_box(index).west(); } glm::vec2 Sprite::get_center(int index) const { return get_box(index).center(); } void Sprite::set_top(float top) { move({0, top - get_top()}); } void Sprite::set_right(float right) { move({right - get_right(), 0}); } void Sprite::set_bottom(float bottom) { move({0, bottom - get_bottom()}); } void Sprite::set_left(float left) { move({left - get_left(), 0}); } void Sprite::set_center_x(float x) { move({x - get_center_x(), 0}); } void Sprite::set_center_y(float y) { move({0, y - get_center_y()}); } void Sprite::set_nw(const glm::vec2& nw) { move(nw - get_nw()); } void Sprite::set_north(const glm::vec2& north) { move(north - get_north()); } void Sprite::set_ne(const glm::vec2& ne) { move(ne - get_ne()); } void Sprite::set_se(const glm::vec2& se) { move(se - get_se()); } void Sprite::set_south(const glm::vec2& south) { move(south - get_south()); } void Sprite::set_sw(const glm::vec2& sw) { move(sw - get_sw()); } void Sprite::set_west(const glm::vec2& west) { move(west - get_west()); } void Sprite::set_center(const glm::vec2& center) { move(center - get_center()); } void Sprite::add_wrap(bool x, bool y) { add_wrap(x, y, get_display().window_box()); } void Sprite::add_wrap(bool x, bool y, Box frame) { wrap = {x, y}; int original_box_count = boxes.size(); for (int ii = 0; ii < original_box_count; ii++) { if (x) { add_box({frame.width(), 0}); } if (y) { add_box({0, frame.height()}); } if (x && y) { add_box({frame.width(), frame.height()}); } } wrap_frame = frame; } void Sprite::add_hue_shift_frames(int count) { float step = 360 / (count + 1); SDL_Texture* base = get_current_frame(); for (float offset = step; offset <= 359.9; offset += step) { add_frames(sb::get_hue_shifted_texture(get_renderer(), base, offset)); } } glm::vec2 Sprite::move(const glm::vec2& delta) { for (Box& box : boxes) { box.move(delta); } if (wrap.x) { if (get_right() > wrap_frame.right()) { move({-wrap_frame.width(), 0}); } else if (get_right() < wrap_frame.left()) { move({wrap_frame.width(), 0}); } } if (wrap.y) { if (get_bottom() > wrap_frame.bottom()) { move({0, -wrap_frame.height()}); } else if (get_bottom() < wrap_frame.top()) { move({0, wrap_frame.height()}); } } return delta; } glm::vec2 Sprite::move_weighted(const glm::vec2& delta) { glm::vec2 delta_weighted = get_root()->weight(delta); move(delta_weighted); return delta_weighted; } 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 { Box o; if (precise) { int texture_access; SDL_QueryTexture(get_current_frame(), nullptr, &texture_access, nullptr, nullptr); 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 != nullptr) { SDL_QueryTexture(other_texture, nullptr, &texture_access, nullptr, nullptr); 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 = nullptr; } } if (precise && overlap == nullptr) { overlap = &o; } } for (auto ii = 0; ii < static_cast(get_boxes().size()); ii++) { if (get_box(ii).collide(box, overlap)) { if (precise) { bool collision_detected = false; SDL_Texture* collision_check_frame; if (get_scale() == 1) { collision_check_frame = get_current_frame(); } else { collision_check_frame = sb::duplicate_texture( const_cast(get_renderer()), get_current_frame(), get_box(ii).size()); } Pixels region_pixels = Pixels( const_cast(get_renderer()), collision_check_frame, overlap->stamp(-get_nw(ii))); if (other_texture == nullptr) { for (int x = 0; x < region_pixels.rect.w; x++) { for (int y = 0; y < region_pixels.rect.h; y++) { if (region_pixels.get(x, y).a > 0) { collision_detected = true; break; } } } } else { Pixels other_region_pixels = Pixels( const_cast(get_renderer()), other_texture, overlap->stamp(-box.nw())); for (int x = 0; x < region_pixels.rect.w && x < other_region_pixels.rect.w; x++) { for (int y = 0; y < region_pixels.rect.h && y < other_region_pixels.rect.h; y++) { if (region_pixels.get(x, y).a > 0 && other_region_pixels.get(x, y).a > 0) { collision_detected = true; break; } } } } if (get_scale() != 1) { SDL_DestroyTexture(collision_check_frame); } 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_collision_check_texture = nullptr; int limit; bool collision = false; if (all_other) { limit = sprite.get_boxes().size(); } else { limit = 1; } for (int ii = 0; ii < limit; ii++) { if (precise) { if (sprite.get_scale() == 1) { other_sprite_collision_check_texture = sprite.get_current_frame(); } else { other_sprite_collision_check_texture = sb::duplicate_texture( const_cast(get_renderer()), sprite.get_current_frame(), sprite.get_box(ii).size()); } } if (collide(sprite.get_box(ii), precise, overlap, all, other_sprite_collision_check_texture)) { collision = true; break; } } if (sprite.get_scale() != 1) { SDL_DestroyTexture(other_sprite_collision_check_texture); } return collision; } 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::wipe(float delay) { wipe_blinds = sb::get_blinds_boxes(get_size()); if (wipe_increment < 0) { wipe_index = static_cast(wipe_blinds.size() - 1); } else { wipe_index = 0; } wipe_animation.play(delay); for (Child& child : children) { child.sprite.wipe(delay); } } void Sprite::advance_wipe_frame() { wipe_index += wipe_increment; if (wipe_index < 0 || wipe_index >= static_cast(wipe_blinds.size())) { if (wipe_index < 0) { wipe_index = static_cast(wipe_blinds.size() - 1); hide(); } else { wipe_index = 0; } wipe_animation.reset(); } } const std::vector& Sprite::get_current_wipe_blinds() const { return wipe_blinds[wipe_index]; } void Sprite::reverse_wipe_direction() { wipe_increment *= -1; } Sprite& Sprite::insert_child(std::string name, std::list::iterator position) { children.emplace(position, name, this); return get_child(name); } Sprite& Sprite::insert_child(std::string name, std::string before) { return insert_child(name, std::find(children.begin(), children.end(), before)); } Sprite& Sprite::insert_child(std::string name, int index) { auto position = children.begin(); for (int ii = 0; position != children.end(); ii++, position++) { if (ii == index) { break; } } return insert_child(name, position); } Sprite& Sprite::insert_child(std::string name) { return insert_child(name, children.size()); } Sprite& Sprite::get_child(std::string name) { auto position = std::find(children.begin(), children.end(), name); if (position == children.end()) { throw std::runtime_error("child not found"); } return position->sprite; } bool Sprite::has_child(std::string name) const { return std::find(children.begin(), children.end(), name) != children.end(); } void Sprite::remove_child(std::string name) { auto position = std::find(children.begin(), children.end(), name); if (position != children.end()) { children.erase(position); } } void Sprite::set_draw_children_on_frame(bool on_frame) { draw_children_on_frame = on_frame; } void Sprite::update(const std::vector& subsections) { if (is_active()) { if (step != glm::vec2(0, 0)) { move_weighted(step); } frame_animation.update(); blink_animation.update(); wipe_animation.update(); toggle_hidden_animation.update(); SDL_Texture* texture = get_current_frame(); if (is_loaded() && !is_hidden() && get_current_frameset().get_frame_count()) { 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, get_canvas()); if (wrap.x || wrap.y) { SDL_Rect wrap_frame_rect = wrap_frame; SDL_RenderSetClipRect(renderer, &wrap_frame_rect); } for (std::size_t box_ii = 0; box_ii < boxes.size(); box_ii++) { if (subsections.size() == 0 && !wipe_animation.playing(false)) { SDL_RenderCopyF(renderer, texture, nullptr, &boxes[box_ii]); } else { if (wipe_animation.playing(false)) { for (const Box& blind : get_current_wipe_blinds()) { render_subsection(renderer, texture, blind, get_box(box_ii)); } } for (const Box& subsection : subsections) { render_subsection(renderer, texture, subsection, get_box(box_ii)); } } } if (wrap.x || wrap.y) { SDL_RenderSetClipRect(renderer, nullptr); } } for (Child& child : children) { if (draw_children_on_frame) { child.sprite.set_canvas(texture); } else { child.sprite.set_canvas(get_canvas()); child_relative_position = child.sprite.get_nw(); child.sprite.move(get_nw()); } child.sprite.update(); if (!draw_children_on_frame) { child.sprite.set_nw(child_relative_position); } } } } void Sprite::update() { update({}); } void Sprite::render_subsection(SDL_Renderer* renderer, SDL_Texture* texture, const Box& subsection, const Box& box) { bottom_save = std::round(subsection.bottom()); subsection_int_rect = SDL_Rect(subsection); subsection_int_rect.y += bottom_save - (subsection_int_rect.y + subsection_int_rect.h); subsection_destination = subsection_int_rect; subsection_destination.x += box.left(); subsection_destination.y += box.top(); if (get_scale() != 1) { unscaled_subsection = subsection; unscaled_subsection.nw(unscaled_subsection.nw() / get_scale()); unscaled_subsection.size(unscaled_subsection.size() / get_scale()); subsection_int_rect = unscaled_subsection; } SDL_RenderCopy(renderer, texture, &subsection_int_rect, &subsection_destination); } void Sprite::set_to_leave_memory_allocated() { leave_memory_allocated = true; } void Sprite::set_to_deallocate_memory() { leave_memory_allocated = false; } Sprite::Frameset::Frameset() : Frameset(NULL) {} Sprite::Frameset::Frameset(Sprite* sprite) : sprite(sprite) {} void Sprite::Frameset::add_frame_indicies(int index) { order.push_back(index); set_size(); } void Sprite::Frameset::set_frame_length(float length) { frame_length = length; if (&sprite->get_current_frameset() == this) { sprite->frame_animation.set_frame_length(length); } } float Sprite::Frameset::get_frame_length() const { return frame_length; } void Sprite::Frameset::reset() { set_order_index(0); } void Sprite::Frameset::clear() { order.clear(); reset(); } int Sprite::Frameset::get_order_index() const { return order_index; } void Sprite::Frameset::set_order_index(int index) { order_index = index; } int Sprite::Frameset::get_current_frame_index() const { return order[get_order_index()]; } glm::vec2 Sprite::Frameset::measure() const { glm::vec2 s(0, 0); int w, h; for (std::size_t index : order) { if (index < sprite->get_frames().size()) { SDL_QueryTexture(sprite->get_frames()[index], nullptr, nullptr, &w, &h); s.x = std::max(static_cast(w * sprite->get_scale()), s.x); s.y = std::max(static_cast(h * sprite->get_scale()), s.y); } } return s; } void Sprite::Frameset::set_size() { set_size(measure()); } void Sprite::Frameset::set_size(const glm::vec2& s) { size = s; } const glm::vec2& Sprite::Frameset::get_size() const { return size; } void Sprite::Frameset::step() { if (order.size() > 0) { increment_index(); } } void Sprite::Frameset::increment_index() { increment_index(reversed ? -1 : 1); } void Sprite::Frameset::increment_index(int increment) { set_order_index((order_index + increment) % order.size()); } int Sprite::Frameset::get_frame_count() const { return order.size(); } void Sprite::Frameset::reverse() { reversed = !reversed; } Pixels::Pixels(Sprite& sprite) : Pixels(sprite.get_renderer(), sprite.get_current_frame()) {} Pixels::Pixels(Sprite& sprite, const Box& box) : Pixels(sprite.get_renderer(), sprite.get_current_frame(), box) {} Segment::Segment(const Sprite& start, const Sprite& end) : Segment(start.get_center(), end.get_center()) {}