4#include <SDL3_image/SDL_image.h>
5#include <SDL3_ttf/SDL_ttf.h>
58 float height { 0.0F };
64 template <
typename K> std::size_t
operator()(
const K& key)
const
67 std::size_t seed = std::hash<const asw::Renderer*> { }(v.
renderer);
79 template <
typename A,
typename B>
bool operator()(
const A& a,
const B& b)
const
97 std::size_t seed = std::hash<asw::Font> { }(key.
font);
121 if (pixel_font || SDL_GetRenderTarget(renderer) !=
nullptr) {
130 return static_cast<uint32_t
>(std::lround(scale *
static_cast<float>(
RENDER_SCALE_ONE)));
139 return cached->get();
142 TTF_Font* copy = TTF_CopyFont(font.get());
143 if (copy ==
nullptr) {
147 const float scale =
static_cast<float>(render_scale) /
static_cast<float>(
RENDER_SCALE_ONE);
148 if (!TTF_SetFontSize(copy, TTF_GetFontSize(font.get()) * scale)) {
153 asw::Font scaled { copy, [](TTF_Font* f) {
154 if (TTF_WasInit() > 0) {
159 return scaled_fonts.insert(std::move(key), std::move(scaled)).get();
166 return { texture, [](SDL_Texture* t) {
168 SDL_DestroyTexture(t);
181 SDL_SetRenderDrawColor(r, color.
r, color.
g, color.
b, color.
a);
200 SDL_RenderTexture(r, tex.get(),
nullptr, &dest);
219 SDL_FlipMode flip = SDL_FLIP_NONE;
222 flip =
static_cast<SDL_FlipMode
>(flip | SDL_FLIP_HORIZONTAL);
226 flip =
static_cast<SDL_FlipMode
>(flip | SDL_FLIP_VERTICAL);
229 SDL_RenderTextureRotated(r, tex.get(),
nullptr, &dest, 0,
nullptr, flip);
242 dest.w = position.
size.x;
243 dest.h = position.
size.y;
245 SDL_RenderTexture(r, tex.get(),
nullptr, &dest);
265 const double angleDeg = angle * (180.0 / std::numbers::pi);
267 SDL_RenderTextureRotated(r, tex.get(),
nullptr, &dest, angleDeg,
nullptr, SDL_FLIP_NONE);
281 r_dest.w = dest.
size.x;
282 r_dest.h = dest.
size.y;
285 const double angleDeg = angle * (180.0 / std::numbers::pi);
287 SDL_FlipMode flip = SDL_FLIP_NONE;
290 flip =
static_cast<SDL_FlipMode
>(flip | SDL_FLIP_HORIZONTAL);
294 flip =
static_cast<SDL_FlipMode
>(flip | SDL_FLIP_VERTICAL);
297 SDL_RenderTextureRotated(r, tex.get(),
nullptr, &r_dest, angleDeg,
nullptr, flip);
311 r_src.w = source.
size.x;
312 r_src.h = source.
size.y;
317 r_dest.w = dest.
size.x;
318 r_dest.h = dest.
size.y;
320 SDL_RenderTexture(r, tex.get(), &r_src, &r_dest);
334 r_src.w = source.
size.x;
335 r_src.h = source.
size.y;
340 r_dest.w = dest.
size.x;
341 r_dest.h = dest.
size.y;
343 const double angleDeg = angle * (180.0 / std::numbers::pi);
345 int flip = SDL_FLIP_NONE;
347 flip |= SDL_FLIP_HORIZONTAL;
350 flip |= SDL_FLIP_VERTICAL;
353 SDL_RenderTextureRotated(
354 r, tex.get(), &r_src, &r_dest, angleDeg,
nullptr,
static_cast<SDL_FlipMode
>(flip));
361 if (
text.empty() || font ==
nullptr || r ==
nullptr) {
367 const bool pixel_font = TTF_GetFontHinting(font.get()) == TTF_HINTING_MONO;
368 const uint32_t font_generation = TTF_GetFontGeneration(font.get());
373 const TextCacheEntry* cached_text
374 =
text_cache.find(TextKeyView { r, font.get(),
text, font_generation, render_scale });
375 if (cached_text ==
nullptr) {
376 TTF_Font* render_font = font.get();
377 uint32_t used_scale = render_scale;
379 if (
auto* scaled =
get_scaled_font(font, font_generation, render_scale);
381 render_font = scaled;
390 const float scale =
static_cast<float>(used_scale) /
static_cast<float>(
RENDER_SCALE_ONE);
392 const auto sdlColor = SDL_Color { 255, 255, 255, 255 };
393 SDL_Surface* textSurface = pixel_font
394 ? TTF_RenderText_Solid(render_font,
text.c_str(), 0, sdlColor)
395 : TTF_RenderText_Blended(render_font,
text.c_str(), 0, sdlColor);
397 if (textSurface ==
nullptr) {
401 SDL_Texture* textTexture = SDL_CreateTextureFromSurface(r, textSurface);
402 if (textTexture ==
nullptr) {
403 SDL_DestroySurface(textSurface);
407 SDL_SetTextureBlendMode(textTexture, SDL_BLENDMODE_BLEND);
410 SDL_SetTextureScaleMode(textTexture, SDL_SCALEMODE_NEAREST);
412 SDL_SetTextureScaleMode(textTexture, SDL_SCALEMODE_LINEAR);
419 float width =
static_cast<float>(textSurface->w) / scale;
420 float height =
static_cast<float>(textSurface->h) / scale;
421 if (render_font != font.get()) {
424 if (TTF_GetStringSize(font.get(),
text.c_str(), 0, &logical_w, &logical_h)) {
425 width =
static_cast<float>(logical_w);
426 height =
static_cast<float>(logical_h);
430 TextCacheEntry entry {
435 SDL_DestroySurface(textSurface);
438 { r, font,
text, font_generation, render_scale }, std::move(entry));
444 dest.w = cached_text->width;
445 dest.h = cached_text->height;
449 dest.x -= dest.w / 2.0F;
456 dest.x = std::round(dest.x);
457 dest.y = std::round(dest.y);
459 SDL_SetTextureColorMod(cached_text->texture.get(), color.
r, color.
g, color.
b);
460 SDL_SetTextureAlphaMod(cached_text->texture.get(), color.
a);
461 SDL_RenderTexture(r, cached_text->texture.get(),
nullptr, &dest);
468 shadow.
a =
static_cast<uint8_t
>(
469 (
static_cast<float>(shadow.
a) *
static_cast<float>(color.
a)) / 255.0F);
488 SDL_SetRenderDrawColor(r, color.
r, color.
g, color.
b, color.
a);
489 SDL_RenderPoint(r, position.
x, position.
y);
500 SDL_SetRenderDrawColor(r, color.
r, color.
g, color.
b, color.
a);
501 SDL_RenderLine(r, position1.
x, position1.
y, position2.
x, position2.
y);
511 SDL_SetRenderDrawColor(r, color.
r, color.
g, color.
b, color.
a);
513 const float x = position.
position.x;
514 const float y = position.
position.y;
515 const float w = position.
size.x;
516 const float h = position.
size.y;
518 if (thickness <= 1.0F) {
519 const SDL_FRect
rect { x, y, w, h };
520 SDL_RenderRect(r, &
rect);
525 const float t = std::min({ thickness, w / 2.0F, h / 2.0F });
526 const std::array<SDL_FRect, 4> bands { {
528 { x, y + h - t, w, t },
529 { x, y + t, t, h - (t * 2.0F) },
530 { x + w - t, y + t, t, h - (t * 2.0F) },
533 SDL_RenderFillRects(r, bands.data(),
static_cast<int>(bands.size()));
543 SDL_SetRenderDrawColor(r, color.
r, color.
g, color.
b, color.
a);
550 SDL_RenderFillRect(r, &
rect);
561 const float half_w = position.
size.x / 2.0F;
562 const float half_h = position.
size.y / 2.0F;
563 const float cos_a = std::cos(angle);
564 const float sin_a = std::sin(angle);
567 const std::array<SDL_FPoint, 4> corners { {
568 { -half_w, -half_h },
576 std::array<SDL_Vertex, 4> vertices { };
577 for (std::size_t i = 0; i < corners.size(); i++) {
578 vertices[i].position = { center.x + (corners[i].x * cos_a) - (corners[i].y * sin_a),
579 center.y + (corners[i].x * sin_a) + (corners[i].y * cos_a) };
580 vertices[i].color = fcolor;
583 constexpr std::array<int, 6> indices { 0, 1, 2, 0, 2, 3 };
585 SDL_RenderGeometry(r,
nullptr, vertices.data(),
static_cast<int>(vertices.size()),
586 indices.data(),
static_cast<int>(indices.size()));
596 SDL_SetRenderDrawColor(r, color.
r, color.
g, color.
b, color.
a);
602 const float cx = position.
x;
603 const float cy = position.
y;
606 static std::vector<SDL_FPoint> points;
611 points.push_back({ cx, cy });
612 }
else if (y == 0.0F) {
614 points.push_back({ cx + x, cy });
615 points.push_back({ cx - x, cy });
616 points.push_back({ cx, cy + x });
617 points.push_back({ cx, cy - x });
620 points.push_back({ cx + x, cy + y });
621 points.push_back({ cx - x, cy + y });
622 points.push_back({ cx + x, cy - y });
623 points.push_back({ cx - x, cy - y });
625 points.push_back({ cx + x, cy + y });
626 points.push_back({ cx - x, cy + y });
627 points.push_back({ cx + x, cy - y });
628 points.push_back({ cx - x, cy - y });
629 points.push_back({ cx + y, cy + x });
630 points.push_back({ cx - y, cy + x });
631 points.push_back({ cx + y, cy - x });
632 points.push_back({ cx - y, cy - x });
636 err += (2.0F * y) + 1.0F;
639 err += (2.0F * (y - x)) + 1.0F;
644 SDL_RenderPoints(r, points.data(),
static_cast<int>(points.size()));
654 SDL_SetRenderDrawColor(r, color.
r, color.
g, color.
b, color.
a);
660 const float cx = std::round(position.
x);
661 const float cy = std::round(position.
y);
662 const int rows =
static_cast<int>(radius);
664 static std::vector<SDL_FRect> spans;
667 for (
int dy = -rows; dy <= rows; ++dy) {
668 const auto fy =
static_cast<float>(dy);
669 const float half = std::floor(std::sqrt(std::max((radius * radius) - (fy * fy), 0.0F)));
670 spans.push_back({ cx - half, cy + fy, (half * 2.0F) + 1.0F, 1.0F });
673 SDL_RenderFillRects(r, spans.data(),
static_cast<int>(spans.size()));
685 const std::array<SDL_Vertex, 3> vertices { {
686 { { a.
x, a.
y }, fcolor, { 0.0F, 0.0F } },
687 { { b.
x, b.
y }, fcolor, { 0.0F, 0.0F } },
688 { { c.
x, c.
y }, fcolor, { 0.0F, 0.0F } },
691 SDL_RenderGeometry(r,
nullptr, vertices.data(), 3,
nullptr, 0);
697 if (r ==
nullptr || points.size() < 2) {
701 static std::vector<SDL_FPoint> line_points;
703 for (
const auto& p : points) {
704 line_points.push_back({ p.x, p.y });
706 line_points.push_back({ points.front().x, points.front().y });
708 SDL_SetRenderDrawColor(r, color.
r, color.
g, color.
b, color.
a);
709 SDL_RenderLines(r, line_points.data(),
static_cast<int>(line_points.size()));
715 if (r ==
nullptr || points.size() < 3) {
722 const auto n =
static_cast<int>(points.size());
725 static std::vector<int> prev;
726 static std::vector<int> next;
727 static std::vector<char> reflex;
728 static std::vector<int> indices;
729 prev.resize(points.size());
730 next.resize(points.size());
731 reflex.resize(points.size());
735 = [&](
int i) ->
const asw::Vec2f& {
return points[
static_cast<std::size_t
>(i)]; };
738 const auto is_reflex = [&](
int i) {
739 const auto& a =
point(prev[i]);
740 const auto& b =
point(i);
741 const auto& c =
point(next[i]);
742 return (b - a).cross(c - b) * winding <= 0.0F;
745 bool any_reflex =
false;
746 for (
int i = 0; i < n; ++i) {
747 prev[i] = (i + n - 1) % n;
748 next[i] = (i + 1) % n;
750 for (
int i = 0; i < n; ++i) {
751 reflex[i] =
static_cast<char>(is_reflex(i));
752 any_reflex = any_reflex || reflex[i] != 0;
761 while (count > 3 && tried < count) {
762 const int p = prev[cur];
763 const int nx = next[cur];
765 bool ear = reflex[cur] == 0;
769 const auto& a =
point(p);
770 const auto& b =
point(cur);
771 const auto& c =
point(nx);
772 for (
int o = next[nx]; o != p; o = next[o]) {
773 const auto& q =
point(o);
774 if (reflex[o] != 0 && q != a && q != b && q != c
788 indices.insert(indices.end(), { p, cur, nx });
792 reflex[p] =
static_cast<char>(is_reflex(p));
793 reflex[nx] =
static_cast<char>(is_reflex(nx));
803 for (
int i = next[cur]; next[i] != cur; i = next[i]) {
804 indices.insert(indices.end(), { cur, i, next[i] });
808 static std::vector<SDL_Vertex> vertices;
810 for (
const auto& p : points) {
811 vertices.push_back({ { p.x, p.y }, fcolor, { 0.0F, 0.0F } });
814 SDL_RenderGeometry(r,
nullptr, vertices.data(),
static_cast<int>(vertices.size()),
815 indices.data(),
static_cast<int>(indices.size()));
820 SDL_SetTextureBlendMode(texture.get(),
static_cast<SDL_BlendMode
>(mode));
825 SDL_SetTextureAlphaModFloat(texture.get(), alpha);
830 SDL_SetTextureColorMod(texture.get(), tint.
r, tint.
g, tint.
b);
835 SDL_SetTextureScaleMode(texture.get(),
static_cast<SDL_ScaleMode
>(mode));
Vec2< T > get_center() const
Get center of the rectangle.
Vec2< T > size
The size of the rectangle.
Vec2< T > position
The position of the rectangle.
T y
The y component of the vector.
T x
The x component of the vector.
A hash map with a size limit. When full, the least recently used half is dropped, so entries in use e...
Display and window routines for the ASW library.
Routines for drawing sprites and primitives to the screen.
Bounded cache that drops the least recently used entries. Private to the library.
asw::Texture make_cached_texture(SDL_Texture *texture)
TTF_Font * get_scaled_font(const asw::Font &font, uint32_t font_generation, uint32_t render_scale)
TextKeyView view(const TextCacheKey &key)
asw::detail::LruCache< ScaledFontKey, asw::Font, ScaledFontKeyHash, std::equal_to<> > scaled_fonts(SCALED_FONT_LIMIT)
uint32_t text_render_scale(asw::Renderer *renderer, bool pixel_font)
asw::detail::LruCache< TextCacheKey, TextCacheEntry, TextKeyHash, TextKeyEqual > text_cache(TEXT_CACHE_LIMIT)
constexpr uint32_t RENDER_SCALE_ONE
constexpr std::size_t TEXT_CACHE_LIMIT
constexpr std::size_t SCALED_FONT_LIMIT
void hash_combine(std::size_t &seed, std::size_t hash)
Combine a hash into a seed, as boost::hash_combine does.
asw::Vec2< float > get_scale()
Get how many output pixels one logical pixel covers, after letterboxing. 2.0 means the game is drawn ...
asw::Renderer * get_renderer()
Get the SDL renderer.
void sprite(const asw::Texture &tex, const asw::Vec2< float > &position)
Draw a sprite.
void circle_fill(const asw::Vec2< float > &position, float radius, asw::Color color)
Draw a filled circle.
void stretch_sprite_rotate(const asw::Texture &tex, const asw::Quad< float > &dest, float angle, bool flip_x=false, bool flip_y=false)
Draw a sprite stretched to a quad, rotated around its centre and optionally flipped.
void polygon(const asw::Polygonf &points, asw::Color color)
Draw the outline of a polygon. The last point joins the first.
void set_scale_mode(const asw::Texture &texture, asw::ScaleMode mode)
Set the scale mode of a texture. Nearest keeps hard pixel edges, linear smooths the texture when it i...
void clear_color(asw::Color color)
Clear the screen to a color.
void rect(const asw::Quad< float > &position, asw::Color color, float thickness=1.0F)
Draw a rectangle.
void line(const asw::Vec2< float > &position1, const asw::Vec2< float > &position2, asw::Color color)
Draw a line.
void triangle_fill(const asw::Vec2< float > &a, const asw::Vec2< float > &b, const asw::Vec2< float > &c, asw::Color color)
Draw a filled triangle.
void set_tint(const asw::Texture &texture, asw::Color tint)
Set the tint of a texture. Each colour channel is multiplied by the tint when drawn,...
void rotate_sprite(const asw::Texture &tex, const asw::Vec2< float > &position, float angle)
Draw a sprite with the option to rotate it.
void stretch_sprite(const asw::Texture &tex, const asw::Quad< float > &position)
Draw a sprite with the option to stretch it.
void text_shadow(const asw::Font &font, const std::string &text, const asw::Vec2< float > &position, asw::Color color, asw::Color shadow=asw::Color(0, 0, 0), const asw::Vec2< float > &offset=asw::Vec2< float >(2.0F, 2.0F), asw::TextJustify justify=asw::TextJustify::Left)
Draw text with a drop shadow behind it, so it reads on any background. The shadow fades with the text...
void clear_text_cache()
Clear cached rendered text textures.
void text(const asw::Font &font, const std::string &text, const asw::Vec2< float > &position, asw::Color color, asw::TextJustify justify=asw::TextJustify::Left)
Draw text. The position is rounded to whole pixels after justification so glyphs stay sharp....
void polygon_fill(const asw::Polygonf &points, asw::Color color)
Draw a filled polygon. The polygon can be concave, but its edges must not cross.
void circle(const asw::Vec2< float > &position, float radius, asw::Color color)
Draw a circle.
void rect_fill(const asw::Quad< float > &position, asw::Color color)
Draw a filled rectangle.
void sprite_flip(const asw::Texture &tex, const asw::Vec2< float > &position, bool flip_x, bool flip_y)
Draw a sprite with the option to flip it.
void point(const asw::Vec2< float > &position, asw::Color color)
Draw a point.
void set_blend_mode(const asw::Texture &texture, asw::BlendMode mode)
Set the blend mode of a texture.
void stretch_sprite_blit(const asw::Texture &tex, const asw::Quad< float > &source, const asw::Quad< float > &dest)
Draw a sprite with the option to stretch a portion of it.
void stretch_sprite_rotate_blit(const asw::Texture &tex, const asw::Quad< float > &source, const asw::Quad< float > &dest, float angle, bool flip_x=false, bool flip_y=false)
Draw a sprite with the option to stretch and rotate a portion of it.
void set_alpha(const asw::Texture &texture, float alpha)
Set the alpha of a texture.
void rect_fill_rotate(const asw::Quad< float > &position, float angle, asw::Color color)
Draw a filled rectangle rotated around its centre.
Vec2< T >::Real signed_area(const Polygon< T > &polygon)
Get the signed area of a polygon.
bool point_in_triangle(const Vec2< T > &point, const Vec2< T > &a, const Vec2< T > &b, const Vec2< T > &c)
Check if a point is inside a triangle. Points on an edge are inside.
asw::Vec2< float > get_texture_size(const asw::Texture &tex)
Get texture size.
std::shared_ptr< TTF_Font > Font
Alias for a shared pointer to an TTF_Font.
Polygon< float > Polygonf
SDL_FColor FColor
A colour with each channel as a float from 0.0 to 1.0.
std::shared_ptr< SDL_Texture > Texture
Alias for a shared pointer to an SDL_Texture.
BlendMode
Mappings from SDL_BLENDMODE to ASW BlendMode.
ScaleMode
Mappings from SDL_SCALEMODE to ASW ScaleMode.
TextJustify
Text justification options for text rendering.
SDL_Renderer Renderer
Alias for a shared pointer to an SDL_Renderer.
std::size_t operator()(const ScaledFontKey &key) const
bool operator==(const ScaledFontKey &) const =default
bool operator()(const A &a, const B &b) const
std::size_t operator()(const K &key) const
const asw::Renderer * renderer
bool operator==(const TextKeyView &) const =default
RGBA color struct with 8-bit channels.
FColor to_fcolor() const
To a float colour, with each channel from 0.0 to 1.0.
General utility functions.