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
lighting.cpp
Go to the documentation of this file.
2
3#include <SDL3/SDL.h>
4#include <algorithm>
5#include <array>
6#include <cmath>
7#include <numbers>
8
11#include "./asw/modules/draw.h"
13
14namespace {
15constexpr float TAU = std::numbers::pi_v<float> * 2.0F;
16
17// Size of the gradient texture lights are drawn from
18constexpr int GRADIENT_SIZE = 256;
19
20// How far shadows are pushed away from a light. Far past any screen
21constexpr float SHADOW_FAR = 100000.0F;
22
23using asw::Polygonf;
24using asw::Vec2;
25
26// Hash of an integer to 0-1
27float hash(uint32_t x)
28{
29 x = ((x >> 16U) ^ x) * 0x45d9f3bU;
30 x = ((x >> 16U) ^ x) * 0x45d9f3bU;
31 x = (x >> 16U) ^ x;
32 return static_cast<float>(x & 0xffffU) / 65535.0F;
33}
34
35// Smooth random value from 0 to 1 that changes with x
36float noise(double x)
37{
38 const double cell = std::floor(x);
39 const float t = asw::easing::smoothstep(static_cast<float>(x - cell));
40 const auto i = static_cast<uint32_t>(static_cast<int64_t>(cell));
41 return hash(i) + ((hash(i + 1) - hash(i)) * t);
42}
43
44// Time wrapped into 0 to length
45float wrap(float time, float length)
46{
47 const float t = std::fmod(time, length);
48 return t < 0.0F ? t + length : t;
49}
50
51asw::Color scale_color(const asw::Color& color, float amount)
52{
53 return { asw::Color::to_channel(static_cast<float>(color.r) * amount),
54 asw::Color::to_channel(static_cast<float>(color.g) * amount),
55 asw::Color::to_channel(static_cast<float>(color.b) * amount) };
56}
57
58// Draw a texture as a fan from a centre point. Texture coordinates are taken
59// from a square of half size radius around the centre
60void textured_fan(const asw::Texture& texture, const Vec2<float>& center, float radius,
61 const Polygonf& points, const asw::Color& color, bool closed)
62{
64 if (r == nullptr || points.size() < 2) {
65 return;
66 }
67
68 const asw::FColor fcolor = color.to_fcolor();
69 auto uv = [&](const Vec2<float>& p) {
70 return SDL_FPoint { std::clamp(0.5F + ((p.x - center.x) / (radius * 2.0F)), 0.0F, 1.0F),
71 std::clamp(0.5F + ((p.y - center.y) / (radius * 2.0F)), 0.0F, 1.0F) };
72 };
73
74 static std::vector<SDL_Vertex> vertices;
75 static std::vector<int> indices;
76 vertices.clear();
77 indices.clear();
78
79 vertices.push_back({ { center.x, center.y }, fcolor, { 0.5F, 0.5F } });
80 for (const auto& p : points) {
81 vertices.push_back({ { p.x, p.y }, fcolor, uv(p) });
82 }
83
84 const int count = static_cast<int>(points.size());
85 for (int i = 1; i < count; ++i) {
86 indices.insert(indices.end(), { 0, i, i + 1 });
87 }
88 if (closed) {
89 indices.insert(indices.end(), { 0, count, 1 });
90 }
91
92 SDL_RenderGeometry(r, texture.get(), vertices.data(), static_cast<int>(vertices.size()),
93 indices.data(), static_cast<int>(indices.size()));
94}
95
96// Arc of a spot light's beam
97Polygonf arc(const Vec2<float>& center, float radius, float direction, float cone)
98{
99 const int segments = std::max(4, static_cast<int>(std::ceil(cone / TAU * 64.0F)));
100 Polygonf points;
101 for (int i = 0; i <= segments; ++i) {
102 const float angle = direction - (cone / 2.0F)
103 + (cone * static_cast<float>(i) / static_cast<float>(segments));
104 points.emplace_back(
105 center.x + (std::cos(angle) * radius), center.y + (std::sin(angle) * radius));
106 }
107 return points;
108}
109
110// Index of a tile in a row-major grid
111std::size_t cell(int x, int y, int width)
112{
113 return (static_cast<std::size_t>(y) * static_cast<std::size_t>(width))
114 + static_cast<std::size_t>(x);
115}
116
117bool is_spot(float cone)
118{
119 return cone > 0.0F && cone < TAU;
120}
121} // namespace
122
123// --- AmbientCycle ---
124
126 : length(std::max(length, 0.0001F))
127{
128}
129
130void asw::lighting::AmbientCycle::add(float time, const asw::Color& color)
131{
132 keys.emplace_back(wrap(time, length), color);
133 std::ranges::sort(keys, { }, &std::pair<float, asw::Color>::first);
134}
135
137{
138 if (keys.empty()) {
139 return { };
140 }
141
142 const float t = wrap(time, length);
143
144 // First key after t, wrapping to the start of the next cycle
145 auto next = std::ranges::upper_bound(keys, t, { }, &std::pair<float, asw::Color>::first);
146 const auto& to = next == keys.end() ? keys.front() : *next;
147 const auto& from = next == keys.begin() ? keys.back() : *std::prev(next);
148
149 float span = to.first - from.first;
150 float into = t - from.first;
151 if (span <= 0.0F) {
152 span += length;
153 }
154 if (into < 0.0F) {
155 into += length;
156 }
157
158 const float f = std::clamp(into / span, 0.0F, 1.0F);
159 return from.second.lerp(to.second, asw::easing::smoothstep(f));
160}
161
163{
164 return length;
165}
166
167// --- TileLight ---
168
169asw::lighting::TileLight::TileLight(int width, int height, float tile_size)
170 : width(std::max(width, 1))
171 , height(std::max(height, 1))
172 , tile_size(tile_size)
173 , solid(static_cast<std::size_t>(this->width) * static_cast<std::size_t>(this->height), 0)
174 , light(
175 static_cast<std::size_t>(this->width) * static_cast<std::size_t>(this->height) * 3, 0.0F)
176{
177}
178
179bool asw::lighting::TileLight::in_grid(int x, int y) const
180{
181 return x >= 0 && y >= 0 && x < width && y < height;
182}
183
184void asw::lighting::TileLight::set_solid(int x, int y, bool is_solid)
185{
186 if (in_grid(x, y)) {
187 solid[cell(x, y, width)] = is_solid ? 1 : 0;
188 }
189}
190
192{
193 return !in_grid(x, y) || solid[cell(x, y, width)] != 0;
194}
195
197{
198 falloff = std::clamp(amount, 0.001F, 1.0F);
199}
200
202{
203 sources.clear();
204}
205
206void asw::lighting::TileLight::add_light(int x, int y, const asw::Color& color)
207{
208 if (in_grid(x, y)) {
209 sources.push_back({ x, y, color });
210 }
211}
212
214{
215 std::ranges::fill(light, 0.0F);
216
217 const auto cells = static_cast<std::size_t>(width) * static_cast<std::size_t>(height);
218 if (level.size() != cells) {
219 level.assign(cells, 0.0F);
220 }
221
222 // Spread each light out one tile at a time, keeping the brightest light
223 // that reaches each tile. The queue is a list with a read position, so
224 // after the spread it holds every tile the light reached
225 for (const auto& source : sources) {
226 const auto start = cell(source.x, source.y, width);
227 level[start] = 1.0F;
228 open.clear();
229 open.push_back(start);
230
231 for (std::size_t head = 0; head < open.size(); ++head) {
232 const auto index = open[head];
233 const int x = static_cast<int>(index % static_cast<std::size_t>(width));
234 const int y = static_cast<int>(index / static_cast<std::size_t>(width));
235
236 const float here = level[index];
237 light[(index * 3) + 0]
238 = std::max(light[(index * 3) + 0], here * static_cast<float>(source.color.r));
239 light[(index * 3) + 1]
240 = std::max(light[(index * 3) + 1], here * static_cast<float>(source.color.g));
241 light[(index * 3) + 2]
242 = std::max(light[(index * 3) + 2], here * static_cast<float>(source.color.b));
243
244 // Solid tiles are lit, but pass nothing on
245 if (is_solid(x, y) && index != start) {
246 continue;
247 }
248
249 const float next = here - falloff;
250 if (next <= 0.0F) {
251 continue;
252 }
253
254 constexpr std::array<std::pair<int, int>, 4> STEPS { {
255 { 1, 0 },
256 { -1, 0 },
257 { 0, 1 },
258 { 0, -1 },
259 } };
260 for (const auto& [dx, dy] : STEPS) {
261 const int nx = x + dx;
262 const int ny = y + dy;
263 if (!in_grid(nx, ny)) {
264 continue;
265 }
266 const auto n = cell(nx, ny, width);
267 if (level[n] < next) {
268 level[n] = next;
269 open.push_back(n);
270 }
271 }
272 }
273
274 // Reset only the tiles this light reached, not the whole grid
275 for (const auto index : open) {
276 level[index] = 0.0F;
277 }
278 }
279
280 upload();
281}
282
283// Copy the light into the texture, one pixel per tile
285{
286 const auto cells = static_cast<std::size_t>(width) * static_cast<std::size_t>(height);
287
288 auto* r = asw::display::get_renderer();
289 if (r == nullptr) {
290 return;
291 }
292
293 if (texture == nullptr) {
294 SDL_Texture* txr = SDL_CreateTexture(
295 r, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, width, height);
296 if (txr == nullptr) {
297 return;
298 }
299 SDL_SetTextureScaleMode(txr, SDL_SCALEMODE_LINEAR);
300 SDL_SetTextureBlendMode(txr, SDL_BLENDMODE_ADD);
301 texture = { txr, [](SDL_Texture* t) {
302 if (asw::display::get_renderer() != nullptr) {
303 SDL_DestroyTexture(t);
304 }
305 } };
306 }
307
308 pixels.resize(cells * 4);
309 for (std::size_t i = 0; i < cells; ++i) {
310 pixels[(i * 4) + 0] = asw::Color::to_channel(light[(i * 3) + 0]);
311 pixels[(i * 4) + 1] = asw::Color::to_channel(light[(i * 3) + 1]);
312 pixels[(i * 4) + 2] = asw::Color::to_channel(light[(i * 3) + 2]);
313 pixels[(i * 4) + 3] = 255;
314 }
315 SDL_UpdateTexture(texture.get(), nullptr, pixels.data(), width * 4);
316}
317
319{
320 if (!in_grid(x, y)) {
321 return { };
322 }
323 const auto index = cell(x, y, width) * 3;
324 return { asw::Color::to_channel(light[index]), asw::Color::to_channel(light[index + 1]),
325 asw::Color::to_channel(light[index + 2]) };
326}
327
329{
330 return texture;
331}
332
334{
335 return { static_cast<float>(width) * tile_size, static_cast<float>(height) * tile_size };
336}
337
338// --- LightMap ---
339
341{
342 ambient = color;
343}
344
346{
347 return ambient;
348}
349
351{
352 camera = cam;
353}
354
356{
357 shadow_mode = mode;
358}
359
364
366{
367 time += static_cast<double>(dt);
368}
369
371{
372 lights.clear();
373 glows.clear();
374 tiles.clear();
375}
376
378{
379 lights.push_back(light);
380}
381
383 const asw::Texture& texture, const asw::Quad<float>& dest, const asw::Color& tint)
384{
385 glows.push_back({ texture, dest, tint });
386}
387
389 const TileLight& tile_light, const asw::Vec2<float>& position)
390{
391 tiles.push_back({ &tile_light, position });
392}
393
395{
396 occluders.push_back(polygon);
397}
398
400{
401 const auto& p = rect.position;
402 const auto& s = rect.size;
403 occluders.push_back({ p, { p.x + s.x, p.y }, p + s, { p.x, p.y + s.y } });
404}
405
407{
408 occluders.clear();
409}
410
411const std::vector<asw::Polygonf>& asw::lighting::LightMap::get_occluders() const
412{
413 return occluders;
414}
415
417{
418 return camera != nullptr ? camera->world_to_screen(point) : point;
419}
420
422{
423 return camera != nullptr ? camera->world_to_screen(rect) : rect;
424}
425
427{
428 for (const auto& [kind, texture] : gradients) {
429 if (kind == falloff) {
430 return texture;
431 }
432 }
433
435 GRADIENT_SIZE, asw::Color(255, 255, 255), asw::Color(0, 0, 0), falloff);
437 gradients.emplace_back(falloff, texture);
438 return gradients.back().second;
439}
440
441// Move the occluders to screen space, and work out what every light needs
442// from them once per frame instead of once per light
444{
445 screen_occluders.resize(occluders.size());
446 screen_bounds.resize(occluders.size());
447 screen_winding.resize(occluders.size());
448
449 for (std::size_t i = 0; i < occluders.size(); ++i) {
450 auto& screen = screen_occluders[i];
451 screen.clear();
452 for (const auto& p : occluders[i]) {
453 screen.push_back(to_screen(p));
454 }
455 screen_bounds[i] = asw::geometry::bounds(screen);
456 screen_winding[i] = asw::geometry::signed_area(screen) < 0.0F ? -1.0F : 1.0F;
457 }
458}
459
461{
462 for (std::size_t i = 0; i < screen_occluders.size(); ++i) {
463 if (screen_occluders[i].size() >= 2 && screen_bounds[i].collides(reach)) {
464 return true;
465 }
466 }
467 return false;
468}
469
470// Black out the shadow behind each edge that faces away from the light, in
471// one draw call
473 const asw::Vec2<float>& center, const asw::Quad<float>& reach)
474{
475 auto* r = asw::display::get_renderer();
476 shadow_vertices.clear();
477 shadow_indices.clear();
478
479 const asw::FColor black = asw::Color(0, 0, 0).to_fcolor();
480 auto away = [&](const Vec2<float>& p) {
481 const Vec2<float> dir = p - center;
482 const float length = dir.magnitude();
483 return length > 0.0F ? p + (dir * (SHADOW_FAR / length)) : p;
484 };
485
486 for (std::size_t i = 0; i < screen_occluders.size(); ++i) {
487 const auto& polygon = screen_occluders[i];
488 if (polygon.size() < 2 || !screen_bounds[i].collides(reach)) {
489 continue;
490 }
491
492 for (std::size_t j = 0; j < polygon.size(); ++j) {
493 const auto& a = polygon[j];
494 const auto& b = polygon[(j + 1) % polygon.size()];
495 const Vec2<float> edge = b - a;
496 const Vec2<float> outward = Vec2<float>(edge.y, -edge.x) * screen_winding[i];
497 const Vec2<float> mid = (a + b) / 2.0F;
498
499 // Only edges facing away from the light cast a shadow, so the
500 // side facing the light stays lit
501 if (outward.dot(mid - center) <= 0.0F) {
502 continue;
503 }
504
505 // The shadow is convex, so a fan from a covers it
506 const int base = static_cast<int>(shadow_vertices.size());
507 for (const auto& p : { a, b, away(b), away(mid), away(a) }) {
508 shadow_vertices.push_back({ { p.x, p.y }, black, { 0.0F, 0.0F } });
509 }
510 shadow_indices.insert(shadow_indices.end(),
511 { base, base + 1, base + 2, base, base + 2, base + 3, base, base + 3, base + 4 });
512 }
513 }
514
515 if (shadow_indices.empty()) {
516 return;
517 }
518
519 // Geometry with no texture uses the draw blend mode. Replace, so the
520 // shadow is black whatever the game left it on
521 SDL_BlendMode previous = SDL_BLENDMODE_NONE;
522 SDL_GetRenderDrawBlendMode(r, &previous);
523 SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_NONE);
524 SDL_RenderGeometry(r, nullptr, shadow_vertices.data(), static_cast<int>(shadow_vertices.size()),
525 shadow_indices.data(), static_cast<int>(shadow_indices.size()));
526 SDL_SetRenderDrawBlendMode(r, previous);
527}
528
530{
531 // Flicker dims the light at random, pulse brightens and dims it steadily.
532 // Phases are worked out in double, then wrapped, so they stay smooth
533 const float flicker = 1.0F
534 - (std::clamp(light.flicker, 0.0F, 1.0F)
535 * noise((time * 12.0) + (static_cast<double>(light.seed) * 31.7)));
536 const auto phase
537 = static_cast<float>(std::fmod(time * static_cast<double>(light.pulse_speed), 1.0));
538 const float pulse = 1.0F
539 + (std::clamp(light.pulse, 0.0F, 1.0F)
540 * std::sin((phase * TAU) + static_cast<float>(light.seed)));
541 const float strength = std::max(flicker * pulse, 0.0F);
542
543 const float radius = light.radius * (1.0F + ((strength - 1.0F) * 0.35F));
544 if (radius <= 0.0F) {
545 return;
546 }
547
548 const auto center = to_screen(light.position);
549 const asw::Quad<float> square(
550 center - Vec2<float>(radius, radius), Vec2<float>(radius * 2.0F, radius * 2.0F));
551
552 // Skip lights that do not reach the screen
553 const asw::Quad<float> screen(
554 0.0F, 0.0F, static_cast<float>(map_size.x), static_cast<float>(map_size.y));
555 if (!square.collides(screen)) {
556 return;
557 }
558
559 const auto color = scale_color(light.color, std::clamp(light.intensity, 0.0F, 1.0F) * strength);
560 const auto& glow = gradient(light.falloff);
561 const bool spot = is_spot(light.cone);
562
563 // Draw the lit shape with no shadows
564 auto draw_shape = [&]() {
565 if (spot) {
566 textured_fan(glow, center, radius, arc(center, radius, light.direction, light.cone),
567 color, false);
568 } else {
569 asw::draw::set_tint(glow, color);
570 asw::draw::stretch_sprite(glow, square);
571 asw::draw::set_tint(glow, asw::Color(255, 255, 255));
572 }
573 };
574
575 if (!light.shadows || shadow_mode == ShadowMode::None || !has_blockers(square)) {
576 draw_shape();
577 return;
578 }
579
580 if (shadow_mode == ShadowMode::Visibility) {
582 seen, center, radius, screen_occluders, screen_bounds, light.direction, light.cone);
583 textured_fan(glow, center, radius, seen, color, !spot);
584 return;
585 }
586
587 // Cast: light the scratch texture, black out the shadows, then add it to
588 // the map. Only the part of the screen the light reaches is touched
589 auto* r = asw::display::get_renderer();
590 const float left = std::max(std::floor(square.position.x), 0.0F);
591 const float top = std::max(std::floor(square.position.y), 0.0F);
592 const float right = std::min(std::ceil(square.position.x + square.size.x), screen.size.x);
593 const float bottom = std::min(std::ceil(square.position.y + square.size.y), screen.size.y);
594 const asw::Quad<float> area(left, top, right - left, bottom - top);
595 const SDL_Rect clip { static_cast<int>(left), static_cast<int>(top),
596 static_cast<int>(right - left), static_cast<int>(bottom - top) };
597
599 SDL_SetRenderClipRect(r, &clip);
600
601 // The fill uses the draw blend mode. Replace, so the area is cleared to
602 // black whatever the game left it on. A clear would ignore the clip.
603 SDL_BlendMode previous = SDL_BLENDMODE_NONE;
604 SDL_GetRenderDrawBlendMode(r, &previous);
605 SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_NONE);
606 asw::draw::rect_fill(area, asw::Color(0, 0, 0));
607 SDL_SetRenderDrawBlendMode(r, previous);
608
609 draw_shape();
610 draw_shadows(center, square);
611 SDL_SetRenderClipRect(r, nullptr);
612
614 asw::draw::stretch_sprite_blit(scratch, area, area);
615}
616
618{
619 auto* r = asw::display::get_renderer();
620 const auto size = asw::display::get_logical_size();
621 if (r == nullptr || size.x <= 0 || size.y <= 0) {
622 return map;
623 }
624
625 if (map == nullptr || size != map_size) {
626 map = asw::assets::create_texture(size.x, size.y);
628 scratch = asw::assets::create_texture(size.x, size.y);
630 map_size = size;
631 }
632
633 // Put the render target back as it was when done, so a game drawing into
634 // its own texture keeps drawing there
635 SDL_Texture* previous_target = SDL_GetRenderTarget(r);
636
637 if (shadow_mode != ShadowMode::None) {
638 prepare_occluders();
639 }
640
642 asw::display::clear(asw::Color(ambient.r, ambient.g, ambient.b));
643
644 for (const auto& grid : tiles) {
645 if (grid.grid->get_texture() != nullptr) {
646 asw::draw::stretch_sprite(grid.grid->get_texture(),
647 to_screen(asw::Quad<float>(grid.position, grid.grid->get_size())));
648 }
649 }
650
651 for (const auto& light : lights) {
652 draw_light(light);
653 }
654
655 // Draw glows added, then put the texture back as it was
656 for (const auto& glow : glows) {
657 SDL_Texture* txr = glow.texture.get();
658 if (txr == nullptr) {
659 continue;
660 }
661
662 SDL_BlendMode mode = SDL_BLENDMODE_NONE;
663 uint8_t red = 255;
664 uint8_t green = 255;
665 uint8_t blue = 255;
666 uint8_t alpha = 255;
667 SDL_GetTextureBlendMode(txr, &mode);
668 SDL_GetTextureColorMod(txr, &red, &green, &blue);
669 SDL_GetTextureAlphaMod(txr, &alpha);
670
671 SDL_SetTextureBlendMode(txr, SDL_BLENDMODE_ADD);
672 SDL_SetTextureColorMod(txr, glow.tint.r, glow.tint.g, glow.tint.b);
673 SDL_SetTextureAlphaMod(txr, glow.tint.a);
674 asw::draw::stretch_sprite(glow.texture, to_screen(glow.dest));
675
676 SDL_SetTextureBlendMode(txr, mode);
677 SDL_SetTextureColorMod(txr, red, green, blue);
678 SDL_SetTextureAlphaMod(txr, alpha);
679 }
680
681 SDL_SetRenderTarget(r, previous_target);
682 return map;
683}
684
686{
687 const auto& texture = render();
688 if (texture == nullptr) {
689 return;
690 }
691
693 texture, { 0.0F, 0.0F, static_cast<float>(map_size.x), static_cast<float>(map_size.y) });
694}
Asset routines for the ASW library.
2D camera that follows a target, stays inside the world and shakes.
Definition camera.h:21
A 2D rectangle in space.
Definition geometry.h:504
bool collides(const Quad &other) const
Check if a rectangle is inside the rectangle.
Definition geometry.h:597
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
Real magnitude() const
Calculate the magnitude of the vector.
Definition geometry.h:138
T dot(const Vec2 &other) const
Calculate the dot product of two vectors.
Definition geometry.h:119
float get_length() const
Get the length of one cycle.
Definition lighting.cpp:162
AmbientCycle(float length=1.0F)
Create a cycle.
Definition lighting.cpp:125
asw::Color at(float time) const
Get the colour at a time. Times past the length wrap around.
Definition lighting.cpp:136
void add(float time, const asw::Color &color)
Add a key colour. Colours between keys are blended.
Definition lighting.cpp:130
void clear()
Remove the lights, glows and tiles added this frame.
Definition lighting.cpp:370
void add_glow(const asw::Texture &texture, const asw::Quad< float > &dest, const asw::Color &tint=asw::Color(255, 255, 255))
Add a texture that gives off light, such as lava or a lamp sprite. Its colours are added to the light...
Definition lighting.cpp:382
void add_occluder(const asw::Polygonf &polygon)
Add a polygon that blocks light.
Definition lighting.cpp:394
void set_ambient(const asw::Color &color)
Set the colour of unlit areas. Black is pitch dark, white is fully lit.
Definition lighting.cpp:340
void draw_light(const Light &light)
Definition lighting.cpp:529
const asw::Texture & gradient(asw::Falloff falloff)
Definition lighting.cpp:426
void set_camera(const asw::Camera *camera)
Take positions in world space and convert them with a camera. The camera must outlive the light map,...
Definition lighting.cpp:350
const std::vector< asw::Polygonf > & get_occluders() const
Get the occluders.
Definition lighting.cpp:411
bool has_blockers(const asw::Quad< float > &reach) const
Definition lighting.cpp:460
void draw_shadows(const asw::Vec2< float > &center, const asw::Quad< float > &reach)
Definition lighting.cpp:472
void draw()
Draw the light map and multiply it over the current render target.
Definition lighting.cpp:685
void add_tiles(const TileLight &tile_light, const asw::Vec2< float > &position={ })
Add tile lighting for this frame. The grid must outlive the next render() or draw().
Definition lighting.cpp:388
void update(float dt)
Advance flicker and pulse. Call once per update.
Definition lighting.cpp:365
void clear_occluders()
Remove all occluders.
Definition lighting.cpp:406
const asw::Texture & render()
Render the light map without drawing it, to draw it yourself. The render target is the same afterward...
Definition lighting.cpp:617
asw::Color get_ambient() const
Get the colour of unlit areas.
Definition lighting.cpp:345
asw::Vec2< float > to_screen(const asw::Vec2< float > &point) const
Definition lighting.cpp:416
void set_shadow_mode(ShadowMode mode)
Set how occluders block light.
Definition lighting.cpp:355
void add(const Light &light)
Add a light for this frame.
Definition lighting.cpp:377
ShadowMode get_shadow_mode() const
Get how occluders block light.
Definition lighting.cpp:360
Light that spreads across a grid of tiles and stops at solid tiles.
Definition lighting.h:121
bool is_solid(int x, int y) const
Get whether a tile blocks light. Tiles off the grid are solid.
Definition lighting.cpp:191
void compute()
Spread the lights and update the texture. Call after lights or solid tiles change.
Definition lighting.cpp:213
void add_light(int x, int y, const asw::Color &color)
Add a light at a tile.
Definition lighting.cpp:206
asw::Color get(int x, int y) const
Get the light at a tile after compute().
Definition lighting.cpp:318
asw::Vec2< float > get_size() const
Get the size of the grid in pixels.
Definition lighting.cpp:333
const asw::Texture & get_texture() const
Get the texture, with one pixel per tile. Empty before compute().
Definition lighting.cpp:328
void clear_lights()
Remove all lights.
Definition lighting.cpp:201
bool in_grid(int x, int y) const
Definition lighting.cpp:179
TileLight(int width, int height, float tile_size)
Create a grid with no solid tiles and no lights.
Definition lighting.cpp:169
void set_falloff(float amount)
Set how much light is lost per tile, from 0 to 1.
Definition lighting.cpp:196
void set_solid(int x, int y, bool solid)
Set whether a tile blocks light. A solid tile is lit, but does not pass light on.
Definition lighting.cpp:184
Display and window routines for the ASW library.
Routines for drawing sprites and primitives to the screen.
Easing functions for animations.
2D lighting with a light map, shadows and tile lighting
float wrap(float time, float length)
Definition lighting.cpp:45
void textured_fan(const asw::Texture &texture, const Vec2< float > &center, float radius, const Polygonf &points, const asw::Color &color, bool closed)
Definition lighting.cpp:60
Polygonf arc(const Vec2< float > &center, float radius, float direction, float cone)
Definition lighting.cpp:97
std::size_t cell(int x, int y, int width)
Definition lighting.cpp:111
asw::Color scale_color(const asw::Color &color, float amount)
Definition lighting.cpp:51
asw::Texture create_texture(int w, int h)
Create a Texture given the specified dimensions.
Definition assets.cpp:120
asw::Texture create_radial_gradient(int size, asw::Color inner, asw::Color outer, asw::Falloff falloff=asw::Falloff::Smooth)
Create a square texture with a radial gradient. The colour goes from inner at the centre to outer at ...
Definition assets.cpp:144
asw::Vec2< int > get_logical_size()
Get the logical size of the window. This may differ from the actual size if scaling is enabled.
Definition display.cpp:198
void set_render_target(const asw::Texture &texture)
Set the render target to the window.
Definition display.cpp:224
void clear()
Clear the window to black.
Definition display.cpp:242
asw::Renderer * get_renderer()
Get the SDL renderer.
Definition display.cpp:139
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 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 rect_fill(const asw::Quad< float > &position, asw::Color color)
Draw a filled rectangle.
Definition draw.cpp:536
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
float smoothstep(float t)
Soft at both ends, 3t^2 - 2t^3.
Definition easing.cpp:13
Polygonf visibility(const Vec2f &from, float radius, const std::vector< Polygonf > &occluders, float direction=0.0F, float cone=0.0F)
Find the area that can be seen from a point.
Definition geometry.cpp:104
Vec2< T >::Real signed_area(const Polygon< T > &polygon)
Get the signed area of a polygon.
Definition geometry.h:751
Quad< T > bounds(const Polygon< T > &polygon)
Get the smallest rectangle that holds every corner of a polygon.
Definition geometry.h:766
ShadowMode
How lights are blocked by occluders.
Definition lighting.h:26
@ None
Light goes through occluders.
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
Falloff
How a gradient or light fades from its centre to its edge.
Definition types.h:46
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
static uint8_t to_channel(float value)
Round a 0-255 channel value and clamp it into range. Casting an out of range float straight to uint8_...
Definition color.h:25
uint8_t g
Definition color.h:31
FColor to_fcolor() const
To a float colour, with each channel from 0.0 to 1.0.
Definition color.h:77
A light. Positions are in world space when the light map has a camera, otherwise in screen space.
Definition lighting.h:42
float pulse
Size of a steady pulse from 0 to 1.
Definition lighting.h:72
asw::Color color
Colour of the light.
Definition lighting.h:50
float flicker
Random flicker from 0 to 1. 0.2 suits a torch.
Definition lighting.h:69
float cone
Width of a spot light's beam, in radians. 0 lights all round.
Definition lighting.h:63
float intensity
Brightness from 0 to 1. Scales the colour.
Definition lighting.h:53
float direction
Direction a spot light points, in radians. 0 is right, and angles turn clockwise on screen.
Definition lighting.h:60
bool shadows
Whether occluders block this light.
Definition lighting.h:66
asw::Vec2< float > position
Centre of the light.
Definition lighting.h:44
float pulse_speed
Pulses per second.
Definition lighting.h:75
float radius
Distance the light reaches, in pixels.
Definition lighting.h:47
int seed
Gives lights their own flicker and pulse timing. Use a different seed for each light.
Definition lighting.h:79
asw::Falloff falloff
How the light fades to its edge.
Definition lighting.h:56