/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2014-2015, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ #include "C4Include.h" #ifndef USE_CONSOLE #include "C4FoWLightSection.h" #include "C4FoWBeamTriangle.h" #include "C4FoWBeam.h" #include "C4FoWLight.h" #include "C4FoWRegion.h" #include "C4Landscape.h" #include "float.h" #include C4FoWLightSection::C4FoWLightSection(C4FoWLight *pLight, int r) : pLight(pLight), iRot(r) { // Rotation matrices iRot = r % 360; switch(iRot % 360) { default: assert(false); case 0: a = 1; b = 0; c = 0; d = 1; ra = 1; rb = 0; rc = 0; rd = 1; break; case 90: a = 0; b = 1; c = -1; d = 0; ra = 0; rb = -1; rc = 1; rd = 0; break; case 180: a = -1; b = 0; c = 0; d = -1; ra = -1; rb = 0; rc = 0; rd = -1; break; case 270: a = 0; b = -1; c =1; d = 0; ra = 0; rb = 1; rc = -1; rd = 0; break; } // Beam list pBeams = new C4FoWBeam(-1, +1, +1, +1); } C4FoWLightSection::~C4FoWLightSection() { ClearBeams(); } inline void C4FoWLightSection::LightBallExtremePoint(float x, float y, float dir, float &lightX, float &lightY) const { float d = sqrt(x * x + y * y); float s = Min(float(pLight->getSize()), d / 5.0f); lightX = dir * y * s / d; lightY = dir * -x * s / d; } inline void C4FoWLightSection::LightBallRightMostPoint(float x, float y, float &lightX, float &lightY) const { LightBallExtremePoint(x,y,+1.0f,lightX,lightY); } inline void C4FoWLightSection::LightBallLeftMostPoint(float x, float y, float &lightX, float &lightY) const { LightBallExtremePoint(x,y,-1.0f,lightX,lightY); } template T C4FoWLightSection::transDX(T dx, T dy) const { return T(a) * dx + T(b) * dy; } template T C4FoWLightSection::transDY(T dx, T dy) const { return T(c) * dx + T(d) * dy; } template T C4FoWLightSection::transX(T x, T y) const { return transDX(x, y) + T(pLight->getX()); } template T C4FoWLightSection::transY(T x, T y) const { return transDY(x, y) + T(pLight->getY()); } template T C4FoWLightSection::rtransDX(T dx, T dy) const { return T(ra) * dx + T(rb) * dy; } template T C4FoWLightSection::rtransDY(T dx, T dy) const { return T(rc) * dx + T(rd) * dy; } template T C4FoWLightSection::rtransX(T x, T y) const { return rtransDX(x-T(pLight->getX()),y-T(pLight->getY())); } template T C4FoWLightSection::rtransY(T x, T y) const { return rtransDY(x-T(pLight->getX()),y-T(pLight->getY())); } void C4FoWLightSection::ClearBeams() { while (C4FoWBeam *beam = pBeams) { pBeams = beam->getNext(); delete beam; } } void C4FoWLightSection::Prune(int32_t reach) { if (reach == 0) { ClearBeams(); pBeams = new C4FoWBeam(-1, 1, 1, 1); return; } // TODO PeterW: Merge active pBeams that we have pruned to same length for (C4FoWBeam *beam = pBeams; beam; beam = beam->getNext()) beam->Prune(reach); } void C4FoWLightSection::Dirty(int32_t reach) { for (C4FoWBeam *beam = pBeams; beam; beam = beam->getNext()) if (beam->getLeftEndY() >= reach || beam->getRightEndY() >= reach) beam->Dirty(Min(beam->getLeftEndY(), beam->getRightEndY())); } C4FoWBeam *C4FoWLightSection::FindBeamLeftOf(int32_t x, int32_t y) const { // Trivial y = Max(y, 0); if (!pBeams || !pBeams->isRight(x, y)) return NULL; // Go through list // Note: In case this turns out expensive, one might think about implementing // a skip-list. But I highly doubt it. C4FoWBeam *beam = pBeams; while (beam->getNext() && beam->getNext()->isRight(x, y)) beam = beam->getNext(); return beam; } void C4FoWLightSection::Update(C4Rect RectIn) { // Transform rectangle into our coordinate system C4Rect Rect = rtransRect(RectIn); C4Rect Bounds = rtransRect(C4Rect(0,0,GBackWdt,GBackHgt)); #ifdef LIGHT_DEBUG if (!::Game.iTick255) { LogSilentF("Full beam list:"); StdStrBuf beamsString; for(C4FoWBeam *beam = pBeams; beam; beam = beam->getNext()) { beamsString.ApendBeamChar(' '); beamsString.ApendBeam(beam->getDesc()); } LogSilent(beamsString.getData()); } #endif #ifdef LIGHT_DEBUG LogSilentF("Update %d/%d-%d/%d", Rect.x, Rect.y, Rect.x+Rect.Wdt, Rect.y+Rect.Hgt); #endif // Out of reach? if (Rect.y > pLight->getTotalReach()) return; // will be clipped on rendering anyway, don't bother to run the algorithm if (Rect.y + Rect.Hgt < 0) return; // Get last beam that's positively *not* affected int32_t ly = RectLeftMostY(Rect), lx = RectLeftMostX(Rect), ry = RectRightMostY(Rect), rx = RectRightMostX(Rect); C4FoWBeam *startBeam = FindBeamLeftOf(lx, ly); // Skip clean beams while (C4FoWBeam *nextBeam = startBeam ? startBeam->getNext() : pBeams) { if (nextBeam->isDirty()) break; startBeam = nextBeam; } // Find end beam, determine at which position we have to start scanning C4FoWBeam *beam = startBeam ? startBeam->getNext() : pBeams; #ifdef LIGHT_DEBUG if (beam) LogSilentF("Start beam is %s", beam->getDesc().getData()); #endif C4FoWBeam *endBeam = NULL; int32_t startY = Rect.GetBottom(); while (beam && !beam->isLeft(rx, ry)) { if (beam->isDirty() && beam->getLeftEndY() <= Rect.y + Rect.Hgt) { endBeam = beam; startY = Min(startY, beam->getLeftEndY()); } beam = beam->getNext(); } // Can skip scan completely? if (!endBeam) return; // Update right end coordinates #ifdef LIGHT_DEBUG LogSilentF("End beam is %s", endBeam->getDesc().getData()); #endif if (endBeam->isRight(rx, ry)) { rx = endBeam->getRightEndX() + 1; ry = endBeam->getRightEndY(); // We want endBeam itself to get scanned //assert(!endBeam->isLeft(rx, ry)); } #ifdef LIGHT_DEBUG //LogSilentF("Right limit at %d, start line %d", 1000 * (iRX - iX) / (iRY - iY), iStartY); #endif // Bottom of scan - either bound by rectangle or by light's reach int32_t endY = Min(Rect.GetBottom(), pLight->getReach()); // Scan lines int32_t y; for(y = Max(0, startY); y < endY; y++) { // We ignore all material up to a certain distance. This is the X // range for that in this scan line int32_t ignoreX = 0; if (y < pLight->getSize()) { ignoreX = int(sqrt(pLight->getSize() * pLight->getSize() - y * y)); } // Scan all pBeams C4FoWBeam *lastBeam = startBeam; int32_t dirty = 0; for(C4FoWBeam *beam = startBeam ? startBeam->getNext() : pBeams; beam; lastBeam = beam, beam = beam->getNext()) { assert(lastBeam ? lastBeam->getNext() == beam : beam == pBeams); // Clean (enough)? if (!beam->isDirty() || y < beam->getLeftEndY()) continue; // Out left? if (beam->isRight(lx, ly)) continue; // Out right? if (beam->isLeft(rx, ry)) break; // We have an active beam that we're about to scan dirty++; beam->Dirty(y+1); // Do a scan int32_t xl = Max(beam->getLeftX(y), Bounds.x), xr = Min(beam->getRightX(y), Bounds.x+Bounds.Wdt-1); for(int32_t x = xl; x <= xr; x++) { // Ignore material up to a certain distance (see above) if (abs(x) < ignoreX) continue; // Fast free? if (!Landscape._FastSolidCheck(transX(x,y), transY(x,y))) { if (a == 1) x = rtransX(Landscape.FastSolidCheckNextX(transX(x,y)) - 1, 0); continue; } // Free? if (!GBackSolid(transX(x,y), transY(x,y))) continue; // Split points int32_t x1 = x - 1, x2 = x + 1; bool splitLeft = !beam->isLeft(x1, y); bool splitRight = !beam->isRight(x2, y); // Double merge? if (!splitLeft && !splitRight && lastBeam && beam->getNext()) { if(lastBeam->EliminateRight(x,y)) { beam = lastBeam; break; // no typo. fSplitRight => x == xr } } // Merge possible? if (!splitLeft && splitRight && lastBeam) if (lastBeam->MergeRight(x2, y)) { beam->SetLeft(x2, y); assert(beam->isDirty()); continue; } if (splitLeft && !splitRight && beam->getNext()) if (beam->getNext()->MergeLeft(x1, y)) { beam->SetRight(x1, y); break; // no typo. fSplitRight => x == xr } // Split out left if (splitLeft) { lastBeam = beam; beam = lastBeam->Split(x1, y); assert(lastBeam->getNext() == beam); } // Split out right if(splitRight) { lastBeam = beam; beam = lastBeam->Split(x2, y); assert(lastBeam->getNext() == beam); // Deactivate left/middle beam lastBeam->Clean(y); assert(beam->isDirty()); } else { // Deactivate beam beam->Clean(y); break; } } } // No active pBeams left? if (!dirty) break; } // At end of light's reach? Mark all pBeams that got scanned all the way to the end as clean. // There's no need to scan them anymore. if (y >= pLight->getReach()) { for (C4FoWBeam *beam = startBeam ? startBeam->getNext() : pBeams; beam; beam = beam->getNext()) if (beam->isDirty() && beam->getLeftEndY() > pLight->getReach()) beam->Clean(pLight->getReach()); } #ifdef LIGHT_DEBUG LogSilentF("Updated beam list:"); for(C4FoWBeam *beam = startBeam ? startBeam->getNext() : pBeams; beam; beam = beam->getNext()) { if (beam->isLeft(iRX, iRY)) break; LogSilent(beam->getDesc().getData()); } #endif } bool C4FoWLightSection::isConsistent() const { return (a * c + b * d == 1) && (ra * rc + rb * rd == 1) && (a * ra + b * rc == 1) && (a * rb + b * rd == 0) && (c * ra + d * rc == 0) && (c * rb + d * rd == 1); } void C4FoWLightSection::Invalidate(C4Rect r) { // Assume normalized rectangle assert(r.Wdt > 0 && r.Hgt > 0); // Get rectangle corners that bound the possibly affected pBeams int32_t ly = RectLeftMostY(r), lx = RectLeftMostX(r), ry = RectRightMostY(r), rx = RectRightMostX(r); C4FoWBeam *lastBeam = FindBeamLeftOf(lx, ly); C4FoWBeam *beam = lastBeam ? lastBeam->getNext() : pBeams; // Scan over beams while (beam && !beam->isLeft(rx, ry)) { // Dirty beam? if (beam->getLeftEndY() > r.y || beam->getRightEndY() > r.y) beam->Dirty(r.y); // Merge with last beam? if (lastBeam && lastBeam->isDirty() && beam->isDirty()) { lastBeam->MergeDirty(); beam = lastBeam->getNext(); } else // Advance otherwise { lastBeam = beam; beam = beam->getNext(); } } // Final check for merging dirty pBeams on the right end if (lastBeam && beam && lastBeam->isDirty() && beam->isDirty()) lastBeam->MergeDirty(); } int32_t C4FoWLightSection::FindBeamsClipped(const C4Rect &rect, C4FoWBeam *&firstBeam, C4FoWBeam *&endBeam) const { if(rect.y + rect.Hgt < 0) return 0; int32_t ly = RectLeftMostY(rect), lx = RectLeftMostX(rect), ry = RectRightMostY(rect), rx = RectRightMostX(rect); C4FoWBeam *pPrev = FindBeamLeftOf(lx, ly); firstBeam = pPrev ? pPrev->getNext() : pBeams; // Find end beam - determine the number of pBeams we actually need to draw C4FoWBeam *beam = firstBeam; int32_t beamCount = 0; while (beam && !beam->isLeft(rx, ry)) { beam = beam->getNext(); beamCount++; } endBeam = beam; return beamCount; } // Gives the unique point where the line through (x1,y1) and (x2,y2) crosses // through the line through (x3,y3) and (x4,y4). Returns false if the point does // not exist or the two lines are parallel. static inline bool find_cross(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float *px, float *py, float *pb = NULL) { // We are looking for a, b so that // px = a*x1 + (1-a)*x2 = b*x3 + (1-b)*x4 // py = a*y1 + (1-a)*y2 = b*y3 + (1-b)*y4 // Cross product float d = (x3-x4)*(y1-y2) - (y3-y4)*(x1-x2); if (d == 0) return false; // parallel - or vector(s) 0 // We actually just need b - the unique solution // to above equation. A refreshing piece of elementary math // that I got wrong two times. float b = ((y4-y2)*(x1-x2) - (x4-x2)*(y1-y2)) / d; *px = b*x3 + (1-b)*x4; *py = b*y3 + (1-b)*y4; if (pb) *pb = b; return true; } std::list C4FoWLightSection::CalculateTriangles(C4FoWRegion *region) const { C4FoWBeam *startBeam = NULL, *endBeam = NULL; int32_t beamCount = FindBeamsClipped(rtransRect(region->getRegion()), startBeam, endBeam); std::list result; float crossX=0.0f, crossY=0.0f; // no beams inside the rectangle? Good, nothing to render if(!beamCount) return result; bool isStartClipped = startBeam != pBeams; bool isEndClipped = !!endBeam; C4FoWBeam *beam = startBeam; for (int32_t i = 0; i < beamCount; i++, beam = beam->getNext()) { C4FoWBeamTriangle tri; tri.fanLX = beam->getLeftEndXf(); tri.fanLY = float(beam->getLeftEndY()); tri.fanRX = beam->getRightEndXf(); tri.fanRY = float(beam->getRightEndY()); if(i == 0 && isStartClipped) tri.clipLeft = true; if(i == beamCount - 1 && isEndClipped) { tri.clipRight = true; } if(tri.fanLX != tri.fanRX || tri.fanLY != tri.fanRY) result.push_back(tri); } if(result.empty()) return result; // Phase 1: Project lower point so it lies on a line with outer left/right // light lines. float scanLevel = 0; for (int step = 0; step < 100000; step++) { // 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) { nextIt = it; ++nextIt; float level = Min(it->fanRY, nextIt->fanLY); if (level <= scanLevel || level >= bestLevel) continue; bestLevel = level; } if(bestLevel == FLT_MAX) 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; // Skip ray pairs that do not match the current level (see above) float level = Min(tri.fanRY, nextTri.fanLY); if(level != bestLevel) continue; // Debugging //#define FAN_STEP_DEBUG #ifdef FAN_STEP_DEBUG LogSilentF("Fan step %d (i=%d)", step, std::distance(result.begin(),it)); for (std::list::iterator it2 = result.begin(); it2 != result.end(); it2++) { const char *marker = ""; if (it2 == it) marker = " (it)"; if (it2 == nextIt) marker = " (nextIt)"; LogSilentF(" %.010f %.010f%s", it2->fanLX, it2->fanLY, marker); LogSilentF(" %.010f %.010f%s", it2->fanRX, it2->fanRY, marker); } #endif // Calculate light bounds. We assume a "smaller" light for closer beams float lightLX, lightLY, lightRX, lightRY; LightBallLeftMostPoint(tri.fanRX, tri.fanRY, lightLX, lightLY); LightBallRightMostPoint(nextTri.fanLX, nextTri.fanLY, lightRX, lightRY); // Ascending bool descendCollision = false; const float ascendDescendDelta = 0.1f; if (tri.fanRY > nextTri.fanLY) { // Left beam surface self-shadowing? We test whether the scalar product // of the beam's normal and the light vector is positive. if ( (tri.fanRY - tri.fanLY) * (nextTri.fanLX - lightRX) >= (tri.fanRX - tri.fanLX) * (nextTri.fanLY - lightRY)) { // Reduce to upper point (Yep, we know that the upper point // must be the right one. Try to figure out why!) 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 // (see below). Our goal is to not reduce the ray width below // a certain eta. We are using Manhattan distance, which is a // preliminary optimisation, but we *like* being evil here. float threshold = 1.0f; float fanWidth = fabs(tri.fanRX - tri.fanLX) + fabs(tri.fanRY - tri.fanLY); threshold -= ascendDescendDelta / fanWidth; // Left beam reduced? float fanRXp = tri.fanRX; if (tri.fanRX == tri.fanLX && tri.fanRY == tri.fanLY) { // Move point to the right for the purpose of finding the cross // point - after all, given that tri.fanRX == tri.fanLX, we // only care about whether to eliminate or insert an additional // point for the descend collision, so the exact value doesn't // really matter - but we don't want find_cross to bail out! fanRXp += 1.0; threshold = 0.0; } // Move right point of left beam to the left (the original point is partly shadowed) float b; bool f = find_cross(lightRX, lightRY, nextTri.fanLX, nextTri.fanLY, tri.fanLX, tri.fanLY, fanRXp, tri.fanRY, &crossX, &crossY, &b); #ifdef FAN_STEP_DEBUG LogSilentF("Ascend, b=%.010f, cross=%.010f/%.010f", b, crossX, crossY); #endif // The self-shadow-check should have made sure that the two are // never parallel. assert(f); (void)f; // Cross point to left of surface? Then the surface itself is // shadowed, and we don't need to draw it. if (b >= threshold) { // 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 // down far enough between this and the next beam to hit anything. // As a result, we insert a new zero-width surface where the light // stops. } else if (b < 0.0) { // As it doesn't matter from this point on whether we were // in an ascend or a descend case, this gets handled top-level. // ... still debating with myself whether a "goto" would be // cleaner here ;) descendCollision = true; } else { // 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); // Write back *it = tri; continue; } // Descending - same, but mirrored. And without comments. } else if (tri.fanRY < nextTri.fanLY) { if ( (nextTri.fanRY - nextTri.fanLY) * (tri.fanRX - lightLX) >= (nextTri.fanRX - nextTri.fanLX) * (tri.fanRY - lightLY)) { assert(nextTri.fanLY <= nextTri.fanRY); nextTri.fanRX = nextTri.fanLX; nextTri.fanRY = nextTri.fanLY; *nextIt = nextTri; } float fanRXp = nextTri.fanRX; float threshold = 0.0f; float fanWidth = fabs(nextTri.fanRX - nextTri.fanLX) + fabs(nextTri.fanRY - nextTri.fanLY); threshold += ascendDescendDelta / fanWidth; if (nextTri.fanRX == nextTri.fanLX && nextTri.fanRY == nextTri.fanLY) fanRXp += 1.0; float b; bool f = find_cross(lightLX, lightLY, tri.fanRX, tri.fanRY, nextTri.fanLX, nextTri.fanLY, fanRXp, nextTri.fanRY, &crossX, &crossY, &b); #ifdef FAN_STEP_DEBUG LogSilentF("Descend, b=%.010f, cross=%.010f/%.010f", b, crossX, crossY); #endif assert(f); if (b <= threshold) { 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) { descendCollision = true; } else { nextTri.fanLX = crossX; nextTri.fanLY = crossY; 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; } } // We should only reach this place with a descend collision assert(descendCollision); // 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 // 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; // This should always follow an elimination, but better check assert(beamCount > result.size()); C4FoWBeamTriangle newTriangle; newTriangle.fanLX = crossX; newTriangle.fanLY = crossY; newTriangle.fanRX = crossX; newTriangle.fanRY = crossY; 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) { C4FoWBeamTriangle &tri = *it; // Calculate light bounds. Note that the way light size is calculated // and we are using it below, we need to consider an "asymetrical" light. float lightLX, lightLY, lightRX, lightRY; LightBallLeftMostPoint(tri.fanLX, tri.fanLY, lightLX, lightLY); LightBallRightMostPoint(tri.fanRX, tri.fanRY, lightRX, lightRY); // This is simply the projection of the left point using the left-most // light point, as well as the projection of the right point using the // right-most light point. // For once we actually calculate this using the real distance float dx = tri.fanLX - lightLX; float dy = tri.fanLY - lightLY; float d = float(pLight->getFadeout()) / sqrt(dx*dx + dy*dy); tri.fadeLX = tri.fanLX + d * dx; tri.fadeLY = tri.fanLY + d * dy; dx = tri.fanRX - lightRX; dy = tri.fanRY - lightRY; d = float(pLight->getFadeout()) / sqrt(dx*dx + dy*dy); tri.fadeRX = tri.fanRX + d * dx; tri.fadeRY = tri.fanRY + d * dy; // Do the fades cross? const double fadeCrossEta = 0.01; if ((tri.fadeRX - lightRX) / (tri.fadeRY - lightRY) < (tri.fadeLX - lightRX) / (tri.fadeLY - lightRY) + fadeCrossEta) { // Average it tri.fadeLX = tri.fadeRX = (tri.fadeLX + tri.fadeRX) / 2; tri.fadeLY = tri.fadeRY = (tri.fadeLY + tri.fadeRY) / 2; } } transTriangles(result); return result; } void C4FoWLightSection::transTriangles(std::list &triangles) const { for (std::list::iterator it = triangles.begin(); it != triangles.end(); ++it) { C4FoWBeamTriangle &tri = *it; float x,y; x = tri.fanRX, y = tri.fanRY; tri.fanRX = transX(x,y); tri.fanRY = transY(x,y); x = tri.fanLX, y = tri.fanLY; tri.fanLX = transX(x,y); tri.fanLY = transY(x,y); x = tri.fadeRX, y = tri.fadeRY; tri.fadeRX = transX(x,y); tri.fadeRY = transY(x,y); x = tri.fadeLX, y = tri.fadeLY; tri.fadeLX = transX(x,y); tri.fadeLY = transY(x,y); } } void C4FoWLightSection::CompileFunc(StdCompiler *pComp) { pComp->Value(mkNamingAdapt(iRot, "iRot")); pComp->Value(mkNamingAdapt(a, "a")); pComp->Value(mkNamingAdapt(b, "b")); pComp->Value(mkNamingAdapt(c, "c")); pComp->Value(mkNamingAdapt(d, "d")); pComp->Value(mkNamingAdapt(ra, "ra")); pComp->Value(mkNamingAdapt(rb, "rb")); pComp->Value(mkNamingAdapt(rc, "rc")); pComp->Value(mkNamingAdapt(rd, "rd")); if (pComp->isDecompiler()) { for (C4FoWBeam *beam = pBeams; beam; beam = beam->getNext()) pComp->Value(mkNamingAdapt(*beam, "Beam")); } else { ClearBeams(); int32_t beam_count = 0; pComp->Value(mkNamingCountAdapt(beam_count, "Beam")); C4FoWBeam *last_beam = NULL; for (int32_t i = 0; i < beam_count; ++i) { std::unique_ptr beam(new C4FoWBeam(0, 0, 0, 0)); pComp->Value(mkNamingAdapt(*beam, "Beam")); C4FoWBeam *new_beam = beam.release(); if (!last_beam) pBeams = new_beam; else last_beam->setNext(new_beam); last_beam = new_beam; } } } #endif