tge/engine/lightingSystem/sgLightMap.cc
2017-04-17 06:17:10 -06:00

1287 lines
35 KiB
C++
Executable File

//-----------------------------------------------
// Synapse Gaming - Lighting Code Pack
// Copyright © Synapse Gaming 2003 - 2005
// Written by John Kabus
//
// Overview:
// Code from the Lighting Pack's (Torque Lighting Kit)
// lighting system, which was modified for use
// with Constructor.
//-----------------------------------------------
#include "math/mBox.h"
#include "math/mathUtils.h"
#include "sceneGraph/sceneGraph.h"
#include "terrain/terrData.h"
#include "platform/profiler.h"
#include "interior/interior.h"
#include "interior/interiorInstance.h"
#include "lightingSystem/sgLightMap.h"
#include "lightingSystem/sgLightingModel.h"
#include "math/mRandom.h"
#include "math/mathUtils.h"
#include "util/triRayCheck.h"
/// used to calculate the start and end points
/// for ray casting directional light.
#define SG_STATIC_LIGHT_VECTOR_DIST 100
#define SG_MIN_LEXEL_INTENSITY 0.0039215f
#define SG_STATICMESH_BVPT_SHADOWS
bool badtexgen = false;
Vector<sgShadowObjects::sgObjectInfo *> sgShadowObjects::sgObjectInfoStorage;
sgShadowObjects::sgStaticMeshBVPTEntry sgShadowObjects::sgStaticMeshBVPTMap;
VectorPtr<SceneObject *> sgShadowObjects::sgObjects;
U32 sgPlanarLightMap::sgCurrentOccluderMaskId = 0;
/// used to generate light map indexes that wrap around
/// instead of exceeding the index bounds.
inline S32 sgGetIndex(S32 width, S32 height, S32 x, S32 y)
{
if(x > (width-1))
x -= width;
else if(x < 0)
x += width;
if(y > (height-1))
y -= height;
else if(y < 0)
y += height;
return (y * width) + x;
}
inline F32 sgGetDistanceSquared(const Point3F &linepointa,
const Point3F &linepointb, const Point3F &point)
{
Point3F vect = linepointb - linepointa;
F32 dist = vect.lenSquared();
if(dist <= 0.0f)
return 100000.0f;
F32 tang = ((point.x + linepointa.x) * (linepointb.x + linepointa.x)) +
((point.y + linepointa.y) * (linepointb.y + linepointa.y)) +
((point.z + linepointa.z) * (linepointb.z + linepointa.z));
tang /= dist;
if(tang >= 1.0f)
{
vect = linepointb - point;
return vect.lenSquared();
}
if(tang <= 0.0f)
{
vect = linepointa - point;
return vect.lenSquared();
}
vect = linepointa + (vect * tang);
vect = vect - point;
return vect.lenSquared();
}
void sgObjectCallback(SceneObject *object, void *vector)
{
VectorPtr<SceneObject *> *intersectingobjects = static_cast<VectorPtr<SceneObject *> *>(vector);
intersectingobjects->push_back(object);
}
void sgShadowObjects::sgGetObjects(SceneObject *obj)
{
sgObjects.clear();
obj->getContainer()->findObjects(ShadowCasterObjectType, &sgObjectCallback, &sgObjects);
}
bool sgShadowObjects::sgCastRayStaticMesh(Point3F s, Point3F e, ConstructorSimpleMesh *staticmesh)
{
sgStaticMeshBVPTEntry *entry = sgStaticMeshBVPTMap.find(U32(staticmesh));
AssertFatal((entry), "hash_map should always return an entry!");
// is the BVPT there?
if(!entry->info)
{
sgObjectInfo *objinfo = new sgObjectInfo();
entry->info = objinfo;
sgObjectInfoStorage.push_back(objinfo);
objinfo->sgBVPT.init(staticmesh->bounds);
objinfo->sgInverseTransform = staticmesh->transform;
objinfo->sgInverseTransform.inverse();
// get the tri count...
U32 tricount = 0;
for(U32 p=0; p<staticmesh->primitives.size(); p++)
{
ConstructorSimpleMesh::primitive &prim = staticmesh->primitives[p];
if(prim.alpha)
continue;
tricount += prim.count;
}
objinfo->sgTris.reserve(tricount);
// add the tris...
for(U32 p=0; p<staticmesh->primitives.size(); p++)
{
ConstructorSimpleMesh::primitive &prim = staticmesh->primitives[p];
if(prim.alpha || (prim.count < 3))
continue;
PlaneF plane = PlaneF(staticmesh->verts[0], staticmesh->verts[1], staticmesh->verts[2]);
Point3F norm = (staticmesh->norms[0] + staticmesh->norms[1] + staticmesh->norms[2]) * 0.3333f;
bool flip = (mDot(plane, norm) < 0.0f);
for(U32 t=2; t<prim.count; t++)
{
U32 baseindex = prim.start + t;
objinfo->sgTris.increment();
sgStaticMeshTri &tri = objinfo->sgTris.last();
bool winding = (t & 0x1);
if(flip)
winding = !winding;
if(winding)
{
tri.sgVert[0] = staticmesh->verts[baseindex-1];
tri.sgVert[1] = staticmesh->verts[baseindex-2];
tri.sgVert[2] = staticmesh->verts[baseindex];
tri.sgPlane = PlaneF(tri.sgVert[0], tri.sgVert[1], tri.sgVert[2]);
}
else
{
tri.sgVert[0] = staticmesh->verts[baseindex-2];
tri.sgVert[1] = staticmesh->verts[baseindex-1];
tri.sgVert[2] = staticmesh->verts[baseindex];
tri.sgPlane = PlaneF(tri.sgVert[0], tri.sgVert[1], tri.sgVert[2]);
}
}
}
// need to do this last so any vector copy/resize doesn't kill the pointers...
for(U32 t=0; t<objinfo->sgTris.size(); t++)
{
sgStaticMeshTri &tri = objinfo->sgTris[t];
Box3F box;
box.min = tri.sgVert[0];
box.max = tri.sgVert[0];
for(U32 v=1; v<3; v++)
{
box.min.setMin(tri.sgVert[v]);
box.max.setMax(tri.sgVert[v]);
}
objinfo->sgBVPT.storeObject(box, &tri);
}
}
// convert to static mesh space...
sgObjectInfo *objinfo = entry->info;
objinfo->sgInverseTransform.mulP(s);
objinfo->sgInverseTransform.mulP(e);
s.convolveInverse(staticmesh->scale);
e.convolveInverse(staticmesh->scale);
// early out test...
F32 t;
Point3F n;
if(!staticmesh->bounds.collideLine(s, e, &t, &n))
return false;
// get the likely occluders...
sgStaticMeshBVPT::objectList list;
objinfo->sgBVPT.collectObjectsClipped(s, e, list);
//objinfo->sgBVPT.collectObjects(staticmesh->bounds, list);
Point3F vect = e - s;
F32 raycastdist;
Point2F temp2;
// now cast against them...
U32 tc = list.size();
sgStaticMeshTri **tris = list.address();
for(U32 i=0; i<tc; i++)
{
sgStaticMeshTri *tri = tris[i];
if(mDot(tri->sgPlane, vect) >= 0.0f)
continue;
//stats
sgStatistics::sgStaticMeshSurfaceOccluderCount++;
if(castRayTriangle(s, vect, tri->sgVert[0], tri->sgVert[1], tri->sgVert[2], raycastdist, temp2))
return true;
}
return false;
}
void sgShadowObjects::sgClearStaticMeshBVPTData()
{
for(U32 i=0; i<sgObjectInfoStorage.size(); i++)
delete sgObjectInfoStorage[i];
sgObjectInfoStorage.clear();
sgStaticMeshBVPTMap.clear();
}
void sgColorMap::sgFillInLighting()
{
U32 x, y;
U32 lastgoodx, lastgoody;
U32 lastgoodyoffset, yoffset;
if(LightManager::sgAllowFullLightMaps())
return;
U32 lmscalemask = LightManager::sgGetLightMapScale() - 1;
for(y=0; y<sgHeight; y++)
{
// do we have a good y?
if((y & lmscalemask) == 0)
{
lastgoody = y;
lastgoodyoffset = lastgoody * sgWidth;
}
yoffset = y * sgWidth;
for(x=0; x<sgWidth; x++)
{
// do we have a good x?
if((x & lmscalemask) == 0)
{
lastgoodx = x;
// only bailout if we're on a good y, otherwise
// all of the x entries are empty...
if((y & lmscalemask) == 0)
continue;
}
ColorF &last = sgData[(lastgoodyoffset + lastgoodx)];
sgData[(yoffset + x)] = last;
}
}
}
void sgColorMap::sgBlur()
{
static F32 blur[3][3] = {{0.1, 0.125, 0.1}, {0.125, 0.1, 0.125}, {0.1, 0.125, 0.1}};
ColorF *buffer = new ColorF[sgWidth * sgHeight];
// lets get the values that we don't blur...
dMemcpy(buffer, sgData, (sizeof(ColorF) * sgWidth * sgHeight));
for(U32 y=1; y<(sgHeight-1); y++)
{
for(U32 x=1; x<(sgWidth-1); x++)
{
ColorF &col = buffer[((y * sgWidth) + x)];
col.set(0.0f, 0.0f, 0.0f);
for(S32 by=-1; by<2; by++)
{
for(S32 bx=-1; bx<2; bx++)
{
col += sgData[(((y + by) * sgWidth) + (x + bx))] * blur[bx+1][by+1];
}
}
}
}
delete[] sgData;
sgData = buffer;
}
void sgLightMap::sgGetIntersectingObjects(const Box3F &surfacebox, const SceneObject *skipobject)
{
sgIntersectingSceneObjects.clear();
sgIntersectingStaticMeshObjects.clear();
for(U32 i=0; i<sgShadowObjects::sgObjects.size(); i++)
{
SceneObject *obj = sgShadowObjects::sgObjects[i];
if(obj == skipobject)
continue;
if(!surfacebox.isOverlapped(obj->getWorldBox()))
continue;
sgIntersectingSceneObjects.push_back(obj);
InteriorInstance *inst = dynamic_cast<InteriorInstance *>(obj);
if(!inst)
continue;
Interior *detail = inst->getDetailLevel(0);
for(U32 sm=0; sm<detail->getStaticMeshCount(); sm++)
{
const ConstructorSimpleMesh *smobj = detail->getStaticMesh(sm);
Box3F bounds = smobj->bounds;
Box3F worldbounds;
bounds.min.convolve(smobj->scale);
bounds.max.convolve(smobj->scale);
MathUtils::transformBoundingBox(bounds, smobj->transform, worldbounds);
bounds = worldbounds;
bounds.min.convolve(inst->getScale());
bounds.max.convolve(inst->getScale());
MathUtils::transformBoundingBox(bounds, inst->getTransform(), worldbounds);
if(!surfacebox.isOverlapped(worldbounds))
continue;
sgIntersectingStaticMeshObjects.increment(1);
sgStaticMeshInfo &sminfo = sgIntersectingStaticMeshObjects.last();
sminfo.sgStaticMesh = (ConstructorSimpleMesh *)smobj;
sminfo.sgInteriorInstance = inst;
}
}
}
bool sgPlanarLightMap::sgCastRay(Point3F s, Point3F e, SceneObject *obj, Interior *detail, ConstructorSimpleMesh *sm, sgOccluder &occluderinfo)
{
obj->getWorldTransform().mulP(s);
obj->getWorldTransform().mulP(e);
s.convolveInverse(obj->getScale());
e.convolveInverse(obj->getScale());
if(sm)
{
#ifdef SG_STATICMESH_BVPT_SHADOWS
// expects points in interior space...
if(!sgShadowObjects::sgCastRayStaticMesh(s, e, sm))
return false;
#else
MatrixF mat = sm->transform;
mat.inverse();
mat.mulP(s);
mat.mulP(e);
s.convolveInverse(sm->scale);
e.convolveInverse(sm->scale);
F32 t;
Point3F n;
if(!sm->bounds.collideLine(s, e, &t, &n))
return false;
RayInfo ri;
if(!sm->castRay(s, e, &ri))
return false;
#endif
occluderinfo.sgObject = sm;
occluderinfo.sgSurface = SG_NULL_SURFACE;
}
else if(detail)
{
RayInfo ri;
if(!detail->castRay(s, e, &ri))
return false;
occluderinfo.sgObject = obj;
occluderinfo.sgSurface = ri.face;
}
else
{
RayInfo ri;
if(!obj->castRay(s, e, &ri))
return false;
occluderinfo.sgObject = obj;
occluderinfo.sgSurface = ri.face;
}
return true;
}
bool sgPlanarLightMap::sgIsValidOccluder(const sgOccluder &occluderinfo, Vector<sgOccluder> &validoccluders, bool isinnerlexel)
{
if(isinnerlexel)
{
validoccluders.push_back(occluderinfo);
return true;
}
for(U32 i=0; i<validoccluders.size(); i++)
{
sgOccluder &oc = validoccluders[i];
if((oc.sgObject == occluderinfo.sgObject) && (oc.sgSurface == occluderinfo.sgSurface))
return true;
}
return false;
}
void sgPlanarLightMap::sgSetupLighting()
{
// stats
elapsedTimeAggregate time = elapsedTimeAggregate(sgStatistics::sgInteriorSurfaceSetupTime);
sgStatistics::sgInteriorSurfaceSetupCount++;
// get tranformed points...
U32 windingcount = triStrip.size();
Vector<sgSmoothingTri> tris;
if(windingcount < 3)
return;
tris.increment(windingcount - 2);
// test for smoothing...
Point3F normal = triStrip[0].sgNorm;
for(U32 i=1; i<windingcount; i++)
{
if(mDot(triStrip[i].sgNorm, normal) < 0.98f)
{
sgUseSmoothing = true;
break;
}
}
// get the axis info...
F64 smax = 0.0;
F64 tmax = 0.0;
for(S32 i=0; i<3; i++)
{
F64 s = mFabs(sgLightMapSVector[i]);
F64 t = mFabs(sgLightMapTVector[i]);
//if((s > 0.0f) && (t > 0.0f))
// continue;
if(s > smax)
{
sgSAxis = i;
smax = s;
}
if(t > tmax)
{
sgTAxis = i;
tmax = t;
}
}
// try again...
badtexgen = false;
if(sgSAxis == sgTAxis)
{
// find the axis with the minimal diff (the bad axis)...
U32 a = (sgSAxis + 1) % 3;
U32 b = (sgSAxis + 2) % 3;
F64 absa = mFabs(mFabs(sgLightMapSVector[sgSAxis]) - mFabs(sgLightMapSVector[a]));
F64 absb = mFabs(mFabs(sgLightMapSVector[sgSAxis]) - mFabs(sgLightMapSVector[b]));
smax = getMin(absa, absb);
a = (sgTAxis + 1) % 3;
b = (sgTAxis + 2) % 3;
absa = mFabs(mFabs(sgLightMapTVector[sgTAxis]) - mFabs(sgLightMapTVector[a]));
absb = mFabs(mFabs(sgLightMapTVector[sgTAxis]) - mFabs(sgLightMapTVector[b]));
tmax = getMin(absa, absb);
S32 avoidaxis = -1;
S32 *newaxis = NULL;
Point3D *vector = NULL;
if(smax < tmax)
{
avoidaxis = sgTAxis;
newaxis = &sgSAxis;
vector = &sgLightMapSVector;
}
else
{
avoidaxis = sgSAxis;
newaxis = &sgTAxis;
vector = &sgLightMapTVector;
}
// find the max on the bad axis that's not on the original axis...
F64 max = 0.0f;
for(S32 i=0; i<3; i++)
{
if(i == avoidaxis)
continue;
F64 val = mFabs((*vector)[i]);
if(val > max)
{
(*newaxis) = i;
max = val;
}
}
//Con::warnf("Questionable light map axis gen...");
badtexgen = true;
}
AssertFatal(((sgSAxis != -1) && (sgTAxis != -1) && (sgSAxis != sgTAxis)),
"Unable to determine axis info!");
// these are tristrips!!!
for(U32 t=0; t<tris.size(); t++)
{
for(U32 v=0; v<3; v++)
{
U32 w = v + t;
U32 k;
// reverse winding?
if(t & 0x1)
k = ((v+2) % 3) + t;
else
k = ((v+1) % 3) + t;
sgSmoothingVert &vect = tris[t].sgVerts[v];
const Point3F &pointw = triStrip[w].sgVert;
const Point3F &pointk = triStrip[k].sgVert;
const Point3F &normalw = triStrip[w].sgNorm;
//const Point3F &normalk = triStrip[k].sgNorm;
vect.sgVert = pointw;
vect.sgVect = pointk - pointw;
vect.sgNorm = normalw;
AssertFatal((vect.sgVect.lenSquared() > 0.0f), "!");
// could break this out for speed...
if((t == 0) && (v == 0))
sgSurfaceBox = Box3F(pointw, pointw);
else
{
sgSurfaceBox.max.setMax(pointw);
sgSurfaceBox.min.setMin(pointw);
}
}
if(sgUseSmoothing)
sgBuildDerivatives(tris[t]);
}
sgBuildLexels(tris);
// stats...
sgStatistics::sgInteriorSurfaceIncludedCount++;
if(sgUseSmoothing)
{
sgStatistics::sgInteriorSurfaceSmoothedCount++;
sgStatistics::sgInteriorSurfaceSmoothedLexelCount +=
sgInnerLexels.size() + sgOuterLexels.size();
}
}
void sgPlanarLightMap::sgBuildDerivatives(sgSmoothingTri &tri)
{
// build the derivatives for linear interpolation...
sgSmoothingVert &va = tri.sgVerts[0];
sgSmoothingVert &vb = tri.sgVerts[1];
sgSmoothingVert &vc = tri.sgVerts[2];
F32 vsac = va.sgVert[sgSAxis] - vc.sgVert[sgSAxis];
F32 vsbc = vb.sgVert[sgSAxis] - vc.sgVert[sgSAxis];
F32 vtac = va.sgVert[sgTAxis] - vc.sgVert[sgTAxis];
F32 vtbc = vb.sgVert[sgTAxis] - vc.sgVert[sgTAxis];
F32 spartial = 1.0f / ((vsbc * vtac) - (vsac * vtbc));
F32 tpartial = 1.0f / ((vsac * vtbc) - (vsbc * vtac));
for(S32 c=0; c<3; c++)
{
F32 nac = va.sgNorm[c] - vc.sgNorm[c];
F32 nbc = vb.sgNorm[c] - vc.sgNorm[c];
tri.sgSDerivative[c] = ((nbc * vtac) - (nac * vtbc)) * spartial;
tri.sgTDerivative[c] = ((nbc * vsac) - (nac * vsbc)) * tpartial;
}
}
void sgPlanarLightMap::sgBuildLexels(const Vector<sgSmoothingTri> &tris)
{
// loop through the texels...
// sort by inner and outer...
const U32 lexelmax = sgHeight * sgWidth;
const U32 buffersize = lexelmax * sizeof(sgLexel);
sgInnerLexels.clear();
sgInnerLexels.reserve(lexelmax);
sgOuterLexels.clear();
sgOuterLexels.reserve(lexelmax);
// this is faster than Vector[]...
sgSmoothingTri *trisptr = tris.address();
bool outer;
Point3F vec2;
Point3F cross;
Point3D pos = sgWorldPosition;
Point3D run = sgLightMapSVector * sgWidth;
sgSmoothingTri *container;
bool halfsize = !LightManager::sgAllowFullLightMaps();
U32 lmscalemask = LightManager::sgGetLightMapScale() - 1;
for(U32 y=0; y<sgHeight; y++)
{
if(halfsize && (y & lmscalemask))
{
pos += sgLightMapTVector;
continue;
}
for(U32 x=0; x<sgWidth; x++)
{
if(halfsize && (x & lmscalemask))
{
pos += sgLightMapSVector;
continue;
}
Point3F pos32 = Point3F(pos.x, pos.y, pos.z);
// find containing tri...
outer = true;
container = NULL;
for(U32 t=0; t<tris.size(); t++)
{
for(U32 v=0; v<3; v++)
{
vec2 = pos32 - trisptr[t].sgVerts[v].sgVert;
mCross(vec2, trisptr[t].sgVerts[v].sgVect, &cross);
// don't check against 0.0f, otherwise lexels on the edge become "inner"
// and cause shadowing from adjacent brushes...
if(mDot(surfacePlane, cross) < 0.001f)
break;//not this one...
if(v == 2)
{
//must be this one...
outer = false;
container = &trisptr[t];
}
}
if(container)
break;//already found...
}
// use line test to find a container for the outer lexels if smoothing...
// get the normal...
Point3F normal = surfacePlane;
if(sgUseSmoothing)
{
if(!container)
{
// set a default...
container = trisptr;
F32 maxdist = 1000000.0f;
for(U32 t=0; t<tris.size(); t++)
{
for(U32 v=0; v<3; v++)
{
U32 v2 = (v + 1) % 3;
F32 d = sgGetDistanceSquared(
trisptr[t].sgVerts[v].sgVert,
trisptr[t].sgVerts[v2].sgVert, pos32);
if(d < maxdist)
{
maxdist = d;
container = &trisptr[t];
}
}
}
}
// get the normal...
Point3F posrelative = pos32 - container->sgVerts[0].sgVert;
normal = container->sgVerts[0].sgNorm;
normal += (container->sgSDerivative * posrelative[sgSAxis]) +
(container->sgTDerivative * posrelative[sgTAxis]);
normal.normalize();
}
// find respective vector...
if(outer)
{
sgOuterLexels.increment();
sgLexel &temp = sgOuterLexels.last();
temp.lmPos.x = x;
temp.lmPos.y = y;
temp.worldPos = pos32;
temp.normal = normal;
}
else
{
sgInnerLexels.increment();
sgLexel &temp = sgInnerLexels.last();
temp.lmPos.x = x;
temp.lmPos.y = y;
temp.worldPos = pos32;
temp.normal = normal;
}
pos += sgLightMapSVector;
}
pos -= run;
pos += sgLightMapTVector;
}
// if no inner lexels exist fake some for shadow testing...
if(sgInnerLexels.size() < 1)
{
for(U32 i=0; i<tris.size(); i++)
{
Point3F pos = (tris[i].sgVerts[0].sgVert + tris[i].sgVerts[1].sgVert + tris[i].sgVerts[2].sgVert) * 0.3333333;
Point3F norm = tris[i].sgVerts[0].sgNorm + tris[i].sgVerts[1].sgNorm + tris[i].sgVerts[2].sgNorm;
norm.normalize();
// center
sgInnerLexels.increment();
sgLexel &tempa = sgInnerLexels.last();
tempa.lmPos.x = 0;
tempa.lmPos.y = 0;
tempa.worldPos = pos;
tempa.normal = norm;
// vert 0
sgInnerLexels.increment();
sgLexel &tempb = sgInnerLexels.last();
tempb.lmPos.x = 0;
tempb.lmPos.y = 0;
tempb.worldPos = tris[i].sgVerts[0].sgVert;
tempb.normal = tris[i].sgVerts[0].sgNorm;
// last 2
if(i == (tris.size() - 1))
{
for(U32 v=1; v<3; v++)
{
sgInnerLexels.increment();
sgLexel &temp = sgInnerLexels.last();
temp.lmPos.x = 0;
temp.lmPos.y = 0;
temp.worldPos = tris[i].sgVerts[v].sgVert;
temp.normal = tris[i].sgVerts[v].sgNorm;
}
}
}
}
}
void sgPlanarLightMap::sgCalculateLighting(LightInfo *light)
{
U32 o;
U32 i, ii;
// stats...
sgStatistics::sgInteriorSurfaceIlluminationCount++;
elapsedTimeAggregate time = elapsedTimeAggregate(sgStatistics::sgInteriorLexelTime);
// setup zone info...
bool isinzone = false;
if(light->sgDiffuseRestrictZone || light->sgAmbientRestrictZone)
{
for(U32 z=0; z<sgInteriorInstance->getNumCurrZones(); z++)
{
S32 zone = sgInteriorInstance->getCurrZone(z);
if(zone > 0)
{
if((zone == light->sgZone[0]) || (zone == light->sgZone[1]))
{
isinzone = true;
break;
}
}
}
if(!isinzone && (sgInteriorSurface != SG_NULL_SURFACE))
{
S32 zone = sgInteriorInstance->getSurfaceZone(sgInteriorSurface, sgInteriorDetail);
if((light->sgZone[0] == zone) || (light->sgZone[1] == zone))
isinzone = true;
}
if(!isinzone && sgInteriorStaticMesh)
{
// need to find zone(s)...
}
}
// allow what?
bool allowdiffuse = (!light->sgDiffuseRestrictZone) || isinzone;
bool allowambient = (!light->sgAmbientRestrictZone) || isinzone;
// should I bother?
if((!allowdiffuse) && (!allowambient))
return;
// first get lighting model...
sgLightingModel &model = sgLightingModelManager::sgGetLightingModel(light->sgLightingModelName);
model.sgSetState(light);
// test for early out...
if(!model.sgCanIlluminate(sgSurfaceBox))
{
model.sgResetState();
return;
}
// this is slow, so do it after the early out...
model.sgInitStateLM();
// setup shadow objects...
Box3F lightvolume = sgSurfaceBox;
if(light->mType == LightInfo::Vector)
{
const Point3F lightVector = (SG_STATIC_LIGHT_VECTOR_DIST * -1) * light->mDirection;
for(U32 i=0; i<triStrip.size(); i++)
{
Point3F lightpos = triStrip[i].sgVert + lightVector;
lightvolume.max.setMax(lightpos);
lightvolume.min.setMin(lightpos);
}
}
else
{
lightvolume.max.setMax(light->mPos);
lightvolume.min.setMin(light->mPos);
}
// build a list of potential shadow casters...
if(light->sgCastsShadows && LightManager::sgAllowShadows())
sgGetIntersectingObjects(lightvolume, sgInteriorInstance);
// stats...
sgStatistics::sgInteriorSurfaceIlluminatedCount++;
sgStatistics::sgInteriorLexelCount += sgInnerLexels.size() + sgOuterLexels.size();
// put rayCast into lighting mode...
Interior::smLightingCastRays = true;
Vector<sgOccluder> shadowingsurfaces;
ColorF diffuse;
ColorF ambient;
Point3F lightingnormal;
for(i=0; i<sgPlanarLightMap::sglpCount; i++)
{
// set which list...
U32 templexelscount;
sgLexel *templexels;
if(i == sgPlanarLightMap::sglpInner)
{
templexelscount = sgInnerLexels.size();
templexels = sgInnerLexels.address();
}
else
{
templexelscount = sgOuterLexels.size();
templexels = sgOuterLexels.address();
}
for(ii=0; ii<templexelscount; ii++)
{
// get the current lexel...
const sgLexel &lexel = templexels[ii];
// too often unset, must do these here...
ambient.set(0.0f, 0.0f, 0.0f);
diffuse.set(0.0f, 0.0f, 0.0f);
lightingnormal.set(0.0f, 0.0f, 0.0f);
model.sgLightingLM(lexel.worldPos, lexel.normal, diffuse, ambient, lightingnormal);
// testing...
//diffuse = ColorF(lexel.normal.x, lexel.normal.y, lexel.normal.z);
/*if(i == sgPlanarLightMap::sglpOuter)
diffuse = ColorF(1.0, 0, 1.0);
else
diffuse = ColorF(0.0, 1.0, 1.0);
if((x & 0x1) == (y & 0x1))
diffuse.green = 0;
else
diffuse.blue = 0;*/
/*Point3F p = lexel.worldPos;
diffuse = ColorF(mFabs(p.x - mFloor(p.x)),
mFabs(p.y - mFloor(p.y)),
mFabs(p.z - mFloor(p.z)));*/
//if(badtexgen)
// diffuse = ColorF(1.0, 1.0, 0.0);
// increment the occluder mask...
sgCurrentOccluderMaskId++;
if(sgCurrentOccluderMaskId == 0)
sgCurrentOccluderMaskId++;
// in the event the diffuse is too small or
// the alpha occluders attenuated the value too much...
if(allowdiffuse && ((diffuse.red > SG_MIN_LEXEL_INTENSITY) || (diffuse.green > SG_MIN_LEXEL_INTENSITY) || (diffuse.blue > SG_MIN_LEXEL_INTENSITY)))
{
// stats
sgStatistics::sgInteriorLexelDiffuseCount++;
// step four: check for shadows...
bool shadowed = false;
if(LightManager::sgAllowShadows())
{
// set light pos for shadows...
Point3F lightpos;
if(light->mType == LightInfo::Vector)
{
lightpos = lexel.worldPos + ((SG_STATIC_LIGHT_VECTOR_DIST * -1) * light->mDirection);
}
else
{
lightpos = light->mPos;
}
// potential problem with this setup is the rare but possible
// occurrence of outer lexel light leaks from raycast hitting an
// interior surface that's not in the shadow list but also has valid
// occluders between it and the lexel (ie: stops the ray before
// finding the valid occluders).
//
// possible solutions:
// -Ctor style shadow system
// -custom raycast that collects ALL occluding surfaces
//
// both solutions are heavyweight - first see if the problem occurs
// in the wild, this may be a theoretical problem.
// start by finding any shadow casters
sgOccluder info;
for(U32 o=0; o<sgIntersectingSceneObjects.size(); o++)
{
if(!sgCastRay(lightpos, lexel.worldPos, sgIntersectingSceneObjects[o], NULL, NULL, info))
continue;
if(!sgIsValidOccluder(info, shadowingsurfaces, (i == sglpInner)))
continue;
shadowed = true;
break;
}
// if false then try the sm list...
if(!shadowed)
{
for(U32 o=0; o<sgIntersectingStaticMeshObjects.size(); o++)
{
sgStaticMeshInfo &sminfo = sgIntersectingStaticMeshObjects[o];
if(!sgCastRay(lightpos, lexel.worldPos, sminfo.sgInteriorInstance, NULL, sminfo.sgStaticMesh, info))
continue;
if(!sgIsValidOccluder(info, shadowingsurfaces, (i == sglpInner)))
continue;
shadowed = true;
break;
}
}
// if false then self interior test (include sms at the end)...
if(!shadowed)
{
if(sgCastRay(lightpos, lexel.worldPos, sgInteriorInstance, sgInteriorDetail, NULL, info))
{
if(info.sgSurface != sgInteriorSurface)
{
if(sgIsValidOccluder(info, shadowingsurfaces, (i == sglpInner)))
shadowed = true;
}
}
if(!shadowed)
{
for(U32 sm=0; sm<sgInteriorDetail->getStaticMeshCount(); sm++)
{
ConstructorSimpleMesh *mesh = (ConstructorSimpleMesh *)sgInteriorDetail->getStaticMesh(sm);
if(mesh == sgInteriorStaticMesh)
continue;
if(!sgCastRay(lightpos, lexel.worldPos, sgInteriorInstance, NULL, mesh, info))
continue;
if(!sgIsValidOccluder(info, shadowingsurfaces, (i == sglpInner)))
continue;
shadowed = true;
break;
}
}
}
// if false then self sm test...
if(!shadowed && sgInteriorStaticMesh)
{
// reverse cast direction...
if(sgCastRay(lexel.worldPos, lightpos, sgInteriorInstance, NULL, sgInteriorStaticMesh, info))
{
if(sgIsValidOccluder(info, shadowingsurfaces, (i == sglpInner)))
shadowed = true;
}
}
}
if(!shadowed)
{
// cool, got this far so either there was no occluder
// or it was an alpha and eventually we found a light
// ray segment that didn't get blocked...
//
// step five: apply the lighting to the light map...
sgDirty = true;
const U32 lmindex = (U32)((lexel.lmPos.y * sgWidth) + lexel.lmPos.x);
sgTexels->sgData[lmindex] += diffuse;
}
}
if(allowambient && ((ambient.red > SG_MIN_LEXEL_INTENSITY) || (ambient.green > SG_MIN_LEXEL_INTENSITY) || (ambient.blue > SG_MIN_LEXEL_INTENSITY)))
{
sgDirty = true;
const U32 lmindex = (U32)((lexel.lmPos.y * sgWidth) + lexel.lmPos.x);
sgTexels->sgData[lmindex] += ambient;
}
}
}
// put rayCast back to normal mode...
Interior::smLightingCastRays = false;
model.sgResetState();
}
void sgPlanarLightMap::sgMergeLighting(GBitmap *lightmap, U32 xoffset, U32 yoffset)
{
// stats
elapsedTimeAggregate time = elapsedTimeAggregate(sgStatistics::sgInteriorSurfaceMergeTime);
sgStatistics::sgInteriorSurfaceMergeCount++;
sgTexels->sgFillInLighting();
sgTexels->sgBlur();
U32 y, x, d, s, frag;
U8 *bits;
ColorF *texels = sgTexels->sgData;
for(y=0; y<sgHeight; y++)
{
bits = lightmap->getAddress(xoffset, (yoffset + y));
d = 0;
for(x=0; x<sgWidth; x++)
{
s = ((y * sgWidth) + x);
ColorF &texel = texels[s];
frag = bits[d] + (texel.red * 255.0f);
if(frag > 255) frag = 255;
bits[d++] = frag;
frag = bits[d] + (texel.green * 255.0f);
if(frag > 255) frag = 255;
bits[d++] = frag;
frag = bits[d] + (texel.blue * 255.0f);
if(frag > 255) frag = 255;
bits[d++] = frag;
}
}
}
//----------------------------------------------
void sgTerrainLightMap::sgCalculateLighting(LightInfo *light)
{
// setup zone info...
bool isinzone = (light->sgZone[0] == 0) || (light->sgZone[1] == 0);
// allow what?
bool allowdiffuse = (!light->sgDiffuseRestrictZone) || isinzone;
bool allowambient = (!light->sgAmbientRestrictZone) || isinzone;
// should I bother?
if((!allowdiffuse) && (!allowambient))
return;
AssertFatal((sgTerrain), "Member 'sgTerrain' must be populated.");
// setup constants...
F32 terrainlength = (sgTerrain->getSquareSize() * TerrainBlock::BlockSize);
const F32 halfterrainlength = terrainlength * 0.5;
U32 time = Platform::getRealMilliseconds();
Point2F s, t;
s[0] = sgLightMapSVector[0];
s[1] = sgLightMapSVector[1];
t[0] = sgLightMapTVector[0];
t[1] = sgLightMapTVector[1];
Point2F run = t * sgWidth;
Point2F start;
start[0] = sgWorldPosition[0] + halfterrainlength;
start[1] = sgWorldPosition[1] + halfterrainlength;
sgLightingModel &model = sgLightingModelManager::sgGetLightingModel(
light->sgLightingModelName);
model.sgSetState(light);
model.sgInitStateLM();
Point2I lmindexmin, lmindexmax;
if(light->mType == LightInfo::Vector)
{
lmindexmin.x = 0;
lmindexmin.y = 0;
lmindexmax.x = (sgWidth - 1);
lmindexmax.y = (sgHeight - 1);
}
else
{
F32 maxrad = model.sgGetMaxRadius();
Box3F worldbox = Box3F(light->mPos, light->mPos);
worldbox.min -= Point3F(maxrad, maxrad, maxrad);
worldbox.max += Point3F(maxrad, maxrad, maxrad);
lmindexmin.x = (worldbox.min.x - sgWorldPosition.x) / s.x;
lmindexmin.y = (worldbox.min.y - sgWorldPosition.y) / t.y;
lmindexmax.x = (worldbox.max.x - sgWorldPosition.x) / s.x;
lmindexmax.y = (worldbox.max.y - sgWorldPosition.y) / t.y;
lmindexmin.x = getMax(lmindexmin.x, S32(0));
lmindexmin.y = getMax(lmindexmin.y, S32(0));
lmindexmax.x = getMin(lmindexmax.x, S32(sgWidth - 1));
lmindexmax.y = getMin(lmindexmax.y, S32(sgHeight - 1));
}
S32 lmx, lmy, lmindex;
Point3F lexelworldpos;
Point3F normal;
ColorF diffuse(0.0f, 0.0f, 0.0f);
ColorF ambient(0.0f, 0.0f, 0.0f);
Point3F lightingnormal(0.0f, 0.0f, 0.0f);
Point2F point = ((t * lmindexmin.y) + start + (s * lmindexmin.x));
for(lmy=lmindexmin.y; lmy<lmindexmax.y; lmy++)
{
for(lmx=lmindexmin.x; lmx<lmindexmax.x; lmx++)
{
lmindex = (lmx + (lmy * sgWidth));
// get lexel 2D world pos...
lexelworldpos[0] = point[0] - halfterrainlength;
lexelworldpos[1] = point[1] - halfterrainlength;
// use 2D terrain space pos to get the world space z and normal...
sgTerrain->getNormalAndHeight(point, &normal, &lexelworldpos.z, false);
// too often unset, must do these here...
ambient.set(0.0f, 0.0f, 0.0f);
diffuse.set(0.0f, 0.0f, 0.0f);
lightingnormal.set(0.0f, 0.0f, 0.0f);
model.sgLightingLM(lexelworldpos, normal, diffuse, ambient, lightingnormal);
if(allowdiffuse && ((diffuse.red > SG_MIN_LEXEL_INTENSITY) ||
(diffuse.green > SG_MIN_LEXEL_INTENSITY) || (diffuse.blue > SG_MIN_LEXEL_INTENSITY)))
{
// step four: check for shadows...
bool shadowed = false;
RayInfo info;
if(light->sgCastsShadows && LightManager::sgAllowShadows())
{
// set light pos for shadows...
Point3F lightpos = light->mPos;
if(light->mType == LightInfo::Vector)
{
lightpos = SG_STATIC_LIGHT_VECTOR_DIST * light->mDirection * -1;
lightpos = lexelworldpos + lightpos;
}
// make texels terrain space coord into a world space coord...
RayInfo info;
if(sgTerrain->getContainer()->castRay(lightpos, (lexelworldpos + (lightingnormal * 0.5f)),
ShadowCasterObjectType, &info))
{
shadowed = true;
}
}
if(!shadowed)
{
// step five: apply the lighting to the light map...
sgTexels->sgData[lmindex] += diffuse;
}
}
if(allowambient && ((ambient.red > 0.0f) || (ambient.green > 0.0f) || (ambient.blue > 0.0f)))
{
//sgTexels->sgData[lmindex] += ambient;
}
/*if((lmx & 0x1) == (lmy & 0x1))
sgTexels[lmindex] += ColorF(1.0, 0.0, 0.0);
else
sgTexels[lmindex] += ColorF(0.0, 1.0, 0.0);*/
// stats...
sgStatistics::sgTerrainLexelCount++;
point += s;
}
point = ((t * lmy) + start + (s * lmindexmin.x));
}
model.sgResetState();
// stats...
sgStatistics::sgTerrainLexelTime += Platform::getRealMilliseconds() - time;
}
void sgTerrainLightMap::sgMergeLighting(ColorF *lightmap)
{
sgTexels->sgBlur();
U32 y, x, index;
for(y=0; y<sgHeight; y++)
{
for(x=0; x<sgWidth; x++)
{
index = (y * sgWidth) + x;
ColorF &pixel = lightmap[index];
pixel += sgTexels->sgData[index];
}
}
}