forked from Mirrors/openclonk
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 documentationissue1247
parent
c2f57db54b
commit
21e532da23
|
@ -286,8 +286,16 @@ set(OC_CLONK_SOURCES
|
||||||
src/gui/C4StartupScenSelDlg.h
|
src/gui/C4StartupScenSelDlg.h
|
||||||
src/gui/C4UpperBoard.cpp
|
src/gui/C4UpperBoard.cpp
|
||||||
src/gui/C4UpperBoard.h
|
src/gui/C4UpperBoard.h
|
||||||
src/landscape/C4FoW.cpp
|
src/landscape/fow/C4FoW.cpp
|
||||||
src/landscape/C4FoW.h
|
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.cpp
|
||||||
src/landscape/C4Landscape.h
|
src/landscape/C4Landscape.h
|
||||||
src/landscape/C4LandscapeRenderClassic.cpp
|
src/landscape/C4LandscapeRenderClassic.cpp
|
||||||
|
@ -718,6 +726,7 @@ include_directories(
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/graphics
|
${CMAKE_CURRENT_SOURCE_DIR}/src/graphics
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/gui
|
${CMAKE_CURRENT_SOURCE_DIR}/src/gui
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/landscape
|
${CMAKE_CURRENT_SOURCE_DIR}/src/landscape
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/landscape/fow
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/lib
|
${CMAKE_CURRENT_SOURCE_DIR}/src/lib
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/network
|
${CMAKE_CURRENT_SOURCE_DIR}/src/network
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/object
|
${CMAKE_CURRENT_SOURCE_DIR}/src/object
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
#include <C4PlayerList.h>
|
#include <C4PlayerList.h>
|
||||||
#include <C4GameObjects.h>
|
#include <C4GameObjects.h>
|
||||||
#include <C4Network2.h>
|
#include <C4Network2.h>
|
||||||
#include <C4FoW.h>
|
#include <C4FoWRegion.h>
|
||||||
|
|
||||||
void C4Viewport::DropFile(const char* fileName, float x, float y)
|
void C4Viewport::DropFile(const char* fileName, float x, float y)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
#include "C4Landscape.h"
|
#include "C4Landscape.h"
|
||||||
#include "C4Texture.h"
|
#include "C4Texture.h"
|
||||||
#include "C4FoW.h"
|
#include "C4FoWRegion.h"
|
||||||
|
|
||||||
#include "C4GroupSet.h"
|
#include "C4GroupSet.h"
|
||||||
#include "C4Components.h"
|
#include "C4Components.h"
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
|
@ -1,121 +1,49 @@
|
||||||
|
|
||||||
#include "C4Include.h"
|
#include "C4Include.h"
|
||||||
#include "C4FoW.h"
|
#include "C4FoWLightSection.h"
|
||||||
#include "C4Rect.h"
|
#include "C4FoWRay.h"
|
||||||
|
#include "C4FoWLight.h"
|
||||||
|
#include "C4FoWRegion.h"
|
||||||
#include "C4Landscape.h"
|
#include "C4Landscape.h"
|
||||||
#include "C4DrawGL.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() {
|
// Sanity-test solution
|
||||||
if (int err = glGetError()) {
|
#ifdef _DEBUG
|
||||||
LogF("GL error %d: %s", err, gluErrorString(err));
|
if (x1 != x2) {
|
||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float C4FoWSmooth = 8.0;
|
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)
|
C4FoWLightSection::C4FoWLightSection(C4FoWLight *pLight, int r, C4FoWLightSection *pNext)
|
||||||
: pLight(pLight), iRot(r), pNext(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);
|
pRays = new C4FoWRay(-1, +1, +1, +1);
|
||||||
}
|
}
|
||||||
|
|
||||||
C4FoWLight::~C4FoWLight()
|
|
||||||
{
|
|
||||||
while (C4FoWLightSection *pSect = pSections) {
|
|
||||||
pSections = pSect->getNext();
|
|
||||||
delete pSect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
C4FoWLightSection::~C4FoWLightSection()
|
C4FoWLightSection::~C4FoWLightSection()
|
||||||
{
|
{
|
||||||
ClearRays();
|
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)
|
void C4FoWLightSection::Prune(int32_t iReach)
|
||||||
{
|
{
|
||||||
if (iReach == 0) {
|
if (iReach == 0) {
|
||||||
|
@ -230,23 +127,6 @@ C4FoWRay *C4FoWLightSection::FindRayOver(int32_t x, int32_t y)
|
||||||
return pPrev ? pPrev->getNext() : pRays;
|
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)
|
void C4FoWLightSection::Update(C4Rect RectIn)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -441,6 +321,14 @@ void C4FoWLightSection::Update(C4Rect RectIn)
|
||||||
#endif
|
#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)
|
void C4FoWLightSection::Invalidate(C4Rect r)
|
||||||
{
|
{
|
||||||
// Assume normalized rectangle
|
// 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)
|
void C4FoWLightSection::Render(C4FoWRegion *pRegion, const C4TargetFacet *pOnScreen)
|
||||||
{
|
{
|
||||||
C4Rect Reg = rtransRect(pRegion->getRegion());
|
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
|
// For once we actually calculate this using the real distance
|
||||||
float dx = gFanLX[i] - gLightLX, dy = gFanLY[i] - gLightLY;
|
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;
|
gFadeLX[i] = gFanLX[i] + d * dx;
|
||||||
gFadeLY[i] = gFanLY[i] + d * dy;
|
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);
|
|
||||||
}
|
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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)
|
||||||
|
{
|
||||||
|
}
|
|
@ -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
|
Loading…
Reference in New Issue