diff --git a/src/landscape/fow/C4FoWLightSection.cpp b/src/landscape/fow/C4FoWLightSection.cpp index 496b157df..0d21a1a7a 100644 --- a/src/landscape/fow/C4FoWLightSection.cpp +++ b/src/landscape/fow/C4FoWLightSection.cpp @@ -468,8 +468,11 @@ std::list 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::iterator it = result.begin(), nextIt; it != --result.end(); ++it) { @@ -483,11 +486,16 @@ std::list 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::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 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 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 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 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 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 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 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 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::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::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::iterator it = result.begin(); it != result.end(); ++it) {