//----------------------------------------------- // Synapse Gaming - Lighting System // Copyright © Synapse Gaming 2003 // Written by John Kabus //----------------------------------------------- #include "editor/editTSCtrl.h" #include "editor/worldEditor.h" #include "game/shadow.h" #include "game/vehicles/wheeledVehicle.h" #include "game/gameConnection.h" #include "sceneGraph/sceneGraph.h" #include "terrain/terrRender.h" #include "game/shapeBase.h" #include "gui/core/guiCanvas.h" #include "ts/tsShape.h" #include "ts/tsShapeInstance.h" #include "game/staticShape.h" #include "game/tsStatic.h" #include "collision/concretePolyList.h" #include "lightingSystem/sgSceneLighting.h" #include "lightingSystem/sgLightMap.h" #include "lightingSystem/sgSceneLightingGlobals.h" #define TERRAIN_OVERRANGE 2.0f extern SceneLighting *gLighting; /// adds the ability to bake point lights into terrain light maps. void SceneLighting::TerrainProxy::sgAddUniversalPoint(LightInfo *light) { // add the light... sgLights.push_back(light); } void SceneLighting::TerrainProxy::sgLightUniversalPoint(LightInfo *light, TerrainBlock * terrain) { // only continue on the first light... if(sgLights.first() != light) return; // process the lighting... const F32 length = (terrain->getSquareSize() * TerrainBlock::BlockSize); const F32 halflength = 0 - (length * 0.5); // texel world space size... const F32 offset = length / ((F32)TerrainBlock::LightmapSize); sgTerrainLightMap *lightmap = new sgTerrainLightMap(TerrainBlock::LightmapSize, TerrainBlock::LightmapSize, terrain); lightmap->sgWorldPosition = Point3D(halflength, halflength, 0); lightmap->sgLightMapSVector = Point3D(offset, 0, 0); lightmap->sgLightMapTVector = Point3D(0, offset, 0); for(U32 i=0; isgCalculateLighting(sgLights[i]); lightmap->sgMergeLighting(sgBakedLightmap); delete lightmap; } SceneLighting::TerrainProxy::TerrainProxy(SceneObject * obj) : Parent(obj) { mLightmap = 0; sgBakedLightmap = 0; } SceneLighting::TerrainProxy::~TerrainProxy() { delete [] mLightmap; if(sgBakedLightmap) delete[] sgBakedLightmap; } //------------------------------------------------------------------------------- void SceneLighting::TerrainProxy::init() { mLightmap = new ColorF[TerrainBlock::LightmapSize * TerrainBlock::LightmapSize]; dMemset(mLightmap, 0, TerrainBlock::LightmapSize * TerrainBlock::LightmapSize * sizeof(ColorF)); mShadowMask.setSize(TerrainBlock::BlockSize * TerrainBlock::BlockSize); sgBakedLightmap = new ColorF[TerrainBlock::LightmapSize * TerrainBlock::LightmapSize]; dMemset(sgBakedLightmap, 0, TerrainBlock::LightmapSize * TerrainBlock::LightmapSize * sizeof(ColorF)); } /// reroutes TerrainProxy::preLight for point light and TSStatic support. bool SceneLighting::TerrainProxy::preLight(LightInfo * light) { if(!bool(mObj)) return(false); if((light->mType != LightInfo::Vector) && (light->mType != LightInfo::SGStaticPoint) && (light->mType != LightInfo::SGStaticSpot)) return(false); if((light->mType == LightInfo::SGStaticPoint) || (light->mType == LightInfo::SGStaticSpot)) sgAddUniversalPoint(light); mShadowMask.clear(); return(true); } static inline U16 convertColor(ColorF color) { return((U32(color.blue * 31.f + 0.5f) << 11) | (U32(color.green * 31.f + 0.5f) << 6) | (U32(color.red * 31.f + 0.5f) << 1) | 1); } void sgProcessLightMap(TerrainBlock *terrain, ColorF *mLightmap, ColorF *sgBakedLightmap) { ColorF color; U16 * lPtr = (U16*)terrain->lightMap->getAddress(0,0); for(U32 i = 0; i < (TerrainBlock::LightmapSize * TerrainBlock::LightmapSize); i++) { color.red = mLightmap[i].red + sgBakedLightmap[i].red; color.green = mLightmap[i].green + sgBakedLightmap[i].green; color.blue = mLightmap[i].blue + sgBakedLightmap[i].blue; color.clamp(); lPtr[i] = convertColor(color); } } /// reroutes TerrainProxy::postLight for point light and TSStatic support. void SceneLighting::TerrainProxy::postLight(bool lastLight) { TerrainBlock * terrain = getObject(); if((!terrain) || (!lastLight)) return; // set the lightmap... //ColorF *sgBakedLightmap = sgBakedLightmap; sgProcessLightMap(terrain, mLightmap, sgBakedLightmap); // trigger a reload... terrain->triggerLightmapReload(); terrain->buildMaterialMap(); } /// reroutes TerrainProxy::light for point light and TSStatic support. void SceneLighting::TerrainProxy::light(LightInfo * light) { TerrainBlock * terrain = getObject(); if(!terrain) return; AssertFatal(((light->mType == LightInfo::Vector) || (light->mType == LightInfo::SGStaticPoint) || (light->mType == LightInfo::SGStaticSpot)), "wrong light type"); //S32 time = Platform::getRealMilliseconds(); if((light->mType == LightInfo::SGStaticPoint) || (light->mType == LightInfo::SGStaticSpot)) { //do baked point light here... sgLightUniversalPoint(light, terrain); //Con::printf(" = terrain lit in %3.3f seconds (sg)", (Platform::getRealMilliseconds()-time)/1000.f); return; } // reset mShadowVolume = new ShadowVolumeBSP; if((light->mType == LightInfo::Vector) && LightManager::sgAllowShadows()) { // build interior shadow volume for(ObjectProxy ** itr = gLighting->mLitObjects.begin(); itr != gLighting->mLitObjects.end(); itr++) { if(!gLighting->isInterior((*itr)->mObj)) continue; if(markInteriorShadow(static_cast(*itr))) gLighting->addInterior(mShadowVolume, *static_cast(*itr), light, SceneLighting::SHADOW_DETAIL); } // Static object support... Vector objects; gClientContainer.findObjects(ShadowCasterObjectType, sgFindObjectsCallback, &objects); // build static shadow volume for(U32 o=0; osgIsCorrectStaticObjectType(obj)) { if(sgMarkStaticShadow(this, obj, light)) gLighting->addStatic(this, mShadowVolume, obj, light, SceneLighting::SHADOW_DETAIL); } } } //-------------------- lightVector(light); delete mShadowVolume; //Con::printf(" = terrain lit in %3.3f seconds", (Platform::getRealMilliseconds()-time)/1000.f); } //------------------------------------------------------------------------------ S32 SceneLighting::TerrainProxy::testSquare(const Point3F & min, const Point3F & max, S32 mask, F32 expand, const Vector & clipPlanes) { expand = 0; S32 retMask = 0; Point3F minPoint, maxPoint; for(S32 i = 0; i < clipPlanes.size(); i++) { if(mask & (1 << i)) { if(clipPlanes[i].x > 0) { maxPoint.x = max.x; minPoint.x = min.x; } else { maxPoint.x = min.x; minPoint.x = max.x; } if(clipPlanes[i].y > 0) { maxPoint.y = max.y; minPoint.y = min.y; } else { maxPoint.y = min.y; minPoint.y = max.y; } if(clipPlanes[i].z > 0) { maxPoint.z = max.z; minPoint.z = min.z; } else { maxPoint.z = min.z; minPoint.z = max.z; } F32 maxDot = mDot(maxPoint, clipPlanes[i]); F32 minDot = mDot(minPoint, clipPlanes[i]); F32 planeD = clipPlanes[i].d; if(maxDot <= -(planeD + expand)) return(U16(-1)); if(minDot <= -planeD) retMask |= (1 << i); } } return(retMask); } bool SceneLighting::TerrainProxy::getShadowedSquares(const Vector & clipPlanes, Vector & shadowList) { TerrainBlock * terrain = getObject(); if(!terrain) return(false); SquareStackNode stack[TerrainBlock::BlockShift * 4]; stack[0].mLevel = TerrainBlock::BlockShift; stack[0].mClipFlags = 0xff; stack[0].mPos.set(0,0); U32 stackSize = 1; Point3F blockPos; terrain->getTransform().getColumn(3, &blockPos); S32 squareSize = terrain->getSquareSize(); bool marked = false; // push through all the levels of the quadtree while(stackSize) { SquareStackNode * node = &stack[stackSize - 1]; S32 clipFlags = node->mClipFlags; Point2I pos = node->mPos; GridSquare * sq = terrain->findSquare(node->mLevel, pos); Point3F minPoint, maxPoint; minPoint.set(squareSize * pos.x + blockPos.x, squareSize * pos.y + blockPos.y, fixedToFloat(sq->minHeight)); maxPoint.set(minPoint.x + (squareSize << node->mLevel), minPoint.y + (squareSize << node->mLevel), fixedToFloat(sq->maxHeight)); // test the square against the current level if(clipFlags) { clipFlags = testSquare(minPoint, maxPoint, clipFlags, squareSize, clipPlanes); if(clipFlags == U16(-1)) { stackSize--; continue; } } // shadowed? if(node->mLevel == 0) { marked = true; shadowList.push_back(pos.x + (pos.y << TerrainBlock::BlockShift)); stackSize--; continue; } // setup the next level of squares U8 nextLevel = node->mLevel - 1; S32 squareHalfSize = 1 << nextLevel; for(U32 i = 0; i < 4; i++) { node[i].mLevel = nextLevel; node[i].mClipFlags = clipFlags; } node[3].mPos = pos; node[2].mPos.set(pos.x + squareHalfSize, pos.y); node[1].mPos.set(pos.x, pos.y + squareHalfSize); node[0].mPos.set(pos.x + squareHalfSize, pos.y + squareHalfSize); stackSize += 3; } return(marked); } bool SceneLighting::TerrainProxy::markInteriorShadow(InteriorProxy * proxy) { U32 i; // setup the clip planes: add the test and the lit planes Vector clipPlanes; clipPlanes = proxy->mTerrainTestPlanes; for(i = 0; i < proxy->mLitBoxSurfaces.size(); i++) clipPlanes.push_back(proxy->mLitBoxSurfaces[i]->mPlane); Vector shadowList; if(!getShadowedSquares(clipPlanes, shadowList)) return(false); // set the correct bit for(i = 0; i < shadowList.size(); i++) mShadowMask.set(shadowList[i]); return(true); } //------------------------------------------------------------------------------- // BUGS: does not work with x or y directions of 0 // : light dir of 0.1, 0.3, -0.8 causes strange behavior void SceneLighting::TerrainProxy::lightVector(LightInfo * light) { TerrainBlock * terrain = getObject(); if(!terrain) return; Point3F lightDir = light->mDirection; lightDir.normalize(); ColorF & ambient = light->mAmbient; ColorF & lightColor = light->mColor; if(lightDir.x == 0 && lightDir.y == 0) return; S32 generateLevel = Con::getIntVariable("$pref::sceneLighting::terrainGenerateLevel", 0); generateLevel = mClamp(generateLevel, 0, 4); bool allowLexelSplits = Con::getBoolVariable("$pref::sceneLighting::terrainAllowLexelSplits", false); U32 generateDim = TerrainBlock::LightmapSize << generateLevel; U32 generateShift = TerrainBlock::LightmapShift + generateLevel; U32 generateMask = generateDim - 1; F32 zStep; F32 frac; Point2I blockColStep; Point2I blockRowStep; Point2I blockFirstPos; Point2I lmFirstPos; F32 terrainDim = F32(terrain->getSquareSize()) * F32(TerrainBlock::BlockSize); F32 stepSize = F32(terrain->getSquareSize()) / F32(generateDim / TerrainBlock::BlockSize); if(mFabs(lightDir.x) >= mFabs(lightDir.y)) { if(lightDir.x > 0) { zStep = lightDir.z / lightDir.x; frac = lightDir.y / lightDir.x; blockColStep.set(1, 0); blockRowStep.set(0, 1); blockFirstPos.set(0, 0); lmFirstPos.set(0,0); } else { zStep = -lightDir.z / lightDir.x; frac = -lightDir.y / lightDir.x; blockColStep.set(-1, 0); blockRowStep.set(0, 1); blockFirstPos.set(255, 0); lmFirstPos.set(TerrainBlock::LightmapSize - 1,0); } } else { if(lightDir.y > 0) { zStep = lightDir.z / lightDir.y; frac = lightDir.x / lightDir.y; blockColStep.set(0, 1); blockRowStep.set(1, 0); blockFirstPos.set(0, 0); lmFirstPos.set(0,0); } else { zStep = -lightDir.z / lightDir.y; frac = -lightDir.x / lightDir.y; blockColStep.set(0, -1); blockRowStep.set(1, 0); blockFirstPos.set(0, 255); lmFirstPos.set(0, TerrainBlock::LightmapSize - 1); } } zStep *= stepSize; F32 * heightArray = new F32[generateDim]; S32 fracStep = -1; if(frac < 0) { fracStep = 1; frac = -frac; } F32 * nextHeightArray = new F32[generateDim]; F32 oneMinusFrac = 1 - frac; U32 blockShift = generateShift - TerrainBlock::BlockShift; U32 lightmapShift = generateShift - TerrainBlock::LightmapShift; U32 blockStep = 1 << blockShift; U32 blockMask = blockStep - 1; U32 lightmapStep = 1 << lightmapShift; U32 lightmapNormalOffset = lightmapStep >> 1; U32 lightmapMask = lightmapStep - 1; Point2I bp = blockFirstPos; F32 terrainHeights[2][TerrainBlock::BlockSize]; U32 i; F32 * pTerrainHeights = static_cast(terrainHeights[0]); F32 * pNextTerrainHeights = static_cast(terrainHeights[1]); // get first set of heights for(i = 0; i < TerrainBlock::BlockSize; i++) { pTerrainHeights[i] = fixedToFloat(terrain->getHeight(bp.x, bp.y)); bp += blockRowStep; } // get second set of heights bp = blockFirstPos + blockColStep; for(i = 0; i < TerrainBlock::BlockSize; i++) { pNextTerrainHeights[i] = fixedToFloat(terrain->getHeight(bp.x, bp.y)); bp += blockRowStep; } F32 heightStep = 1.f / blockStep; F32 terrainZRowStep[TerrainBlock::BlockSize]; F32 nextTerrainZRowStep[TerrainBlock::BlockSize]; F32 terrainZColStep[TerrainBlock::BlockSize]; // fill in the row steps for(i = 0; i < TerrainBlock::BlockSize; i++) { terrainZRowStep[i] = (pTerrainHeights[(i+1) & TerrainBlock::BlockMask] - pTerrainHeights[i]) * heightStep; nextTerrainZRowStep[i] = (pNextTerrainHeights[(i+1) & TerrainBlock::BlockMask] - pNextTerrainHeights[i]) * heightStep; terrainZColStep[i] = (pNextTerrainHeights[i] - pTerrainHeights[i]) * heightStep; } // get first row of process heights for(i = 0; i < generateDim; i++) { U32 bi = i >> blockShift; heightArray[i] = pTerrainHeights[bi] + (i & blockMask) * terrainZRowStep[bi]; } bp = blockFirstPos; if(generateDim == TerrainBlock::BlockSize) bp += blockColStep; // generate the initial run U32 x, y; for(x = 1; x < generateDim; x++) { U32 xmask = x & blockMask; // generate new height step rows? if(!xmask) { F32 * tmp = pTerrainHeights; pTerrainHeights = pNextTerrainHeights; pNextTerrainHeights = tmp; bp += blockColStep; Point2I bwalk = bp; for(i = 0; i < TerrainBlock::BlockSize; i++, bwalk += blockRowStep) pNextTerrainHeights[i] = fixedToFloat(terrain->getHeight(bwalk.x, bwalk.y)); // fill in the row steps for(i = 0; i < TerrainBlock::BlockSize; i++) { terrainZRowStep[i] = (pTerrainHeights[(i+1) & TerrainBlock::BlockMask] - pTerrainHeights[i]) * heightStep; nextTerrainZRowStep[i] = (pNextTerrainHeights[(i+1) & TerrainBlock::BlockMask] - pNextTerrainHeights[i]) * heightStep; terrainZColStep[i] = (pNextTerrainHeights[i] - pTerrainHeights[i]) * heightStep; } } Point2I bwalk = bp - blockRowStep; for(y = 0; y < generateDim; y++) { U32 ymask = y & blockMask; if(!ymask) bwalk += blockRowStep; U32 bi = y >> blockShift; U32 binext = (bi + 1) & TerrainBlock::BlockMask; F32 height; // 135? if((bwalk.x ^ bwalk.y) & 1) { U32 xsub = blockStep - xmask; if(xsub > ymask) // bottom height = pTerrainHeights[bi] + xmask * terrainZColStep[bi] + ymask * terrainZRowStep[bi]; else // top height = pNextTerrainHeights[bi] - xmask * terrainZColStep[binext] + ymask * nextTerrainZRowStep[bi]; } else { if(xmask > ymask) // bottom height = pTerrainHeights[bi] + xmask * terrainZColStep[bi] + ymask * nextTerrainZRowStep[bi]; else // top height = pTerrainHeights[bi] + xmask * terrainZColStep[binext] + ymask * terrainZRowStep[bi]; } F32 intHeight = heightArray[y] * oneMinusFrac + heightArray[(y + fracStep) & generateMask] * frac + zStep; nextHeightArray[y] = getMax(height, intHeight); } // swap the height rows F32 * tmp = heightArray; heightArray = nextHeightArray; nextHeightArray = tmp; } F32 squareSize = terrain->getSquareSize(); F32 lexelDim = squareSize * F32(TerrainBlock::BlockSize) / F32(TerrainBlock::LightmapSize); // calculate normal runs Point3F normals[2][TerrainBlock::BlockSize]; Point3F * pNormals = static_cast(normals[0]); Point3F * pNextNormals = static_cast(normals[1]); // calculate the normal lookup table F32 * normTable = new F32 [blockStep * blockStep * 4]; Point2F corners[4] = { Point2F(0.f, 0.f), Point2F(1.f, 0.f), Point2F(1.f, 1.f), Point2F(0.f, 1.f) }; U32 idx = 0; F32 step = 1.f / blockStep; Point2F pos(0.f, 0.f); // fill it for(x = 0; x < blockStep; x++, pos.x += step, pos.y = 0.f) { for(y = 0; y < blockStep; y++, pos.y += step) { for(i = 0; i < 4; i++, idx++) normTable[idx] = 1.f - getMin(Point2F(pos - corners[i]).len(), 1.f); } } // fill first column bp = blockFirstPos; for(x = 0; x < TerrainBlock::BlockSize; x++) { pNextTerrainHeights[x] = fixedToFloat(terrain->getHeight(bp.x, bp.y)); Point2F pos(bp.x * squareSize, bp.y * squareSize); terrain->getNormal(pos, &pNextNormals[x]); bp += blockRowStep; } // get swapped on first pass pTerrainHeights = static_cast(terrainHeights[1]); pNextTerrainHeights = static_cast(terrainHeights[0]); // get the world offset of the terrain const MatrixF & transform = terrain->getTransform(); Point3F worldOffset; transform.getColumn(3, &worldOffset); F32 ratio = F32(1 << lightmapShift); F32 ratioSquared = ratio * ratio; F32 inverseRatioSquared = 1.f / ratioSquared; F32 lightScale[TerrainBlock::LightmapSize]; // walk it... bp = blockFirstPos - blockColStep; for(x = 0; x < generateDim; x++) { U32 xmask = x & blockMask; U32 lxmask = x & lightmapMask; // generate new runs? if(!xmask) { bp += blockColStep; // do the normals Point3F * temp = pNormals; pNormals = pNextNormals; pNextNormals = temp; // fill the row Point2I bwalk = bp + blockColStep; for(i = 0; i < TerrainBlock::BlockSize; i++) { Point2F pos(bwalk.x * squareSize, bwalk.y * squareSize); terrain->getNormal(pos, &pNextNormals[i]); bwalk += blockRowStep; } // do the heights F32 * tmp = pTerrainHeights; pTerrainHeights = pNextTerrainHeights; pNextTerrainHeights = tmp; bwalk = bp + blockColStep; for(i = 0; i < TerrainBlock::BlockSize; i++, bwalk += blockRowStep) pNextTerrainHeights[i] = fixedToFloat(terrain->getHeight(bwalk.x, bwalk.y)); // fill in the row steps for(i = 0; i < TerrainBlock::BlockSize; i++) { terrainZRowStep[i] = (pTerrainHeights[(i+1) & TerrainBlock::BlockMask] - pTerrainHeights[i]) * heightStep; nextTerrainZRowStep[i] = (pNextTerrainHeights[(i+1) & TerrainBlock::BlockMask] - pNextTerrainHeights[i]) * heightStep; terrainZColStep[i] = (pNextTerrainHeights[i] - pTerrainHeights[i]) * heightStep; } } // reset the light scale table if(!lxmask) for(i = 0; i < TerrainBlock::LightmapSize; i++) lightScale[i] = 1.f; Point2I bwalk = bp - blockRowStep; for(y = 0; y < generateDim; y++) { U32 lymask = y & lightmapMask; U32 ymask = y & blockMask; if(!ymask) bwalk += blockRowStep; U32 bi = y >> blockShift; U32 binext = (bi + 1) & TerrainBlock::BlockMask; F32 height; F32 xstep, ystep; // 135? if((bwalk.x ^ bwalk.y) & 1) { U32 xsub = blockStep - xmask; if(xsub > ymask) // bottom { xstep = terrainZColStep[bi]; ystep = terrainZRowStep[bi]; height = pTerrainHeights[bi] + xmask * xstep + ymask * ystep; } else // top { xstep = -terrainZColStep[binext]; ystep = nextTerrainZRowStep[bi]; height = pNextTerrainHeights[bi] + xsub * xstep + ymask * ystep; } } else { if(xmask > ymask) // bottom { xstep = terrainZColStep[bi]; ystep = nextTerrainZRowStep[bi]; height = pTerrainHeights[bi] + xmask * xstep + ymask * ystep; } else // top { xstep = terrainZColStep[binext]; ystep = terrainZRowStep[bi]; height = pTerrainHeights[bi] + xmask * xstep + ymask * ystep; } } F32 intHeight = heightArray[y] * oneMinusFrac + heightArray[(y + fracStep) & generateMask] * frac + zStep; U32 lsi = y >> lightmapShift; Point2I lmPos = lmFirstPos + blockColStep * (x >> lightmapShift) + blockRowStep * lsi; ColorF & col = mLightmap[lmPos.x + (lmPos.y << TerrainBlock::LightmapShift)]; // lexel shaded by an interior? Point2I terrPos = lmPos; terrPos.x >>= TerrainBlock::LightmapShift - TerrainBlock::BlockShift; terrPos.y >>= TerrainBlock::LightmapShift - TerrainBlock::BlockShift; if(!lxmask && !lymask && mShadowMask.test(terrPos.x + (terrPos.y << TerrainBlock::BlockShift))) { U32 idx = (xmask + lightmapNormalOffset + ((ymask + lightmapNormalOffset) << blockShift)) << 2; // get the normal for this lexel Point3F normal = pNormals[bi] * normTable[idx++]; normal += pNormals[binext] * normTable[idx++]; normal += pNextNormals[binext] * normTable[idx++]; normal += pNextNormals[bi] * normTable[idx]; normal.normalize(); nextHeightArray[y] = height; F32 colorScale = -mDot(normal, lightDir); if(colorScale > 0.f) { // split lexels which cross the square split? if(allowLexelSplits) { // jff:todo } else { Point2F wPos((lmPos.x) * lexelDim + worldOffset.x, (lmPos.y) * lexelDim + worldOffset.y); F32 xh = xstep * ratio; F32 yh = ystep * ratio; ShadowVolumeBSP::SVPoly * poly = mShadowVolume->createPoly(); poly->mWindingCount = 4; poly->mWinding[0].set(wPos.x, wPos.y, height); poly->mWinding[1].set(wPos.x + lexelDim, wPos.y, height + xh); poly->mWinding[2].set(wPos.x + lexelDim, wPos.y + lexelDim, height + xh + yh); poly->mWinding[3].set(wPos.x, wPos.y + lexelDim, height + yh); poly->mPlane.set(poly->mWinding[0], poly->mWinding[1], poly->mWinding[2]); F32 lexelSize = mShadowVolume->getPolySurfaceArea(poly); F32 intensity = mShadowVolume->getClippedSurfaceArea(poly) / lexelSize; lightScale[lsi] = mClampF(intensity, 0.f, 1.f); } } else lightScale[lsi] = 0.f; } // non shadowed? if(height >= intHeight) { U32 idx = (xmask + (ymask << blockShift)) << 2; Point3F normal = pNormals[bi] * normTable[idx++]; normal += pNormals[binext] * normTable[idx++]; normal += pNextNormals[binext] * normTable[idx++]; normal += pNextNormals[bi] * normTable[idx]; normal.normalize(); nextHeightArray[y] = height; F32 colorScale = -mDot(normal, lightDir); if(colorScale > 0.f) col += ambient + lightColor * colorScale * lightScale[lsi]; else col += ambient; } else { nextHeightArray[y] = intHeight; col += ambient; } } F32 * tmp = heightArray; heightArray = nextHeightArray; nextHeightArray = tmp; } // set the proper color for(i = 0; i < TerrainBlock::LightmapSize * TerrainBlock::LightmapSize; i++) { mLightmap[i] *= inverseRatioSquared; mLightmap[i].clamp(); } delete [] normTable; delete [] heightArray; delete [] nextHeightArray; } //-------------------------------------------------------------------------- U32 SceneLighting::TerrainProxy::getResourceCRC() { TerrainBlock * terrain = getObject(); if(!terrain) return(0); return(terrain->getCRC()); } //-------------------------------------------------------------------------- bool SceneLighting::TerrainProxy::setPersistInfo(PersistInfo::PersistChunk * info) { if(!Parent::setPersistInfo(info)) return(false); PersistInfo::TerrainChunk * chunk = dynamic_cast(info); AssertFatal(chunk, "SceneLighting::TerrainProxy::setPersistInfo: invalid info chunk!"); TerrainBlock * terrain = getObject(); if(!terrain || !terrain->lightMap) return(false); // trigger a reload... terrain->triggerLightmapReload(); dMemcpy(terrain->lightMap->getAddress(0,0), chunk->mLightmap, sizeof(U16) * TerrainBlock::LightmapSize * TerrainBlock::LightmapSize); terrain->buildMaterialMap(); return(true); } bool SceneLighting::TerrainProxy::getPersistInfo(PersistInfo::PersistChunk * info) { if(!Parent::getPersistInfo(info)) return(false); PersistInfo::TerrainChunk * chunk = dynamic_cast(info); AssertFatal(chunk, "SceneLighting::TerrainProxy::getPersistInfo: invalid info chunk!"); TerrainBlock * terrain = getObject(); if(!terrain || !terrain->lightMap) return(false); chunk->mLightmap = new U16[TerrainBlock::LightmapSize * TerrainBlock::LightmapSize]; dMemcpy(chunk->mLightmap, terrain->lightMap->getAddress(0,0), sizeof(U16) * TerrainBlock::LightmapSize * TerrainBlock::LightmapSize); return(true); } /// adds TSStatic objects as shadow casters. bool SceneLighting::TerrainProxy::sgMarkStaticShadow(void *terrainproxy, SceneObject *sceneobject, LightInfo *light) { SceneLighting::TerrainProxy *terrain = (SceneLighting::TerrainProxy *)terrainproxy; if((!terrain) || (!sceneobject)) return false; // create shadow volume of the bounding box of this object if(light->mType != LightInfo::Vector) return(false); Vector shadowvolumesurfaces; Vector shadowplanes; const Box3F & objBox = sceneobject->getObjBox(); const MatrixF & objTransform = sceneobject->getTransform(); const VectorF & objScale = sceneobject->getScale(); // grab the surfaces which form the shadow volume U32 numPlanes = 0; PlaneF testPlanes[3]; U32 planeIndices[3]; // grab the bounding planes which face the light U32 i; for(i = 0; (i < 6) && (numPlanes < 3); i++) { PlaneF plane; plane.x = BoxNormals[i].x; plane.y = BoxNormals[i].y; plane.z = BoxNormals[i].z; if(i&1) plane.d = (((const float*)objBox.min)[(i-1)>>1]); else plane.d = -(((const float*)objBox.max)[i>>1]); // project mTransformPlane(objTransform, objScale, plane, &testPlanes[numPlanes]); planeIndices[numPlanes] = i; if(mDot(testPlanes[numPlanes], light->mDirection) < gParellelVectorThresh) numPlanes++; } AssertFatal(numPlanes, "SceneLighting::InteriorProxy::preLight: no planes found"); // project the points Point3F projPnts[8]; for(i = 0; i < 8; i++) { Point3F pnt; pnt.set(BoxPnts[i].x ? objBox.max.x : objBox.min.x, BoxPnts[i].y ? objBox.max.y : objBox.min.y, BoxPnts[i].z ? objBox.max.z : objBox.min.z); // scale it pnt.convolve(objScale); objTransform.mulP(pnt, &projPnts[i]); } ShadowVolumeBSP *mBoxShadowBSP = new ShadowVolumeBSP; // insert the shadow volumes for the surfaces for(i = 0; i < numPlanes; i++) { ShadowVolumeBSP::SVPoly * poly = mBoxShadowBSP->createPoly(); poly->mWindingCount = 4; U32 j; for(j = 0; j < 4; j++) poly->mWinding[j] = projPnts[BoxVerts[planeIndices[i]][j]]; testPlanes[i].neg(); poly->mPlane = testPlanes[i]; mBoxShadowBSP->buildPolyVolume(poly, light); shadowvolumesurfaces.push_back(mBoxShadowBSP->copyPoly(poly)); mBoxShadowBSP->insertPoly(poly); // create the opposite planes for testing against terrain Point3F pnts[3]; for(j = 0; j < 3; j++) pnts[j] = projPnts[BoxVerts[planeIndices[i]^1][j]]; } // grab the unique planes for terrain checks for(i = 0; i < numPlanes; i++) { U32 mask = 0; for(U32 j = 0; j < numPlanes; j++) mask |= BoxSharedEdgeMask[planeIndices[i]][planeIndices[j]]; ShadowVolumeBSP::SVNode * traverse = mBoxShadowBSP->getShadowVolume(shadowvolumesurfaces[i]->mShadowVolume); while(traverse->mFront) { if(!(mask & 1)) shadowplanes.push_back(mBoxShadowBSP->getPlane(traverse->mPlaneIndex)); mask >>= 1; traverse = traverse->mFront; } } // there will be 2 duplicate node planes if there were only 2 planes lit if(numPlanes == 2) { for(S32 i = 0; i < shadowvolumesurfaces.size(); i++) for(U32 j = 0; j < shadowvolumesurfaces.size(); j++) { if(i == j) continue; if((mDot(shadowplanes[i], shadowplanes[j]) > gPlaneNormThresh) && (mFabs(shadowplanes[i].d - shadowplanes[j].d) < gPlaneDistThresh)) { shadowplanes.erase(i); i--; break; } } } // setup the clip planes: add the test and the lit planes Vector clipPlanes; clipPlanes = shadowplanes; for(i = 0; i < shadowvolumesurfaces.size(); i++) clipPlanes.push_back(shadowvolumesurfaces[i]->mPlane); Vector shadowList; if(!terrain->getShadowedSquares(clipPlanes, shadowList)) { delete mBoxShadowBSP; return(false); } // set the correct bit for(i = 0; i < shadowList.size(); i++) terrain->mShadowMask.set(shadowList[i]); delete mBoxShadowBSP; return(true); }