Reworked CalculateTriangles, fixed descend collision

Descend collision was broken because of two reasons - one complicated and
one easy. Firstly, we would not re-visit beams after eliminations if they
were closer to the light source than the remaining beam. Especially nasty
because the comments claimed the opposite. Secondly, the coordinates
passed to find_cross were actually flipped.

I took the opportunity to clean up the control structure a bit, update
the comments (brr), and fix the bug in one swoop.
alternate-lights
Peter Wortmann 2015-02-22 23:07:24 +01:00
parent 52a2f4bb8e
commit d976a4b2a7
1 changed files with 106 additions and 61 deletions

View File

@ -468,8 +468,11 @@ std::list<C4FoWBeamTriangle> C4FoWLightSection::CalculateTriangles(C4FoWRegion *
float scanLevel = 0;
for (int step = 0; step < 100000; step++)
{
// Find the beam to project. This makes this whole algorithm O(n²),
// but I see no other way to make the whole thing robust :/
// Find the beam to project. The whole idea here is that we reduce the
// beams starting with the closest ones. Reason for this is that they
// generally shadow the most, and this makes it easy to make the
// algorithm robust against light size depending on distance. Sadly
// makes the whole algorithm O(n^2)...
float bestLevel = FLT_MAX;
for (std::list<C4FoWBeamTriangle>::iterator it = result.begin(), nextIt; it != --result.end(); ++it)
{
@ -483,11 +486,16 @@ std::list<C4FoWBeamTriangle> C4FoWLightSection::CalculateTriangles(C4FoWRegion *
break;
scanLevel = bestLevel;
// Now look for all beams at this length. Will propably be only one
// most of the time, but can't be too careful. Especially note that
// we will make extra loops after removing rays, so we can check the
// new neighbouring relation for consistency.
for(std::list<C4FoWBeamTriangle>::iterator it = result.begin(), nextIt; it != --result.end(); it = nextIt)
{
nextIt = it; ++nextIt;
C4FoWBeamTriangle &tri = *it, &nextTri = *nextIt;
C4FoWBeamTriangle tri = *it, nextTri = *nextIt;
// Skip ray pairs that do not match the current level (see above)
float level = Min(tri.fanRY, nextTri.fanLY);
if(level != bestLevel)
continue;
@ -535,6 +543,7 @@ std::list<C4FoWBeamTriangle> C4FoWLightSection::CalculateTriangles(C4FoWRegion *
assert(tri.fanRY <= tri.fanLY);
tri.fanLX = tri.fanRX;
tri.fanLY = tri.fanRY;
*it = tri;
}
// The threshold decides at what point we are going to eliminate
@ -559,7 +568,6 @@ std::list<C4FoWBeamTriangle> C4FoWLightSection::CalculateTriangles(C4FoWRegion *
}
// Move right point of left beam to the left (the original point is partly shadowed)
bool eliminate = false;
float b;
bool f = find_cross(lightRX, lightRY, nextTri.fanLX, nextTri.fanLY,
tri.fanLX, tri.fanLY, fanRXp, tri.fanRY,
@ -576,7 +584,31 @@ std::list<C4FoWBeamTriangle> C4FoWLightSection::CalculateTriangles(C4FoWRegion *
// shadowed, and we don't need to draw it.
if (b >= threshold)
{
eliminate = true;
// Can't eliminate it?
if (it == result.begin())
continue;
// Remove the beam.
it = nextIt = result.erase(it);
tri = *--it;
// Now decide how to proceed: If the new previous ray (it)
// is farther away, we have to repeat this whole check
// because this one (nextIt) might shadow it as well.
if (tri.fanRY > nextTri.fanLY)
{
nextIt = it;
continue;
}
// However if the previous one is *closer*, this means we
// cannot possible shadow it. Furthermore, we know that
// it cannot shadow this one either. The fact that there was
// a beam between the two means that there is now a "hole"
// that needs to be filled. Hence we have a descend
// collision. We could get there by looping like above,
// but this is more elegant.
descendCollision = true;
LightBallLeftMostPoint(tri.fanLX, tri.fanLY, lightLX, lightLY);
// Cross point actually right of surface? This can happen when
// we eliminated surfaces. It means that the light doesn't reach
@ -597,23 +629,10 @@ std::list<C4FoWBeamTriangle> C4FoWLightSection::CalculateTriangles(C4FoWRegion *
// Set cross point
tri.fanRX = crossX;
tri.fanRY = crossY;
}
// This shouldn't change the case we are in (uh, I think)
assert(tri.fanRY > nextTri.fanLY);
// Did we eliminate the surface with this step?
if (eliminate && it != result.begin())
{
// With the elimination, we need to re-process the last
// beam, as it might be more shadowed than we realized.
// Note that the last point might have been projected already -
// but that's okay
nextIt = it; nextIt--;
// Remove it (note that this invalidates "it". The iterator,
// that is.)
result.erase(it);
// This shouldn't change the case we are in (uh, I think)
assert(tri.fanRY > nextTri.fanLY);
// Write back
*it = tri;
continue;
}
@ -637,6 +656,7 @@ std::list<C4FoWBeamTriangle> C4FoWLightSection::CalculateTriangles(C4FoWRegion *
assert(nextTri.fanLY <= nextTri.fanRY);
nextTri.fanRX = nextTri.fanLX;
nextTri.fanRY = nextTri.fanLY;
*nextIt = nextTri;
}
float fanRXp = nextTri.fanRX;
float threshold = 0.0f;
@ -645,7 +665,6 @@ std::list<C4FoWBeamTriangle> C4FoWLightSection::CalculateTriangles(C4FoWRegion *
if (nextTri.fanRX == nextTri.fanLX && nextTri.fanRY == nextTri.fanLY)
fanRXp += 1.0;
bool eliminate = false;
float b;
bool f = find_cross(lightLX, lightLY, tri.fanRX, tri.fanRY,
nextTri.fanLX, nextTri.fanLY, fanRXp, nextTri.fanRY,
@ -656,7 +675,17 @@ std::list<C4FoWBeamTriangle> C4FoWLightSection::CalculateTriangles(C4FoWRegion *
assert(f);
if (b <= threshold)
{
eliminate = true;
if (nextIt == --result.end())
continue;
nextIt = result.erase(nextIt);
nextTri = *nextIt;
if (nextTri.fanLY > tri.fanRY)
{
nextIt = it;
continue;
}
descendCollision = true;
LightBallRightMostPoint(nextTri.fanLX, nextTri.fanLY, lightRX, lightRY);
}
else if (b > 1.0)
{
@ -666,56 +695,72 @@ std::list<C4FoWBeamTriangle> C4FoWLightSection::CalculateTriangles(C4FoWRegion *
{
nextTri.fanLX = crossX;
nextTri.fanLY = crossY;
}
assert(tri.fanRY < nextTri.fanLY);
if (eliminate && nextIt != --result.end())
{
// We remove the next triangle, so re-process this one.
nextIt = it;
it++;
result.erase(it);
assert(tri.fanRY < nextTri.fanLY);
*nextIt = nextTri;
continue;
}
} else { // tri.fanRY == nextTri.fanLY
// Check whether we have a significant gap
if (nextTri.fanLX - tri.fanRX > 0.5) {
descendCollision = true;
} else {
// Nothing to do
continue;
}
}
if (descendCollision)
{
// Should never be parallel -- otherwise we wouldn't be here
// in the first place.
bool f = find_cross(lightRX, lightRY, tri.fanRX, tri.fanRY,
lightLX, lightLY, nextTri.fanLX, nextTri.fanLY,
&crossX, &crossY);
assert(f);
// We should only reach this place with a descend collision
assert(descendCollision);
// Ensure some minimum distance to existing points - don't
// bother with too small bumps. This also catches some floating
// point inacurracies.
const float descendEta = 0.5;
if (crossY <= tri.fanRY + descendEta || crossY <= nextTri.fanLY + descendEta)
continue;
// Should never be parallel -- otherwise we wouldn't be here
// in the first place.
bool f = find_cross(lightLX, lightLY, tri.fanRX, tri.fanRY,
lightRX, lightRY, nextTri.fanLX, nextTri.fanLY,
&crossX, &crossY);
assert(f);
#ifdef FAN_STEP_DEBUG
LogSilentF("Collision, cross=%.02f/%.02f", crossX, crossY);
#endif
// This should always follow an elimination, but better check
assert(beamCount > result.size());
// Ensure some minimum distance to existing points - don't
// bother with too small bumps. This also catches some floating
// point inacurracies.
const float descendEta = 3;
if (crossY <= tri.fanRY + descendEta || crossY <= nextTri.fanLY + descendEta)
continue;
C4FoWBeamTriangle newTriangle;
newTriangle.fanLX = crossX;
newTriangle.fanLY = crossY;
newTriangle.fanRX = crossX;
newTriangle.fanRY = crossY;
// This should always follow an elimination, but better check
assert(beamCount > result.size());
nextIt = result.insert(nextIt, newTriangle);
C4FoWBeamTriangle newTriangle;
newTriangle.fanLX = crossX;
newTriangle.fanLY = crossY;
newTriangle.fanRX = crossX;
newTriangle.fanRY = crossY;
// Jump over surface. Note that our right beam might get
// eliminated later on, causing us to back-track into this
// zero-length pseudo-surface. This will cause find_cross
// above to eliminate the pseudo-surface and back-track
// further to the left, which is exactly how it should work.
++nextIt;
}
nextIt = result.insert(nextIt, newTriangle);
// Jump over surface. Note that our right beam might get
// eliminated later on, causing us to back-track into this
// zero-length pseudo-surface. This will cause find_cross
// above to eliminate the pseudo-surface and back-track
// further to the left, which is exactly how it should work.
++nextIt;
} // end for(std::list<C4FoWBeamTriangle>::iterator it = result.begin(), nextIt = it; it != --result.end(); ++it) loop
} // end for (int step = 0; step < 100000; step++) loop
#ifdef FAN_STEP_DEBUG
LogSilentF("Fan output");
for (std::list<C4FoWBeamTriangle>::iterator it2 = result.begin(); it2 != result.end(); it2++) {
LogSilentF(" %.010f %.010f", it2->fanLX, it2->fanLY);
LogSilentF(" %.010f %.010f", it2->fanRX, it2->fanRY);
}
#endif
// Phase 2: Calculate fade points
for (std::list<C4FoWBeamTriangle>::iterator it = result.begin(); it != result.end(); ++it)
{