ASW Lib
A.D.S. Games SDL Wrapper Library. A library targeted at Allegro4 users who want to switch to SDL3 and use modern c++.
Loading...
Searching...
No Matches
draw.cpp
Go to the documentation of this file.
2
3#include <SDL3/SDL.h>
4#include <SDL3_image/SDL_image.h>
5#include <SDL3_ttf/SDL_ttf.h>
6#include <algorithm>
7#include <array>
8#include <cmath>
9#include <numbers>
10#include <string_view>
11#include <vector>
12
14#include "./asw/modules/util.h"
15#include "./lru_cache.h"
16
17namespace {
20
21 // Holds the font, so its address is not reused while cached
23 std::string text;
24
25 // Changes when the font's size, style or hinting change
27
28 // Output pixels per logical pixel asked for, in hundredths
29 uint32_t render_scale;
30};
31
32// What a lookup compares, so a lookup does not copy the text
35 const TTF_Font* font;
36 std::string_view text;
38 uint32_t render_scale;
39
40 bool operator==(const TextKeyView&) const = default;
41};
42
44{
45 return { key.renderer, key.font.get(), key.text, key.font_generation, key.render_scale };
46}
47
48const TextKeyView& view(const TextKeyView& key)
49{
50 return key;
51}
52
55
56 // Size in logical pixels
57 float width { 0.0F };
58 float height { 0.0F };
59};
60
62 using is_transparent = void;
63
64 template <typename K> std::size_t operator()(const K& key) const
65 {
66 const TextKeyView v = view(key);
67 std::size_t seed = std::hash<const asw::Renderer*> { }(v.renderer);
68 asw::detail::hash_combine(seed, std::hash<const TTF_Font*> { }(v.font));
69 asw::detail::hash_combine(seed, std::hash<std::string_view> { }(v.text));
70 asw::detail::hash_combine(seed, std::hash<uint32_t> { }(v.font_generation));
71 asw::detail::hash_combine(seed, std::hash<uint32_t> { }(v.render_scale));
72 return seed;
73 }
74};
75
77 using is_transparent = void;
78
79 template <typename A, typename B> bool operator()(const A& a, const B& b) const
80 {
81 return view(a) == view(b);
82 }
83};
84
85// A font resized to render text for a larger output
89 uint32_t render_scale;
90
91 bool operator==(const ScaledFontKey&) const = default;
92};
93
95 std::size_t operator()(const ScaledFontKey& key) const
96 {
97 std::size_t seed = std::hash<asw::Font> { }(key.font);
98 asw::detail::hash_combine(seed, std::hash<uint32_t> { }(key.font_generation));
99 asw::detail::hash_combine(seed, std::hash<uint32_t> { }(key.render_scale));
100 return seed;
101 }
102};
103
104// Scale is stored in hundredths, so 100 is the logical size
105constexpr uint32_t RENDER_SCALE_ONE = 100;
106
107constexpr std::size_t SCALED_FONT_LIMIT = 32;
110
111constexpr std::size_t TEXT_CACHE_LIMIT = 256;
114
115// Output pixels per logical pixel to render text at, in hundredths. Smooth
116// text is rendered at the output resolution so it stays sharp when the window
117// is scaled or on high density displays. Pixel fonts keep their hard pixels
118// at the logical size, and text drawn into a texture is not scaled.
119uint32_t text_render_scale(asw::Renderer* renderer, bool pixel_font)
120{
121 if (pixel_font || SDL_GetRenderTarget(renderer) != nullptr) {
122 return RENDER_SCALE_ONE;
123 }
124
125 const float scale = asw::display::get_scale().x;
126 if (scale <= 1.0F) {
127 return RENDER_SCALE_ONE;
128 }
129
130 return static_cast<uint32_t>(std::lround(scale * static_cast<float>(RENDER_SCALE_ONE)));
131}
132
133// Copy of a font at render_scale times its size. The copy keeps the style and
134// hinting, and leaves the caller's font untouched.
135TTF_Font* get_scaled_font(const asw::Font& font, uint32_t font_generation, uint32_t render_scale)
136{
137 ScaledFontKey key { font, font_generation, render_scale };
138 if (const auto* cached = scaled_fonts.find(key)) {
139 return cached->get();
140 }
141
142 TTF_Font* copy = TTF_CopyFont(font.get());
143 if (copy == nullptr) {
144 return nullptr;
145 }
146
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)) {
149 TTF_CloseFont(copy);
150 return nullptr;
151 }
152
153 asw::Font scaled { copy, [](TTF_Font* f) {
154 if (TTF_WasInit() > 0) {
155 TTF_CloseFont(f);
156 }
157 } };
158
159 return scaled_fonts.insert(std::move(key), std::move(scaled)).get();
160}
161
163{
164 // Cached text is explicitly cleared before renderer teardown in the normal
165 // shutdown path, mirroring the renderer-guarded asset deleters.
166 return { texture, [](SDL_Texture* t) {
167 if (asw::display::get_renderer() != nullptr) {
168 SDL_DestroyTexture(t);
169 }
170 } };
171}
172} // namespace
173
175{
176 auto* r = asw::display::get_renderer();
177 if (r == nullptr) {
178 return;
179 }
180
181 SDL_SetRenderDrawColor(r, color.r, color.g, color.b, color.a);
182 SDL_RenderClear(r);
183}
184
185void asw::draw::sprite(const asw::Texture& tex, const asw::Vec2<float>& position)
186{
187 auto* r = asw::display::get_renderer();
188 if (r == nullptr) {
189 return;
190 }
191
192 auto size = asw::util::get_texture_size(tex);
193
194 SDL_FRect dest;
195 dest.x = position.x;
196 dest.y = position.y;
197 dest.w = size.x;
198 dest.h = size.y;
199
200 SDL_RenderTexture(r, tex.get(), nullptr, &dest);
201}
202
204 const asw::Texture& tex, const asw::Vec2<float>& position, bool flip_x, bool flip_y)
205{
206 auto* r = asw::display::get_renderer();
207 if (r == nullptr) {
208 return;
209 }
210
211 auto size = asw::util::get_texture_size(tex);
212
213 SDL_FRect dest;
214 dest.x = position.x;
215 dest.y = position.y;
216 dest.w = size.x;
217 dest.h = size.y;
218
219 SDL_FlipMode flip = SDL_FLIP_NONE;
220
221 if (flip_x) {
222 flip = static_cast<SDL_FlipMode>(flip | SDL_FLIP_HORIZONTAL);
223 }
224
225 if (flip_y) {
226 flip = static_cast<SDL_FlipMode>(flip | SDL_FLIP_VERTICAL);
227 }
228
229 SDL_RenderTextureRotated(r, tex.get(), nullptr, &dest, 0, nullptr, flip);
230}
231
233{
234 auto* r = asw::display::get_renderer();
235 if (r == nullptr) {
236 return;
237 }
238
239 SDL_FRect dest;
240 dest.x = position.position.x;
241 dest.y = position.position.y;
242 dest.w = position.size.x;
243 dest.h = position.size.y;
244
245 SDL_RenderTexture(r, tex.get(), nullptr, &dest);
246}
247
249 const asw::Texture& tex, const asw::Vec2<float>& position, float angle)
250{
251 auto* r = asw::display::get_renderer();
252 if (r == nullptr) {
253 return;
254 }
255
256 auto size = asw::util::get_texture_size(tex);
257
258 SDL_FRect dest;
259 dest.x = position.x;
260 dest.y = position.y;
261 dest.w = size.x;
262 dest.h = size.y;
263
264 // Rad to deg
265 const double angleDeg = angle * (180.0 / std::numbers::pi);
266
267 SDL_RenderTextureRotated(r, tex.get(), nullptr, &dest, angleDeg, nullptr, SDL_FLIP_NONE);
268}
269
271 const asw::Texture& tex, const asw::Quad<float>& dest, float angle, bool flip_x, bool flip_y)
272{
273 auto* r = asw::display::get_renderer();
274 if (r == nullptr) {
275 return;
276 }
277
278 SDL_FRect r_dest;
279 r_dest.x = dest.position.x;
280 r_dest.y = dest.position.y;
281 r_dest.w = dest.size.x;
282 r_dest.h = dest.size.y;
283
284 // Rad to deg
285 const double angleDeg = angle * (180.0 / std::numbers::pi);
286
287 SDL_FlipMode flip = SDL_FLIP_NONE;
288
289 if (flip_x) {
290 flip = static_cast<SDL_FlipMode>(flip | SDL_FLIP_HORIZONTAL);
291 }
292
293 if (flip_y) {
294 flip = static_cast<SDL_FlipMode>(flip | SDL_FLIP_VERTICAL);
295 }
296
297 SDL_RenderTextureRotated(r, tex.get(), nullptr, &r_dest, angleDeg, nullptr, flip);
298}
299
301 const asw::Texture& tex, const asw::Quad<float>& source, const asw::Quad<float>& dest)
302{
303 auto* r = asw::display::get_renderer();
304 if (r == nullptr) {
305 return;
306 }
307
308 SDL_FRect r_src;
309 r_src.x = source.position.x;
310 r_src.y = source.position.y;
311 r_src.w = source.size.x;
312 r_src.h = source.size.y;
313
314 SDL_FRect r_dest;
315 r_dest.x = dest.position.x;
316 r_dest.y = dest.position.y;
317 r_dest.w = dest.size.x;
318 r_dest.h = dest.size.y;
319
320 SDL_RenderTexture(r, tex.get(), &r_src, &r_dest);
321}
322
324 const asw::Quad<float>& dest, float angle, bool flip_x, bool flip_y)
325{
326 auto* r = asw::display::get_renderer();
327 if (r == nullptr) {
328 return;
329 }
330
331 SDL_FRect r_src;
332 r_src.x = source.position.x;
333 r_src.y = source.position.y;
334 r_src.w = source.size.x;
335 r_src.h = source.size.y;
336
337 SDL_FRect r_dest;
338 r_dest.x = dest.position.x;
339 r_dest.y = dest.position.y;
340 r_dest.w = dest.size.x;
341 r_dest.h = dest.size.y;
342
343 const double angleDeg = angle * (180.0 / std::numbers::pi);
344
345 int flip = SDL_FLIP_NONE;
346 if (flip_x) {
347 flip |= SDL_FLIP_HORIZONTAL;
348 }
349 if (flip_y) {
350 flip |= SDL_FLIP_VERTICAL;
351 }
352
353 SDL_RenderTextureRotated(
354 r, tex.get(), &r_src, &r_dest, angleDeg, nullptr, static_cast<SDL_FlipMode>(flip));
355}
356
357void asw::draw::text(const asw::Font& font, const std::string& text,
358 const asw::Vec2<float>& position, asw::Color color, asw::TextJustify justify)
359{
360 auto* r = asw::display::get_renderer();
361 if (text.empty() || font == nullptr || r == nullptr) {
362 return;
363 }
364
365 // Pixel fonts (mono hinting) render without anti-aliasing. Blended
366 // output can still hold partial alpha, solid output never does.
367 const bool pixel_font = TTF_GetFontHinting(font.get()) == TTF_HINTING_MONO;
368 const uint32_t font_generation = TTF_GetFontGeneration(font.get());
369 const uint32_t render_scale = text_render_scale(r, pixel_font);
370
371 // Text is cached in white and the color is applied when drawing, so text
372 // that changes color or fades reuses one cached texture
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;
378 if (render_scale != RENDER_SCALE_ONE) {
379 if (auto* scaled = get_scaled_font(font, font_generation, render_scale);
380 scaled != nullptr) {
381 render_font = scaled;
382 } else {
383 // Could not resize, fall back to scaling the logical size
384 // text. Still cached under the scale asked for, so the next
385 // draw finds it.
386 used_scale = RENDER_SCALE_ONE;
387 }
388 }
389
390 const float scale = static_cast<float>(used_scale) / static_cast<float>(RENDER_SCALE_ONE);
391
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);
396
397 if (textSurface == nullptr) {
398 return;
399 }
400
401 SDL_Texture* textTexture = SDL_CreateTextureFromSurface(r, textSurface);
402 if (textTexture == nullptr) {
403 SDL_DestroySurface(textSurface);
404 return;
405 }
406
407 SDL_SetTextureBlendMode(textTexture, SDL_BLENDMODE_BLEND);
408
409 if (pixel_font) {
410 SDL_SetTextureScaleMode(textTexture, SDL_SCALEMODE_NEAREST);
411 } else {
412 SDL_SetTextureScaleMode(textTexture, SDL_SCALEMODE_LINEAR);
413 }
414
415 // Drawn at its logical size, so the extra pixels fill the output.
416 // Glyphs advance slightly differently at the larger size, so use the
417 // logical font's measurements to keep layout the same as
418 // util::get_text_size.
419 float width = static_cast<float>(textSurface->w) / scale;
420 float height = static_cast<float>(textSurface->h) / scale;
421 if (render_font != font.get()) {
422 int logical_w = 0;
423 int logical_h = 0;
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);
427 }
428 }
429
430 TextCacheEntry entry {
431 make_cached_texture(textTexture),
432 width,
433 height,
434 };
435 SDL_DestroySurface(textSurface);
436
437 cached_text = &text_cache.insert(
438 { r, font, text, font_generation, render_scale }, std::move(entry));
439 }
440
441 SDL_FRect dest;
442 dest.x = position.x;
443 dest.y = position.y;
444 dest.w = cached_text->width;
445 dest.h = cached_text->height;
446
447 // Justification settings
448 if (justify == asw::TextJustify::Center) {
449 dest.x -= dest.w / 2.0F;
450 } else if (justify == asw::TextJustify::Right) {
451 dest.x -= dest.w;
452 }
453
454 // Snap to whole pixels. Centred odd width text lands on a half pixel,
455 // which blurs the glyphs.
456 dest.x = std::round(dest.x);
457 dest.y = std::round(dest.y);
458
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);
462}
463
464void asw::draw::text_shadow(const asw::Font& font, const std::string& text,
465 const asw::Vec2<float>& position, asw::Color color, asw::Color shadow,
466 const asw::Vec2<float>& offset, asw::TextJustify justify)
467{
468 shadow.a = static_cast<uint8_t>(
469 (static_cast<float>(shadow.a) * static_cast<float>(color.a)) / 255.0F);
470
471 asw::draw::text(font, text, position + offset, shadow, justify);
472 asw::draw::text(font, text, position, color, justify);
473}
474
476{
477 text_cache.clear();
478 scaled_fonts.clear();
479}
480
481void asw::draw::point(const asw::Vec2<float>& position, asw::Color color)
482{
483 auto* r = asw::display::get_renderer();
484 if (r == nullptr) {
485 return;
486 }
487
488 SDL_SetRenderDrawColor(r, color.r, color.g, color.b, color.a);
489 SDL_RenderPoint(r, position.x, position.y);
490}
491
493 const asw::Vec2<float>& position1, const asw::Vec2<float>& position2, asw::Color color)
494{
495 auto* r = asw::display::get_renderer();
496 if (r == nullptr) {
497 return;
498 }
499
500 SDL_SetRenderDrawColor(r, color.r, color.g, color.b, color.a);
501 SDL_RenderLine(r, position1.x, position1.y, position2.x, position2.y);
502}
503
504void asw::draw::rect(const asw::Quad<float>& position, asw::Color color, float thickness)
505{
506 auto* r = asw::display::get_renderer();
507 if (r == nullptr) {
508 return;
509 }
510
511 SDL_SetRenderDrawColor(r, color.r, color.g, color.b, color.a);
512
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;
517
518 if (thickness <= 1.0F) {
519 const SDL_FRect rect { x, y, w, h };
520 SDL_RenderRect(r, &rect);
521 return;
522 }
523
524 // Four bands inside the quad, the sides fit between the top and bottom
525 const float t = std::min({ thickness, w / 2.0F, h / 2.0F });
526 const std::array<SDL_FRect, 4> bands { {
527 { x, y, w, t },
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) },
531 } };
532
533 SDL_RenderFillRects(r, bands.data(), static_cast<int>(bands.size()));
534}
535
537{
538 auto* r = asw::display::get_renderer();
539 if (r == nullptr) {
540 return;
541 }
542
543 SDL_SetRenderDrawColor(r, color.r, color.g, color.b, color.a);
544 SDL_FRect rect;
545 rect.x = position.position.x;
546 rect.y = position.position.y;
547 rect.w = position.size.x;
548 rect.h = position.size.y;
549
550 SDL_RenderFillRect(r, &rect);
551}
552
553void asw::draw::rect_fill_rotate(const asw::Quad<float>& position, float angle, asw::Color color)
554{
555 auto* r = asw::display::get_renderer();
556 if (r == nullptr) {
557 return;
558 }
559
560 const auto center = position.get_center();
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);
565
566 // Screen y points down, so this turns clockwise on screen
567 const std::array<SDL_FPoint, 4> corners { {
568 { -half_w, -half_h },
569 { half_w, -half_h },
570 { half_w, half_h },
571 { -half_w, half_h },
572 } };
573
574 const asw::FColor fcolor = color.to_fcolor();
575
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;
581 }
582
583 constexpr std::array<int, 6> indices { 0, 1, 2, 0, 2, 3 };
584
585 SDL_RenderGeometry(r, nullptr, vertices.data(), static_cast<int>(vertices.size()),
586 indices.data(), static_cast<int>(indices.size()));
587}
588
589void asw::draw::circle(const asw::Vec2<float>& position, float radius, asw::Color color)
590{
591 auto* r = asw::display::get_renderer();
592 if (r == nullptr) {
593 return;
594 }
595
596 SDL_SetRenderDrawColor(r, color.r, color.g, color.b, color.a);
597
598 // Midpoint circle algorithm — no trig, integer arithmetic only
599 auto x = radius;
600 auto y = 0.0F;
601 auto err = 1.0F - x;
602 const float cx = position.x;
603 const float cy = position.y;
604
605 // Reused between calls so drawing does not allocate each frame
606 static std::vector<SDL_FPoint> points;
607 points.clear();
608
609 while (x >= y) {
610 if (x == 0.0F) {
611 points.push_back({ cx, cy });
612 } else if (y == 0.0F) {
613 // The octants meet on the axes, so each axis pixel once
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 });
618 } else if (x == y) {
619 // The octants meet on the diagonals, so each diagonal pixel once
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 });
624 } else {
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 });
633 }
634 y++;
635 if (err < 0) {
636 err += (2.0F * y) + 1.0F;
637 } else {
638 x--;
639 err += (2.0F * (y - x)) + 1.0F;
640 }
641 }
642
643 // One call, not one per point
644 SDL_RenderPoints(r, points.data(), static_cast<int>(points.size()));
645}
646
647void asw::draw::circle_fill(const asw::Vec2<float>& position, float radius, asw::Color color)
648{
649 auto* r = asw::display::get_renderer();
650 if (r == nullptr) {
651 return;
652 }
653
654 SDL_SetRenderDrawColor(r, color.r, color.g, color.b, color.a);
655
656 // One span per pixel row, all sent in a single call. Snapping the centre
657 // to a whole pixel keeps every row on the pixel grid, so a centre between
658 // pixels leaves no gaps, and no row is drawn twice, so translucent circles
659 // blend evenly.
660 const float cx = std::round(position.x);
661 const float cy = std::round(position.y);
662 const int rows = static_cast<int>(radius);
663
664 static std::vector<SDL_FRect> spans;
665 spans.clear();
666
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 });
671 }
672
673 SDL_RenderFillRects(r, spans.data(), static_cast<int>(spans.size()));
674}
675
677 const asw::Vec2<float>& c, asw::Color color)
678{
679 auto* r = asw::display::get_renderer();
680 if (r == nullptr) {
681 return;
682 }
683
684 const asw::FColor fcolor = color.to_fcolor();
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 } },
689 } };
690
691 SDL_RenderGeometry(r, nullptr, vertices.data(), 3, nullptr, 0);
692}
693
695{
696 auto* r = asw::display::get_renderer();
697 if (r == nullptr || points.size() < 2) {
698 return;
699 }
700
701 static std::vector<SDL_FPoint> line_points;
702 line_points.clear();
703 for (const auto& p : points) {
704 line_points.push_back({ p.x, p.y });
705 }
706 line_points.push_back({ points.front().x, points.front().y });
707
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()));
710}
711
713{
714 auto* r = asw::display::get_renderer();
715 if (r == nullptr || points.size() < 3) {
716 return;
717 }
718
719 // Ear clipping: cut off corners that bulge out and hold no other point
720 // until one triangle is left
721 const float winding = asw::geometry::signed_area(points) < 0.0F ? -1.0F : 1.0F;
722 const auto n = static_cast<int>(points.size());
723
724 // Remaining corners as a ring, so cutting one off is constant time
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());
732 indices.clear();
733
734 const auto point
735 = [&](int i) -> const asw::Vec2f& { return points[static_cast<std::size_t>(i)]; };
736
737 // A corner that does not bulge out. Only these can sit inside an ear.
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;
743 };
744
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;
749 }
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;
753 }
754
755 int count = n;
756 int cur = 0;
757
758 // Convex shapes, such as most drawn shapes, need no search
759 if (any_reflex) {
760 int tried = 0;
761 while (count > 3 && tried < count) {
762 const int p = prev[cur];
763 const int nx = next[cur];
764
765 bool ear = reflex[cur] == 0;
766 if (ear) {
767 // A point on a corner of the ear, such as a repeated point,
768 // does not block it
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
775 && asw::geometry::point_in_triangle(q, a, b, c)) {
776 ear = false;
777 break;
778 }
779 }
780 }
781
782 if (!ear) {
783 cur = nx;
784 ++tried;
785 continue;
786 }
787
788 indices.insert(indices.end(), { p, cur, nx });
789 next[p] = nx;
790 prev[nx] = p;
791 --count;
792 reflex[p] = static_cast<char>(is_reflex(p));
793 reflex[nx] = static_cast<char>(is_reflex(nx));
794
795 // The neighbours are the corners that changed, so look there next
796 cur = p;
797 tried = 0;
798 }
799 }
800
801 // What is left is convex, or has crossing edges or repeated points and
802 // no ear. Fill it as a fan rather than drawing nothing
803 for (int i = next[cur]; next[i] != cur; i = next[i]) {
804 indices.insert(indices.end(), { cur, i, next[i] });
805 }
806
807 const asw::FColor fcolor = color.to_fcolor();
808 static std::vector<SDL_Vertex> vertices;
809 vertices.clear();
810 for (const auto& p : points) {
811 vertices.push_back({ { p.x, p.y }, fcolor, { 0.0F, 0.0F } });
812 }
813
814 SDL_RenderGeometry(r, nullptr, vertices.data(), static_cast<int>(vertices.size()),
815 indices.data(), static_cast<int>(indices.size()));
816}
817
819{
820 SDL_SetTextureBlendMode(texture.get(), static_cast<SDL_BlendMode>(mode));
821}
822
823void asw::draw::set_alpha(const asw::Texture& texture, float alpha)
824{
825 SDL_SetTextureAlphaModFloat(texture.get(), alpha);
826}
827
829{
830 SDL_SetTextureColorMod(texture.get(), tint.r, tint.g, tint.b);
831}
832
834{
835 SDL_SetTextureScaleMode(texture.get(), static_cast<SDL_ScaleMode>(mode));
836}
A 2D rectangle in space.
Definition geometry.h:504
Vec2< T > get_center() const
Get center of the rectangle.
Definition geometry.h:562
Vec2< T > size
The size of the rectangle.
Definition geometry.h:728
Vec2< T > position
The position of the rectangle.
Definition geometry.h:725
A 2D vector in space.
Definition geometry.h:27
T y
The y component of the vector.
Definition geometry.h:280
T x
The x component of the vector.
Definition geometry.h:277
A hash map with a size limit. When full, the least recently used half is dropped, so entries in use e...
Definition lru_cache.h:23
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)
Definition draw.cpp:162
TTF_Font * get_scaled_font(const asw::Font &font, uint32_t font_generation, uint32_t render_scale)
Definition draw.cpp:135
TextKeyView view(const TextCacheKey &key)
Definition draw.cpp:43
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)
Definition draw.cpp:119
asw::detail::LruCache< TextCacheKey, TextCacheEntry, TextKeyHash, TextKeyEqual > text_cache(TEXT_CACHE_LIMIT)
constexpr uint32_t RENDER_SCALE_ONE
Definition draw.cpp:105
constexpr std::size_t TEXT_CACHE_LIMIT
Definition draw.cpp:111
constexpr std::size_t SCALED_FONT_LIMIT
Definition draw.cpp:107
void hash_combine(std::size_t &seed, std::size_t hash)
Combine a hash into a seed, as boost::hash_combine does.
Definition lru_cache.h:103
asw::Vec2< float > get_scale()
Get how many output pixels one logical pixel covers, after letterboxing. 2.0 means the game is drawn ...
Definition display.cpp:207
asw::Renderer * get_renderer()
Get the SDL renderer.
Definition display.cpp:139
void sprite(const asw::Texture &tex, const asw::Vec2< float > &position)
Draw a sprite.
Definition draw.cpp:185
void circle_fill(const asw::Vec2< float > &position, float radius, asw::Color color)
Draw a filled circle.
Definition draw.cpp:647
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.
Definition draw.cpp:270
void polygon(const asw::Polygonf &points, asw::Color color)
Draw the outline of a polygon. The last point joins the first.
Definition draw.cpp:694
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...
Definition draw.cpp:833
void clear_color(asw::Color color)
Clear the screen to a color.
Definition draw.cpp:174
void rect(const asw::Quad< float > &position, asw::Color color, float thickness=1.0F)
Draw a rectangle.
Definition draw.cpp:504
void line(const asw::Vec2< float > &position1, const asw::Vec2< float > &position2, asw::Color color)
Draw a line.
Definition draw.cpp:492
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.
Definition draw.cpp:676
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,...
Definition draw.cpp:828
void rotate_sprite(const asw::Texture &tex, const asw::Vec2< float > &position, float angle)
Draw a sprite with the option to rotate it.
Definition draw.cpp:248
void stretch_sprite(const asw::Texture &tex, const asw::Quad< float > &position)
Draw a sprite with the option to stretch it.
Definition draw.cpp:232
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...
Definition draw.cpp:464
void clear_text_cache()
Clear cached rendered text textures.
Definition draw.cpp:475
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....
Definition draw.cpp:357
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.
Definition draw.cpp:712
void circle(const asw::Vec2< float > &position, float radius, asw::Color color)
Draw a circle.
Definition draw.cpp:589
void rect_fill(const asw::Quad< float > &position, asw::Color color)
Draw a filled rectangle.
Definition draw.cpp:536
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.
Definition draw.cpp:203
void point(const asw::Vec2< float > &position, asw::Color color)
Draw a point.
Definition draw.cpp:481
void set_blend_mode(const asw::Texture &texture, asw::BlendMode mode)
Set the blend mode of a texture.
Definition draw.cpp:818
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.
Definition draw.cpp:300
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.
Definition draw.cpp:323
void set_alpha(const asw::Texture &texture, float alpha)
Set the alpha of a texture.
Definition draw.cpp:823
void rect_fill_rotate(const asw::Quad< float > &position, float angle, asw::Color color)
Draw a filled rectangle rotated around its centre.
Definition draw.cpp:553
Vec2< T >::Real signed_area(const Polygon< T > &polygon)
Get the signed area of a polygon.
Definition geometry.h:751
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.
Definition geometry.h:790
asw::Vec2< float > get_texture_size(const asw::Texture &tex)
Get texture size.
Definition util.cpp:74
std::shared_ptr< TTF_Font > Font
Alias for a shared pointer to an TTF_Font.
Definition types.h:79
Polygon< float > Polygonf
Definition geometry.h:741
SDL_FColor FColor
A colour with each channel as a float from 0.0 to 1.0.
Definition color.h:14
std::shared_ptr< SDL_Texture > Texture
Alias for a shared pointer to an SDL_Texture.
Definition types.h:76
BlendMode
Mappings from SDL_BLENDMODE to ASW BlendMode.
Definition types.h:26
ScaleMode
Mappings from SDL_SCALEMODE to ASW ScaleMode.
Definition types.h:40
TextJustify
Text justification options for text rendering.
Definition types.h:58
SDL_Renderer Renderer
Alias for a shared pointer to an SDL_Renderer.
Definition types.h:88
std::size_t operator()(const ScaledFontKey &key) const
Definition draw.cpp:95
bool operator==(const ScaledFontKey &) const =default
bool operator()(const A &a, const B &b) const
Definition draw.cpp:79
std::size_t operator()(const K &key) const
Definition draw.cpp:64
bool operator==(const TextKeyView &) const =default
RGBA color struct with 8-bit channels.
Definition color.h:18
uint8_t b
Definition color.h:32
uint8_t r
Definition color.h:30
uint8_t g
Definition color.h:31
uint8_t a
Definition color.h:33
FColor to_fcolor() const
To a float colour, with each channel from 0.0 to 1.0.
Definition color.h:77
General utility functions.