split up C4FoW header and source file into one file for each class

* added some class and method documentation, removed some superfluous comments like

	void C4FoW::Update(C4Rect r)
	{
		// Update all lights
		...

* added ASK comments that need clarification before proper documentation
issue1247
Tobias Zwick 2014-10-11 23:13:10 +02:00
parent c2f57db54b
commit 21e532da23
14 changed files with 906 additions and 778 deletions

View File

@ -286,8 +286,16 @@ set(OC_CLONK_SOURCES
src/gui/C4StartupScenSelDlg.h
src/gui/C4UpperBoard.cpp
src/gui/C4UpperBoard.h
src/landscape/C4FoW.cpp
src/landscape/C4FoW.h
src/landscape/fow/C4FoW.cpp
src/landscape/fow/C4FoW.h
src/landscape/fow/C4FoWRay.cpp
src/landscape/fow/C4FoWRay.h
src/landscape/fow/C4FoWLight.cpp
src/landscape/fow/C4FoWLight.h
src/landscape/fow/C4FoWLightSection.cpp
src/landscape/fow/C4FoWLightSection.h
src/landscape/fow/C4FoWRegion.cpp
src/landscape/fow/C4FoWRegion.h
src/landscape/C4Landscape.cpp
src/landscape/C4Landscape.h
src/landscape/C4LandscapeRenderClassic.cpp
@ -718,6 +726,7 @@ include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/src/graphics
${CMAKE_CURRENT_SOURCE_DIR}/src/gui
${CMAKE_CURRENT_SOURCE_DIR}/src/landscape
${CMAKE_CURRENT_SOURCE_DIR}/src/landscape/fow
${CMAKE_CURRENT_SOURCE_DIR}/src/lib
${CMAKE_CURRENT_SOURCE_DIR}/src/network
${CMAKE_CURRENT_SOURCE_DIR}/src/object

View File

@ -36,7 +36,7 @@
#include <C4PlayerList.h>
#include <C4GameObjects.h>
#include <C4Network2.h>
#include <C4FoW.h>
#include <C4FoWRegion.h>
void C4Viewport::DropFile(const char* fileName, float x, float y)
{

View File

@ -1,222 +0,0 @@
#ifndef C4FOW_H
#define C4FOW_H
#include "C4Rect.h"
#include "C4Surface.h"
#include "C4DrawGL.h"
class C4FoW
{
public:
C4FoW();
private:
class C4FoWLight *pLights;
public:
void Clear();
void Add(C4Object *pObj);
void Remove(C4Object *pObj);
void Update(C4Rect r);
void Invalidate(C4Rect r);
void Render(class C4FoWRegion *pRegion, const C4TargetFacet *pOnScreen = NULL);
};
class C4FoWRegion
{
public:
C4FoWRegion(C4FoW *pFoW, C4Player *pPlayer);
~C4FoWRegion();
private:
C4FoW *pFoW;
C4Player *pPlayer;
C4Rect Region, OldRegion;
C4Surface *pSurface, *pBackSurface;
GLuint hFrameBufDraw, hFrameBufRead;
public:
const C4Rect &getRegion() const { return Region; }
const C4Surface *getSurface() const { return pSurface; }
const C4Surface *getBackSurface() const { return pBackSurface; }
void Clear();
void Update(C4Rect r);
void Render(const C4TargetFacet *pOnScreen = NULL);
private:
bool BindFramebuf();
};
class C4FoWLight
{
friend class C4FoW;
public:
C4FoWLight(C4Object *pObj);
~C4FoWLight();
private:
int32_t iX, iY; // center position
int32_t iReach; // maximum length of rays
int32_t iFadeout; // number of pixels over which rays fade out
int32_t iSize; // size of the light source. Decides smoothness of shadows
class C4FoWLightSection *pSections;
C4FoWLight *pNext;
C4Object *pObj; // Associated object
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; }
int32_t getTotalReach() const { return iReach + iFadeout; }
int32_t getSize() const { return iSize; }
C4FoWLight *getNext() const { return pNext; }
C4Object *getObj() const { return pObj; }
void SetReach(int32_t iReach, int32_t iFadeout);
void Invalidate(C4Rect r);
void Update(C4Rect r);
void Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScreen = NULL);
};
class C4FoWLightSection
{
public:
C4FoWLightSection(C4FoWLight *pLight, int r, C4FoWLightSection *pNext = NULL);
~C4FoWLightSection();
private:
// Center light
C4FoWLight *pLight;
// Transformation matrix
int iRot;
int a, b, c, d;
int ra, rb, rc, rd;
// Rays
class C4FoWRay *pRays;
// List
C4FoWLightSection *pNext;
public:
C4FoWLightSection *getNext() const { return pNext; }
void Invalidate(C4Rect r);
void Update(C4Rect r);
void Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScreen = NULL);
void Prune(int32_t iReach);
void Dirty(int32_t iReach);
void ClearRays();
private:
// Ray to landscape. Ray coordinates are with light source at (0,0).
template <class T> T transDX(T dx, T dy) const { return T(a) * dx + T(b) * dy; }
template <class T> T transDY(T dx, T dy) const { return T(c) * dx + T(d) * dy; }
template <class T> T transX(T x, T y) const { return transDX(x, y) + T(pLight->getX()); }
template <class T> T transY(T x, T y) const { return transDY(x, y) + T(pLight->getY()); }
// Landscape to ray
template <class T> T rtransDX(T dx, T dy) const { return T(ra) * dx + T(rb) * dy; }
template <class T> T rtransDY(T dx, T dy) const { return T(rc) * dx + T(rd) * dy; }
template <class T> T rtransX(T x, T y) const { return rtransDX(x-T(pLight->getX()),y-T(pLight->getY())); }
template <class T> T rtransY(T x, T y) const { return rtransDY(x-T(pLight->getX()),y-T(pLight->getY())); }
C4Rect rtransRect(C4Rect r) const {
C4Rect Rect(rtransX(r.x, r.y), rtransY(r.x, r.y),
rtransDX(r.Wdt, r.Hgt), rtransDY(r.Wdt, r.Hgt));
Rect.Normalize();
return Rect;
}
bool isConsistent() const;
int32_t RectLeftMostY(const C4Rect &r) const { return r.x >= 0 ? r.y+r.Hgt : r.y; }
int32_t RectRightMostY(const C4Rect &r) const { return r.x + r.Wdt <= 0 ? r.y+r.Hgt : r.y; }
C4FoWRay *FindRayLeftOf(int32_t x, int32_t y); // find right-most ray left of point
C4FoWRay *FindRayOver(int32_t x, int32_t y); // find left-most ray to extend over point
};
class C4FoWRay
{
public:
C4FoWRay(int32_t iLeftX, int32_t iLeftY, int32_t iRightX, int32_t iRightY)
: iLeftX(iLeftX), iLeftY(iLeftY), iRightX(iRightX), iRightY(iRightY),
iLeftEndY(0), iRightEndY(0),
iError(0),
fDirty(true),
pNext(NULL)
{ }
private:
int32_t iLeftX, iLeftY; // left delimiter point
int32_t iRightX, iRightY; // right delimiter point
int32_t iLeftEndY, iRightEndY; // where it hit solid material. C4FoWRayActive while currently being followed.
int32_t iError; // How much error this ray has
bool fDirty; // landscape changed since it was followed?
C4FoWRay *pNext;
public:
bool isDirty() const { return fDirty; }
bool isClean() const { return !fDirty; }
C4FoWRay *getNext() const { return pNext; }
// Get a point on the ray boundary.
inline int32_t getLeftX(int32_t y) const { return iLeftX * y / iLeftY; }
inline int32_t getRightX(int32_t y) const { return iRightX * y / iRightY; }
inline float getLeftXf(int32_t y) const { return float(iLeftX * y) / float(iLeftY); }
inline float getRightXf(int32_t y) const { return float(iRightX * y) / float(iRightY); }
int32_t getLeftEndY() const { return iLeftEndY; }
int32_t getLeftEndX() const { return getLeftX(iLeftEndY); }
float getLeftEndXf() const { return getLeftXf(iLeftEndY); }
int32_t getRightEndY() const { return iRightEndY; }
int32_t getRightEndX() const { return getRightX(iRightEndY); }
float getRightEndXf() const { return getRightXf(iRightEndY); }
StdStrBuf getDesc() const;
bool isLeft(int x, int y) const {
return iLeftX * y > x * iLeftY;
}
bool isRight(int x, int y) const {
return iRightX * y < x * iRightY;
}
bool isInside(int x, int 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; }
bool MergeRight(int x, int y);
bool MergeLeft(int x, int y);
bool Eliminate(int x, int y);
C4FoWRay *Split(int x, int y);
void MergeDirty();
void Clean(int32_t y);
void Dirty(int32_t y);
void Prune(int32_t y);
};
#endif // C4FOW_H

View File

@ -4,7 +4,7 @@
#include "C4Landscape.h"
#include "C4Texture.h"
#include "C4FoW.h"
#include "C4FoWRegion.h"
#include "C4GroupSet.h"
#include "C4Components.h"

View File

@ -0,0 +1,79 @@
#include "C4Include.h"
#include "C4FoW.h"
#include <float.h>
// TODO: Make sure to use int32_t throughout!
//#define LIGHT_DEBUG
C4FoW::C4FoW()
: pLights(NULL)
{
}
void C4FoW::Add(C4Object *pObj)
{
// No view range? Probably want to remove instead
if(!pObj->PlrViewRange)
{
Remove(pObj);
return;
}
// Look for matching light
C4FoWLight *pLight;
for (pLight = pLights; pLight; pLight = pLight->getNext())
if (pLight->getObj() == pObj)
break;
if (pLight)
{
// Update reach
pLight->SetReach(pObj->PlrViewRange, 50);
}
else
{
// Create new light otherwise
pLight = new C4FoWLight(pObj);
pLight->pNext = pLights;
pLights = pLight;
}
}
void C4FoW::Remove(C4Object *pObj)
{
// Look for matching light
C4FoWLight *pPrev = NULL, *pLight;
for (pLight = pLights; pLight; pPrev = pLight, pLight = pLight->getNext())
if (pLight->getObj() == pObj)
break;
if (!pLight)
return;
// Remove
(pPrev ? pLights : pPrev->pNext) = pLight->getNext();
delete pLight;
}
void C4FoW::Invalidate(C4Rect r)
{
for (C4FoWLight *pLight = pLights; pLight; pLight = pLight->getNext())
pLight->Invalidate(r);
}
void C4FoW::Update(C4Rect r)
{
for (C4FoWLight *pLight = pLights; pLight; pLight = pLight->getNext())
pLight->Update(r);
}
void C4FoW::Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScreen)
{
for (C4FoWLight *pLight = pLights; pLight; pLight = pLight->getNext())
pLight->Render(pRegion, pOnScreen);
}

View File

@ -0,0 +1,41 @@
#ifndef C4FOW_H
#define C4FOW_H
#include "C4Surface.h"
#include "C4FacetEx.h"
#include "C4Rect.h"
#include "C4Object.h"
#include "C4FoWLight.h"
/**
This class holds all lights for the objects. It forwards the update, invalidation and render calls each to the
lights.
*/
class C4FoW
{
public:
C4FoW();
private:
/** linked list of all lights */
class C4FoWLight *pLights;
public:
void Clear();
/** Updates the view range of the given object in its associated light or create a new light if none exists yet. */
void Add(C4Object *pObj);
/** Removes the light associated with the given object, if any */
void Remove(C4Object *pObj);
/** Update all light rays within the given rectangle */
// ASK: only called by C4FoWRegion??
void Update(C4Rect r);
/** Triggers the recalculation of all light rays within the given rectangle because the landscape changed. */
void Invalidate(C4Rect r);
void Render(class C4FoWRegion *pRegion, const C4TargetFacet *pOnScreen = NULL);
};
#endif // C4FOW_H

View File

@ -0,0 +1,77 @@
#include "C4Include.h"
#include "C4FoWLight.h"
#include "C4FoWLightSection.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)
{
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);
}
void C4FoWLight::SetReach(int32_t iReach2, int32_t iFadeout2)
{
// Fadeout changes don't matter
iFadeout = iFadeout2;
if (iReach == iReach2) return;
// Reach decreased? Prune rays
if (iReach2 < iReach) {
iReach = iReach2;
for (C4FoWLightSection *pSect = pSections; pSect; pSect = pSect->getNext())
pSect->Prune(iReach);
} else {
// Reach increased? Dirty rays that might get longer now
iReach = iReach2;
for (C4FoWLightSection *pSect = pSections; pSect; pSect = pSect->getNext())
pSect->Dirty(iReach);
}
}
void C4FoWLight::Update(C4Rect Rec)
{
// Update position from object. Clear if we moved in any way
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 rays being cleared and new ones created
pSect->Prune(0);
iX = iNX; iY = iNY;
}
for (C4FoWLightSection *pSect = pSections; pSect; pSect = pSect->getNext())
pSect->Update(Rec);
}
void C4FoWLight::Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScreen)
{
for (C4FoWLightSection *pSect = pSections; pSect; pSect = pSect->getNext())
pSect->Render(pRegion, pOnScreen);
}

View File

@ -0,0 +1,54 @@
#ifndef C4FOWLIGHT_H
#define C4FOWLIGHT_H
#include "C4Object.h"
#include "C4Rect.h"
#include "C4Surface.h"
#include "C4FacetEx.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 rays for each direction (up, down, left, right).
Furthermore, each light source has a size. This is usually the object's PlrViewRange. See SetReach.
*/
class C4FoWLight
{
friend class C4FoW;
public:
C4FoWLight(C4Object *pObj);
~C4FoWLight();
private:
int32_t iX, iY; // center position
int32_t iReach; // maximum length of rays
int32_t iFadeout; // number of pixels over which rays fade out
int32_t iSize; // size of the light source. Decides smoothness of shadows
class C4FoWLightSection *pSections;
C4FoWLight *pNext;
C4Object *pObj; // Associated object
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
int32_t getTotalReach() const { return iReach + iFadeout; }
int32_t getSize() const { return iSize; }
C4FoWLight *getNext() const { return pNext; }
C4Object *getObj() const { return pObj; }
/** Sets the light's size in pixels. The reach is the total radius of the light while the fadeout is the number of
pixels after which the light should dim down */
void SetReach(int32_t iReach, int32_t iFadeout);
/** Triggers the recalculation of all light rays within the given rectangle for this light because the landscape changed. */
void Invalidate(C4Rect r);
/** Update all light rays within the given rectangle for this light */
void Update(C4Rect r);
void Render(class C4FoWRegion *pRegion, const C4TargetFacet *pOnScreen = NULL);
};
#endif

View File

@ -1,121 +1,49 @@
#include "C4Include.h"
#include "C4FoW.h"
#include "C4Rect.h"
#include "C4FoWLightSection.h"
#include "C4FoWRay.h"
#include "C4FoWLight.h"
#include "C4FoWRegion.h"
#include "C4Landscape.h"
#include "C4DrawGL.h"
#include "C4Object.h"
#include <float.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
// TODO: Make sure to use int32_t throughout!
// Cross product
float d = (x3-x4)*(y1-y2) - (y3-y4)*(x1-x2);
if (d == 0) return false; // parallel - or vector(s) 0
//#define LIGHT_DEBUG
// 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;
bool glCheck() {
if (int err = glGetError()) {
LogF("GL error %d: %s", err, gluErrorString(err));
return false;
// 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;
// Maximum error allowed while merging rays. Actually double, see below.
const int32_t C4FoWMergeThreshold = 10; // (in landscape pixels)
// A = 1/2 | a x b |
static inline int32_t getTriangleSurface(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3)
{
int32_t ax = x2 - x1, ay = y2 - y1;
int32_t bx = x3 - x1, by = y3 - y1;
// We don't bother to actually halve so we can stay with integers.
// Doesn't matter as long as we keep in mind the threshold needs to
// be doubled.
return abs(ax * by - ay * bx);
}
C4FoW::C4FoW()
: pLights(NULL)
{
}
void C4FoW::Add(C4Object *pObj)
{
// No view range? Probably want to remove instead
if(!pObj->PlrViewRange) {
Remove(pObj);
return;
}
// Look for matching light
C4FoWLight *pLight;
for (pLight = pLights; pLight; pLight = pLight->getNext())
if (pLight->getObj() == pObj)
break;
if (pLight) {
// Update reach
pLight->SetReach(pObj->PlrViewRange, 50);
} else {
// Create new light otherwise
pLight = new C4FoWLight(pObj);
pLight->pNext = pLights;
pLights = pLight;
}
}
void C4FoW::Remove(C4Object *pObj)
{
// Look for matching light
C4FoWLight *pPrev = NULL, *pLight;
for (pLight = pLights; pLight; pPrev = pLight, pLight = pLight->getNext())
if (pLight->getObj() == pObj)
break;
if (!pLight)
return;
// Remove
(pPrev ? pLights : pPrev->pNext) = pLight->getNext();
delete pLight;
}
void C4FoW::Invalidate(C4Rect r)
{
// Invalidate all lights
for (C4FoWLight *pLight = pLights; pLight; pLight = pLight->getNext())
pLight->Invalidate(r);
}
void C4FoWLight::Invalidate(C4Rect r)
{
// Invalidate all sections
for (C4FoWLightSection *pSect = pSections; pSect; pSect = pSect->getNext())
pSect->Invalidate(r);
}
void C4FoW::Update(C4Rect r)
{
// Update all lights
for (C4FoWLight *pLight = pLights; pLight; pLight = pLight->getNext())
pLight->Update(r);
}
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)
{
pSections = new C4FoWLightSection(this, 0);
pSections = new C4FoWLightSection(this, 90, pSections);
pSections = new C4FoWLightSection(this, 180, pSections);
pSections = new C4FoWLightSection(this, 270, pSections);
}
C4FoWLightSection::C4FoWLightSection(C4FoWLight *pLight, int r, C4FoWLightSection *pNext)
: pLight(pLight), iRot(r), pNext(pNext)
@ -146,14 +74,6 @@ C4FoWLightSection::C4FoWLightSection(C4FoWLight *pLight, int r, C4FoWLightSectio
pRays = new C4FoWRay(-1, +1, +1, +1);
}
C4FoWLight::~C4FoWLight()
{
while (C4FoWLightSection *pSect = pSections) {
pSections = pSect->getNext();
delete pSect;
}
}
C4FoWLightSection::~C4FoWLightSection()
{
ClearRays();
@ -167,29 +87,6 @@ void C4FoWLightSection::ClearRays()
}
}
void C4FoWLight::SetReach(int32_t iReach2, int32_t iFadeout2)
{
// Fadeout changes don't matter
iFadeout = iFadeout2;
// Reach unchanged? Easy.
if (iReach == iReach2) return;
// Reach decreased? Prune rays
if (iReach2 < iReach) {
iReach = iReach2;
for (C4FoWLightSection *pSect = pSections; pSect; pSect = pSect->getNext())
pSect->Prune(iReach);
} else {
// Reach increased? Dirty rays that might get longer now
iReach = iReach2;
for (C4FoWLightSection *pSect = pSections; pSect; pSect = pSect->getNext())
pSect->Dirty(iReach);
}
}
void C4FoWLightSection::Prune(int32_t iReach)
{
if (iReach == 0) {
@ -230,23 +127,6 @@ C4FoWRay *C4FoWLightSection::FindRayOver(int32_t x, int32_t y)
return pPrev ? pPrev->getNext() : pRays;
}
void C4FoWLight::Update(C4Rect Rec)
{
// Update position from object. Clear if we moved in any way
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())
pSect->Prune(0);
iX = iNX; iY = iNY;
}
// Update all sections
for (C4FoWLightSection *pSect = pSections; pSect; pSect = pSect->getNext())
pSect->Update(Rec);
}
void C4FoWLightSection::Update(C4Rect RectIn)
{
@ -441,6 +321,14 @@ void C4FoWLightSection::Update(C4Rect RectIn)
#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
@ -476,390 +364,6 @@ void C4FoWLightSection::Invalidate(C4Rect r)
}
bool C4FoWRay::MergeRight(int x, int y)
{
// Note: Right-merging is the most common and most important optimization.
// This procedure will probably be *hammered* as a result. Worth inlining?
assert(!isDirty()); assert(isRight(x, y));
// Calculate error. Note that simply summing up errors is not correct,
// strictly speaking (as new and old error surfaces might overlap). Still,
// this is quite elaborate already, no need to make it even more
int32_t iErr = getTriangleSurface(
getLeftEndX(), iLeftEndY,
getRightEndX(), iRightEndY,
x, y);
if (iError + iErr > C4FoWMergeThreshold)
return false;
// Move right endpoint.
iRightX = x;
iRightY = y;
iRightEndY = y;
iError += iErr;
return true;
}
bool C4FoWRay::MergeLeft(int x, int y)
{
assert(!isDirty()); assert(isLeft(x, y));
// Calculate error.
float iErr = getTriangleSurface(
getLeftEndX(), iLeftEndY,
getRightEndX(), iRightEndY,
x, y);
if (iError + iErr > C4FoWMergeThreshold)
return false;
// Move left endpoint.
iLeftX = x;
iLeftY = y;
iLeftEndY = y;
iError += iErr;
return true;
}
bool C4FoWRay::Eliminate(int x, int y)
{
// Called on the ray left of the one getting eliminated
C4FoWRay *pElim = pNext, *pMerge = pNext->pNext;
assert(!!pElim); assert(!!pMerge);
assert(!isDirty()); assert(!pMerge->isDirty());
// Calc errors, add those accumulated on both merged rays
int32_t iErr = getTriangleSurface(
getLeftEndX(), iLeftEndY,
pMerge->getRightEndX(), pMerge->iLeftEndY,
x, y);
iErr += iError + pMerge->iError;
if (iErr > C4FoWMergeThreshold)
return false;
// Do elimination
iRightX = pMerge->iRightX;
iRightY = pMerge->iRightY;
iRightEndY = pMerge->iRightEndY;
pNext = pMerge->pNext;
delete pElim;
delete pMerge;
return true;
}
C4FoWRay *C4FoWRay::Split(int x, int y)
{
// Make sure to newer create negative-surface rays
assert(isDirty()); assert(isInside(x, y));
// Allocate a new ray. Ugh, expensive.
C4FoWRay *pRay = new C4FoWRay(x, y, iRightX, iRightY);
pRay->Dirty(iLeftEndY);
// Move to make space
iRightX = x;
iRightY = y;
// Relink
pRay->pNext = pNext;
pNext = pRay;
return pRay;
}
void C4FoWRay::MergeDirty()
{
// As a rule, dirty rays following each other should
// always be merged, so splits can be reverted once
// the landscape changes.
C4FoWRay *pWith = pNext;
assert(isDirty()); assert(!!pWith); assert(pWith->isDirty());
// Figure out how far the new dirty ray reaches. Note that
// we might lose information about the landscape here.
Dirty(Min(getLeftEndY(), pWith->getLeftEndY()));
// Set right
iRightX = pWith->iRightX;
iRightY = pWith->iRightY;
// Relink & delete
pNext = pWith->getNext();
delete pWith;
}
void C4FoWRay::Clean(int y)
{
// Search hit something, this ray is now clean.
assert(isDirty());
iLeftEndY = y;
iRightEndY = y;
fDirty = false;
}
void C4FoWRay::Dirty(int y)
{
// Got invalidated, ray is dirty until updated
iLeftEndY = y;
iRightEndY = y;
fDirty = true;
}
void C4FoWRay::Prune(int32_t y)
{
// Check which sides we need to prune
bool fLeft = (iLeftEndY >= y),
fRight = (iRightEndY >= y);
// If both sides got pruned, we are clean
// (can't possibly extend this ray further)
if (fLeft && fRight)
Clean(y);
else if (fLeft)
iLeftEndY = y;
if (fRight)
iRightEndY = y;
}
C4FoWRegion::C4FoWRegion(C4FoW *pFoW, C4Player *pPlayer)
: pFoW(pFoW)
, pPlayer(pPlayer)
, hFrameBufDraw(0), hFrameBufRead(0)
, Region(0,0,0,0), OldRegion(0,0,0,0)
, pSurface(NULL), pBackSurface(NULL)
{
}
C4FoWRegion::~C4FoWRegion()
{
Clear();
}
bool C4FoWRegion::BindFramebuf()
{
// Flip texture
C4Surface *pSfc = pSurface;
pSurface = pBackSurface;
pBackSurface = pSfc;
// Can simply reuse old texture?
if (!pSurface || pSurface->Wdt < Region.Wdt || pSurface->Hgt < Region.Hgt)
{
// Create texture. Round up to next power of two in order to
// prevent rounding errors, as well as preventing lots of
// re-allocations when region size changes quickly (think zoom).
if (!pSurface)
pSurface = new C4Surface();
int iWdt = 1, iHgt = 1;
while (iWdt < Region.Wdt) iWdt *= 2;
while (iHgt < Region.Hgt) iHgt *= 2;
if (!pSurface->Create(iWdt, iHgt))
return false;
}
// Generate frame buffer object
if (!hFrameBufDraw) {
glGenFramebuffersEXT(1, &hFrameBufDraw);
glGenFramebuffersEXT(1, &hFrameBufRead);
}
// Bind current texture to frame buffer
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, hFrameBufDraw);
glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, hFrameBufRead);
glFramebufferTexture2DEXT(GL_DRAW_FRAMEBUFFER_EXT,
GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D,
pSurface->ppTex[0]->texName, 0);
if (pBackSurface)
glFramebufferTexture2DEXT(GL_READ_FRAMEBUFFER_EXT,
GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D,
pBackSurface->ppTex[0]->texName, 0);
// Check status, unbind if something was amiss
GLenum status1 = glCheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER_EXT),
status2 = glCheckFramebufferStatusEXT(GL_DRAW_FRAMEBUFFER_EXT);
if (status1 != GL_FRAMEBUFFER_COMPLETE_EXT ||
(pBackSurface && status2 != GL_FRAMEBUFFER_COMPLETE_EXT) ||
!glCheck())
{
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
return false;
}
// Worked!
return true;
}
void C4FoWRegion::Clear()
{
if (hFrameBufDraw) {
glDeleteFramebuffersEXT(1, &hFrameBufDraw);
glDeleteFramebuffersEXT(1, &hFrameBufRead);
}
hFrameBufDraw = hFrameBufRead = 0;
delete pSurface; pSurface = NULL;
delete pBackSurface; pBackSurface = NULL;
}
void C4FoWRegion::Update(C4Rect r)
{
// Set the new region
Region = r;
}
void C4FoWRegion::Render(const C4TargetFacet *pOnScreen)
{
// Update FoW at interesting location
pFoW->Update(Region);
// On screen? No need to set up frame buffer - simply shortcut
if (pOnScreen)
{
pFoW->Render(this, pOnScreen);
return;
}
// Create & bind the frame buffer
pDraw->StorePrimaryClipper();
if(!BindFramebuf())
{
pDraw->RestorePrimaryClipper();
return;
}
assert(pSurface && hFrameBufDraw);
if (!pSurface || !hFrameBufDraw)
return;
// Set up a clean context
glViewport(0, 0, getSurface()->Wdt, getSurface()->Hgt);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
gluOrtho2D(0, getSurface()->Wdt, getSurface()->Hgt, 0);
// Clear texture contents
glClearColor(0.0f, 0.5f/1.5f, 0.5f/1.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Copy over the old state
if (OldRegion.Wdt > 0) {
int dx0 = Region.x - OldRegion.x,
dy0 = Region.y - OldRegion.y,
dx1 = Region.x + Region.Wdt - OldRegion.x - OldRegion.Wdt,
dy1 = Region.y + Region.Hgt - OldRegion.y - OldRegion.Hgt;
/*glBlitFramebufferEXT(
Max(0, dx0), Max(0, -dy1),
OldRegion.Wdt - Max(0, -dx1), OldRegion.Hgt - Max(0, dy0),
Max(0, -dx0), Max(0, dy1),
Region.Wdt - Max(0, dx1), Region.Hgt - Max(0, -dy0),
GL_COLOR_BUFFER_BIT, GL_LINEAR);
*/
int sx0 = Max(0, dx0),
sy0 = Max(0, dy1),
sx1 = OldRegion.Wdt - Max(0, -dx1),
sy1 = OldRegion.Hgt - Max(0, -dy0),
tx0 = Max(0, -dx0),
ty0 = Max(0, -dy1),
tx1 = Region.Wdt - Max(0, dx1),
ty1 = Region.Hgt - Max(0, dy0);
glEnable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, getBackSurface()->ppTex[0]->texName);
glBlendFunc(GL_ONE, GL_ZERO);
glBegin(GL_QUADS);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glTexCoord2f(float(sx0)/getBackSurface()->Wdt,float(getBackSurface()->Hgt-sy0)/getBackSurface()->Hgt); glVertex2i(tx0, ty0);
glTexCoord2f(float(sx0)/getBackSurface()->Wdt,float(getBackSurface()->Hgt-sy1)/getBackSurface()->Hgt); glVertex2i(tx0, ty1);
glTexCoord2f(float(sx1)/getBackSurface()->Wdt,float(getBackSurface()->Hgt-sy1)/getBackSurface()->Hgt); glVertex2i(tx1, ty1);
glTexCoord2f(float(sx1)/getBackSurface()->Wdt,float(getBackSurface()->Hgt-sy0)/getBackSurface()->Hgt); glVertex2i(tx1, ty0);
glEnd();
glDisable(GL_TEXTURE_2D);
glCheck();
// Fade out. Note we constantly vary the alpha factor all the time -
// this is barely visible but makes it a lot less likely that we
// hit cases where we add the same thing every time, but still don't
// converge to the same color due to rounding.
int iAdd = (Game.FrameCounter/3) % 2;
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(0.0f, 0.5f/1.5f, 0.5f/1.5f, 1.0f/16.0f+iAdd*1.0f/256.0f);
glBegin(GL_QUADS);
glVertex2i(0, 0);
glVertex2i(getSurface()->Wdt, 0);
glVertex2i(getSurface()->Wdt, getSurface()->Hgt);
glVertex2i(0, getSurface()->Hgt);
glEnd();
}
// Render FoW to frame buffer object
glBlendFunc(GL_ONE, GL_ONE);
pFoW->Render(this);
// Done!
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
glMatrixMode(GL_PROJECTION);
glPopMatrix();
pDraw->RestorePrimaryClipper();
glCheck();
OldRegion = Region;
}
void C4FoW::Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScreen)
{
// Render all lights
for (C4FoWLight *pLight = pLights; pLight; pLight = pLight->getNext())
pLight->Render(pRegion, pOnScreen);
}
void C4FoWLight::Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScreen)
{
// Render all sections
//C4FoWLightSection *pSect = pSections;
for (C4FoWLightSection *pSect = pSections; pSect; pSect = pSect->getNext())
pSect->Render(pRegion, pOnScreen);
}
// 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;
}
void C4FoWLightSection::Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScreen)
{
C4Rect Reg = rtransRect(pRegion->getRegion());
@ -1156,7 +660,7 @@ void C4FoWLightSection::Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScr
// 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);;
float d = float(pLight->getFadeout()) / sqrt(dx*dx + dy*dy);
gFadeLX[i] = gFanLX[i] + d * dx;
gFadeLY[i] = gFanLY[i] + d * dy;
@ -1478,18 +982,3 @@ void C4FoWLightSection::Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScr
}
StdStrBuf C4FoWRay::getDesc() const {
return FormatString("%d:%d@%d:%d%s",
getLeftX(1000),
getRightX(1000),
getLeftEndY(),
getRightEndY(),
fDirty ? "*" : "");
}
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);
}

View File

@ -0,0 +1,94 @@
#ifndef C4FOWLIGHTSECTION_H
#define C4FOWLIGHTSECTION_H
#include "C4Rect.h"
#include "C4Surface.h"
class C4FoWLight;
class C4FoWRegion;
class C4FoWRay;
/** The light section manages the rays for one light for one direction of 90°. */
class C4FoWLightSection
{
public:
C4FoWLightSection(C4FoWLight *pLight, int r, C4FoWLightSection *pNext = NULL);
~C4FoWLightSection();
private:
/* Center light */
C4FoWLight *pLight;
/* Transformation matrix */
int iRot;
int a, b, c, d;
int ra, rb, rc, rd;
/* This section's rays */
class C4FoWRay *pRays;
C4FoWLightSection *pNext;
public:
C4FoWLightSection *getNext() const { return pNext; }
/** Recalculate of all light rays within the given rectangle because the landscape changed. */
void Invalidate(C4Rect r);
/** Update all light rays within the given rectangle */
void Update(C4Rect r);
void Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScreen = NULL);
/** Shorten all light rays to the given reach.
Called when the size of the light has decreased to the given value */
void Prune(int32_t iReach);
/** Extend all light rays to the given reach.
Called when the size of the light has increased to the given value */
void Dirty(int32_t iReach);
private:
/** Remove all rays. pRays is NULL after that. */
void ClearRays();
// Ray coordinate to landscape coordinate. Ray coordinates are relative to the light source.
template <class T> T transDX(T dx, T dy) const { return T(a) * dx + T(b) * dy; }
template <class T> T transDY(T dx, T dy) const { return T(c) * dx + T(d) * dy; }
template <class T> T transX(T x, T y) const { return transDX(x, y) + T(pLight->getX()); }
template <class T> T transY(T x, T y) const { return transDY(x, y) + T(pLight->getY()); }
// Landscape coordinate to ray coordinate. Ray coordinates are relative to the light source.
template <class T> T rtransDX(T dx, T dy) const { return T(ra) * dx + T(rb) * dy; }
template <class T> T rtransDY(T dx, T dy) const { return T(rc) * dx + T(rd) * dy; }
template <class T> T rtransX(T x, T y) const { return rtransDX(x-T(pLight->getX()),y-T(pLight->getY())); }
template <class T> T rtransY(T x, T y) const { return rtransDY(x-T(pLight->getX()),y-T(pLight->getY())); }
/** Returns a rectangle in ray coordinates */
C4Rect rtransRect(C4Rect r) const {
C4Rect Rect(rtransX(r.x, r.y), rtransY(r.x, r.y),
rtransDX(r.Wdt, r.Hgt), rtransDY(r.Wdt, r.Hgt));
Rect.Normalize();
return Rect;
}
bool isConsistent() const;
/** Returns the Y-position of the given rectangle's left most point when observed from the origin.
This function assumes a cartesian coordinate system (y axis up) */
int32_t RectLeftMostY(const C4Rect &r) const { return r.x >= 0 ? r.y+r.Hgt : r.y; }
/** Returns the Y-position of the given rectangle's right most point when observed from the origin.
This function assumes a cartesian coordinate system (y axis up) */
int32_t RectRightMostY(const C4Rect &r) const { return r.x + r.Wdt <= 0 ? r.y+r.Hgt : r.y; }
/** Find right-most ray left of point */
C4FoWRay *FindRayLeftOf(int32_t x, int32_t y);
/** Find left-most ray to extend over point */
C4FoWRay *FindRayOver(int32_t x, int32_t y);
};
#endif

View File

@ -0,0 +1,169 @@
#include "C4Include.h"
#include "C4FoWRay.h"
// Maximum error allowed while merging rays.
const int32_t C4FoWMergeThreshold = 10; // (in landscape pixels * 2)
// A = 1/2 | a x b |
static inline int32_t getDoubleTriangleSurface(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t x3, int32_t y3)
{
int32_t ax = x2 - x1, ay = y2 - y1;
int32_t bx = x3 - x1, by = y3 - y1;
// We don't bother to actually halve so we can stay with integers.
// Doesn't matter as long as we keep in mind the threshold needs to
// be doubled.
return abs(ax * by - ay * bx);
}
StdStrBuf C4FoWRay::getDesc() const {
return FormatString("%d:%d@%d:%d%s",
getLeftX(1000),
getRightX(1000),
getLeftEndY(),
getRightEndY(),
fDirty ? "*" : "");
}
bool C4FoWRay::MergeRight(int x, int y)
{
// Note: Right-merging is the most common and most important optimization.
// This procedure will probably be *hammered* as a result. Worth inlining?
assert(!isDirty()); assert(isRight(x, y));
// Calculate error. Note that simply summing up errors is not correct,
// strictly speaking (as new and old error surfaces might overlap). Still,
// this is quite elaborate already, no need to make it even more
int32_t iErr = getDoubleTriangleSurface(
getLeftEndX(), iLeftEndY,
getRightEndX(), iRightEndY,
x, y);
if (iError + iErr > C4FoWMergeThreshold)
return false;
// Move right endpoint.
iRightX = x;
iRightY = y;
iRightEndY = y;
iError += iErr;
return true;
}
bool C4FoWRay::MergeLeft(int x, int y)
{
assert(!isDirty()); assert(isLeft(x, y));
// Calculate error.
float iErr = getDoubleTriangleSurface(
getLeftEndX(), iLeftEndY,
getRightEndX(), iRightEndY,
x, y);
if (iError + iErr > C4FoWMergeThreshold)
return false;
// Move left endpoint.
iLeftX = x;
iLeftY = y;
iLeftEndY = y;
iError += iErr;
return true;
}
bool C4FoWRay::Eliminate(int x, int y)
{
// Called on the ray left of the one getting eliminated
C4FoWRay *pElim = pNext, *pMerge = pNext->pNext;
assert(!!pElim); assert(!!pMerge);
assert(!isDirty()); assert(!pMerge->isDirty());
// Calc errors, add those accumulated on both merged rays
int32_t iErr = getDoubleTriangleSurface(
getLeftEndX(), iLeftEndY,
pMerge->getRightEndX(), pMerge->iLeftEndY,
x, y);
iErr += iError + pMerge->iError;
if (iErr > C4FoWMergeThreshold)
return false;
// Do elimination
iRightX = pMerge->iRightX;
iRightY = pMerge->iRightY;
iRightEndY = pMerge->iRightEndY;
pNext = pMerge->pNext;
delete pElim;
delete pMerge;
return true;
}
C4FoWRay *C4FoWRay::Split(int x, int y)
{
// Make sure to never create negative-surface rays
assert(isDirty()); assert(isInside(x, y));
// Allocate a new ray. Ugh, expensive.
C4FoWRay *pRay = new C4FoWRay(x, y, iRightX, iRightY);
pRay->Dirty(iLeftEndY);
// Move to make space
iRightX = x;
iRightY = y;
// Relink
pRay->pNext = pNext;
pNext = pRay;
return pRay;
}
void C4FoWRay::MergeDirty()
{
// As a rule, dirty rays following each other should
// always be merged, so splits can be reverted once
// the landscape changes.
C4FoWRay *pWith = pNext;
assert(isDirty()); assert(!!pWith); assert(pWith->isDirty());
// Figure out how far the new dirty ray reaches. Note that
// we might lose information about the landscape here.
Dirty(Min(getLeftEndY(), pWith->getLeftEndY()));
// Set right
iRightX = pWith->iRightX;
iRightY = pWith->iRightY;
// Relink & delete
pNext = pWith->getNext();
delete pWith;
}
void C4FoWRay::Clean(int y)
{
// Search hit something, this ray is now clean.
assert(isDirty());
iLeftEndY = y;
iRightEndY = y;
fDirty = false;
}
void C4FoWRay::Dirty(int y)
{
// Got invalidated, ray is dirty until updated
iLeftEndY = y;
iRightEndY = y;
fDirty = true;
}
void C4FoWRay::Prune(int32_t y)
{
// Check which sides we need to prune
bool fLeft = (iLeftEndY >= y),
fRight = (iRightEndY >= y);
// If both sides got pruned, we are clean
// (can't possibly extend this ray further)
if (fLeft && fRight)
Clean(y);
else if (fLeft)
iLeftEndY = y;
if (fRight)
iRightEndY = y;
}

View File

@ -0,0 +1,98 @@
#ifndef C4FOW_H
#define C4FOW_H
#include "StdBuf.h"
/** This class represents one ray. ...TODO*/
class C4FoWRay
{
public:
C4FoWRay(int32_t iLeftX, int32_t iLeftY, int32_t iRightX, int32_t iRightY)
: iLeftX(iLeftX), iLeftY(iLeftY), iRightX(iRightX), iRightY(iRightY),
iLeftEndY(0), iRightEndY(0),
iError(0),
fDirty(true),
pNext(NULL)
{ }
private:
int32_t iLeftX, iLeftY; // left delimiter point
int32_t iRightX, iRightY; // right delimiter point
int32_t iLeftEndY, iRightEndY; // where it hit solid material. C4FoWRayActive while currently being followed.
int32_t iError; // How much error this ray has
bool fDirty; // landscape changed since it was followed?
C4FoWRay *pNext;
public:
bool isDirty() const { return fDirty; }
bool isClean() const { return !fDirty; }
C4FoWRay *getNext() const { return pNext; }
// Get a point on the ray boundary.
inline int32_t getLeftX(int32_t y) const { return iLeftX * y / iLeftY; }
inline int32_t getRightX(int32_t y) const { return iRightX * y / iRightY; }
inline float getLeftXf(int32_t y) const { return (iLeftX * y) / float(iLeftY); }
inline float getRightXf(int32_t y) const { return (iRightX * y) / float(iRightY); }
// TODO: why is iLeftY or iRightY never 0?
int32_t getLeftEndY() const { return iLeftEndY; }
int32_t getLeftEndX() const { return getLeftX(iLeftEndY); }
float getLeftEndXf() const { return getLeftXf(iLeftEndY); }
int32_t getRightEndY() const { return iRightEndY; }
int32_t getRightEndX() const { return getRightX(iRightEndY); }
float getRightEndXf() const { return getRightXf(iRightEndY); }
StdStrBuf getDesc() const;
/* returns whether the given point is left of an imaginery line drawn from the left delimiter point to the origin */
bool isLeft(int x, int y) const {
return iLeftX * y > x * iLeftY;
}
/* returns whether the given point is right of an imaginery line drawn from the right delimiter point to the origin */
bool isRight(int x, int y) const {
return iRightX * y < x * iRightY;
}
/* returns whether the given point is inside a cone spanned by the ray between the left delimiter and right
delimiter point and the ray spanned from the origin to the left delimiter point.
In case the origin is on the line with the two delimiter points, this function returns whether the point
is on the line spanned by the left and right delimiter points. */
bool isInside(int x, int 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; }
bool MergeRight(int x, int y);
bool MergeLeft(int x, int y);
/* Split this ray into two: this ray and the returned one. The given point x,y is the position at
which the two resulting rays are connected with their left/right endpoints.
It is asserted that the given point is on the same line as the delimiting points. */
C4FoWRay *Split(int x, int y);
/* Remove the two following vertices and connect this ray to the right delimiter point of the next but one ray.
The position x,y is only used for the error calcualation and not actually inserted.
Returns false and does not do the action in case the error threshold would be reached with these parameters
*/
// ASK: iError is not added to the error counter of this ray on success
// ASK: this removes TWO rays (but the comment says otherwise), why not one?
// ASK: the x,y coordinate given is never actually added - even thout it counts towards the error counter
bool Eliminate(int x, int y);
void MergeDirty();
void Clean(int32_t y);
void Dirty(int32_t y);
void Prune(int32_t y);
// ASK: why are they called left/right delimiter and not start and endpoints?
// ASK: left delimiter, right delimiter, origin always on one line? (if yes: add assertions)
// TODO: find out more about this DIRTY stuff
};
#endif // C4FOWRAY_H

View File

@ -0,0 +1,202 @@
#include "C4Include.h"
#include "C4FoWRegion.h"
bool glCheck() {
if (int err = glGetError()) {
LogF("GL error %d: %s", err, gluErrorString(err));
return false;
}
return true;
}
C4FoWRegion::~C4FoWRegion()
{
Clear();
}
bool C4FoWRegion::BindFramebuf()
{
// Flip texture
C4Surface *pSfc = pSurface;
pSurface = pBackSurface;
pBackSurface = pSfc;
// Can simply reuse old texture?
if (!pSurface || pSurface->Wdt < Region.Wdt || pSurface->Hgt < Region.Hgt)
{
// Create texture. Round up to next power of two in order to
// prevent rounding errors, as well as preventing lots of
// re-allocations when region size changes quickly (think zoom).
if (!pSurface)
pSurface = new C4Surface();
int iWdt = 1, iHgt = 1;
while (iWdt < Region.Wdt) iWdt *= 2;
while (iHgt < Region.Hgt) iHgt *= 2;
if (!pSurface->Create(iWdt, iHgt))
return false;
}
// Generate frame buffer object
if (!hFrameBufDraw) {
glGenFramebuffersEXT(1, &hFrameBufDraw);
glGenFramebuffersEXT(1, &hFrameBufRead);
}
// Bind current texture to frame buffer
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, hFrameBufDraw);
glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, hFrameBufRead);
glFramebufferTexture2DEXT(GL_DRAW_FRAMEBUFFER_EXT,
GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D,
pSurface->ppTex[0]->texName, 0);
if (pBackSurface)
glFramebufferTexture2DEXT(GL_READ_FRAMEBUFFER_EXT,
GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D,
pBackSurface->ppTex[0]->texName, 0);
// Check status, unbind if something was amiss
GLenum status1 = glCheckFramebufferStatusEXT(GL_READ_FRAMEBUFFER_EXT),
status2 = glCheckFramebufferStatusEXT(GL_DRAW_FRAMEBUFFER_EXT);
if (status1 != GL_FRAMEBUFFER_COMPLETE_EXT ||
(pBackSurface && status2 != GL_FRAMEBUFFER_COMPLETE_EXT) ||
!glCheck())
{
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
return false;
}
// Worked!
return true;
}
void C4FoWRegion::Clear()
{
if (hFrameBufDraw) {
glDeleteFramebuffersEXT(1, &hFrameBufDraw);
glDeleteFramebuffersEXT(1, &hFrameBufRead);
}
hFrameBufDraw = hFrameBufRead = 0;
delete pSurface; pSurface = NULL;
delete pBackSurface; pBackSurface = NULL;
}
void C4FoWRegion::Update(C4Rect r)
{
// Set the new region
Region = r;
}
void C4FoWRegion::Render(const C4TargetFacet *pOnScreen)
{
// Update FoW at interesting location
pFoW->Update(Region);
// On screen? No need to set up frame buffer - simply shortcut
if (pOnScreen)
{
pFoW->Render(this, pOnScreen);
return;
}
// Create & bind the frame buffer
pDraw->StorePrimaryClipper();
if(!BindFramebuf())
{
pDraw->RestorePrimaryClipper();
return;
}
assert(pSurface && hFrameBufDraw);
if (!pSurface || !hFrameBufDraw)
return;
// Set up a clean context
glViewport(0, 0, getSurface()->Wdt, getSurface()->Hgt);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
gluOrtho2D(0, getSurface()->Wdt, getSurface()->Hgt, 0);
// Clear texture contents
glClearColor(0.0f, 0.5f/1.5f, 0.5f/1.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Copy over the old state
if (OldRegion.Wdt > 0) {
int dx0 = Region.x - OldRegion.x,
dy0 = Region.y - OldRegion.y,
dx1 = Region.x + Region.Wdt - OldRegion.x - OldRegion.Wdt,
dy1 = Region.y + Region.Hgt - OldRegion.y - OldRegion.Hgt;
/*glBlitFramebufferEXT(
Max(0, dx0), Max(0, -dy1),
OldRegion.Wdt - Max(0, -dx1), OldRegion.Hgt - Max(0, dy0),
Max(0, -dx0), Max(0, dy1),
Region.Wdt - Max(0, dx1), Region.Hgt - Max(0, -dy0),
GL_COLOR_BUFFER_BIT, GL_LINEAR);
*/
int sx0 = Max(0, dx0),
sy0 = Max(0, dy1),
sx1 = OldRegion.Wdt - Max(0, -dx1),
sy1 = OldRegion.Hgt - Max(0, -dy0),
tx0 = Max(0, -dx0),
ty0 = Max(0, -dy1),
tx1 = Region.Wdt - Max(0, dx1),
ty1 = Region.Hgt - Max(0, dy0);
glEnable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, getBackSurface()->ppTex[0]->texName);
glBlendFunc(GL_ONE, GL_ZERO);
glBegin(GL_QUADS);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
glTexCoord2f(float(sx0)/getBackSurface()->Wdt,float(getBackSurface()->Hgt-sy0)/getBackSurface()->Hgt); glVertex2i(tx0, ty0);
glTexCoord2f(float(sx0)/getBackSurface()->Wdt,float(getBackSurface()->Hgt-sy1)/getBackSurface()->Hgt); glVertex2i(tx0, ty1);
glTexCoord2f(float(sx1)/getBackSurface()->Wdt,float(getBackSurface()->Hgt-sy1)/getBackSurface()->Hgt); glVertex2i(tx1, ty1);
glTexCoord2f(float(sx1)/getBackSurface()->Wdt,float(getBackSurface()->Hgt-sy0)/getBackSurface()->Hgt); glVertex2i(tx1, ty0);
glEnd();
glDisable(GL_TEXTURE_2D);
glCheck();
// Fade out. Note we constantly vary the alpha factor all the time -
// this is barely visible but makes it a lot less likely that we
// hit cases where we add the same thing every time, but still don't
// converge to the same color due to rounding.
int iAdd = (Game.FrameCounter/3) % 2;
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(0.0f, 0.5f/1.5f, 0.5f/1.5f, 1.0f/16.0f+iAdd*1.0f/256.0f);
glBegin(GL_QUADS);
glVertex2i(0, 0);
glVertex2i(getSurface()->Wdt, 0);
glVertex2i(getSurface()->Wdt, getSurface()->Hgt);
glVertex2i(0, getSurface()->Hgt);
glEnd();
}
// Render FoW to frame buffer object
glBlendFunc(GL_ONE, GL_ONE);
pFoW->Render(this);
// Done!
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
glMatrixMode(GL_PROJECTION);
glPopMatrix();
pDraw->RestorePrimaryClipper();
glCheck();
OldRegion = Region;
}
C4FoWRegion::C4FoWRegion(C4FoW *pFoW, C4Player *pPlayer)
: pFoW(pFoW)
, pPlayer(pPlayer)
, hFrameBufDraw(0), hFrameBufRead(0)
, Region(0,0,0,0), OldRegion(0,0,0,0)
, pSurface(NULL), pBackSurface(NULL)
{
}

View File

@ -0,0 +1,38 @@
#ifndef C4FOWREGION_H
#define C4FOWREGION_H
#include "C4Rect.h"
#include "C4Surface.h"
#include "C4FacetEx.h"
#include "C4Player.h"
#include "C4FoW.h"
class C4FoWRegion
{
public:
C4FoWRegion(C4FoW *pFoW, C4Player *pPlayer);
~C4FoWRegion();
private:
C4FoW *pFoW;
C4Player *pPlayer;
C4Rect Region, OldRegion;
C4Surface *pSurface, *pBackSurface;
GLuint hFrameBufDraw, hFrameBufRead;
public:
const C4Rect &getRegion() const { return Region; }
const C4Surface *getSurface() const { return pSurface; }
const C4Surface *getBackSurface() const { return pBackSurface; }
void Clear();
void Update(C4Rect r);
void Render(const C4TargetFacet *pOnScreen = NULL);
private:
bool BindFramebuf();
};
#endif