refactor and document: C4FoWLightSection only calculates the triangles now, C4FoWLight stitches everything together and renders

* intermediate fade triangles are now calculated in C4FoWLight
* rendering takes also place in C4FoWLight, using different C4FoWDrawStrategies
* solved an old TODO from Peter (int -> int32_t)
* refactor and simplify portions of the light vertex calculation code
issue1247
Tobias Zwick 2014-11-16 18:57:42 +01:00
parent 00447dd524
commit fd0163ebc6
11 changed files with 543 additions and 392 deletions

View File

@ -299,6 +299,9 @@ set(OC_CLONK_SOURCES
src/landscape/fow/C4FoWLightSection.h
src/landscape/fow/C4FoWRegion.cpp
src/landscape/fow/C4FoWRegion.h
src/landscape/fow/C4FoWDrawStrategy.cpp
src/landscape/fow/C4FoWDrawStrategy.h
src/landscape/fow/C4FoWBeamTriangle.h
src/landscape/C4Landscape.cpp
src/landscape/C4Landscape.h
src/landscape/C4LandscapeRenderClassic.cpp

View File

@ -4,9 +4,6 @@
#include <float.h>
// TODO: Make sure to use int32_t throughout!
//#define LIGHT_DEBUG
C4FoW::C4FoW()
: pLights(NULL)

View File

@ -25,7 +25,7 @@ StdStrBuf C4FoWBeam::getDesc() const {
fDirty ? "*" : "");
}
bool C4FoWBeam::MergeRight(int x, int y)
bool C4FoWBeam::MergeRight(int32_t x, int32_t y)
{
// Note: Right-merging is the most common and most important optimization.
// This procedure will probably be *hammered* as a result. Worth inlining?
@ -50,7 +50,7 @@ bool C4FoWBeam::MergeRight(int x, int y)
return true;
}
bool C4FoWBeam::MergeLeft(int x, int y)
bool C4FoWBeam::MergeLeft(int32_t x, int32_t y)
{
assert(!isDirty()); assert(isLeft(x, y));
@ -70,7 +70,7 @@ bool C4FoWBeam::MergeLeft(int x, int y)
return true;
}
bool C4FoWBeam::EliminateRight(int x, int y)
bool C4FoWBeam::EliminateRight(int32_t x, int32_t y)
{
// Called on the beams left of the one getting eliminated
C4FoWBeam *pElim = pNext, *pMerge = pNext->pNext;
@ -97,7 +97,7 @@ bool C4FoWBeam::EliminateRight(int x, int y)
return true;
}
C4FoWBeam *C4FoWBeam::Split(int x, int y)
C4FoWBeam *C4FoWBeam::Split(int32_t x, int32_t y)
{
// Make sure to never create negative-surface beams
assert(isDirty()); assert(isInside(x, y));
@ -137,7 +137,7 @@ void C4FoWBeam::MergeDirty()
delete pWith;
}
void C4FoWBeam::Clean(int y)
void C4FoWBeam::Clean(int32_t y)
{
// Search hit something, this beam is now clean.
assert(isDirty());
@ -146,7 +146,7 @@ void C4FoWBeam::Clean(int y)
fDirty = false;
}
void C4FoWBeam::Dirty(int y)
void C4FoWBeam::Dirty(int32_t y)
{
// Got invalidated, beam is dirty until updated
iLeftEndY = y;

View File

@ -10,10 +10,9 @@
are the positions at which this beam did not hit but merely streaked a solid pixel (= the neighboring beam hits it)
and thus continues until *EndX/Y.
It is assumed that the beam always goes up (in cartesian coordinate system, or down in display coordinate system),
thus the Y-position of the delimiting points is always > 0.
C4FoWLightSection transforms the coordinate system after calculation to implement the beams that go into other
directions. This class here always assumes one direction.
It is assumed that the beam always goes down in display coordinate system, thus the Y-position of the delimiting
points is always > 0. C4FoWLightSection transforms the coordinate system after calculation to implement the beams
that go into other directions. This class here always assumes one direction.
A beam is initially marked as "dirty", meaning that the end points of the beam haven't been found yet (by the ray
trace algorithm) and the beam will extend further. When all beams are "clean", the algorithm is done.
@ -58,47 +57,47 @@ public:
StdStrBuf getDesc() const;
/** returns whether the given point is left of an imaginery line drawn from the origin to the left delimiter point (point is left of beam) */
bool isLeft(int x, int y) const {
bool isLeft(int32_t x, int32_t y) const {
return iLeftX * y > x * iLeftY;
}
/** returns whether the given point is right of an imaginery line drawn from the origin to the right delimiter point (point is right of beam) */
bool isRight(int x, int y) const {
bool isRight(int32_t x, int32_t y) const {
return iRightX * y < x * iRightY;
}
/** returns whether the given point is inside the triangle that is defined by this beam */
bool isInside(int x, int y) const {
bool isInside(int32_t x, int32_t y) const {
return !isLeft(x, y) && !isRight(x, y);
}
void SetLeft(int x, int y) { iLeftX = x; iLeftY = y; }
void SetRight(int x, int y) { iRightX = x; iRightY = y; }
void SetLeft(int32_t x, int32_t y) { iLeftX = x; iLeftY = y; }
void SetRight(int32_t x, int32_t y) { iRightX = x; iRightY = y; }
/** Set the new right delimiter point to the given point if the resulting difference in size is less than the error
threshold. If successfull, adds the introduced error to iError.
Asserts that the given point is really right of the right delimiter point
*/
bool MergeRight(int x, int y);
bool MergeRight(int32_t x, int32_t y);
/** Set the new left delimiter point to the given point if the resulting difference in size is less than the error
threshold. If successfull, adds the introduced error to iError.
Asserts that the given point is really left of the right delimiter point
*/
bool MergeLeft(int x, int y);
bool MergeLeft(int32_t x, int32_t y);
/** Split this beam into two: this beam and the returned one. The given point x,y is the position at
which the two resulting beams are connected with their left/right endpoints.
It is asserted that the given point is inside the previous beam. (So the beam can only made smaller) */
C4FoWBeam *Split(int x, int y);
C4FoWBeam *Split(int32_t x, int32_t y);
/** Makes this beam span from its left delimiter point to the right delimiter point of the next but one beam,
removing the two beams in between in the process.
In other words, removes the next beam and merges this beam with the next but one beam.
Returns false and does not do the action in case the error threshold would be reached.
*/
bool EliminateRight(int x, int y);
bool EliminateRight(int32_t x, int32_t y);
void MergeDirty();

View File

@ -0,0 +1,26 @@
#ifndef C4FOWBEAMTRIANGLE_H
#define C4FOWBEAMTRIANGLE_H
/** A simple data structure holding data, mainly positions, about each beam
that is used for the rendering. The coordinates are global.
*/
class C4FoWBeamTriangle
{
public:
C4FoWBeamTriangle() : clipLeft(false), clipRight(false) {};
// whether this triangle is the last one in a row because the triangle
// that would normally be left/right of it is clipped
bool clipLeft, clipRight;
float fanLX, fanLY, // left point of the triangle that has 100% light
fanRX, fanRY, // right point of the triangle that has 100% light
fadeLX, fadeLY, // left point of the quad in which the light fades out
fadeRY, fadeRX, // right point of the quad in which the light fades out
fadeIX, fadeIY; // intermediate fade point filling the space between the fade quads
// whether the next triangle's edge is shorter than the edge of this one
bool descending;
};
#endif

View File

@ -0,0 +1,103 @@
#include "C4Include.h"
#include "C4FoWDrawStrategy.h"
#include "C4FoWLight.h"
#include "C4FoWRegion.h"
#include "C4DrawGL.h"
const float C4FoWDrawLightTextureStrategy::C4FoWSmooth = 8.0;
void C4FoWDrawLightTextureStrategy::Begin(int32_t passPar)
{
pass = passPar;
glShadeModel( GL_SMOOTH );
glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
glBlendFunc(GL_ONE, GL_ONE);
if(pass == 0)
{
glBlendEquation( GL_FUNC_ADD );
}
else if(pass == 1)
{
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
}
}
void C4FoWDrawLightTextureStrategy::End(int32_t pass)
{
glBlendEquation( GL_FUNC_ADD );
}
void C4FoWDrawLightTextureStrategy::DrawVertex(float x, float y, bool shadeLight)
{
if(pass == 0)
{
float dx = x - light->getX();
float dy = y - light->getY();
float dist = sqrt(dx*dx+dy*dy);
float mult = Min(0.5f / light->getSize(), 0.5f / dist);
float normX = (0.5f + dx * mult) / 1.5f / C4FoWSmooth;
float normY = (0.5f + dy * mult) / 1.5f / C4FoWSmooth;
if(shadeLight) glColor3f(0.5f/C4FoWSmooth, normX, normY);
else glColor3f(0.0f, normX, normY);
}
else
{
glColor3f(0.0f, 0.5f/1.5f/C4FoWSmooth, 0.5f/1.5f/C4FoWSmooth);
}
// global coords -> region coords
x += -region->getRegion().x;
y += -region->getRegion().y;
glVertex2f(x,y);
}
void C4FoWDrawLightTextureStrategy::DrawDarkVertex(float x, float y)
{
DrawVertex(x,y, false);
}
void C4FoWDrawLightTextureStrategy::DrawLightVertex(float x, float y)
{
DrawVertex(x,y, true);
}
void C4FoWDrawWireframeStrategy::Begin(int32_t pass)
{
glShadeModel( GL_SMOOTH );
glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
}
void C4FoWDrawWireframeStrategy::End(int32_t pass)
{
glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
glBlendEquation( GL_FUNC_ADD );
}
void C4FoWDrawWireframeStrategy::DrawVertex(float x, float y)
{
// global coords -> screen pos and zoom
x += screen->X - screen->TargetX;
y += screen->Y - screen->TargetY;
pGL->ApplyZoom(x,y);
glVertex2f(x,y);
}
void C4FoWDrawWireframeStrategy::DrawDarkVertex(float x, float y)
{
if(!draw) return;
glColor3f(0.5f, 0.5f, 0.0f);
DrawVertex(x, y);
}
void C4FoWDrawWireframeStrategy::DrawLightVertex(float x, float y)
{
if(!draw) return;
glColor3f(1.0f, 0.0f, 0.0f);
DrawVertex(x, y);
}

View File

@ -0,0 +1,97 @@
#ifndef C4FOWDRAWSTRATEGY_H
#define C4FOWDRAWSTRATEGY_H
#include "C4DrawGL.h"
#include <list>
class C4FoWRegion;
class C4TargetFacet;
class C4FoWLight;
/** A C4FoWDrawStrategy is a connector to OpenGL calls used to draw the light.
C4FoWLight tells this class which part of the light should be drawn now
and subsequently pushes the vertices with the information whether a vertex
is light or dark.
This class is an abstract base class, it is up to the implementing classes
to actually draw anything here.*/
class C4FoWDrawStrategy
{
public:
virtual ~C4FoWDrawStrategy() {};
/** Returns in how many rendering passes the light should be rendered */
virtual int32_t GetRequestedPasses() { return 1; };
/** Called before each rendering pass */
virtual void Begin(int32_t pass) = 0;
/** Called after each rendering pass */
virtual void End(int32_t pass) = 0;
virtual void DrawLightVertex(float x, float y) = 0;
virtual void DrawDarkVertex(float x, float y) = 0;
/** Called before rendering the inner triangle fan (the area with 100% light) */
virtual void BeginFan() { glBegin(GL_TRIANGLE_FAN); };
/** Called after rendering the inner triangle fan */
virtual void EndFan() { glEnd(); };
/** Called before rendering the quads in which the light fades out */
virtual void BeginFade() { glBegin(GL_QUADS); };
/** Called after rendering the quads in which the light fades out */
virtual void EndFade() { glEnd(); };
/** Called before rendering the triangles that fill the space between the fadeout quads */
virtual void BeginIntermediateFade() { glBegin(GL_TRIANGLES); };
/** Called after rendering the triangles that fill the space between the fadeout quads */
virtual void EndIntermediateFade() { glEnd(); };
};
/** This draw strategy is the default draw strategy that draws the light
onto the given region. */
class C4FoWDrawLightTextureStrategy : public C4FoWDrawStrategy
{
public:
C4FoWDrawLightTextureStrategy(const C4FoWLight* light, const C4FoWRegion* region) : light(light), region(region) {};
virtual int32_t GetRequestedPasses() { return 2; };
virtual void DrawLightVertex(float x, float y);
virtual void DrawDarkVertex(float x, float y);
virtual void Begin(int32_t pass);
virtual void End(int32_t pass);
private:
void DrawVertex(float x, float y, bool shadeLight);
static const float C4FoWSmooth;
const C4FoWLight* light;
const C4FoWRegion* region;
int32_t pass;
};
/** This draw strategy is the debug draw strategy (press Ctrl+F7,...) that
draws a wireframe of the light triangles (except the inner fan, but you can
change that in the code below) directly onto the screen. */
class C4FoWDrawWireframeStrategy : public C4FoWDrawStrategy
{
public:
C4FoWDrawWireframeStrategy(const C4FoWLight* light, const C4TargetFacet *screen) : light(light), screen(screen), draw(true) {};
virtual void DrawLightVertex(float x, float y);
virtual void DrawDarkVertex(float x, float y);
virtual void Begin(int32_t pass);
virtual void End(int32_t pass);
virtual void BeginFan() { C4FoWDrawStrategy::BeginFan(); draw = false; };
virtual void EndFan() { C4FoWDrawStrategy::EndFan(); draw = true; };
private:
void C4FoWDrawWireframeStrategy::DrawVertex(float x, float y);
const C4FoWLight* light;
const C4TargetFacet* screen;
bool draw;
};
#endif

View File

@ -2,33 +2,36 @@
#include "C4Include.h"
#include "C4FoWLight.h"
#include "C4FoWLightSection.h"
#include "C4FoWBeamTriangle.h"
#include "C4FoWDrawStrategy.h"
C4FoWLight::C4FoWLight(C4Object *pObj)
: iX(fixtoi(pObj->fix_x)), iY(fixtoi(pObj->fix_y)),
iReach(pObj->PlrViewRange), iFadeout(50), iSize(20),
pNext(NULL), pObj(pObj)
: iX(fixtoi(pObj->fix_x)),
iY(fixtoi(pObj->fix_y)),
iReach(pObj->PlrViewRange),
iFadeout(50),
iSize(20),
pNext(NULL),
pObj(pObj),
sectionUp(this, 0),
sectionLeft(this, 270),
sectionDown(this, 180),
sectionRight(this, 90)
{
pSections = new C4FoWLightSection(this, 0);
pSections = new C4FoWLightSection(this, 90, pSections);
pSections = new C4FoWLightSection(this, 180, pSections);
pSections = new C4FoWLightSection(this, 270, pSections);
}
C4FoWLight::~C4FoWLight()
{
while (C4FoWLightSection *pSect = pSections) {
pSections = pSect->getNext();
delete pSect;
}
}
void C4FoWLight::Invalidate(C4Rect r)
{
for (C4FoWLightSection *pSect = pSections; pSect; pSect = pSect->getNext())
pSect->Invalidate(r);
sectionUp.Invalidate(r);
sectionDown.Invalidate(r);
sectionLeft.Invalidate(r);
sectionRight.Invalidate(r);
}
void C4FoWLight::SetReach(int32_t iReach2, int32_t iFadeout2)
{
// Fadeout changes don't matter
@ -37,17 +40,22 @@ void C4FoWLight::SetReach(int32_t iReach2, int32_t iFadeout2)
if (iReach == iReach2) return;
// Reach decreased? Prune beams
if (iReach2 < iReach) {
if (iReach2 < iReach)
{
iReach = iReach2;
for (C4FoWLightSection *pSect = pSections; pSect; pSect = pSect->getNext())
pSect->Prune(iReach);
sectionUp.Prune(iReach);
sectionDown.Prune(iReach);
sectionLeft.Prune(iReach);
sectionRight.Prune(iReach);
} else {
// Reach increased? Dirty beams that might get longer now
iReach = iReach2;
for (C4FoWLightSection *pSect = pSections; pSect; pSect = pSect->getNext())
pSect->Dirty(iReach);
sectionUp.Dirty(iReach);
sectionDown.Dirty(iReach);
sectionLeft.Dirty(iReach);
sectionRight.Dirty(iReach);
}
}
@ -58,20 +66,201 @@ void C4FoWLight::Update(C4Rect Rec)
int32_t iNX = fixtoi(pObj->fix_x), iNY = fixtoi(pObj->fix_y);
if (iNX != iX || iNY != iY)
{
for (C4FoWLightSection *pSect = pSections; pSect; pSect = pSect->getNext())
// pruning to zero length results in the beam being cleared and new ones created
pSect->Prune(0);
sectionUp.Prune(0);
sectionDown.Prune(0);
sectionLeft.Prune(0);
sectionRight.Prune(0);
iX = iNX; iY = iNY;
}
for (C4FoWLightSection *pSect = pSections; pSect; pSect = pSect->getNext())
pSect->Update(Rec);
sectionUp.Update(Rec);
sectionDown.Update(Rec);
sectionLeft.Update(Rec);
sectionRight.Update(Rec);
}
void C4FoWLight::Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScreen)
void C4FoWLight::Render(C4FoWRegion *region, const C4TargetFacet *onScreen)
{
for (C4FoWLightSection *pSect = pSections; pSect; pSect = pSect->getNext())
pSect->Render(pRegion, pOnScreen);
std::list<C4FoWBeamTriangle> triangles;
triangles.splice(triangles.end(), sectionUp.CalculateTriangles(region));
triangles.splice(triangles.end(), sectionRight.CalculateTriangles(region));
triangles.splice(triangles.end(), sectionDown.CalculateTriangles(region));
triangles.splice(triangles.end(), sectionLeft.CalculateTriangles(region));
CalculateIntermediateFadeTriangles(triangles);
// 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)
C4FoWDrawStrategy* pen;
if (onScreen) pen = new C4FoWDrawWireframeStrategy(this, onScreen);
else pen = new C4FoWDrawLightTextureStrategy(this, region);
for(int pass = 0; pass < pen->GetRequestedPasses(); pass++)
{
pen->Begin(pass);
DrawFan(pen, triangles);
DrawFade(pen, triangles);
DrawIntermediateFadeTriangles(pen, triangles);
pen->End(pass);
}
delete pen;
}
void C4FoWLight::CalculateIntermediateFadeTriangles(std::list<class C4FoWBeamTriangle> &triangles)
{
for (std::list<C4FoWBeamTriangle>::iterator it = triangles.begin(), nextIt = it; it != triangles.end(); ++it)
{
// wrap around
++nextIt;
if(nextIt == triangles.end()) nextIt = triangles.begin();
C4FoWBeamTriangle &tri = *it, &nextTri = *nextIt; // just for convenience
// don't calculate if it should not be drawn anyway
if (tri.clipRight || nextTri.clipLeft) continue;
// Midpoint
tri.fadeIX = (tri.fadeRX + nextTri.fadeLX) / 2;
tri.fadeIY = (tri.fadeRY + nextTri.fadeLY) / 2;
float distFanR = GetSquaredDistanceTo(tri.fanRX, tri.fanRY);
float distNextFanL = GetSquaredDistanceTo(nextTri.fanLX, nextTri.fanLY);
float distFadeI = GetSquaredDistanceTo(tri.fadeIX, tri.fadeIY);
// an intermediate fade point is not necessery in all cases
tri.descending = distFanR > distNextFanL;
if (tri.descending) {
float distFadeR = GetSquaredDistanceTo(tri.fadeRX, tri.fadeRY);
if (distFadeR < distFadeI)
{
tri.fadeIX = nextTri.fadeLX;
tri.fadeIY = nextTri.fadeLY;
}
}
else
{
float distNextFadeL = GetSquaredDistanceTo(nextTri.fadeLX, nextTri.fadeLY);
if (nextTri.fadeLY < tri.fadeIY)
{
tri.fadeIX = tri.fadeRX;
tri.fadeIY = tri.fadeRY;
}
}
}
}
void C4FoWLight::DrawFan(C4FoWDrawStrategy* pen, std::list<C4FoWBeamTriangle> &triangles)
{
pen->BeginFan();
pen->DrawLightVertex(getX(), getY());
for (std::list<C4FoWBeamTriangle>::iterator it = triangles.begin(), nextIt = it; it != triangles.end(); ++it)
{
// wrap around
++nextIt;
if(nextIt == triangles.end()) nextIt = triangles.begin();
C4FoWBeamTriangle &tri = *it, &nextTri = *nextIt; // just for convenience
pen->DrawLightVertex(tri.fanLX, tri.fanLY);
pen->DrawLightVertex(tri.fanRX, tri.fanRY);
}
pen->EndFan();
}
void C4FoWLight::DrawFade(C4FoWDrawStrategy* pen, std::list<C4FoWBeamTriangle> &triangles)
{
pen->BeginFade();
for (std::list<C4FoWBeamTriangle>::iterator it = triangles.begin(); it != triangles.end(); ++it)
{
C4FoWBeamTriangle &tri = *it; // just for convenience
// The quad will be empty if fan points match
if (tri.fanLX == tri.fanRX && tri.fanLY == tri.fanRY) continue;
pen->DrawLightVertex(tri.fanLX, tri.fanLY);
pen->DrawLightVertex(tri.fanRX, tri.fanRY);
pen->DrawDarkVertex(tri.fadeRX, tri.fadeRY);
pen->DrawDarkVertex(tri.fadeLX, tri.fadeLY);
}
pen->EndFade();
}
void C4FoWLight::DrawIntermediateFadeTriangles(C4FoWDrawStrategy* pen, std::list<C4FoWBeamTriangle> &triangles)
{
pen->BeginIntermediateFade();
for (std::list<C4FoWBeamTriangle>::iterator it = triangles.begin(), nextIt = it; it != triangles.end(); ++it)
{
// wrap around
++nextIt;
if(nextIt == triangles.end()) nextIt = triangles.begin();
C4FoWBeamTriangle &tri = *it, &nextTri = *nextIt; // just for convenience
// no inter-fade triangles when it should be clipped
if (tri.clipRight || nextTri.clipLeft) continue;
if (tri.descending) {
// Lower fade triangle
pen->DrawLightVertex(tri.fanRX, tri.fanRY);
pen->DrawDarkVertex(tri.fadeIX, tri.fadeIY);
pen->DrawDarkVertex(tri.fadeRX, tri.fadeRY);
// Intermediate fade triangle, if necessary
if (tri.fadeIY != nextTri.fadeRY || tri.fadeIX != nextTri.fadeRX) {
pen->DrawLightVertex(tri.fanRX, tri.fanRY);
pen->DrawDarkVertex(nextTri.fadeLX, nextTri.fadeLY);
pen->DrawDarkVertex(tri.fadeIX, tri.fadeIY);
}
// Upper fade triangle
pen->DrawLightVertex(tri.fanRX, tri.fanRY);
pen->DrawLightVertex(nextTri.fanLX, nextTri.fanLY);
pen->DrawDarkVertex(nextTri.fadeLX, nextTri.fadeLY);
} else {
// Lower fade triangle
pen->DrawLightVertex(nextTri.fanLX, nextTri.fanLY);
pen->DrawDarkVertex(nextTri.fadeLX, nextTri.fadeLY);
pen->DrawDarkVertex(tri.fadeIX, tri.fadeIY);
// Intermediate fade triangle, if necessary
if (tri.fadeIY != nextTri.fadeRY || tri.fadeIX != nextTri.fadeRX) {
pen->DrawLightVertex(nextTri.fanLX, nextTri.fanLY);
pen->DrawDarkVertex(tri.fadeIX, tri.fadeIY);
pen->DrawDarkVertex(tri.fadeRX, tri.fadeRY);
}
// Upper fade triangle
pen->DrawLightVertex(nextTri.fanLX, nextTri.fanLY);
pen->DrawDarkVertex(tri.fadeRX, tri.fadeRY);
pen->DrawLightVertex(tri.fanRX, tri.fanRY);
}
}
pen->EndIntermediateFade();
}

View File

@ -2,9 +2,10 @@
#define C4FOWLIGHT_H
#include "C4Object.h"
#include "C4Rect.h"
#include "C4Surface.h"
#include "C4FacetEx.h"
#include "C4FoWLightSection.h"
#include "C4Rect.h"
/** This class represents one light source. A light source has an associated object with which the light source moves
and one light section that handles the light beams for each direction (up, down, left, right).
@ -23,16 +24,19 @@ private:
int32_t iReach; // maximum length of beams
int32_t iFadeout; // number of pixels over which beams fade out
int32_t iSize; // size of the light source. Decides smoothness of shadows
class C4FoWLightSection *pSections;
C4FoWLight *pNext;
C4Object *pObj; // Associated object
C4FoWLightSection sectionUp;
C4FoWLightSection sectionLeft;
C4FoWLightSection sectionDown;
C4FoWLightSection sectionRight;
public:
int32_t getX() const { return iX; }
int32_t getY() const { return iY; }
int32_t getReach() const { return iReach; }
int32_t getFadeout() const { return iFadeout; }
// ASK: the code suggests taht total reach is iReach and iFadeout is subtracted from it, rather than added
int32_t getTotalReach() const { return iReach + iFadeout; }
int32_t getSize() const { return iSize; }
C4FoWLight *getNext() const { return pNext; }
@ -46,9 +50,23 @@ public:
void Invalidate(C4Rect r);
/** Update all light beams within the given rectangle for this light */
void Update(C4Rect r);
/** Render this light*/
void Render(class C4FoWRegion *pRegion, const C4TargetFacet *pOnScreen = NULL);
private:
/** Calculate the intermediate fade points used for constructing the intermediate fade triangles later on */
void CalculateIntermediateFadeTriangles(std::list<class C4FoWBeamTriangle> &triangles);
/** Draws the triangle fan (the area with 100% light around the light source) with the given strategy */
void DrawFan(class C4FoWDrawStrategy* pen, std::list<class C4FoWBeamTriangle> &triangles);
/** Draws the fadeoot triangles - those around the triangle fan - with the given strategy */
void DrawFade(C4FoWDrawStrategy* pen, std::list<C4FoWBeamTriangle> &triangles);
/** Draws the fadeout triangles in between the normal fadeout triangles with the given strategy */
void DrawIntermediateFadeTriangles(C4FoWDrawStrategy* pen, std::list<C4FoWBeamTriangle> &triangles);
/** Returns the (squared) distance from this light source to the given point. Squared simply because we only need this
for comparison of distances. So we don't bother to sqrt it */
float GetSquaredDistanceTo(int32_t x, int32_t y) { return (x - getX()) * (x - getX()) + (y - getY()) * (y - getY()); }
};
#endif

View File

@ -1,11 +1,11 @@
#include "C4Include.h"
#include "C4FoWLightSection.h"
#include "C4FoWBeamTriangle.h"
#include "C4FoWBeam.h"
#include "C4FoWLight.h"
#include "C4FoWRegion.h"
#include "C4Landscape.h"
#include "C4DrawGL.h"
#include "float.h"
@ -44,11 +44,7 @@ bool find_cross(float x1, float y1, float x2, float y2,
return true;
}
const float C4FoWSmooth = 8.0;
C4FoWLightSection::C4FoWLightSection(C4FoWLight *pLight, int r, C4FoWLightSection *pNext)
: pLight(pLight), iRot(r), pNext(pNext)
C4FoWLightSection::C4FoWLightSection(C4FoWLight *pLight, int r) : pLight(pLight), iRot(r)
{
// Rotation matrices
iRot = r % 360;
@ -81,6 +77,20 @@ 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); }
void C4FoWLightSection::ClearBeams()
{
while (C4FoWBeam *pBeam = pBeams) {
@ -96,7 +106,7 @@ void C4FoWLightSection::Prune(int32_t iReach)
pBeams = new C4FoWBeam(-1, 1, 1, 1);
return;
}
// TODO: Merge active beams that we have pruned to same length
// TODO PeterW: Merge active beams that we have pruned to same length
for (C4FoWBeam *pBeam = pBeams; pBeam; pBeam = pBeam->getNext())
pBeam->Prune(iReach);
}
@ -232,7 +242,7 @@ void C4FoWLightSection::Update(C4Rect RectIn)
// Do a scan
int32_t xl = Max(pBeam->getLeftX(y), Bounds.x),
xr = Min(pBeam->getRightX(y), Bounds.x+Bounds.Wdt-1);
for(int x = xl; x <= xr; x++) {
for(int32_t x = xl; x <= xr; x++) {
// Fast free?
if (!Landscape._FastSolidCheck(transX(x,y), transY(x,y)))
@ -246,7 +256,7 @@ void C4FoWLightSection::Update(C4Rect RectIn)
if (!GBackSolid(transX(x,y), transY(x,y))) continue;
// Split points
int x1 = x - 1, x2 = x + 1;
int32_t x1 = x - 1, x2 = x + 1;
bool fSplitLeft = !pBeam->isLeft(x1, y);
bool fSplitRight = !pBeam->isRight(x2, y);
@ -395,12 +405,14 @@ int32_t C4FoWLightSection::FindBeamsClipped(const C4Rect &pInRect, C4FoWBeam *&p
return iBeamCount;
}
void C4FoWLightSection::Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScreen)
std::list<C4FoWBeamTriangle> C4FoWLightSection::CalculateTriangles(C4FoWRegion *pRegion)
{
C4FoWBeam *pStart = NULL, *pEnd = NULL;
int32_t iBeamCount = FindBeamsClipped(rtransRect(pRegion->getRegion()), pStart, pEnd);
// no beams inside the rectangle? Good, nothing to render
if(!iBeamCount) return;
std::list<C4FoWBeamTriangle> result;
if(!iBeamCount) return result;
int32_t iOriginalBeamCount = iBeamCount;
// Allocate array for our points (lots of them)
@ -411,9 +423,7 @@ void C4FoWLightSection::Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScr
*gFadeLX = gFanRY + iBeamCount,
*gFadeLY = gFadeLX + iBeamCount,
*gFadeRX = gFadeLY + iBeamCount,
*gFadeRY = gFadeRX + iBeamCount,
*gFadeIX = gFadeRY + iBeamCount,
*gFadeIY = gFadeIX + iBeamCount;
*gFadeRY = gFadeRX + iBeamCount;
int32_t i;
C4FoWBeam *pBeam = pStart;
for (i = 0, pBeam = pStart; i < iBeamCount; i++, pBeam = pBeam->getNext()) {
@ -447,16 +457,6 @@ void C4FoWLightSection::Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScr
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 < iBeamCnt; 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 beams
float gLightLX, gLightLY, gLightRX, gLightRY;
LightBallLeftMostPoint(gFanRX[i], gFanRY[i], gLightLX, gLightLY);
@ -472,7 +472,7 @@ void C4FoWLightSection::Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScr
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
// Reduce to upper point (Yep, we know that the upper point
// must be the right one. Try to figure out why!)
assert(gFanRY[i] <= gFanLY[i]);
gFanLX[i] = gFanRX[i];
@ -634,9 +634,6 @@ void C4FoWLightSection::Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScr
} // end for (int iStep = 0; iStep < 100000; iStep++) loop
// Phase 2: Calculate fade points
#ifdef FAN_STEP_DEBUG
LogSilent("Fade points");
#endif // FAN_STEP_DEBUG
for (i = 0; i < iBeamCount; i++) {
// Calculate light bounds. Note that the way light size is calculated
@ -650,7 +647,8 @@ void C4FoWLightSection::Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScr
// right-most light point.
// For once we actually calculate this using the real distance
float dx = gFanLX[i] - gLightLX, dy = gFanLY[i] - gLightLY;
float dx = gFanLX[i] - gLightLX;
float 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;
@ -671,306 +669,37 @@ void C4FoWLightSection::Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScr
}
#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
pBeam = pStart;
#endif
bool *fAscend = new bool[iBeamCount];
for (i = 0; i+1 < iBeamCount; i++) {
// Calculate light bounds. We assume a "smaller" light for closer beams
float gLightLX, gLightLY, gLightRX, gLightRY;
LightBallLeftMostPoint(gFanLX[i+1], gFanLY[i+1], gLightRX, gLightRY);
LightBallRightMostPoint(gFanRX[i], gFanRY[i], gLightLX, gLightLY);
#ifdef NEWER_INTER_FADE_CODE
// Midpoint
float mx = (gFadeRX[i] + gFadeLX[i+1]) / 2,
my = (gFadeRY[i] + gFadeLY[i+1]) / 2;
while (pBeam->getNext() && pBeam->isRight(mx, my))
pBeam = pBeam->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,
pBeam->getLeftEndXf(), pBeam->getLeftEndY(), pBeam->getRightEndXf(), pBeam->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 beam'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,
pBeam->getLeftEndXf(), pBeam->getLeftEndY(), pBeam->getRightEndXf(), pBeam->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 beam'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++) {
// Phase 4: Transform all points into global coordinates
for (i = 0; i < 4; i++) {
float *pX = gFanLX + 2 * i * iOriginalBeamCount,
*pY = gFanLY + 2 * i * iOriginalBeamCount;
for (int32_t j = 0; j < iBeamCount; 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;
}
pX[j] = transX(x, y);
pY[j] = transY(x, 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 < iBeamCount; 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 < iBeamCount; 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 >= iBeamCount) 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
for (i = 0; i < iBeamCount; i++)
{
C4FoWBeamTriangle triangle = C4FoWBeamTriangle();
triangle.fanLX = gFanLX[i];
triangle.fanLY = gFanLY[i];
triangle.fanRX = gFanRX[i];
triangle.fanRY = gFanRY[i];
triangle.fadeLX = gFadeLX[i];
triangle.fadeLY = gFadeLY[i];
triangle.fadeRX = gFadeRX[i];
triangle.fadeRY = gFadeRY[i];
triangle.clipLeft = false; // TODO Newton: pBeams.start != pStart
triangle.clipRight = false; // TODO Newton: pBeams.end != pEnd
result.push_back(triangle);
}
delete[] gFanLX;
delete[] fAscend;
// Reset GL state
glBlendEquation(GL_FUNC_ADD);
return result;
}

View File

@ -2,10 +2,11 @@
#define C4FOWLIGHTSECTION_H
#include "C4Rect.h"
#include "C4FoWLight.h"
class C4FoWLight;
class C4FoWRegion;
class C4FoWBeam;
class C4FoWBeamTriangle;
/** The light section manages the beams for one light for one direction of 90°.
@ -16,7 +17,7 @@ class C4FoWBeam;
class C4FoWLightSection
{
public:
C4FoWLightSection(C4FoWLight *pLight, int r, C4FoWLightSection *pNext = NULL);
C4FoWLightSection(C4FoWLight *pLight, int r);
~C4FoWLightSection();
private:
@ -30,20 +31,17 @@ private:
int ra, rb, rc, rd;
/* This section's beams */
class C4FoWBeam *pBeams;
C4FoWLightSection *pNext;
C4FoWBeam *pBeams;
public:
C4FoWLightSection *getNext() const { return pNext; }
/** Recalculate of all light beams within the given rectangle because the landscape changed. */
void Invalidate(C4Rect r);
/** Update all light beams within the given rectangle */
void Update(C4Rect r);
void Render(C4FoWRegion *pRegion, const class C4TargetFacet *pOnScreen = NULL);
std::list<C4FoWBeamTriangle> CalculateTriangles(C4FoWRegion *pRegion);
/** Shorten all light beams to the given reach.
Called when the size of the light has decreased to the given value */
@ -92,23 +90,15 @@ private:
inline int32_t RectRightMostX(const C4Rect &r) const { return r.x + r.Wdt; }
inline int32_t RectRightMostY(const C4Rect &r) const { return Max(0, r.x + r.Wdt <= 0 ? r.y + r.Hgt : r.y); }
inline void 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 LightBallExtremePoint(float x, float y, float dir, float &lightX, float &lightY) const;
/** Outputs the rightmost position of the light ball, as seen from the given point. Shrinks the light if it is too close
to work against excessive fades. The light ball is the imaginery size of the light to enable soft shadows. */
inline void LightBallRightMostPoint(float x, float y, float &lightX, float &lightY) const
{ LightBallExtremePoint(x,y,+1.0f,lightX,lightY); }
inline void LightBallRightMostPoint(float x, float y, float &lightX, float &lightY) const;
/** Outputs the leftmost position of the light ball, as seen from the given point. Shrinks the light if it is too close
to work against excessive fades. The light ball is the imaginery size of the light to enable soft shadows. */
inline void LightBallLeftMostPoint(float x, float y, float &lightX, float &lightY) const
{ LightBallExtremePoint(x,y,-1.0f,lightX,lightY); }
inline void LightBallLeftMostPoint(float x, float y, float &lightX, float &lightY) const;
/** Find right-most beam left of point */