//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- #include "game/shadow.h" #include "sim/sceneObject.h" #include "dgl/dgl.h" #include "core/fileStream.h" #include "core/bitRender.h" #include "platform/platform.h" #include "interior/interiorInstance.h" #include "lightingSystem/sgLighting.h" #include "lightingSystem/sgObjectShadows.h" DepthSortList Shadow::smDepthSortList; TextureHandle* Shadow::smGenericShadowTexture = NULL; S32 Shadow::smGenericShadowDim = 32; S32 Shadow::smInstanceCount = 0; U32 Shadow::smShadowMask = TerrainObjectType | InteriorObjectType; F32 Shadow::smShapeDetailScale = 1.0f; S32 Shadow::smShapeDetailMin = 2; // never select anything less than this detail unless other value supplied F32 Shadow::smSmallestVisibleSize = 6.0f; // stop drawing shadows when less than 6 pixels in size F32 Shadow::smLightDirSkew = 0.0f; F32 Shadow::smLightLenSkew = 0.0f; F32 Shadow::smGenericRadiusSkew = 0.4f; // shrink radius of shape when it always uses generic shadow... bool Shadow::smAlwaysUseGenericBmp = false; F32 Shadow::smGlobalShadowDetail = 1.0f; Vector gShadowBits(__FILE__, __LINE__); Box3F gShadowBox; SphereF gShadowSphere; Point3F gShadowPoly[4]; Shadow::DistanceDetail Shadow::smDefaultDistanceDetails[] = { { 0.0f, 0.0f, 0.0f }, { 100.0f, 0.0f, 0.0f }, { 500.0f, 1.0f, 0.5f }, { 1000.0f, 1.0f, 0.7f } }; Shadow::PixelSizeDetail sgShadowDetailHighest[] = { { 130.0f, 5, 256, 1, false }, { 25.0f, 10, 128, 1, false }, { 5.0f, 20, 64, 1, false }, { 0.0f, 0, 0, 0, true } }; Shadow::PixelSizeDetail sgShadowDetailHigh[] = { { 130.0f, 10, 256, 1, false }, { 25.0f, 20, 128, 1, false }, { 7.0f, 50, 64, 0, false }, { 0.0f, 0, 0, 0, true } }; Shadow::PixelSizeDetail sgShadowDetailMedium[] = { { 130.0f, 10, 128, 1, false }, { 25.0f, 20, 128, 1, false }, { 7.0f, 50, 64, 0, false }, { 0.0f, 0, 0, 0, true } }; Shadow::PixelSizeDetail sgShadowDetailLow[] = { { 130.0f, 20, 64, 1, false }, { 25.0f, 40, 64, 1, false }, { 7.0f, 100, 32, 1, false }, { 0.0f, 0, 0, 0, true } }; Shadow::PixelSizeDetail sgShadowDetailLowest[] = { { 130.0f, 25, 64, 1, false }, { 25.0f, 50, 64, 1, false }, { 10.0f, 100, 32, 1, false }, { 0.0f, 0, 0, 0, true } }; Shadow::PixelSizeDetail sgShadowDetailDamnNearOff[] = { { 130.0f, 25, 64, 1, false }, { 25.0f, 75, 32, 1, false }, { 10.0f, 0, 0, 0, true }, { 0.0f, 0, 0, 0, true } }; Shadow::PixelSizeDetail sgShadowDetailGeneric[] = { { 130.0f, 0, 0, 0, true }, { 25.0f, 0, 0, 0, true }, { 10.0f, 0, 0, 0, true }, { 0.0f, 0, 0, 0, true } }; Shadow::PixelSizeDetail *sgShadowDetailList[] = {sgShadowDetailHighest, sgShadowDetailHigh, sgShadowDetailMedium, sgShadowDetailLow, sgShadowDetailLowest, sgShadowDetailDamnNearOff, sgShadowDetailGeneric}; S32 sgShadowDetailListCount = sizeof(sgShadowDetailList) / sizeof(Shadow::PixelSizeDetail *); void Shadow::setGlobalShadowDetailLevel(F32 d) { smGlobalShadowDetail = d; smShapeDetailScale = 0.5f + d*0.5f; smShapeDetailMin = d>0.49f ? 2 : 3; smSmallestVisibleSize = d>0.7f ? 6 : (d>0.4f ? 7 : (d>0.3f ? 8 : (d>0.2f ? 9 : 10))); smLightDirSkew = 0.85f * (1.0f-d); smLightLenSkew = (1.0f-d) * 0.5f; } //-------------------------------------------------------------- Shadow::Shadow() { mBitmap = NULL; mRadius = 0.0f; mSettings.alwaysUseGenericBmp = false; mSettings.noAnimate = false; mSettings.noMove = false; setDefaultDetailTables(); if (smInstanceCount == 0) { GBitmap* bitmap = generateGenericShadowBitmap(smGenericShadowDim); smGenericShadowTexture = new TextureHandle(NULL,bitmap); // bitmap now owned by texture manager, so we don't delete it } smInstanceCount++; sgLastRenderTime = 0; sgPreviousShadowTime = 0; sgPreviousShadowLightingVector = VectorF(0, 0, -1); } Shadow::~Shadow() { AssertFatal(smInstanceCount > 0, "Error, more destructors than constructors?"); smInstanceCount--; if (smInstanceCount == 0) { delete smGenericShadowTexture; smGenericShadowTexture = NULL; } } GBitmap * Shadow::generateGenericShadowBitmap(S32 dim) { GBitmap * bitmap = new GBitmap(dim,dim,false,GBitmap::Luminance); U8 * bits = bitmap->getWritableBits(); S32 center = dim >> 1; F32 invRadiusSq = 1.0f / (F32)(center*center); F32 tmpF; for (S32 i=0; i0.99f ? 0 : 180 - 180.0f*tmpF); // 180 out of 255 max } return bitmap; } //-------------------------------------------------------------- void Shadow::setDetailTables(const Shadow::DistanceDetail * dd, S32 ddCount, const Shadow::PixelSizeDetail * psd, S32 psdCount) { sgLastShadowDetailSize = mClamp(LightManager::sgGetDynamicShadowQuality(), S32(0), (sgShadowDetailListCount - 1)); LightManager::sgSetDynamicShadowQuality(sgLastShadowDetailSize); mDistanceDetails = dd; mPixelSizeDetails = sgShadowDetailList[sgLastShadowDetailSize]; mNumDistanceDetails = ddCount; mNumPixelSizeDetails = psdCount; } void Shadow::setDefaultDetailTables() { setDetailTables(smDefaultDistanceDetails, sizeof(smDefaultDistanceDetails)/sizeof(DistanceDetail), sgShadowDetailHighest, sizeof(sgShadowDetailHighest)/sizeof(PixelSizeDetail)); } void Shadow::findDistanceDetail(F32 dist, DistanceDetail * dd) { for (S32 i=0; idirectionSkew = interp * mDistanceDetails[i-1].directionSkew + (1.0f-interp) * mDistanceDetails[i].directionSkew; dd->lengthSkew = interp * mDistanceDetails[i-1].lengthSkew + (1.0f-interp) * mDistanceDetails[i].lengthSkew; } return; } } *dd = mDistanceDetails[mNumDistanceDetails-1]; } void Shadow::findPixelSizeDetail(F32 pixelSize, const PixelSizeDetail ** psd) { *psd = NULL; for (S32 i=0; i=mPixelSizeDetails[i].size) return; } } S32 Shadow::selectShapeDetail(TSShapeInstance * shapeInstance, F32 dist, F32 scale, S32 detailMin) { if (detailMin<0) detailMin = Shadow::smShapeDetailMin; S32 dl = shapeInstance->selectCurrentDetail( dglProjectRadius(dist,scale * shapeInstance->getShape()->radius * dglGetPixelScale() * TSShapeInstance::smDetailAdjust * Shadow::smShapeDetailScale)); if (dl>=0 && dl < detailMin) shapeInstance->setCurrentDetail(getMin(detailMin,shapeInstance->getShape()->mSmallestVisibleDL)); return shapeInstance->getCurrentDetail(); } //-------------------------------------------------------------- void Shadow::setLightMatrices(const Point3F & lightDir, const Point3F & pos) { AssertFatal(mDot(lightDir,lightDir)>0.0001f,"Shadow::setLightDir: light direction must be a non-zero vector."); // construct light matrix Point3F x,z; if (mFabs(lightDir.z)>0.001f) { // mCross(Point3F(1,0,0),lightDir,&z); z.x = 0.0f; z.y = lightDir.z; z.z = -lightDir.y; z.normalize(); mCross(lightDir,z,&x); } else { mCross(lightDir,Point3F(0,0,1),&x); x.normalize(); mCross(x,lightDir,&z); } mLightToWorld.identity(); mLightToWorld.setColumn(0,x); mLightToWorld.setColumn(1,lightDir); mLightToWorld.setColumn(2,z); mLightToWorld.setColumn(3,pos); mWorldToLight = mLightToWorld; mWorldToLight.inverse(); } void Shadow::setRadius(F32 radius) { mRadius = radius; } void Shadow::setRadius(TSShapeInstance * shapeInstance, const Point3F & scale) { const Box3F & bounds = shapeInstance->getShape()->bounds; F32 dx = 0.5f * (bounds.max.x-bounds.min.x) * scale.x; F32 dy = 0.5f * (bounds.max.y-bounds.min.y) * scale.y; F32 dz = 0.5f * (bounds.max.z-bounds.min.z) * scale.z; mRadius = mSqrt(dx*dx+dy*dy+dz*dz); } //-------------------------------------------------------------- void Shadow::beginRenderToBitmap() { AssertFatal(mBitmap,"Shadow::beginRenderToBitmap had no bitmap to render to!"); // clean slate... gShadowBits.setSize(mSettings.bmpDim * (mSettings.bmpDim>>5)); dMemset(gShadowBits.address(),0,mSettings.bmpDim*(mSettings.bmpDim>>3)); // dMemset deals in bytes not words, hence the shift by 3 not 5 } void Shadow::endRenderToBitmap() { // copy to bitmap if (mSettings.blur==1) // blur BitRender::bitTo8Bit_3(gShadowBits.address(),(U32*)mBitmap->getWritableBits(),mSettings.bmpDim); else // non-blur version: BitRender::bitTo8Bit(gShadowBits.address(),(U32*)mBitmap->getWritableBits(),mSettings.bmpDim); mShadowTexture.refresh(); } void Shadow::renderToBitmap(TSShapeInstance * shapeInstance, const MatrixF & transform, const Point3F & pos, Point3F scale) { AssertFatal(mBitmap,"Shadow::renderToShadow: must call beginRenderToBitmap first"); MatrixF mat; mat.mul(mWorldToLight,transform); // everything gets scaled by this amount so that we map onto the bitmap correctly F32 k = ((F32)mSettings.bmpDim)/(2.0f*mRadius); // this scales everything but the last row of the matrix (we do that below) scale *= k; mat.scale(scale); // we want pos to map to bitmap center... // the following is a bit convoluted...but it is correct... Point3F p,p2; mat.getColumn(3,&p); mWorldToLight.mulP(pos,&p2); p -= p2; p *= k; F32 halfDim = mSettings.bmpDim>>1; p.x += halfDim; p.z += halfDim; mat.setColumn(3,p); // shape center now falls on bitmap center... shapeInstance->animate(); shapeInstance->renderShadow(shapeInstance->getCurrentDetail(),mat,mSettings.bmpDim,gShadowBits.address()); } //-------------------------------------------------------------- bool Shadow::prepare(const Point3F & pos, Point3F lightDir, F32 shadowLen, const Point3F & scale, F32 dist, F32 fogAmount, TSShapeInstance * shapeInstance) { // 0. use pixel size to do early reject test // 1. use distance from camera to do 1st pass detail selection (light direction, shadow volume distance) // 2. get polys to project shadow onto, build shadow partition // 3. use pixel size to do 2nd pass detail selection (shape detail level, bitmap dimension, blur routine, // sample frequency, substitute generic shadow bitmap) // NOTES: // If using cached partition, do 1&2 only if we don't have a partition yet... // If non-animating shape, do step 3 and re-compute bitmap only if detail info changes... detectShadowDetailSizeChange(); // -------------------------------------- // 0. F32 maxScale = getMax(scale.x,getMax(scale.y,scale.z)); F32 pixelSize = dglProjectRadius(dist/maxScale,shapeInstance->getShape()->radius) * dglGetPixelScale() * TSShapeInstance::smDetailAdjust; F32 smallest = getMax(Shadow::smSmallestVisibleSize,shapeInstance->getShape()->mSmallestVisibleSize); if (pixelSize * Shadow::smShapeDetailScale < smallest) return false; // fade over distance from viewer F32 pseudoFog = smallest/(pixelSize * Shadow::smShapeDetailScale); if (pseudoFog>0.5f) pseudoFog = 2.0f * (pseudoFog-0.5f); else pseudoFog = 0.0f; if (fogAmount=0.99f) // shadow faded out return false; fogAmount = pseudoFog; } // find detail information DistanceDetail dd; findDistanceDetail(mSettings.noMove ? 0.0f : dist,&dd); const PixelSizeDetail * psd; findPixelSizeDetail(pixelSize,&psd); if (!mSettings.noMove || mPartition.empty()) { // -------------------------------------- // 1. F32 dirMult = (1.0f - dd.directionSkew) * (1.0f - smLightDirSkew); if (dirMult < 0.99f) { lightDir.z *= dirMult; lightDir.z -= 1.0f - dirMult; } lightDir.normalize(); shadowLen *= (1.0f - dd.lengthSkew) * (1.0f - smLightLenSkew); // -------------------------------------- // 2. get polys F32 radius = mRadius; if (psd->genericShadowBmp || mSettings.alwaysUseGenericBmp || smAlwaysUseGenericBmp) radius *= smGenericRadiusSkew; buildPartition(pos,lightDir,radius,shadowLen); } updatePartition(fogAmount); if (mPartition.empty()) // no need to draw shadow if nothing to cast it onto return false; // -------------------------------------- // 3. // do we need a new bitmap? anim rate, bmp dim, generic vs generated mSettings.needBmp = false; if (mSettings.alwaysUseGenericBmp || smAlwaysUseGenericBmp || psd->genericShadowBmp) // use generic bitmap -- get rid of old bmp if it's there mBitmap = NULL; else { U32 time = Platform::getVirtualMilliseconds(); bool expired = time-mSettings.lastBmpTime > psd->frameExpiration; bool propertyChange = !mBitmap || psd->bmpDim!=mSettings.bmpDim || psd->blur!=mSettings.blur; if ( (expired && !mSettings.noAnimate) || propertyChange) { // need to generate a new bmp mSettings.blur = psd->blur; mSettings.lastBmpTime = Platform::getVirtualMilliseconds(); mSettings.needBmp = true; if (mSettings.bmpDim!=psd->bmpDim || !mBitmap) { // allocate new bitmap...register texture (no need to delete old one, owned by texture handle) mSettings.bmpDim = psd->bmpDim; mBitmap = new GBitmap(mSettings.bmpDim,mSettings.bmpDim,false,GBitmap::Luminance); mShadowTexture.set(NULL,mBitmap); } } } return true; } //-------------------------------------------------------------- void Shadow::buildPartition(const Point3F & p, const Point3F & lightDir, F32 radius, F32 shadowLen) { setLightMatrices(lightDir,p); Point3F extent(2.0f*radius,shadowLen,2.0f*radius); smDepthSortList.clear(); smDepthSortList.set(mWorldToLight,extent); smDepthSortList.setInterestNormal(lightDir); if (shadowLen<1.0f) // no point in even this short of a shadow... shadowLen = 1.0f; mInvShadowDistance = 1.0f / shadowLen; // build world space box and sphere around shadow Point3F x,y,z; mLightToWorld.getColumn(0,&x); mLightToWorld.getColumn(1,&y); mLightToWorld.getColumn(2,&z); x *= radius; y *= shadowLen; z *= radius; gShadowBox.max.set(mFabs(x.x)+mFabs(y.x)+mFabs(z.x), mFabs(x.y)+mFabs(y.y)+mFabs(z.y), mFabs(x.z)+mFabs(y.z)+mFabs(z.z)); y *= 0.5f; gShadowSphere.radius = gShadowBox.max.len(); gShadowSphere.center = p + y; gShadowBox.min = y + p - gShadowBox.max; gShadowBox.max += y + p; // get polys gClientContainer.findObjects(smShadowMask,Shadow::collisionCallback,this); // setup partition list gShadowPoly[0].set(-radius,0,-radius); gShadowPoly[1].set(-radius,0, radius); gShadowPoly[2].set( radius,0, radius); gShadowPoly[3].set( radius,0,-radius); mPartition.clear(); mPartitionVerts.clear(); smDepthSortList.depthPartition(gShadowPoly,4,mPartition,mPartitionVerts); // now set up tverts & colors mPartitionColors.setSize(mPartitionVerts.size()); mPartitionTVerts.setSize(mPartitionVerts.size()); F32 invRadius = 1.0f / radius; for (S32 i=0; i(thisPtr); if (obj->getWorldBox().isOverlapped(gShadowBox)) { // only interiors clip... ClippedPolyList::allowClipping = (dynamic_cast(obj) != NULL); obj->buildPolyList(&smDepthSortList,gShadowBox,gShadowSphere); ClippedPolyList::allowClipping = true; } } //-------------------------------------------------------------- void Shadow::render() { bool wasLit = glIsEnabled(GL_LIGHTING); glDisable(GL_LIGHTING); // push light matrix glPushMatrix(); dglMultMatrix(&mLightToWorld); // set up texture environment glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glDepthMask(GL_FALSE); glBlendFunc(GL_ZERO,GL_ONE_MINUS_SRC_COLOR); glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE); if (mBitmap) glBindTexture(GL_TEXTURE_2D, mShadowTexture.getGLName()); else { AssertFatal(smGenericShadowTexture != NULL, "Error, shadow texture not initialized!"); glBindTexture(GL_TEXTURE_2D, smGenericShadowTexture->getGLName()); } // set up arrays if( TSMesh::getOverrideFade() >= 1.0f ) { glEnableClientState(GL_COLOR_ARRAY); glColorPointer(4,GL_FLOAT,0,mPartitionColors.address()); } else { F32 color = TSMesh::getOverrideFade(); glColor4f( color, color, color, color ); } glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glVertexPointer(3,GL_FLOAT,0,mPartitionVerts.address()); glTexCoordPointer(2,GL_FLOAT,0,mPartitionTVerts.address()); bool lockArrays = dglDoesSupportCompiledVertexArray(); if (lockArrays) glLockArraysEXT(0,mPartitionVerts.size()); // fight z-fighting glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(-2,-2); // draw for (S32 i=0; ivertexCount; j++) { Point3F p = smDepthSortList.mVertexList[smDepthSortList.mIndexList[poly->vertexStart+j]].point; printDump(avar("(%5.3f, %5.3f, %5.3f) ",p.x,p.y,p.z)); } printDump("\r\n"); } smDepthSortList.sort(); printDump("\r\n\r\nPost-sort: \r\n\r\n"); for (i=0; ivertexCount; j++) { Point3F p = smDepthSortList.mVertexList[smDepthSortList.mIndexList[poly->vertexStart+j]].point; printDump(avar("(%5.3f, %5.3f, %5.3f) ",p.x,p.y,p.z)); } printDump("\r\n"); } file.close(); } #endif