forked from Mirrors/openclonk
379 lines
11 KiB
C++
379 lines
11 KiB
C++
/*
|
|
* OpenClonk, http://www.openclonk.org
|
|
*
|
|
* Copyright (c) 2014-2015, The OpenClonk Team and contributors
|
|
*
|
|
* Distributed under the terms of the ISC license; see accompanying file
|
|
* "COPYING" for details.
|
|
*
|
|
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
|
|
* See accompanying file "TRADEMARK" for details.
|
|
*
|
|
* To redistribute this file separately, substitute the full license texts
|
|
* for the above references.
|
|
*/
|
|
|
|
#include "C4Include.h"
|
|
#include "C4FoWLight.h"
|
|
#include "C4FoWLightSection.h"
|
|
#include "C4FoWBeamTriangle.h"
|
|
#include "C4FoWDrawStrategy.h"
|
|
#include "C4FoWRegion.h"
|
|
#include "C4PlayerList.h"
|
|
#include "C4Player.h"
|
|
|
|
#include <vector>
|
|
|
|
C4FoWLight::C4FoWLight(C4Object *pObj)
|
|
: iX(fixtoi(pObj->fix_x)),
|
|
iY(fixtoi(pObj->fix_y)),
|
|
iReach(pObj->lightRange),
|
|
iFadeout(pObj->lightFadeoutRange),
|
|
iSize(20),
|
|
pNext(NULL),
|
|
pObj(pObj),
|
|
sections(4)
|
|
{
|
|
sections[0] = new C4FoWLightSection(this,0);
|
|
sections[1] = new C4FoWLightSection(this,90);
|
|
sections[2] = new C4FoWLightSection(this,180);
|
|
sections[3] = new C4FoWLightSection(this,270);
|
|
}
|
|
|
|
C4FoWLight::~C4FoWLight()
|
|
{
|
|
for(size_t i = 0; i < sections.size(); ++i )
|
|
delete sections[i];
|
|
}
|
|
|
|
void C4FoWLight::Invalidate(C4Rect r)
|
|
{
|
|
for(size_t i = 0; i < sections.size(); ++i )
|
|
sections[i]->Invalidate(r);
|
|
}
|
|
|
|
void C4FoWLight::SetReach(int32_t iReach2, int32_t iFadeout2)
|
|
{
|
|
// Fadeout changes don't matter
|
|
iFadeout = iFadeout2;
|
|
|
|
if (iReach == iReach2) return;
|
|
|
|
if (iReach2 < iReach)
|
|
{
|
|
// Reach decreased? Prune beams
|
|
iReach = iReach2;
|
|
for(size_t i = 0; i < sections.size(); ++i )
|
|
sections[i]->Prune(iReach);
|
|
|
|
} else {
|
|
|
|
// Reach increased? Dirty beams that might get longer now
|
|
iReach = iReach2;
|
|
for(size_t i = 0; i < sections.size(); ++i )
|
|
sections[i]->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(size_t i = 0; i < sections.size(); ++i )
|
|
sections[i]->Prune(0);
|
|
iX = iNX; iY = iNY;
|
|
}
|
|
|
|
for(size_t i = 0; i < sections.size(); ++i )
|
|
sections[i]->Update(Rec);
|
|
}
|
|
|
|
void C4FoWLight::Render(C4FoWRegion *region, const C4TargetFacet *onScreen)
|
|
{
|
|
TriangleList triangles;
|
|
|
|
bool clip = false;
|
|
|
|
iX = 199; iY = 579;
|
|
iReach = 300;
|
|
iFadeout = 80;
|
|
iSize = 20;
|
|
StdStrBuf test;
|
|
assert(FileExists("light_section_assertion.txt"));
|
|
test.LoadFromFile("light_section_assertion.txt");
|
|
CompileFromBuf<StdCompilerINIRead>(mkNamingAdapt(*sections[0], "LightSection"), test);
|
|
region->Update(C4Rect(0, 0, 1000, 1000), FLOAT_RECT());
|
|
sections[0]->CalculateTriangles(region);
|
|
|
|
for(size_t i = 0; i < sections.size(); ++i )
|
|
{
|
|
TriangleList sectionTriangles = sections[i]->CalculateTriangles(region);
|
|
|
|
// if the triangles of one section are clipped completely, the neighbouring triangles
|
|
// must be marked as clipped
|
|
if(!triangles.empty()) triangles.rbegin()->clipRight |= clip;
|
|
if(!sectionTriangles.empty()) sectionTriangles.begin()->clipLeft |= clip;
|
|
|
|
clip = sectionTriangles.empty();
|
|
triangles.splice(triangles.end(), sectionTriangles);
|
|
}
|
|
|
|
CalculateFanMaxed(triangles);
|
|
CalculateIntermediateFadeTriangles(triangles);
|
|
|
|
// Here's the master plan for updating the lights texture. We
|
|
// want to add intensity (R channel) as well as the normal (GB channels).
|
|
// Normals are obviously meant to be though of as signed, though,
|
|
// so the equation we want would be something like
|
|
//
|
|
// R_new = Clamp(R_old + R, 0.0, 1.0)
|
|
// G_new = Clamp(G_old + G - 0.5, 0.0, 1.0)
|
|
// B_new = Clamp(B_old + B - 0.5, 0.0, 1.0)
|
|
//
|
|
// It seems we can't get that directly though - glBlendFunc only talks
|
|
// about two operands. Even if we make two passes, we have to take
|
|
// care that that we don't over- or underflow in the intermediate pass.
|
|
//
|
|
// Therefore, we store G/1.5 instead of G, losing a bit of accuracy,
|
|
// but allowing us to formulate the following approximation without
|
|
// overflows:
|
|
//
|
|
// G_new = Clamp(Clamp(G_old + G / 1.5), 0.0, 1.0) - 0.5 / 1.5, 0.0, 1.0)
|
|
// B_new = Clamp(Clamp(B_old + B / 1.5), 0.0, 1.0) - 0.5 / 1.5, 0.0, 1.0)
|
|
|
|
C4FoWDrawStrategy* pen;
|
|
if (onScreen) pen = new C4FoWDrawWireframeStrategy(this, onScreen);
|
|
else pen = new C4FoWDrawLightTextureStrategy(this, region);
|
|
|
|
for(int pass = 0; pass < pen->GetRequestedPasses(); pass++)
|
|
{
|
|
pen->Begin(pass);
|
|
|
|
DrawFan(pen, triangles);
|
|
DrawFanMaxed(pen, triangles);
|
|
DrawFade(pen, triangles);
|
|
DrawIntermediateFadeTriangles(pen, triangles);
|
|
|
|
pen->End(pass);
|
|
}
|
|
|
|
delete pen;
|
|
}
|
|
|
|
void C4FoWLight::CalculateFanMaxed(TriangleList &triangles) const
|
|
{
|
|
for (TriangleList::iterator it = triangles.begin(); it != triangles.end(); ++it)
|
|
{
|
|
C4FoWBeamTriangle &tri = *it;
|
|
|
|
// Is the left point close enough that normals don't max out?
|
|
float dist = sqrt(GetSquaredDistanceTo(tri.fanLX, tri.fanLY));
|
|
if (dist <= getNormalSize()) {
|
|
tri.nfanLX = tri.fanLX;
|
|
tri.nfanLY = tri.fanLY;
|
|
} else {
|
|
// Otherwise, calculate point where they do. We will add a seperate
|
|
// triangle/quad later on to capture that.
|
|
float f = float(getNormalSize() / dist);
|
|
tri.nfanLX = f * tri.fanLX + (1.0f - f) * getX();
|
|
tri.nfanLY = f * tri.fanLY + (1.0f - f) * getY();
|
|
}
|
|
|
|
// Same for the right point
|
|
dist = sqrt(GetSquaredDistanceTo(tri.fanRX, tri.fanRY));
|
|
if (dist <= getNormalSize()) {
|
|
tri.nfanRX = tri.fanRX;
|
|
tri.nfanRY = tri.fanRY;
|
|
} else {
|
|
float f = float(getNormalSize()) / dist;
|
|
tri.nfanRX = f * tri.fanRX + (1.0f - f) * getX();
|
|
tri.nfanRY = f * tri.fanRY + (1.0f - f) * getY();
|
|
}
|
|
}
|
|
}
|
|
|
|
void C4FoWLight::ProjectPointOutward(float &x, float &y, float maxDistance) const
|
|
{
|
|
float distanceDifference = Min(maxDistance, (float) getTotalReach()) / sqrt((x - getX()) * (x - getX()) + (y - getY()) * (y - getY()));
|
|
|
|
x = getX() + distanceDifference * (x-getX());
|
|
y = getY() + distanceDifference * (y-getY());
|
|
}
|
|
|
|
void C4FoWLight::CalculateIntermediateFadeTriangles(TriangleList &triangles) const
|
|
{
|
|
for (TriangleList::iterator it = triangles.begin(), nextIt = it; it != triangles.end(); ++it)
|
|
{
|
|
// wrap around
|
|
++nextIt;
|
|
if(nextIt == triangles.end()) nextIt = triangles.begin();
|
|
|
|
C4FoWBeamTriangle &tri = *it, &nextTri = *nextIt; // just for convenience
|
|
|
|
// don't calculate if it should not be drawn anyway
|
|
if (tri.clipRight || nextTri.clipLeft) continue;
|
|
|
|
float distFadeR = GetSquaredDistanceTo(tri.fadeRX, tri.fadeRY);
|
|
float distNextFadeL = GetSquaredDistanceTo(nextTri.fadeLX, nextTri.fadeLY);
|
|
float distFanR = GetSquaredDistanceTo(tri.fanRX, tri.fanRY);
|
|
float distNextFanL = GetSquaredDistanceTo(nextTri.fanLX, nextTri.fanLY);
|
|
|
|
// an extra intermediate fade point is only necessary on cliffs
|
|
tri.descending = distFanR > distNextFanL;
|
|
if (tri.descending) {
|
|
if (distFanR < distNextFadeL)
|
|
{
|
|
tri.fadeIX = nextTri.fadeLX;
|
|
tri.fadeIY = nextTri.fadeLY;
|
|
}
|
|
else
|
|
{
|
|
tri.fadeIX = (tri.fanRX + nextTri.fadeLX) / 2;
|
|
tri.fadeIY = (tri.fanRY + nextTri.fadeLY) / 2;
|
|
ProjectPointOutward(tri.fadeIX, tri.fadeIY, sqrt(distFadeR));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (distNextFanL < distFadeR)
|
|
{
|
|
tri.fadeIX = tri.fadeRX;
|
|
tri.fadeIY = tri.fadeRY;
|
|
}
|
|
else
|
|
{
|
|
tri.fadeIX = (tri.fadeRX + nextTri.fanLX) / 2;
|
|
tri.fadeIY = (tri.fadeRY + nextTri.fanLY) / 2;
|
|
ProjectPointOutward(tri.fadeIX, tri.fadeIY, sqrt(distNextFadeL));
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
void C4FoWLight::DrawFan(C4FoWDrawStrategy* pen, TriangleList &triangles) const
|
|
{
|
|
pen->BeginFan();
|
|
pen->DrawLightVertex(getX(), getY());
|
|
|
|
for (TriangleList::iterator it = triangles.begin(), nextIt = it; it != triangles.end(); ++it)
|
|
{
|
|
// wrap around
|
|
++nextIt;
|
|
if(nextIt == triangles.end()) nextIt = triangles.begin();
|
|
|
|
C4FoWBeamTriangle &tri = *it, &nextTri = *nextIt; // just for convenience
|
|
|
|
pen->DrawLightVertex(tri.nfanLX, tri.nfanLY);
|
|
|
|
if(nextIt == triangles.begin() || nextTri.nfanLX != tri.nfanRX || nextTri.nfanLY != tri.nfanRY)
|
|
pen->DrawLightVertex(tri.nfanRX, tri.nfanRY);
|
|
}
|
|
pen->EndFan();
|
|
}
|
|
|
|
void C4FoWLight::DrawFanMaxed(C4FoWDrawStrategy* pen, TriangleList &triangles) const
|
|
{
|
|
pen->BeginFanMaxed();
|
|
for (TriangleList::iterator it = triangles.begin(), nextIt = it; it != triangles.end(); ++it)
|
|
{
|
|
// Wrap around for next triangle
|
|
++nextIt; if(nextIt == triangles.end()) nextIt = triangles.begin();
|
|
C4FoWBeamTriangle &tri = *it, &nextTri = *nextIt;
|
|
|
|
// First for the current beam
|
|
if (tri.nfanLX != tri.nfanRX || tri.nfanLY != tri.nfanRY)
|
|
{
|
|
pen->DrawLightVertex(tri.nfanLX, tri.nfanLY);
|
|
pen->DrawLightVertex(tri.nfanRX, tri.nfanRY);
|
|
pen->DrawLightVertex(tri.fanRX, tri.fanRY);
|
|
pen->DrawLightVertex(tri.fanLX, tri.fanLY);
|
|
}
|
|
// Then for the space in-between
|
|
if (tri.nfanRX != nextTri.nfanLX || tri.nfanRY != nextTri.nfanLY)
|
|
{
|
|
pen->DrawLightVertex(tri.nfanRX, tri.nfanRY);
|
|
pen->DrawLightVertex(nextTri.nfanLX, nextTri.nfanLY);
|
|
pen->DrawLightVertex(nextTri.fanLX, nextTri.fanLY);
|
|
pen->DrawLightVertex(tri.fanRX, tri.fanRY);
|
|
}
|
|
}
|
|
pen->EndFanMaxed();
|
|
}
|
|
|
|
void C4FoWLight::DrawFade(C4FoWDrawStrategy* pen, TriangleList &triangles) const
|
|
{
|
|
pen->BeginFade();
|
|
|
|
for (TriangleList::iterator it = triangles.begin(); it != triangles.end(); ++it)
|
|
{
|
|
C4FoWBeamTriangle &tri = *it; // just for convenience
|
|
|
|
// The quad will be empty if fan points match
|
|
if (tri.fanLX == tri.fanRX && tri.fanLY == tri.fanRY) continue;
|
|
|
|
pen->DrawLightVertex(tri.fanLX, tri.fanLY);
|
|
pen->DrawLightVertex(tri.fanRX, tri.fanRY);
|
|
pen->DrawDarkVertex(tri.fadeRX, tri.fadeRY);
|
|
pen->DrawDarkVertex(tri.fadeLX, tri.fadeLY);
|
|
}
|
|
pen->EndFade();
|
|
}
|
|
|
|
void C4FoWLight::DrawIntermediateFadeTriangles(C4FoWDrawStrategy* pen, TriangleList &triangles) const
|
|
{
|
|
|
|
for (TriangleList::iterator it = triangles.begin(), nextIt = it; it != triangles.end(); ++it)
|
|
{
|
|
// wrap around
|
|
++nextIt;
|
|
if(nextIt == triangles.end()) nextIt = triangles.begin();
|
|
|
|
C4FoWBeamTriangle &tri = *it, &nextTri = *nextIt; // just for convenience
|
|
|
|
// no inter-fade triangles when it should be clipped
|
|
if (tri.clipRight || nextTri.clipLeft) continue;
|
|
|
|
pen->BeginIntermediateFade();
|
|
|
|
if (tri.descending) {
|
|
|
|
pen->DrawLightVertex(tri.fanRX, tri.fanRY);
|
|
pen->DrawLightVertex(nextTri.fanLX, nextTri.fanLY);
|
|
pen->DrawDarkVertex(nextTri.fadeLX, nextTri.fadeLY);
|
|
|
|
// if necessary
|
|
if (tri.fadeIY != nextTri.fadeLY || tri.fadeIX != nextTri.fadeLX) {
|
|
pen->DrawDarkVertex(tri.fadeIX, tri.fadeIY);
|
|
}
|
|
|
|
pen->DrawDarkVertex(tri.fadeRX, tri.fadeRY);
|
|
|
|
} else {
|
|
|
|
pen->DrawLightVertex(nextTri.fanLX, nextTri.fanLY);
|
|
pen->DrawDarkVertex(nextTri.fadeLX, nextTri.fadeLY);
|
|
|
|
// if necessary
|
|
if (tri.fadeIY != tri.fadeRY || tri.fadeIX != tri.fadeRX) {
|
|
pen->DrawDarkVertex(tri.fadeIX, tri.fadeIY);
|
|
}
|
|
|
|
pen->DrawDarkVertex(tri.fadeRX, tri.fadeRY);
|
|
pen->DrawLightVertex(tri.fanRX, tri.fanRY);
|
|
}
|
|
|
|
pen->EndIntermediateFade();
|
|
}
|
|
}
|
|
|
|
bool C4FoWLight::IsVisibleForPlayer(C4Player *player) const
|
|
{
|
|
// check if attached to an object that is not hostile to the given player
|
|
if (!pObj || !player) return true;
|
|
return !::Hostile(pObj->Owner,player->Number);
|
|
}
|