openclonk/src/landscape/fow/C4FoWLightSection.cpp

985 lines
28 KiB
C++
Raw Blame History

#include "C4Include.h"
#include "C4FoWLightSection.h"
#include "C4FoWRay.h"
#include "C4FoWLight.h"
#include "C4FoWRegion.h"
#include "C4Landscape.h"
#include "C4DrawGL.h"
// Gives the point where the line through (x1,y1) and (x2,y2) crosses through the line
// through (x3,y3) and (x4,y4)
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;
// Sanity-test solution
#ifdef _DEBUG
if (x1 != x2) {
float a = (b*(x3-x4) + (x4-x2)) / (x1-x2);
float eps = 0.01f;
//assert(fabs(a*x1 + (1-a)*x2 - *px) < eps);
//assert(fabs(a*y1 + (1-a)*y2 - *py) < eps);
}
#endif
return true;
}
const float C4FoWSmooth = 8.0;
C4FoWLightSection::C4FoWLightSection(C4FoWLight *pLight, int r, C4FoWLightSection *pNext)
: pLight(pLight), iRot(r), pNext(pNext)
{
// 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;
}
// Ray list
pRays = new C4FoWRay(-1, +1, +1, +1);
}
C4FoWLightSection::~C4FoWLightSection()
{
ClearRays();
}
void C4FoWLightSection::ClearRays()
{
while (C4FoWRay *pRay = pRays) {
pRays = pRay->getNext();
delete pRay;
}
}
void C4FoWLightSection::Prune(int32_t iReach)
{
if (iReach == 0) {
ClearRays();
pRays = new C4FoWRay(-1, 1, 1, 1);
return;
}
// TODO: Merge active rays that we have pruned to same length
for (C4FoWRay *pRay = pRays; pRay; pRay = pRay->getNext())
pRay->Prune(iReach);
}
void C4FoWLightSection::Dirty(int32_t iReach)
{
for (C4FoWRay *pRay = pRays; pRay; pRay = pRay->getNext())
if (pRay->getLeftEndY() >= iReach || pRay->getRightEndY() >= iReach)
pRay->Dirty(Min(pRay->getLeftEndY(), pRay->getRightEndY()));
}
C4FoWRay *C4FoWLightSection::FindRayLeftOf(int32_t x, int32_t y)
{
// Trivial
y = Max(y, 0);
if (!pRays || !pRays->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.
C4FoWRay *pRay = pRays;
while (pRay->getNext() && pRay->getNext()->isRight(x, y))
pRay = pRay->getNext();
return pRay;
}
C4FoWRay *C4FoWLightSection::FindRayOver(int32_t x, int32_t y)
{
C4FoWRay *pPrev = FindRayLeftOf(x, y);
return pPrev ? pPrev->getNext() : pRays;
}
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 ray list:");
StdStrBuf Rays;
for(C4FoWRay *pRay = pRays; pRay; pRay = pRay->getNext()) {
Rays.AppendChar(' ');
Rays.Append(pRay->getDesc());
}
LogSilent(Rays.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;
// Get last ray that's positively *not* affected
int iLY = Max(0, RectLeftMostY(Rect)),
iRX = Rect.x+Rect.Wdt, iRY = Max(0, RectRightMostY(Rect));
C4FoWRay *pStart = FindRayLeftOf(Rect.x, iLY);
// Skip clean rays
while (C4FoWRay *pNext = pStart ? pStart->getNext() : pRays) {
if (pNext->isDirty()) break;
pStart = pNext;
}
// Find end ray, determine at which position we have to start scanning
C4FoWRay *pRay = pStart ? pStart->getNext() : pRays;
#ifdef LIGHT_DEBUG
if (pRay)
LogSilentF("Start ray is %s", pRay->getDesc().getData());
#endif
C4FoWRay *pEnd = NULL;
int32_t iStartY = Rect.GetBottom();
while (pRay && !pRay->isLeft(Rect.x+Rect.Wdt, iRY)) {
if (pRay->isDirty() && pRay->getLeftEndY() <= Rect.y+Rect.Hgt) {
pEnd = pRay;
iStartY = Min(iStartY, pRay->getLeftEndY());
}
pRay = pRay->getNext();
}
// Can skip scan completely?
if (!pEnd)
return;
// Update right end coordinates
#ifdef LIGHT_DEBUG
LogSilentF("End ray is %s", pEnd->getDesc().getData());
#endif
if (pEnd->isRight(iRX, iRY)) {
iRX = pEnd->getRightEndX() + 1;
iRY = pEnd->getRightEndY();
// We want pEnd itself to get scanned
//assert(!pEnd->isLeft(iRX, iRY));
}
#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 iEndY = Min(Rect.GetBottom(), pLight->getReach());
// Scan lines
int32_t y;
for(y = Max(0, iStartY); y < iEndY; y++) {
// Scan all rays
C4FoWRay *pLast = pStart; int32_t iDirty = 0;
for(C4FoWRay *pRay = pStart ? pStart->getNext() : pRays; pRay; pLast = pRay, pRay = pRay->getNext()) {
assert(pLast ? pLast->getNext() == pRay : pRay == pRays);
// Clean (enough)?
if (!pRay->isDirty() || y < pRay->getLeftEndY())
continue;
// Out left?
if (pRay->isRight(Rect.x, y))
continue;
// Out right?
if (pRay->isLeft(Rect.x + Rect.Wdt, y) || pRay->isLeft(iRX, iRY))
break;
// We have an active ray that we're about to scan
iDirty++;
pRay->Dirty(y+1);
// Do a scan
int32_t xl = Max(pRay->getLeftX(y), Bounds.x),
xr = Min(pRay->getRightX(y), Bounds.x+Bounds.Wdt-1);
for(int x = xl; x <= xr; x++) {
// 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
int x1 = x - 1, x2 = x + 1;
bool fSplitLeft = !pRay->isLeft(x1, y);
bool fSplitRight = !pRay->isRight(x2, y);
// Double merge?
if (!fSplitLeft && !fSplitRight && pLast && pRay->getNext()) {
if(pLast->Eliminate(x, y)) {
pRay = pLast;
break; // no typo. fSplitRight => x == xr
}
}
// Merge possible?
if (!fSplitLeft && fSplitRight && pLast)
if (pLast->MergeRight(x2, y)) {
pRay->SetLeft(x2, y);
assert(pRay->isDirty());
continue;
}
if (fSplitLeft && !fSplitRight && pRay->getNext())
if (pRay->getNext()->MergeLeft(x1, y)) {
pRay->SetRight(x1, y);
break; // no typo. fSplitRight => x == xr
}
// Split out left
if (fSplitLeft) {
pLast = pRay;
pRay = pLast->Split(x1, y);
assert(pLast->getNext() == pRay);
}
// Split out right
if(fSplitRight) {
pLast = pRay;
pRay = pLast->Split(x2, y);
assert(pLast->getNext() == pRay);
// Deactivate left/middle ray
pLast->Clean(y);
assert(pRay->isDirty());
} else {
// Deactivate ray
pRay->Clean(y);
break;
}
}
}
// No active rays left?
if (!iDirty)
break;
}
// At end of light's reach? Mark all rays that got scanned all the way to the end as clean.
// There's no need to scan them anymore.
if (y >= pLight->getReach()) {
for (C4FoWRay *pRay = pStart ? pStart->getNext() : pRays; pRay; pRay = pRay->getNext())
if (pRay->isDirty() && pRay->getLeftEndY() > pLight->getReach())
pRay->Clean(pLight->getReach());
}
#ifdef LIGHT_DEBUG
LogSilentF("Updated ray list:");
for(C4FoWRay *pRay = pStart ? pStart->getNext() : pRays; pRay; pRay = pRay->getNext()) {
if (pRay->isLeft(iRX, iRY))
break;
LogSilent(pRay->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 rays
int iLY = RectLeftMostY(r), iRY = RectRightMostY(r);
C4FoWRay *pLast = FindRayLeftOf(r.x, iLY);
C4FoWRay *pRay = pLast ? pLast->getNext() : pRays;
// Scan over rays
while (pRay && !pRay->isLeft(r.x+r.Wdt, iRY)) {
// Dirty ray?
if (pRay->getLeftEndY() > r.y || pRay->getRightEndY() > r.y)
pRay->Dirty(r.y);
// Merge with last ray?
if (pLast && pLast->isDirty() && pRay->isDirty()) {
pLast->MergeDirty();
pRay = pLast->getNext();
// Advance otherwise
} else {
pLast = pRay;
pRay = pRay->getNext();
}
}
// Final check for merging dirty rays on the right end
if (pLast && pRay && pLast->isDirty() && pRay->isDirty())
pLast->MergeDirty();
}
void C4FoWLightSection::Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScreen)
{
C4Rect Reg = rtransRect(pRegion->getRegion());
// Find start ray
int iLY = Max(0, RectLeftMostY(Reg)),
iRY = Max(0, RectRightMostY(Reg)),
iRX = Reg.x + Reg.Wdt;
C4FoWRay *pStart = FindRayOver(Reg.x, iLY);
// Find end ray - determine the number of rays we actually need to draw
C4FoWRay *pRay = pStart; int32_t iRayCnt = 0;
while (pRay && !pRay->isLeft(iRX, iRY)) {
// TODO: Remove clipped rays on the left so we can in the
// most extreme case completely ignore lights here?
pRay = pRay->getNext();
iRayCnt++;
}
int32_t iOriginalRayCnt = iRayCnt;
// Allocate arrays for our points (lots of them)
float *gFanLX = new float [iRayCnt * 10],
*gFanLY = gFanLX + iRayCnt,
*gFanRX = gFanLY + iRayCnt,
*gFanRY = gFanRX + iRayCnt,
*gFadeLX = gFanRY + iRayCnt,
*gFadeLY = gFadeLX + iRayCnt,
*gFadeRX = gFadeLY + iRayCnt,
*gFadeRY = gFadeRX + iRayCnt,
*gFadeIX = gFadeRY + iRayCnt,
*gFadeIY = gFadeIX + iRayCnt;
int32_t i;
for (i = 0, pRay = pStart; i < iRayCnt; i++, pRay = pRay->getNext()) {
gFanLX[i] = pRay->getLeftEndXf();
gFanLY[i] = float(pRay->getLeftEndY());
gFanRX[i] = pRay->getRightEndXf();
gFanRY[i] = float(pRay->getRightEndY());
}
// Outputs the rightmost (l=1) or leftmost (l=-1) position of the
// light, as seen from the given point. Shinks the light if it is
// too close, to work against exessive fades.
#define ROUND_LIGHT
#ifdef ROUND_LIGHT
const float gLightShrink = 5.0f;
#define CALC_LIGHT(x, y, l, gOutX, gOutY) \
float gOutX, gOutY; \
{ \
float d = sqrt(x * x + y * y); \
float s = Min(float(pLight->getSize()), d / gLightShrink); \
gOutX = s / d * l * y; \
gOutY = s / d * l * -x; \
}
// Sadly, this causes rays to cross when the light shrinks...
#else
// I think this is the only version that guarantees no crossing rays.
// Unsatisfying as it might be.
const float gLightShrink = 1.1f;
#define CALC_LIGHT(x, y, l, gOutX, gOutY) \
float gOutX, gOutY; \
{ \
float d = y; \
gOutX = BoundBy(x + l * d * gLightShrink, -float(pLight->getSize()), +float(pLight->getSize())); \
gOutY = 0; \
}
#endif
// Phase 1: Project lower point so it lies on a line with outer left/right
// light lines.
float gScanLevel = 0;
for (int iStep = 0; iStep < 100000; iStep++) {
// Find the ray to project. This makes this whole algorithm O(n<>),
// but I see no other way to make the whole thing robust :/
float gBestLevel = FLT_MAX;
int j;
for (j = 0; j+1 < iRayCnt; j++) {
float gLevel = Min(gFanRY[j], gFanLY[j+1]);
if (gLevel <= gScanLevel || gLevel >= gBestLevel)
continue;
gBestLevel = gLevel;
}
if(gBestLevel == FLT_MAX)
break;
gScanLevel = gBestLevel;
for(int i = 0; i+1 < iRayCnt; i++) {
if(Min(gFanRY[i], gFanLY[i+1]) != gBestLevel)
continue;
// Debugging
// #define FAN_STEP_DEBUG
#ifdef FAN_STEP_DEBUG
LogSilentF("Fan step %d (i=%d)", iStep, i);
for (j = 0; j < iRayCnt; j++) {
LogSilentF(" %.02f %.02f", gFanLX[j], gFanLY[j]);
LogSilentF(" %.02f %.02f", gFanRX[j], gFanRY[j]);
}
#endif
// Calculate light bounds. We assume a "smaller" light for closer rays
CALC_LIGHT(gFanRX[i], gFanRY[i], -1, gLightLX, gLightLY);
CALC_LIGHT(gFanLX[i+1], gFanLY[i+1], 1, gLightRX, gLightRY);
// Ascending
float gCrossX, gCrossY;
bool fDescendCollision = false;
if (gFanRY[i] > gFanLY[i+1]) {
// Left ray surface self-shadowing? We test whether the scalar product
// of the ray's normal and the light vector is positive.
if ( (gFanRY[i] - gFanLY[i]) * (gFanLX[i+1] - gLightRX) >=
(gFanRX[i] - gFanLX[i]) * (gFanLY[i+1] - gLightRY)) {
// Reduce to upper point (Yep, we now that the upper point
// must be the right one. Try to figure out why!)
assert(gFanRY[i] <= gFanLY[i]);
gFanLX[i] = gFanRX[i];
gFanLY[i] = gFanRY[i];
}
// Left ray reduced?
float gFanRXp = gFanRX[i]; float thresh = 1.0;
if (gFanRX[i] == gFanLX[i] && gFanRY[i] == gFanLY[i]) {
// Move point to the right for the purpose of finding the cross
// point - after all, given that gFanRX[i] == gFanLX[i], 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!
gFanRXp += 1.0;
thresh = 0.0;
}
// Move right point of left ray to the left (the original point is partly shadowed)
bool fEliminate = false; float b;
bool f = find_cross(gLightRX, gLightRY, gFanLX[i+1], gFanLY[i+1],
gFanLX[i], gFanLY[i], gFanRXp, gFanRY[i],
&gCrossX, &gCrossY, &b);
// The self-shadow-check should have made sure that the two are
// never parallel.
assert(f);
// Cross point to left of surface? Then the surface itself is
// shadowed, and we don't need to draw it.
if (b >= thresh) {
fEliminate = true;
// 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 ray 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 ;)
fDescendCollision = true;
} else {
// Set cross point
gFanRX[i] = gCrossX;
gFanRY[i] = gCrossY;
}
// This shouldn't change the case we are in (uh, I think)
assert(gFanRY[i] > gFanLY[i+1]);
// Did we eliminate the surface with this step?
if (fEliminate && i) {
// Remove it then
for(int j = i; j+1 < iRayCnt; j++) {
gFanLX[j] = gFanLX[j+1];
gFanLY[j] = gFanLY[j+1];
gFanRX[j] = gFanRX[j+1];
gFanRY[j] = gFanRY[j+1];
}
// With the elimination, we need to re-process the last
// ray, as it might be more shadowed than we realized.
// Note that the last point might have been projected already -
// but that's okay,
iRayCnt--; i-=2;
}
// Descending - same, but mirrored. And without comments.
} else if (gFanRY[i] < gFanLY[i+1]) {
if ( (gFanRY[i+1] - gFanLY[i+1]) * (gFanRX[i] - gLightLX) >=
(gFanRX[i+1] - gFanLX[i+1]) * (gFanRY[i] - gLightLY)) {
assert(gFanLY[i+1] <= gFanRY[i+1]);
gFanRX[i+1] = gFanLX[i+1];
gFanRY[i+1] = gFanLY[i+1];
}
float gFanRXp = gFanRX[i+1], thresh = 0.0;
if (gFanRX[i+1] == gFanLX[i+1] && gFanRY[i+1] == gFanLY[i+1]) {
gFanRXp += 1.0;
thresh = 1.0;
}
bool fEliminate = false;
float b;
bool f = find_cross(gLightLX, gLightLY, gFanRX[i], gFanRY[i],
gFanLX[i+1], gFanLY[i+1], gFanRXp, gFanRY[i+1],
&gCrossX, &gCrossY, &b);
assert(f);
if (b <= thresh) {
fEliminate = true;
} else if (b > 1.0) {
fDescendCollision = true;
} else {
gFanLX[i+1] = gCrossX;
gFanLY[i+1] = gCrossY;
}
assert(gFanRY[i] < gFanLY[i+1]);
if (fEliminate && i+2 < iRayCnt) {
for(int j = i+1; j+1 < iRayCnt; j++) {
gFanLX[j] = gFanLX[j+1];
gFanLY[j] = gFanLY[j+1];
gFanRX[j] = gFanRX[j+1];
gFanRY[j] = gFanRY[j+1];
}
iRayCnt--; i--;
}
}
if (fDescendCollision) {
// Should never be parallel -- otherwise we wouldn't be here
// in the first place.
bool f = find_cross(gLightLX, gLightLY, gFanRX[i], gFanRY[i],
gLightRX, gLightRY, gFanLX[i+1], gFanLY[i+1],
&gCrossX, &gCrossY);
assert(f);
// Ensure some minimum distance to existing
// points - don't bother with too small
// bumps. This also catches some floating
// point inacurracies.
const float gDescendEta = 0.5;
if (gCrossY <= gFanRY[i] + gDescendEta ||
gCrossY <= gFanLY[i+1] + gDescendEta)
continue;
// This should always follow an elimination, but better check
assert(iOriginalRayCnt > iRayCnt);
for (int j = iRayCnt - 1; j >= i+1; j--) {
gFanLX[j+1] = gFanLX[j];
gFanLY[j+1] = gFanLY[j];
gFanRX[j+1] = gFanRX[j];
gFanRY[j+1] = gFanRY[j];
}
// Okay, now i+1 should be free
gFanLX[i+1] = gCrossX;
gFanLY[i+1] = gCrossY;
gFanRX[i+1] = gCrossX;
gFanRY[i+1] = gCrossY;
// Jump over surface. Note that our right ray 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.
iRayCnt++; i++;
}
}
}
// Phase 2: Calculate fade points
#ifdef FAN_STEP_DEBUG
LogSilent("Fade points");
#endif // FAN_STEP_DEBUG
for (i = 0; i < iRayCnt; i++) {
// Calculate light bounds. Note that the way light size is calculated
// and we are using it below, we need to consider an "asymetrical" light.
CALC_LIGHT(gFanRX[i], gFanRY[i], 1, gLightRX, gLightRY);
CALC_LIGHT(gFanLX[i], gFanLY[i], -1, gLightLX, gLightLY);
// 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 = gFanLX[i] - gLightLX, dy = gFanLY[i] - gLightLY;
float d = float(pLight->getFadeout()) / sqrt(dx*dx + dy*dy);
gFadeLX[i] = gFanLX[i] + d * dx;
gFadeLY[i] = gFanLY[i] + d * dy;
dx = gFanRX[i] - gLightRX;
dy = gFanRY[i] - gLightRY;
d = float(pLight->getFadeout()) / sqrt(dx*dx + dy*dy);
gFadeRX[i] = gFanRX[i] + d * dx;
gFadeRY[i] = gFanRY[i] + d * dy;
// Do the fades cross?
if ((gFadeRX[i] - gLightRX) / (gFadeRY[i] - gLightRY)
< (gFadeLX[i] - gLightRX) / (gFadeLY[i] - gLightRY)) {
// Average it
gFadeLX[i] = gFadeRX[i] = (gFadeLX[i] + gFadeRX[i]) / 2;
gFadeLY[i] = gFadeRY[i] = (gFadeLY[i] + gFadeRY[i]) / 2;
}
#ifdef FAN_STEP_DEBUG
LogSilentF(" %.02f %.02f", gFadeLX[i], gFadeLY[i]);
LogSilentF(" %.02f %.02f", gFadeRX[i], gFadeRY[i]);
#endif
}
// Phase 3: Calculate intermediate fade point
#ifdef FAN_STEP_DEBUG
LogSilent("Intermediate points");
#endif // FAN_STEP_DEBUG
#define NEWER_INTER_FADE_CODE
//#define NEW_INTER_FADE_CODE
#ifdef NEWER_INTER_FADE_CODE
pRay = pStart;
#endif
bool *fAscend = new bool[iRayCnt];
for (i = 0; i+1 < iRayCnt; i++) {
// Calculate light bounds. We assume a "smaller" light for closer rays
CALC_LIGHT(gFanRX[i], gFanRY[i], 1, gLightLX, gLightLY);
CALC_LIGHT(gFanLX[i+1], gFanLY[i+1], -1, gLightRX, gLightRY);
#ifdef NEWER_INTER_FADE_CODE
// Midpoint
float mx = (gFadeRX[i] + gFadeLX[i+1]) / 2,
my = (gFadeRY[i] + gFadeLY[i+1]) / 2;
while (pRay->getNext() && pRay->isRight(mx, my))
pRay = pRay->getNext();
#endif
// Ascending?
fAscend[i] = gFanRY[i] > gFanLY[i+1];
if (gFanRY[i] > gFanLY[i+1]) {
#ifdef NEWER_INTER_FADE_CODE
float dx, dy;
find_cross(0,0, mx, my,
pRay->getLeftEndXf(), pRay->getLeftEndY(), pRay->getRightEndXf(), pRay->getRightEndY(),
&dx, &dy);
float d = float(pLight->getFadeout()) / sqrt(dx*dx + dy*dy);
gFadeIX[i] = mx + d * dx;
gFadeIY[i] = my + d * dy;
if (gFadeRY[i] < gFadeIY[i]) {
gFadeIX[i] = gFadeLX[i+1];
gFadeIY[i] = gFadeLY[i+1];
}
#elif defined(NEW_INTER_FADE_CODE)
// Fade intermediate point is on the left side
float dx = gFanRX[i] - gLightLX;
float dy = gFanRY[i] - gLightLY;
float d = float(pLight->getFadeout()) / sqrt(dx*dx + dy*dy);
gFadeIX[i] = gFanRX[i] + d * dx;
gFadeIY[i] = gFanRY[i] + d * dy;
#else
// Fade intermediate point is on the right side
gFadeIX[i] = gFadeLX[i+1];
gFadeIY[i] = gFadeLY[i+1];
// Project on left ray's height where necessary
if (gFadeIY[i] < gFanRY[i]) {
float d = (gFanRY[i] - gFadeIY[i]) / (gFadeIY[i] - gLightY);
gFadeIX[i] += d * (gFadeIX[i] - gLightRX);
gFadeIY[i] = gFanRY[i];
}
#endif
// Descending?
} else {
#ifdef NEWER_INTER_FADE_CODE
float dx, dy;
find_cross(0,0, mx,my,
pRay->getLeftEndXf(), pRay->getLeftEndY(), pRay->getRightEndXf(), pRay->getRightEndY(),
&dx, &dy);
float d = float(pLight->getFadeout()) / sqrt(dx*dx + dy*dy) / 2;
gFadeIX[i] = mx + d * dx;
gFadeIY[i] = my + d * dy;
if (gFadeLY[i+1] < gFadeIY[i]) {
gFadeIX[i] = gFadeRX[i];
gFadeIY[i] = gFadeRY[i];
}
#elif defined(NEW_INTER_FADE_CODE)
// Fade intermediate point is on the right side
float dx = gFanLX[i+1] - gLightRX;
float dy = gFanLY[i+1] - gLightRY;
float d = float(pLight->getFadeout()) / sqrt(dx*dx + dy*dy);
gFadeIX[i] = gFanLX[i+1] + d * dx;
gFadeIY[i] = gFanLY[i+1] + d * dy;
#else
// Fade intermediate point is on the left side
gFadeIX[i] = gFadeRX[i];
gFadeIY[i] = gFadeRY[i];
// Project on right ray's height where necessary
if (gFadeIY[i] < gFanLY[i+1]) {
float d = (gFanLY[i+1] - gFadeIY[i]) / (gFadeIY[i] - gLightY);
gFadeIX[i] += d * (gFadeIX[i] - gLightLX);
gFadeIY[i] = gFanLY[i+1];
}
#endif
}
#ifdef FAN_STEP_DEBUG
LogSilentF(" %.02f %.02f", gFadeIX[i], gFadeIY[i]);
#endif // FAN_STEP_DEBUG
}
// Phase 4: Transform all points into region coordinates
for (i = 0; i < 5; i++) {
float *pX = gFanLX + 2 * i * iOriginalRayCnt,
*pY = gFanLY + 2 * i * iOriginalRayCnt;
for (int32_t j = 0; j < iRayCnt; j++) {
float x = pX[j], y = pY[j];
if (pOnScreen)
{
pX[j] = float(pOnScreen->X) + transX(x, y) - pOnScreen->TargetX,
pY[j] = float(pOnScreen->Y) + transY(x, y) - pOnScreen->TargetY;
pGL->ApplyZoom(pX[j], pY[j]);
}
else
{
pX[j] = transX(x, y) - pRegion->getRegion().x;
pY[j] = transY(x, y) - pRegion->getRegion().y;
}
}
}
// Calculate position of the light in the buffer
float gLightX = transX(0,0) - pRegion->getRegion().x,
gLightY = transY(0,0) - pRegion->getRegion().y;
// Here's the master plan for updating the lights texture. We
// want to add intensity (R channel) as well as the normal (GB channels).
// Normals are obviously meant to be though of as signed, though,
// so the equation we want would be something like
//
// R_new = BoundBy(R_old + R, 0.0, 1.0)
// G_new = BoundBy(G_old + G - 0.5, 0.0, 1.0)
// B_new = BoundBy(B_old + B - 0.5, 0.0, 1.0)
//
// It seems we can't get that directly though - glBlendFunc only talks
// about two operands. Even if we make two passes, we have to take
// care that that we don't over- or underflow in the intermediate pass.
//
// Therefore, we store G/1.5 instead of G, losing a bit of accuracy,
// but allowing us to formulate the following approximation without
// overflows:
//
// G_new = BoundBy(BoundBy(G_old + G / 1.5), 0.0, 1.0) - 0.5 / 1.5, 0.0, 1.0)
// B_new = BoundBy(BoundBy(B_old + B / 1.5), 0.0, 1.0) - 0.5 / 1.5, 0.0, 1.0)
// Two passes
for(int iPass = 0; iPass < (pOnScreen ? 1 : 2); iPass++) {
// Pass 2: Subtract
if (!pOnScreen && iPass == 1) {
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
glBlendFunc(GL_ONE, GL_ONE);
}
// Help me! My brain can't program without local function definitions anymore!
#define VERTEX(x,y,light) \
if(pOnScreen) { \
if(light) glColor3f(1.0f, 0.0f, 0.0f); \
else glColor3f(0.5f, 0.5f, 0.0f); \
} else if(iPass == 0) { \
float dx = (x) - gLightX, dy = (y) - gLightY; \
float gDist = sqrt(dx*dx+dy*dy); \
float gMult = Min(0.5f / pLight->getSize(), 0.5f / gDist); \
float gNormX = (0.5f + dx * gMult) / 1.5f / C4FoWSmooth; \
float gNormY = (0.5f + dy * gMult) / 1.5f / C4FoWSmooth; \
if(light) glColor3f(0.5f/C4FoWSmooth, gNormX, gNormY); \
else glColor3f(0.0f, gNormX, gNormY); \
} else glColor3f(0.0f, 0.5f/1.5f/C4FoWSmooth, 0.5f/1.5f/C4FoWSmooth); \
glVertex2f(x,y)
#define DARK(x,y) VERTEX(x,y,false)
#define LIGHT(x,y) VERTEX(x,y,true)
#define BEGIN_TRIANGLE \
if(pOnScreen) glBegin(GL_LINE_LOOP)
#define END_TRIANGLE \
if(pOnScreen) glEnd()
// Draw the fan
glShadeModel(GL_SMOOTH);
glBegin(pOnScreen ? GL_LINE_STRIP : GL_TRIANGLE_FAN);
if (!pOnScreen) {
LIGHT(gLightX, gLightY);
}
for (i = 0; i < iRayCnt; i++) {
if (i == 0 || gFanRX[i-1] != gFanLX[i] || gFanRY[i-1] != gFanLY[i]) {
LIGHT(gFanLX[i], gFanLY[i]);
}
if (gFanLX[i] != gFanRX[i] || gFanLY[i] != gFanRY[i]) {
LIGHT(gFanRX[i], gFanRY[i]);
}
}
glEnd();
// Draw the fade
glShadeModel(GL_SMOOTH);
if(!pOnScreen) glBegin(GL_TRIANGLES);
for (i = 0; i < iRayCnt; i++) {
// The quad. Will be empty if fan points match
if (gFanLX[i] != gFanRX[i] || gFanLY[i] != gFanRY[i]) {
// upper triangle
BEGIN_TRIANGLE;
LIGHT(gFanLX[i], gFanLY[i]);
LIGHT(gFanRX[i], gFanRY[i]);
DARK(gFadeLX[i], gFadeLY[i]);
END_TRIANGLE;
// lower triangle, if necessary
if (gFadeLX[i] != gFadeRX[i] || gFadeLY[i] != gFadeRY[i]) {
BEGIN_TRIANGLE;
LIGHT(gFanRX[i], gFanRY[i]);
DARK(gFadeRX[i], gFadeRY[i]);
DARK(gFadeLX[i], gFadeLY[i]);
END_TRIANGLE;
}
}
// No intermediate fade for last point
if (i+1 >= iRayCnt) continue;
// Ascending?
if (fAscend[i]) {
// Lower fade triangle
BEGIN_TRIANGLE;
LIGHT(gFanRX[i], gFanRY[i]);
DARK(gFadeIX[i], gFadeIY[i]);
DARK(gFadeRX[i], gFadeRY[i]);
END_TRIANGLE;
// Intermediate fade triangle, if necessary
if (gFadeIY[i] != gFadeLY[i+1]) {
BEGIN_TRIANGLE;
LIGHT(gFanRX[i], gFanRY[i]);
DARK(gFadeLX[i+1], gFadeLY[i+1]);
DARK(gFadeIX[i], gFadeIY[i]);
END_TRIANGLE;
}
// Upper fade triangle
BEGIN_TRIANGLE;
LIGHT(gFanRX[i], gFanRY[i]);
LIGHT(gFanLX[i+1], gFanLY[i+1]);
DARK(gFadeLX[i+1], gFadeLY[i+1]);
END_TRIANGLE;
// Descending?
} else {
// Lower fade triangle
BEGIN_TRIANGLE;
LIGHT(gFanLX[i+1], gFanLY[i+1]);
DARK(gFadeLX[i+1], gFadeLY[i+1]);
DARK(gFadeIX[i], gFadeIY[i]);
END_TRIANGLE;
// Intermediate fade triangle, if necessary
if (gFadeIY[i] != gFadeRY[i]) {
BEGIN_TRIANGLE;
LIGHT(gFanLX[i+1], gFanLY[i+1]);
DARK(gFadeIX[i], gFadeIY[i]);
DARK(gFadeRX[i], gFadeRY[i]);
END_TRIANGLE;
}
// Upper fade triangle
BEGIN_TRIANGLE;
LIGHT(gFanLX[i+1], gFanLY[i+1]);
DARK(gFadeRX[i], gFadeRY[i]);
LIGHT(gFanRX[i], gFanRY[i]);
END_TRIANGLE;
}
}
if (!pOnScreen)
glEnd(); // GL_TRIANGLES
}
delete[] gFanLX;
delete[] fAscend;
// Reset GL state
glBlendEquation(GL_FUNC_ADD);
}