Algorithm is:
For each light for each convex hull do:
1. find two points that will create lines with biggest angle when a
2. take curve enclosed between these points, including them
3. add 3 points: one taken from line made by light point and last edge point, one taken from line made by light point and avg of hull vertices and one taken from line made by light point and last edge point. Take them from far away the light point so that they lie at least 2* radius of light away from it, to be safe.
4. This polygon is the hard shadow for this hull made by this light. That's it.
I won't pretend to understand exactly what you're describing there, but it sounds overly complicated. To form hard edged shadow geometry, you can project (away form the light source) the end points of each non-facing edge. The quad formed by the original end points and the projected end points is then the shadow geometry.
Here is the most drain-dead-simple form I could come up with to demonstrate. To make the shadow always reach the edge of the screen, you'd simply project the end points further out.
#include <SFML/Graphics.hpp>
sf::VertexArray shadow(const sf::Shape& caster, sf::Vector2f emitter, sf::Color ambient)
{
sf::VertexArray geometry(sf::Quads);
// for each edge of 'shape'.
const unsigned size = caster.getPointCount();
for (unsigned i = size - 1, j = 0; j < size; i = j++)
{
const auto& xf = caster.getTransform();
// find world space end points of current edge.
auto a = xf.transformPoint(caster.getPoint(i));
auto b = xf.transformPoint(caster.getPoint(j));
auto ab = b - a;
auto ea = a - emitter;
// if current egde faces away from emitter.
if ((ab.x * ea.y - ab.y * ea.x) < 0.0f)
{
// make quad from edge end points and projected end points.
geometry.append(sf::Vertex(a, ambient));
geometry.append(sf::Vertex(a + ea, ambient));
geometry.append(sf::Vertex(b + (b - emitter), ambient));
geometry.append(sf::Vertex(b, ambient));
}
}
return geometry;
}
int main()
{
sf::RenderWindow window(sf::VideoMode(800, 600), "Shadows");
window.setFramerateLimit(30);
// Light blocker.
sf::RectangleShape shape(sf::Vector2f(100.0f, 100.0f));
shape.setFillColor(sf::Color::Blue);
shape.setPosition(250.0f, 250.0f);
shape.setRotation(60.0f);
sf::Vector2f emitter; // Position of light emitter.
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
{
window.close();
}
else if (event.type == sf::Event::MouseMoved)
{
// Emitter position tracks mouse pointer.
emitter = sf::Vector2f(
static_cast<float>(event.mouseMove.x),
static_cast<float>(event.mouseMove.y)
);
}
}
window.clear(sf::Color::White);
// Draw shadows cast by 'shape'.
window.draw(shadow(shape, emitter, sf::Color::Black));
// Dwaw actual 'shape'.
window.draw(shape);
window.display();
}
return 0;
}
The real tricky part is forming an image composed of multiple, coloured, shadow casting lights using only the blend modes provided by SFML.
I lost my lighting code in the great reformat of 2013. A sad time indeed. Your intuition looks right to me, though. This is from memory so please excuse any mistakes; in pseudo code, it goes something like:
Ambient:Color - The colour of "no light".
Geometry:VertexArray - Shadow geometry.
LightTex:Texture - Light falloff texture.
RT1:RenderTexture - Holds entire light map.
RT2:RenderTexture - Holds current light map pass.
Scene - Anything affected by light.
RT1.clear(Ambient)
foreach light in lights_on_screen()
Geometry.clear()
foreach shape in shapes_in_range_of(light)
Geometry.add(make_shadow(light, shape))
RT2.clear(Black)
RT2.draw(LightTex, BlendNone)
RT2.draw(Geometry, BlendNone)
RT1.draw(RT2, BlendAdd)
Screen.draw(Scene)
Screen.draw(RT1, BlendMultiply)