spacebox/src/Sprite.cpp

800 lines
17 KiB
C++

#include "Sprite.hpp"
#include "Game.hpp"
Sprite::Sprite() : Sprite(NULL) {}
Sprite::Sprite(Node* parent) :
Node(parent), current_frameset_name(get_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();
}
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<fs::path> 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;
get_root()->print_error(message.str());
}
}
void Sprite::load()
{
for (const fs::path &path : frame_paths)
{
load_file(path);
}
}
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 = sfw::duplicate_texture(get_renderer(), base);
SDL_DestroyTexture(base);
}
else
{
texture = base;
}
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, previous_scale_quality);
if (not texture)
{
game->print_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_index(ii);
}
for (auto& [name, frameset] : framesets)
{
frameset.set_size();
}
update_size(preserve_center);
}
void Sprite::add_frames(const std::vector<SDL_Texture*>& frames)
{
for (SDL_Texture* frame : frames)
{
add_frames(frame);
}
}
Frameset& Sprite::get_all_frames_frameset()
{
return framesets[get_configuration()["animation"]["all-frames-frameset-name"]];
}
Frameset& Sprite::add_frameset(std::string name)
{
if (framesets.find(name) == framesets.end())
{
framesets[name] = Frameset(this);
}
return framesets[name];
}
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();
}
Frameset& Sprite::get_current_frameset()
{
return framesets.at(current_frameset_name);
}
const 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
{
return frames[get_current_frameset().get_current_frame_index()];
}
const Box& Sprite::get_box(int index) const
{
return get_boxes()[index];
}
const std::vector<Box>& 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].set_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())
{
SDL_DestroyTexture(frames.back());
frames.pop_back();
}
for (Box& box : boxes)
{
box.clear();
}
}
void Sprite::advance_frame()
{
get_current_frameset().step();
}
void Sprite::hide()
{
hidden = true;
}
void Sprite::unhide()
{
hidden = false;
}
void Sprite::toggle_hidden()
{
hidden = !hidden;
}
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()
{
return get_box().get_w();
}
float Sprite::get_h()
{
return get_box().get_h();
}
glm::vec2 Sprite::get_size()
{
return get_box().get_size();
}
float Sprite::get_top(int index)
{
return get_box(index).get_top();
}
float Sprite::get_right(int index)
{
return get_box(index).get_right();
}
float Sprite::get_bottom(int index)
{
return get_box(index).get_bottom();
}
float Sprite::get_left(int index)
{
return get_box(index).get_left();
}
float Sprite::get_center_x(int index)
{
return get_box(index).get_center_x();
}
float Sprite::get_center_y(int index)
{
return get_box(index).get_center_y();
}
glm::vec2 Sprite::get_nw(int index)
{
return get_box(index).get_nw();
}
glm::vec2 Sprite::get_north(int index)
{
return get_box(index).get_north();
}
glm::vec2 Sprite::get_ne(int index)
{
return get_box(index).get_ne();
}
glm::vec2 Sprite::get_east(int index)
{
return get_box(index).get_east();
}
glm::vec2 Sprite::get_se(int index)
{
return get_box(index).get_se();
}
glm::vec2 Sprite::get_south(int index)
{
return get_box(index).get_south();
}
glm::vec2 Sprite::get_sw(int index)
{
return get_box(index).get_sw();
}
glm::vec2 Sprite::get_west(int index)
{
return get_box(index).get_west();
}
glm::vec2 Sprite::get_center(int index)
{
return get_box(index).get_center();
}
void Sprite::set_top(float top)
{
move({0, top - get_top()}, false);
}
void Sprite::set_right(float right)
{
move({right - get_right(), 0}, false);
}
void Sprite::set_bottom(float bottom)
{
move({0, bottom - get_bottom()}, false);
}
void Sprite::set_left(float left)
{
move({left - get_left(), 0}, false);
}
void Sprite::set_center_x(float x)
{
move({x - get_center_x(), 0}, false);
}
void Sprite::set_center_y(float y)
{
move({0, y - get_center_y()}, false);
}
void Sprite::set_nw(const glm::vec2& nw)
{
move(nw - get_nw(), false);
}
void Sprite::set_ne(const glm::vec2& ne)
{
move(ne - get_ne(), false);
}
void Sprite::set_se(const glm::vec2& se)
{
move(se - get_se(), false);
}
void Sprite::set_south(const glm::vec2& south)
{
move(south - get_south(), false);
}
void Sprite::set_sw(const glm::vec2& sw)
{
move(sw - get_sw(), false);
}
void Sprite::set_west(const glm::vec2& west)
{
move(west - get_west(), false);
}
void Sprite::set_center(const glm::vec2& center)
{
move(center - get_center(), false);
}
void Sprite::add_wrap(bool x, bool y)
{
add_wrap(x, y, get_display().get_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.get_w(), 0});
}
if (y)
{
add_box({0, frame.get_h()});
}
if (x && y)
{
add_box({frame.get_w(), frame.get_h()});
}
}
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(sfw::get_hue_shifted_texture(get_renderer(), base, offset));
}
}
glm::vec2 Sprite::move(glm::vec2 delta, bool weighted)
{
if (weighted)
{
delta = get_root()->weight(delta);
}
for (Box& box : boxes)
{
box.move(delta);
}
if (wrap.x)
{
if (get_right() > wrap_frame.get_right())
{
move({-wrap_frame.get_w(), 0}, false);
}
else if (get_right() < wrap_frame.get_left())
{
move({wrap_frame.get_w(), 0}, false);
}
}
if (wrap.y)
{
if (get_bottom() > wrap_frame.get_bottom())
{
move({0, -wrap_frame.get_h()}, false);
}
else if (get_bottom() < wrap_frame.get_top())
{
move({0, wrap_frame.get_h()}, false);
}
}
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 (auto ii = 0; ii < static_cast<int>(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<SDL_Renderer*>(get_renderer()), other_texture);
SDL_RenderReadPixels(
const_cast<SDL_Renderer*>(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<SDL_Renderer*>(get_renderer()), get_current_frame());
SDL_RenderReadPixels(
const_cast<SDL_Renderer*>(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)
{
move(step);
frame_animation.update();
blink_animation.update();
if (is_loaded() && !is_hidden() && get_current_frameset().get_frame_count())
{
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)
{
SDL_Rect wrap_frame_rect = wrap_frame.get_int_rect();
SDL_RenderSetClipRect(renderer, &wrap_frame_rect);
}
for (auto ii = 0; ii < static_cast<int>(boxes.size()); ii++)
{
SDL_RenderCopyF(renderer, texture, NULL, boxes[ii].get_rect());
}
if (wrap.x || wrap.y)
{
SDL_RenderSetClipRect(renderer, NULL);
}
}
}
}
Frameset::Frameset() : Frameset(NULL) {}
Frameset::Frameset(Sprite* sprite) : sprite(sprite) {}
void Frameset::add_frame_index(int index)
{
add_frame_indicies({index});
}
void Frameset::add_frame_indicies(const std::vector<int>& indicies)
{
order.insert(order.end(), indicies.begin(), indicies.end());
}
void Frameset::set_frame_length(float length)
{
frame_length = length;
}
float Frameset::get_frame_length() const
{
return frame_length;
}
void Frameset::reset()
{
set_order_index(0);
}
void Frameset::clear()
{
order.clear();
reset();
}
int Frameset::get_order_index() const
{
return order_index;
}
void Frameset::set_order_index(int index)
{
order_index = index;
}
int Frameset::get_current_frame_index() const
{
return order[get_order_index()];
}
glm::vec2 Frameset::measure() const
{
glm::vec2 s(0, 0);
int w, h;
for (std::size_t index : order)
{
if (index < sprite->frames.size())
{
SDL_QueryTexture(sprite->frames[index], NULL, NULL, &w, &h);
s.x = std::max(static_cast<float>(w * sprite->scale), s.x);
s.y = std::max(static_cast<float>(h * sprite->scale), s.y);
}
}
return s;
}
void Frameset::set_size()
{
set_size(measure());
}
void Frameset::set_size(const glm::vec2& s)
{
size = s;
}
const glm::vec2& Frameset::get_size() const
{
return size;
}
void Frameset::step()
{
if (order.size() > 0)
{
increment_index();
}
}
void Frameset::increment_index()
{
increment_index(reversed ? -1 : 1);
}
void Frameset::increment_index(int increment)
{
set_order_index((order_index + increment) % order.size());
}
int Frameset::get_frame_count() const
{
return order.size();
}
void Frameset::reverse()
{
reversed = !reversed;
}