//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "lighting/basic/blInteriorSystem.h" #include "lighting/lightingInterfaces.h" #include "lighting/common/shadowVolumeBSP.h" #include "interior/interiorInstance.h" #include "lighting/common/sceneLightingGlobals.h" #include "lighting/basic/basicLightManager.h" #include "gfx/bitmap/gBitmap.h" //#define SET_COLORS bool blInteriorSystem::smUseVertexLighting = false; //------------------------------------------------------------------------------ // Class SceneLighting::PersistInfo::InteriorChunk //------------------------------------------------------------------------------ struct blInteriorChunk : public PersistInfo::PersistChunk { typedef PersistChunk Parent; blInteriorChunk(); ~blInteriorChunk(); Vector sgNormalLightMaps; Vector mDetailLightmapCount; Vector mDetailLightmapIndices; Vector mLightmaps; bool mHasAlarmState; Vector mDetailVertexCount; Vector mVertexColorsNormal; Vector mVertexColorsAlarm; bool read(Stream &); bool write(Stream &); }; blInteriorChunk::blInteriorChunk() { mChunkType = PersistChunk::InteriorChunkType; } blInteriorChunk::~blInteriorChunk() { for(U32 i = 0; i < mLightmaps.size(); i++) delete mLightmaps[i]; } //------------------------------------------------------------------------------ // - always read in vertex lighting, lightmaps may not be needed bool blInteriorChunk::read(Stream & stream) { if(!Parent::read(stream)) return(false); U32 size; U32 i; // lightmaps->vertex-info // BTRTODO: FIX ME if (true) //if(!SceneLighting::smUseVertexLighting) { // size of this minichunk if(!stream.read(&size)) return(false); // lightmaps stream.read(&size); mDetailLightmapCount.setSize(size); for(i = 0; i < size; i++) if(!stream.read(&mDetailLightmapCount[i])) return(false); stream.read(&size); mDetailLightmapIndices.setSize(size); for(i = 0; i < size; i++) if(!stream.read(&mDetailLightmapIndices[i])) return(false); if(!stream.read(&size)) return(false); mLightmaps.setSize(size); for(i = 0; i < size; i++) { mLightmaps[i] = new GBitmap; if(!mLightmaps[i]->readBitmap("png",stream)) return(false); } } else { // step past the lightmaps if(!stream.read(&size)) return(false); if(!stream.setPosition(stream.getPosition() + size)) return(false); } // size of the vertex lighting: need to reset stream position after zipStream reading U32 zipStreamEnd; if(!stream.read(&zipStreamEnd)) return(false); zipStreamEnd += stream.getPosition(); /* // vertex lighting ZipSubRStream zipStream; if(!zipStream.attachStream(&stream)) return(false); if(!zipStream.read(&size)) return(false); mHasAlarmState = bool(size); if(!zipStream.read(&size)) return(false); mDetailVertexCount.setSize(size); for(i = 0; i < size; i++) if(!zipStream.read(&mDetailVertexCount[i])) return(false); size = 0; for(i = 0; i < mDetailVertexCount.size(); i++) size += mDetailVertexCount[i]; mVertexColorsNormal.setSize(size); if(mHasAlarmState) mVertexColorsAlarm.setSize(size); U32 curPos = 0; for(i = 0; i < mDetailVertexCount.size(); i++) { U32 count = mDetailVertexCount[i]; for(U32 j = 0; j < count; j++) if(!zipStream.read(&mVertexColorsNormal[curPos + j])) return(false); // read in the alarm info if(mHasAlarmState) { // same? if(!zipStream.read(&size)) return(false); if(bool(size)) dMemcpy(&mVertexColorsAlarm[curPos], &mVertexColorsNormal[curPos], count * sizeof(ColorI)); else { for(U32 j = 0; j < count; j++) if(!zipStream.read(&mVertexColorsAlarm[curPos + j])) return(false); } } curPos += count; } zipStream.detachStream(); */ // since there is no resizeFilterStream the zipStream happily reads // off the end of the compressed block... reset the position stream.setPosition(zipStreamEnd); return(true); } bool blInteriorChunk::write(Stream & stream) { if(!Parent::write(stream)) return(false); // lightmaps U32 startPos = stream.getPosition(); if(!stream.write(U32(0))) return(false); U32 i; if(!stream.write(U32(mDetailLightmapCount.size()))) return(false); for(i = 0; i < mDetailLightmapCount.size(); i++) if(!stream.write(mDetailLightmapCount[i])) return(false); if(!stream.write(U32(mDetailLightmapIndices.size()))) return(false); for(i = 0; i < mDetailLightmapIndices.size(); i++) if(!stream.write(mDetailLightmapIndices[i])) return(false); if(!stream.write(U32(mLightmaps.size()))) return(false); for(i = 0; i < mLightmaps.size(); i++) { AssertFatal(mLightmaps[i], "SceneLighting::blInteriorChunk::Write: Invalid bitmap!"); if(!mLightmaps[i]->writeBitmap("png",stream)) return(false); } // write out the lightmap portions size U32 endPos = stream.getPosition(); if(!stream.setPosition(startPos)) return(false); // don't include the offset in the size if(!stream.write(U32(endPos - startPos - sizeof(U32)))) return(false); if(!stream.setPosition(endPos)) return(false); // vertex lighting: needs the size of the vertex info because the // zip stream may read off the end of the chunk startPos = stream.getPosition(); if(!stream.write(U32(0))) return(false); // write out the vertex lighting portions size endPos = stream.getPosition(); if(!stream.setPosition(startPos)) return(false); // don't include the offset in the size if(!stream.write(U32(endPos - startPos - sizeof(U32)))) return(false); if(!stream.setPosition(endPos)) return(false); return(true); } // // InteriorProxy (definition) // class blInteriorProxy : public SceneLighting::ObjectProxy { private: typedef ObjectProxy Parent; bool isShadowedBy(blInteriorProxy *); ShadowVolumeBSP::SVPoly * buildInteriorPoly(ShadowVolumeBSP * shadowVolumeBSP, Interior * detail, U32 surfaceIndex, LightInfo * light, bool createSurfaceInfo); public: blInteriorProxy(SceneObject * obj); ~blInteriorProxy(); InteriorInstance * operator->() {return(static_cast(static_cast(mObj)));} InteriorInstance * getObject() {return(static_cast(static_cast(mObj)));} // current light info ShadowVolumeBSP * mBoxShadowBSP; Vector mLitBoxSurfaces; Vector mOppositeBoxPlanes; Vector mTerrainTestPlanes; struct sgSurfaceInfo { const Interior::Surface *sgSurface; U32 sgIndex; Interior *sgDetail; bool sgHasAlarm; }; U32 sgCurrentSurfaceIndex; U32 sgSurfacesPerPass; InteriorInstance *sgInterior; Vector sgLights; Vector sgSurfaces; void sgAddLight(LightInfo *light, InteriorInstance *interior); //void sgLightUniversalPoint(LightInfo *light); void sgProcessSurface(const Interior::Surface &surface, U32 i, Interior *detail, bool hasAlarm); // lighting interface bool loadResources(); void init(); //bool tgePreLight(LightInfo* light); bool preLight(LightInfo *); void light(LightInfo *); void postLight(bool lastLight); //virtual void processLightingStart(); //virtual bool processStartObjectLightingEvent(SceneLighting::ObjectProxy* objproxy, U32 current, U32 max); //virtual void processTGELightProcessEvent(U32 curr, U32 max, LightInfo*); virtual bool supportsShadowVolume(); virtual void getClipPlanes(Vector& planes); virtual void addToShadowVolume(ShadowVolumeBSP * shadowVolume, LightInfo * light, S32 level); // persist U32 getResourceCRC(); bool setPersistInfo(PersistInfo::PersistChunk *); bool getPersistInfo(PersistInfo::PersistChunk *); }; //------------------------------------------------------------------------------ // Class SceneLighting::InteriorProxy: //------------------------------------------------------------------------------ blInteriorProxy::blInteriorProxy(SceneObject * obj) : Parent(obj) { mBoxShadowBSP = 0; } blInteriorProxy::~blInteriorProxy() { delete mBoxShadowBSP; } bool blInteriorProxy::loadResources() { InteriorInstance * interior = getObject(); if(!interior) return(false); Resource & interiorRes = interior->getResource(); if(!bool(interiorRes)) return(false); return(true); } void blInteriorProxy::init() { InteriorInstance * interior = getObject(); if(!interior) return; } bool blInteriorProxy::supportsShadowVolume() { return true; } void blInteriorProxy::getClipPlanes(Vector& planes) { for(U32 i = 0; i < mLitBoxSurfaces.size(); i++) planes.push_back(mLitBoxSurfaces[i]->mPlane); } ShadowVolumeBSP::SVPoly * blInteriorProxy::buildInteriorPoly(ShadowVolumeBSP * shadowVolumeBSP, Interior * detail, U32 surfaceIndex, LightInfo * light, bool createSurfaceInfo) { InteriorInstance* interior = dynamic_cast(getObject()); if (!interior) return NULL; // transform and add the points... const MatrixF & transform = interior->getTransform(); const VectorF & scale = interior->getScale(); const Interior::Surface & surface = detail->mSurfaces[surfaceIndex]; ShadowVolumeBSP::SVPoly * poly = shadowVolumeBSP->createPoly(); poly->mWindingCount = surface.windingCount; // project these points for(U32 j = 0; j < poly->mWindingCount; j++) { Point3F iPnt = detail->mPoints[detail->mWindings[surface.windingStart + j]].point; Point3F tPnt; iPnt.convolve(scale); transform.mulP(iPnt, &tPnt); poly->mWinding[j] = tPnt; } // convert from fan U32 tmpIndices[ShadowVolumeBSP::SVPoly::MaxWinding]; Point3F fanIndices[ShadowVolumeBSP::SVPoly::MaxWinding]; tmpIndices[0] = 0; U32 idx = 1; U32 i; for(i = 1; i < poly->mWindingCount; i += 2) tmpIndices[idx++] = i; for(i = ((poly->mWindingCount - 1) & (~0x1)); i > 0; i -= 2) tmpIndices[idx++] = i; idx = 0; for(i = 0; i < poly->mWindingCount; i++) if(surface.fanMask & (1 << i)) fanIndices[idx++] = poly->mWinding[tmpIndices[i]]; // set the data poly->mWindingCount = idx; for(i = 0; i < poly->mWindingCount; i++) poly->mWinding[i] = fanIndices[i]; // flip the plane - shadow volumes face inwards PlaneF plane = detail->getPlane(surface.planeIndex); if(!Interior::planeIsFlipped(surface.planeIndex)) plane.neg(); // transform the plane mTransformPlane(transform, scale, plane, &poly->mPlane); shadowVolumeBSP->buildPolyVolume(poly, light); // do surface info? if(createSurfaceInfo) { ShadowVolumeBSP::SurfaceInfo * surfaceInfo = new ShadowVolumeBSP::SurfaceInfo; shadowVolumeBSP->mSurfaces.push_back(surfaceInfo); // fill it surfaceInfo->mSurfaceIndex = surfaceIndex; surfaceInfo->mShadowVolume = shadowVolumeBSP->getShadowVolume(poly->mShadowVolume); // POLY and POLY node gets it too ShadowVolumeBSP::SVNode * traverse = shadowVolumeBSP->getShadowVolume(poly->mShadowVolume); while(traverse->mFront) { traverse->mSurfaceInfo = surfaceInfo; traverse = traverse->mFront; } // get some info from the poly node poly->mSurfaceInfo = traverse->mSurfaceInfo = surfaceInfo; surfaceInfo->mPlaneIndex = traverse->mPlaneIndex; } return(poly); } void blInteriorProxy::addToShadowVolume(ShadowVolumeBSP * shadowVolume, LightInfo * light, S32 level) { if(light->getType() != LightInfo::Vector) return; ColorF ambient = light->getAmbient(); bool shadowedTree = true; InteriorInstance* interior = dynamic_cast(getObject()); if (!interior) return; Resource mInteriorRes = interior->getResource(); // check if just getting shadow detail if(level == SceneLighting::SHADOW_DETAIL) { shadowedTree = false; level = mInteriorRes->getNumDetailLevels() - 1; } Interior * detail = mInteriorRes->getDetailLevel(level); bool hasAlarm = detail->hasAlarmState(); // make sure surfaces do not get processed more than once BitVector surfaceProcessed; surfaceProcessed.setSize(detail->mSurfaces.size()); surfaceProcessed.clear(); ColorI color = light->getAmbient(); // go through the zones of the interior and grab outside visible surfaces for(U32 i = 0; i < detail->getNumZones(); i++) { Interior::Zone & zone = detail->mZones[i]; for(U32 j = 0; j < zone.surfaceCount; j++) { U32 surfaceIndex = detail->mZoneSurfaces[zone.surfaceStart + j]; // dont reprocess a surface if(surfaceProcessed.test(surfaceIndex)) continue; surfaceProcessed.set(surfaceIndex); Interior::Surface & surface = detail->mSurfaces[surfaceIndex]; // outside visible? if(!(surface.surfaceFlags & Interior::SurfaceOutsideVisible)) continue; // good surface? PlaneF plane = detail->getPlane(surface.planeIndex); if(Interior::planeIsFlipped(surface.planeIndex)) plane.neg(); // project the plane PlaneF projPlane; mTransformPlane(interior->getTransform(), interior->getScale(), plane, &projPlane); // fill with ambient? (need to do here, because surface will not be // added to the SVBSP tree) F32 dot = mDot(projPlane, light->getDirection()); if(dot > -gParellelVectorThresh)// && !(GFX->getPixelShaderVersion() > 0.0) ) { if(shadowedTree) { // alarm lighting GFXTexHandle normHandle = gInteriorLMManager.duplicateBaseLightmap(detail->getLMHandle(), interior->getLMHandle(), detail->getNormalLMapIndex(surfaceIndex)); GFXTexHandle alarmHandle; GBitmap * normLightmap = normHandle->getBitmap(); GBitmap * alarmLightmap = 0; // check if they share the lightmap if(hasAlarm) { if(detail->getNormalLMapIndex(surfaceIndex) != detail->getAlarmLMapIndex(surfaceIndex)) { alarmHandle = gInteriorLMManager.duplicateBaseLightmap(detail->getLMHandle(), interior->getLMHandle(), detail->getAlarmLMapIndex(surfaceIndex)); alarmLightmap = alarmHandle->getBitmap(); } } // // Support for interior light map border sizes. // U32 xlen, ylen, xoff, yoff; U32 lmborder = detail->getLightMapBorderSize(); xlen = surface.mapSizeX + (lmborder * 2); ylen = surface.mapSizeY + (lmborder * 2); xoff = surface.mapOffsetX - lmborder; yoff = surface.mapOffsetY - lmborder; // attemp to light normal and alarm lighting for(U32 c = 0; c < 2; c++) { GBitmap * lightmap = (c == 0) ? normLightmap : alarmLightmap; if(!lightmap) continue; // fill it for(U32 y = 0; y < ylen; y++) { for(U32 x = 0; x < xlen; x++) { ColorI outColor(255, 0, 0, 255); #ifndef SET_COLORS ColorI lmColor(0, 0, 0, 255); lightmap->getColor(xoff + x, yoff + y, lmColor); U32 _r = static_cast( color.red ) + static_cast( lmColor.red ); U32 _g = static_cast( color.green ) + static_cast( lmColor.green ); U32 _b = static_cast( color.blue ) + static_cast( lmColor.blue ); outColor.red = mClamp(_r, 0, 255); outColor.green = mClamp(_g, 0, 255); outColor.blue = mClamp(_b, 0, 255); #endif lightmap->setColor(xoff + x, yoff + y, outColor); } } } } continue; } ShadowVolumeBSP::SVPoly * poly = buildInteriorPoly(shadowVolume, detail, surfaceIndex, light, shadowedTree); // insert it into the SVBSP tree shadowVolume->insertPoly(poly); } } } bool blInteriorProxy::preLight(LightInfo * light) { // create shadow volume of the bounding box of this object InteriorInstance * interior = getObject(); if(!interior) return(false); if(light->getType() != LightInfo::Vector) return(false); // reset mLitBoxSurfaces.clear(); const Box3F & objBox = interior->getObjBox(); const MatrixF & objTransform = interior->getTransform(); const VectorF & objScale = interior->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.minExtents)[(i-1)>>1]); else plane.d = -(((const float*)objBox.maxExtents)[i>>1]); // project mTransformPlane(objTransform, objScale, plane, &testPlanes[numPlanes]); planeIndices[numPlanes] = i; if(mDot(testPlanes[numPlanes], light->getDirection()) < gParellelVectorThresh) numPlanes++; } AssertFatal(numPlanes, "blInteriorProxy::preLight: no planes found"); // project the points Point3F projPnts[8]; for(i = 0; i < 8; i++) { Point3F pnt; pnt.set(BoxPnts[i].x ? objBox.maxExtents.x : objBox.minExtents.x, BoxPnts[i].y ? objBox.maxExtents.y : objBox.minExtents.y, BoxPnts[i].z ? objBox.maxExtents.z : objBox.minExtents.z); // scale it pnt.convolve(objScale); objTransform.mulP(pnt, &projPnts[i]); } 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); mLitBoxSurfaces.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]]; PlaneF plane(pnts[2], pnts[1], pnts[0]); mOppositeBoxPlanes.push_back(plane); } // 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(mLitBoxSurfaces[i]->mShadowVolume); while(traverse->mFront) { if(!(mask & 1)) mTerrainTestPlanes.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 < mTerrainTestPlanes.size(); i++) for(U32 j = 0; j < mTerrainTestPlanes.size(); j++) { if(i == j) continue; if((mDot(mTerrainTestPlanes[i], mTerrainTestPlanes[j]) > gPlaneNormThresh) && (mFabs(mTerrainTestPlanes[i].d - mTerrainTestPlanes[j].d) < gPlaneDistThresh)) { mTerrainTestPlanes.erase(i); i--; break; } } } return(true); } bool blInteriorProxy::isShadowedBy(blInteriorProxy * test) { // add if overlapping world box if((*this)->getWorldBox().isOverlapped((*test)->getWorldBox())) return(true); // test the box shadow volume for(U32 i = 0; i < mLitBoxSurfaces.size(); i++) { ShadowVolumeBSP::SVPoly * poly = mBoxShadowBSP->copyPoly(mLitBoxSurfaces[i]); if(test->mBoxShadowBSP->testPoly(poly)) return(true); } return(false); } void blInteriorProxy::light(LightInfo * light) { Platform::setMathControlStateKnown(); InteriorInstance * interior = getObject(); if(!interior) return; ColorF ambient = light->getAmbient(); S32 time = Platform::getRealMilliseconds(); // create own shadow volume ShadowVolumeBSP shadowVolume; // add the other objects lit surfaces into shadow volume for(ObjectProxy ** itr = gLighting->mLitObjects.begin(); itr != gLighting->mLitObjects.end(); itr++) { if(!(*itr)->getObject()) continue; ObjectProxy* obj = *itr; if (obj != this) { if (obj->supportsShadowVolume()) obj->addToShadowVolume(&shadowVolume, light, SceneLighting::SHADOW_DETAIL); } /* // insert the terrain squares if(gLighting->isTerrain((*itr)->mObj)) { TerrainProxy * terrain = static_cast(*itr); Vector clipPlanes; clipPlanes = mTerrainTestPlanes; for(U32 i = 0; i < mOppositeBoxPlanes.size(); i++) clipPlanes.push_back(mOppositeBoxPlanes[i]); Vector shadowList; if(terrain->getShadowedSquares(clipPlanes, shadowList)) { TerrainBlock * block = static_cast((*itr)->getObject()); Point3F offset; block->getTransform().getColumn(3, &offset); F32 squareSize = block->getSquareSize(); for(U32 j = 0; j < shadowList.size(); j++) { Point2I pos(shadowList[j] & TerrainBlock::BlockMask, shadowList[j] >> TerrainBlock::BlockShift); Point2F wPos(pos.x * squareSize + offset.x, pos.y * squareSize + offset.y); Point3F pnts[4]; pnts[0].set(wPos.x, wPos.y, fixedToFloat(block->getHeight(pos.x, pos.y))); pnts[1].set(wPos.x + squareSize, wPos.y, fixedToFloat(block->getHeight(pos.x + 1, pos.y))); pnts[2].set(wPos.x + squareSize, wPos.y + squareSize, fixedToFloat(block->getHeight(pos.x + 1, pos.y + 1))); pnts[3].set(wPos.x, wPos.y + squareSize, fixedToFloat(block->getHeight(pos.x, pos.y + 1))); GridSquare * gs = block->findSquare(0, pos); U32 squareIdx = (gs->flags & GridSquare::Split45) ? 0 : 2; for(U32 k = squareIdx; k < (squareIdx + 2); k++) { // face plane inwards PlaneF plane(pnts[TerrainSquareIndices[k][2]], pnts[TerrainSquareIndices[k][1]], pnts[TerrainSquareIndices[k][0]]); if(mDot(plane, light->mDirection) > gParellelVectorThresh) { ShadowVolumeBSP::SVPoly * poly = shadowVolume.createPoly(); poly->mWindingCount = 3; poly->mWinding[0] = pnts[TerrainSquareIndices[k][0]]; poly->mWinding[1] = pnts[TerrainSquareIndices[k][1]]; poly->mWinding[2] = pnts[TerrainSquareIndices[k][2]]; poly->mPlane = plane; // create the shadow volume for this and insert shadowVolume.buildPolyVolume(poly, light); shadowVolume.insertPoly(poly); } } } } }*/ } // light all details for(U32 i = 0; i < interior->getResource()->getNumDetailLevels(); i++) { // clear lightmaps Interior * detail = interior->getResource()->getDetailLevel(i); gInteriorLMManager.clearLightmaps(detail->getLMHandle(), interior->getLMHandle()); // clear out the last inserted interior shadowVolume.removeLastInterior(); bool hasAlarm = detail->hasAlarmState(); addToShadowVolume(&shadowVolume, light, i); //gLighting->addInterior(&shadowVolume, *this, light, i); for(U32 j = 0; j < shadowVolume.mSurfaces.size(); j++) { ShadowVolumeBSP::SurfaceInfo * surfaceInfo = shadowVolume.mSurfaces[j]; U32 surfaceIndex = surfaceInfo->mSurfaceIndex; const Interior::Surface & surface = detail->getSurface(surfaceIndex); // alarm lighting GFXTexHandle normHandle = gInteriorLMManager.duplicateBaseLightmap(detail->getLMHandle(), interior->getLMHandle(), detail->getNormalLMapIndex(surfaceIndex)); GFXTexHandle alarmHandle; GBitmap * normLightmap = normHandle->getBitmap(); GBitmap * alarmLightmap = 0; // check if the lightmaps are shared if(hasAlarm) { if(detail->getNormalLMapIndex(surfaceIndex) != detail->getAlarmLMapIndex(surfaceIndex)) { alarmHandle = gInteriorLMManager.duplicateBaseLightmap(detail->getLMHandle(), interior->getLMHandle(), detail->getAlarmLMapIndex(surfaceIndex)); alarmLightmap = alarmHandle->getBitmap(); } } // points right way? PlaneF plane = detail->getPlane(surface.planeIndex); if(Interior::planeIsFlipped(surface.planeIndex)) plane.neg(); const MatrixF & transform = interior->getTransform(); const Point3F & scale = interior->getScale(); // PlaneF projPlane; mTransformPlane(transform, scale, plane, &projPlane); F32 dot = mDot(projPlane, -light->getDirection()); // cancel out lambert dot product and ambient lighting on hardware // with pixel shaders if( GFX->getPixelShaderVersion() > 0.0 ) { dot = 1.0f; ambient.set( 0.0f, 0.0f, 0.0f ); } // shadowed? if(!surfaceInfo->mShadowed.size()) { // calc the color and convert to U8 rep ColorF tmp = (light->getColor() * dot) + ambient; tmp.clamp(); ColorI color = tmp; // attempt to light both the normal and the alarm states for(U32 c = 0; c < 2; c++) { GBitmap * lightmap = (c == 0) ? normLightmap : alarmLightmap; if(!lightmap) continue; // // Support for interior light map border sizes. // U32 xlen, ylen, xoff, yoff; U32 lmborder = detail->getLightMapBorderSize(); xlen = surface.mapSizeX + (lmborder * 2); ylen = surface.mapSizeY + (lmborder * 2); xoff = surface.mapOffsetX - lmborder; yoff = surface.mapOffsetY - lmborder; // fill it for(U32 y = 0; y < ylen; y++) { for(U32 x = 0; x < xlen; x++) { ColorI outColor(0, 255, 0, 255); #ifndef SET_COLORS ColorI lmColor(0, 0, 0, 255); lightmap->getColor(xoff + x, yoff + y, lmColor); U32 _r = static_cast( color.red ) + static_cast( lmColor.red ); U32 _g = static_cast( color.green ) + static_cast( lmColor.green ); U32 _b = static_cast( color.blue ) + static_cast( lmColor.blue ); outColor.red = mClamp(_r, 0, 255); outColor.green = mClamp(_g, 0, 255); outColor.blue = mClamp(_b, 0, 255); #endif lightmap->setColor(xoff + x, yoff + y, outColor); } } } if(!surfaceInfo->mShadowed.size()) continue; } // // Support for interior light map border sizes. // U32 xlen, ylen, xoff, yoff; U32 lmborder = detail->getLightMapBorderSize(); xlen = surface.mapSizeX + (lmborder * 2); ylen = surface.mapSizeY + (lmborder * 2); xoff = surface.mapOffsetX - lmborder; yoff = surface.mapOffsetY - lmborder; // get the lmagGen... const Interior::TexGenPlanes & lmTexGenEQ = detail->getLMTexGenEQ(surfaceIndex); const F32 * const lGenX = lmTexGenEQ.planeX; const F32 * const lGenY = lmTexGenEQ.planeY; AssertFatal((lGenX[0] * lGenX[1] == 0.f) && (lGenX[0] * lGenX[2] == 0.f) && (lGenX[1] * lGenX[2] == 0.f), "Bad lmTexGen!"); AssertFatal((lGenY[0] * lGenY[1] == 0.f) && (lGenY[0] * lGenY[2] == 0.f) && (lGenY[1] * lGenY[2] == 0.f), "Bad lmTexGen!"); // get the axis index for the texgens (could be swapped) S32 si=0; S32 ti=0; S32 axis = -1; // if(lGenX[0] == 0.f && lGenY[0] == 0.f) // YZ { axis = 0; if(lGenX[1] == 0.f) { // swapped? si = 2; ti = 1; } else { si = 1; ti = 2; } } else if(lGenX[1] == 0.f && lGenY[1] == 0.f) // XZ { axis = 1; if(lGenX[0] == 0.f) { // swapped? si = 2; ti = 0; } else { si = 0; ti = 2; } } else if(lGenX[2] == 0.f && lGenY[2] == 0.f) // XY { axis = 2; if(lGenX[0] == 0.f) { // swapped? si = 1; ti = 0; } else { si = 0; ti = 1; } } AssertFatal(!(axis == -1), "SceneLighting::lightInterior: bad TexGen!"); const F32 * pNormal = ((const F32*)plane); Point3F start; F32 * pStart = ((F32*)start); F32 lumelScale = 1.0 / (lGenX[si] * normLightmap->getWidth()); // get the start point on the lightmap pStart[si] = (((xoff * lumelScale) / (1.0 / lGenX[si])) - lGenX[3] ) / lGenX[si]; pStart[ti] = (((yoff * lumelScale) / (1.0 / lGenY[ti])) - lGenY[3] ) / lGenY[ti]; pStart[axis] = ((pNormal[si] * pStart[si]) + (pNormal[ti] * pStart[ti]) + plane.d) / -pNormal[axis]; start.convolve(scale); transform.mulP(start); // get the s/t vecs oriented on the surface Point3F sVec; Point3F tVec; F32 * pSVec = ((F32*)sVec); F32 * pTVec = ((F32*)tVec); F32 angle; Point3F planeNormal; // s pSVec[si] = 1.f; pSVec[ti] = 0.f; planeNormal = plane; ((F32*)planeNormal)[ti] = 0.f; planeNormal.normalize(); angle = mAcos(mClampF(((F32*)planeNormal)[axis], -1.f, 1.f)); pSVec[axis] = (((F32*)planeNormal)[si] < 0.f) ? mTan(angle) : -mTan(angle); // t pTVec[ti] = 1.f; pTVec[si] = 0.f; planeNormal = plane; ((F32*)planeNormal)[si] = 0.f; planeNormal.normalize(); angle = mAcos(mClampF(((F32*)planeNormal)[axis], -1.f, 1.f)); pTVec[axis] = (((F32*)planeNormal)[ti] < 0.f) ? mTan(angle) : -mTan(angle); // scale the vectors sVec *= lumelScale; tVec *= lumelScale; // project vecs transform.mulV(sVec); sVec.convolve(scale); transform.mulV(tVec); tVec.convolve(scale); Point3F & curPos = start; Point3F sRun = sVec * xlen; // get the lexel area Point3F cross; mCross(sVec, tVec, &cross); F32 maxLexelArea = cross.len(); const PlaneF & surfacePlane = shadowVolume.getPlane(surfaceInfo->mPlaneIndex); // get the world coordinate for each lexel for(U32 y = 0; y < ylen; y++) { for(U32 x = 0; x < xlen; x++) { ShadowVolumeBSP::SVPoly * poly = shadowVolume.createPoly(); poly->mPlane = surfacePlane; poly->mWindingCount = 4; // set the poly indices poly->mWinding[0] = curPos; poly->mWinding[1] = curPos + sVec; poly->mWinding[2] = curPos + sVec + tVec; poly->mWinding[3] = curPos + tVec; //// insert poly which has been clipped to own shadow volume //ShadowVolumeBSP::SVPoly * store = 0; //shadowVolume.clipToSelf(surfaceInfo->mShadowVolume, &store, poly); //if(!store) // continue; //F32 lexelArea = shadowVolume.getPolySurfaceArea(store); //F32 area = shadowVolume.getLitSurfaceArea(store, surfaceInfo); F32 area = shadowVolume.getLitSurfaceArea(poly, surfaceInfo); F32 shadowScale = mClampF(area / maxLexelArea, 0.f, 1.f); // get the color into U8 ColorF tmp = (light->getColor() * dot * shadowScale) + ambient; tmp.clamp(); ColorI color = tmp; // attempt to light both normal and alarm lightmaps for(U32 c = 0; c < 2; c++) { GBitmap * lightmap = (c == 0) ? normLightmap : alarmLightmap; if(!lightmap) continue; ColorI outColor(0, 0, 255, 255); #ifndef SET_COLORS ColorI lmColor(0, 0, 0, 255); lightmap->getColor(xoff + x, yoff + y, lmColor); U32 _r = static_cast( color.red ) + static_cast( lmColor.red ); U32 _g = static_cast( color.green ) + static_cast( lmColor.green ); U32 _b = static_cast( color.blue ) + static_cast( lmColor.blue ); outColor.red = mClamp(_r, 0, 255); outColor.green = mClamp(_g, 0, 255); outColor.blue = mClamp(_b, 0, 255); #endif lightmap->setColor(xoff + x, yoff + y, outColor); } curPos += sVec; } curPos -= sRun; curPos += tVec; } } } Con::printf(" = interior lit in %3.3f seconds", (Platform::getRealMilliseconds()-time)/1000.f); } void blInteriorProxy::postLight(bool lastLight) { delete mBoxShadowBSP; mBoxShadowBSP = 0; InteriorInstance * interior = getObject(); if(!interior) return; } //------------------------------------------------------------------------------ U32 blInteriorProxy::getResourceCRC() { InteriorInstance * interior = getObject(); if(!interior) return(0); return(interior->getCRC()); } //------------------------------------------------------------------------------ bool blInteriorProxy::setPersistInfo(PersistInfo::PersistChunk * info) { if(!Parent::setPersistInfo(info)) return(false); blInteriorChunk * chunk = dynamic_cast(info); AssertFatal(chunk, "blInteriorProxy::setPersistInfo: invalid info chunk!"); InteriorInstance * interior = getObject(); if(!interior) return(false); U32 numDetails = interior->getNumDetailLevels(); // check the lighting method // BTRTODO: Restore // AssertFatal(SceneLighting::smUseVertexLighting == Interior::smUseVertexLighting, "blInteriorProxy::setPersistInfo: invalid vertex lighting state"); // if(SceneLighting::smUseVertexLighting != Interior::smUseVertexLighting) // return(false); /* // process the vertex lighting... if(chunk->mDetailVertexCount.size() != numDetails) return(false); AssertFatal(chunk->mVertexColorsNormal.size(), "blInteriorProxy::setPersistInfo: invalid chunk info"); AssertFatal(!chunk->mHasAlarmState || chunk->mVertexColorsAlarm.size(), "blInteriorProxy::setPersistInfo: invalid chunk info"); AssertFatal(!(chunk->mHasAlarmState ^ interior->getDetailLevel(0)->hasAlarmState()), "blInteriorProxy::setPersistInfo: invalid chunk info"); U32 curPos = 0; for(U32 i = 0; i < numDetails; i++) { U32 count = chunk->mDetailVertexCount[i]; Vector* normal = interior->getVertexColorsNormal(i); Vector* alarm = interior->getVertexColorsAlarm(i); AssertFatal(normal != NULL && alarm != NULL, "Error, bad vectors returned!"); normal->setSize(count); dMemcpy(normal->address(), &chunk->mVertexColorsNormal[curPos], count * sizeof(ColorI)); if(chunk->mHasAlarmState) { alarm->setSize(count); dMemcpy(alarm->address(), &chunk->mVertexColorsAlarm[curPos], count * sizeof(ColorI)); } curPos += count; } */ // need lightmaps? //if(!SceneLighting::smUseVertexLighting) // BTRTODO: Fix me if (!false) { if(chunk->mDetailLightmapCount.size() != numDetails) return(false); LM_HANDLE instanceHandle = interior->getLMHandle(); U32 idx = 0; for(U32 i = 0; i < numDetails; i++) { Interior * detail = interior->getDetailLevel(i); LM_HANDLE interiorHandle = detail->getLMHandle(); Vector & baseHandles = gInteriorLMManager.getHandles(interiorHandle, 0); if(chunk->mDetailLightmapCount[i] > baseHandles.size()) return(false); for(U32 j = 0; j < chunk->mDetailLightmapCount[i]; j++) { U32 baseIndex = chunk->mDetailLightmapIndices[idx]; if(baseIndex >= baseHandles.size()) return(false); AssertFatal(chunk->mLightmaps[idx], "blInteriorProxy::setPersistInfo: bunk bitmap!"); if(chunk->mLightmaps[idx]->getWidth() != baseHandles[baseIndex]->getWidth() || chunk->mLightmaps[idx]->getHeight() != baseHandles[baseIndex]->getHeight()) return(false); GFXTexHandle tHandle = gInteriorLMManager.duplicateBaseLightmap(interiorHandle, instanceHandle, baseIndex); // create the diff bitmap tHandle->getBitmap()->combine( baseHandles[baseIndex]->getBitmap(), chunk->mLightmaps[idx], GFXTOPAdd ); idx++; } } } return(true); } bool blInteriorProxy::getPersistInfo(PersistInfo::PersistChunk * info) { if(!Parent::getPersistInfo(info)) return(false); blInteriorChunk* chunk = dynamic_cast(info); AssertFatal(chunk, "blInteriorProxy::getPersistInfo: invalid info chunk!"); InteriorInstance * interior = getObject(); if(!interior) return(false); LM_HANDLE instanceHandle = interior->getLMHandle(); AssertFatal(!chunk->mDetailLightmapCount.size(), "blInteriorProxy::getPersistInfo: invalid array!"); AssertFatal(!chunk->mDetailLightmapIndices.size(), "blInteriorProxy::getPersistInfo: invalid array!"); AssertFatal(!chunk->mLightmaps.size(), "blInteriorProxy::getPersistInfo: invalid array!"); U32 numDetails = interior->getNumDetailLevels(); U32 i; for(i = 0; i < numDetails; i++) { Interior * detail = interior->getDetailLevel(i); LM_HANDLE interiorHandle = detail->getLMHandle(); Vector & baseHandles = gInteriorLMManager.getHandles(interiorHandle, 0); Vector & instanceHandles = gInteriorLMManager.getHandles(interiorHandle, instanceHandle); U32 litCount = 0; // walk all the instance lightmaps and grab diff lighting from them for(U32 j = 0; j < instanceHandles.size(); j++) { if(!instanceHandles[j]) continue; litCount++; chunk->mDetailLightmapIndices.push_back(j); GBitmap * baseBitmap = baseHandles[j]->getBitmap(); GBitmap * instanceBitmap = instanceHandles[j]->getBitmap(); Point2I extent(baseBitmap->getWidth(), baseBitmap->getHeight()); GBitmap * diffLightmap = new GBitmap(extent.x, extent.y, false); // diffLightmap = instanceBitmap - baseBitmap diffLightmap->combine( instanceBitmap, baseBitmap, GFXTOPSubtract ); chunk->mLightmaps.push_back(diffLightmap); } chunk->mDetailLightmapCount.push_back(litCount); } // process the vertex lighting... AssertFatal(!chunk->mDetailVertexCount.size(), "blInteriorProxy::getPersistInfo: invalid chunk info"); AssertFatal(!chunk->mVertexColorsNormal.size(), "blInteriorProxy::getPersistInfo: invalid chunk info"); AssertFatal(!chunk->mVertexColorsAlarm.size(), "blInteriorProxy::getPersistInfo: invalid chunk info"); chunk->mHasAlarmState = interior->getDetailLevel(0)->hasAlarmState(); chunk->mDetailVertexCount.setSize(numDetails); U32 size = 0; for(i = 0; i < numDetails; i++) { Interior * detail = interior->getDetailLevel(i); U32 count = detail->getWindingCount(); chunk->mDetailVertexCount[i] = count; size += count; } /* chunk->mVertexColorsNormal.setSize(size); if(chunk->mHasAlarmState) chunk->mVertexColorsAlarm.setSize(size); U32 curPos = 0; for(i = 0; i < numDetails; i++) { Vector* normal = interior->getVertexColorsNormal(i); Vector* alarm = interior->getVertexColorsAlarm(i); AssertFatal(normal != NULL && alarm != NULL, "Error, no normal or alarm vertex colors!"); U32 count = chunk->mDetailVertexCount[i]; dMemcpy(&chunk->mVertexColorsNormal[curPos], normal->address(), count * sizeof(ColorI)); if(chunk->mHasAlarmState) dMemcpy(&chunk->mVertexColorsAlarm[curPos], alarm->address(), count * sizeof(ColorI)); curPos += count; } */ return(true); } SceneLighting::ObjectProxy* blInteriorSystem::createObjectProxy(SceneObject* obj, SceneLighting::ObjectProxyList* sceneObjects) { if ((obj->getTypeMask() & InteriorObjectType) != 0) { return new blInteriorProxy(obj); } else { return NULL; } } PersistInfo::PersistChunk* blInteriorSystem::createPersistChunk(const U32 chunkType) { if (chunkType == PersistInfo::PersistChunk::InteriorChunkType) { return new blInteriorChunk; } else { return NULL; } } bool blInteriorSystem::createPersistChunkFromProxy(SceneLighting::ObjectProxy* objproxy, PersistInfo::PersistChunk **ret) { if ((objproxy->mObj->getTypeMask() & InteriorObjectType) != 0) { *ret = new blInteriorChunk; return true; } else { return false; } } void blInteriorSystem::init() { } U32 blInteriorSystem::addObjectType() { return InteriorObjectType; } U32 blInteriorSystem::addToClippingMask() { return InteriorObjectType; } void blInteriorSystem::processLightingBegin() { // Store the vertex lighting state when we being lighting, we compare this when we finish lighting smUseVertexLighting = Interior::smUseVertexLighting; } void blInteriorSystem::processLightingCompleted(bool success) { if(success) { AssertFatal(smUseVertexLighting == Interior::smUseVertexLighting, "SceneLighting::completed: vertex lighting state changed during scene light"); // cannot do anything if vertex state has changed (since we only load in what is needed) if(smUseVertexLighting == Interior::smUseVertexLighting) { if(!smUseVertexLighting) { gInteriorLMManager.downloadGLTextures(); gInteriorLMManager.destroyBitmaps(); } else gInteriorLMManager.destroyTextures(); } } } // Given a ray, this will return the color from the lightmap of this object, return true if handled bool blInteriorSystem::getColorFromRayInfo(RayInfo collision, ColorF& result) { InteriorInstance* interior = dynamic_cast(collision.object); if (interior == NULL) return false; interior->getRenderWorldTransform().mulP(collision.point); Interior *detail = interior->getDetailLevel(0); AssertFatal((detail), "SceneObject::getLightingAmbientColor: invalid interior"); if(collision.face < detail->getSurfaceCount()) { const Interior::Surface &surface = detail->getSurface(collision.face); const Interior::TexGenPlanes &texgen = detail->getLMTexGenEQ(collision.face); GBitmap* lightmap = gInteriorLMManager.getHandle(detail->getLMHandle(), interior->getLMHandle(), detail->getNormalLMapIndex(collision.face)).getBitmap(); if (!lightmap) return false; Point2F uv; uv.x = mDot(texgen.planeX, collision.point) + texgen.planeX.d; uv.y = mDot(texgen.planeY, collision.point) + texgen.planeY.d; U32 size = (U32)(uv.x * F32(lightmap->getWidth())); size = mClamp(size, surface.mapOffsetX, (surface.mapOffsetX + surface.mapSizeX)); uv.x = F32(size) / F32(lightmap->getWidth()); size = (U32)(uv.y * F32(lightmap->getHeight())); size = mClamp(size, surface.mapOffsetY, (surface.mapOffsetY + surface.mapSizeY)); uv.y = F32(size) / F32(lightmap->getHeight()); result = lightmap->sampleTexel(uv.x, uv.y); return true; } return false; }