/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ #include "hsTypes.h" #include "plPipeline.h" #include "plTweak.h" #include "hsFastMath.h" #include "plPerspDirSlave.h" #include void plPerspDirSlave::Init() { plShadowSlave::Init(); fFlags |= kCastInCameraSpace; } hsPoint3 plPerspDirSlave::IProject(const hsMatrix44& world2NDC, const hsPoint3& pos, hsScalar w) const { hsPoint3 retVal; retVal.fX = world2NDC.fMap[0][0] * pos.fX + world2NDC.fMap[0][1] * pos.fY + world2NDC.fMap[0][2] * pos.fZ + world2NDC.fMap[0][3] * w; retVal.fY = world2NDC.fMap[1][0] * pos.fX + world2NDC.fMap[1][1] * pos.fY + world2NDC.fMap[1][2] * pos.fZ + world2NDC.fMap[1][3] * w; retVal.fZ = world2NDC.fMap[2][0] * pos.fX + world2NDC.fMap[2][1] * pos.fY + world2NDC.fMap[2][2] * pos.fZ + world2NDC.fMap[2][3] * w; hsScalar invW = 1.f / ( world2NDC.fMap[3][0] * pos.fX + world2NDC.fMap[3][1] * pos.fY + world2NDC.fMap[3][2] * pos.fZ + world2NDC.fMap[3][3] * w); retVal *= invW; return retVal; } hsBounds3Ext plPerspDirSlave::IGetPerspCasterBound(const hsMatrix44& world2NDC) const { hsPoint3 corners[8]; fCasterWorldBounds.GetCorners(corners); hsPoint3 perspCorners[8]; int i; for( i = 0; i < 8; i++ ) { perspCorners[i] = IProject(world2NDC, corners[i]); if( perspCorners[i].fX < -1.f ) perspCorners[i].fX = -1.f; else if( perspCorners[i].fX > 1.f ) perspCorners[i].fX = 1.f; if( perspCorners[i].fY < -1.f ) perspCorners[i].fY = -1.f; else if( perspCorners[i].fY > 1.f ) perspCorners[i].fY = 1.f; if( perspCorners[i].fZ < 0 ) perspCorners[i].Set(0.f, 0.f, 0.f); } hsBounds3Ext bnd; bnd.MakeEmpty(); for( i = 0; i < 8; i++ ) bnd.Union(&perspCorners[i]); return bnd; } bool plPerspDirSlave::SetupViewTransform(plPipeline* pipe) { plViewTransform pipeView = pipe->GetViewTransform(); plConst(hsScalar) kYon(100.f); plConst(hsScalar) kHither(30.f); pipeView.SetHither(kHither); pipeView.SetYon(kYon); hsMatrix44 cam2NDC = pipeView.GetCameraToNDC(); hsMatrix44 world2NDC = cam2NDC * pipeView.GetWorldToCamera(); fLightDir = fLightToWorld.GetAxis(hsMatrix44::kUp); hsVector3 worldLiDir = fLightDir; hsPoint3 pWorldLiDir(worldLiDir.fX, worldLiDir.fY, worldLiDir.fZ); hsPoint3 perspLiPos = IProject(world2NDC, pWorldLiDir, 0); hsBool reverseZ = fLightDir.InnerProduct(pipe->GetViewDirWorld()) > 0; SetFlag(kReverseZ, reverseZ); SetFlag(kReverseCull, reverseZ); hsPoint3 lookAt; plConst(hsBool) kUsePerspCenter(true); plConst(hsBool) kUseFrustCenter(true); if( kUsePerspCenter ) { hsPoint3 lookAtCam = pipeView.GetWorldToCamera() * fCasterWorldBounds.GetCenter(); hsPoint3 lookAtNDC = IProject(pipeView.GetCameraToNDC(), lookAtCam); lookAt = IProject(world2NDC, fCasterWorldBounds.GetCenter()); } else if( kUseFrustCenter ) { plConst(hsScalar) kDist(50.f); hsPoint3 camFrustCenter(0.f, 0.f, kDist); lookAt = IProject(cam2NDC, camFrustCenter); } else { hsBounds3Ext ndcBnd(IGetPerspCasterBound(world2NDC)); lookAt = ndcBnd.GetCenter(); } hsMatrix44 camNDC2Li; hsMatrix44 li2CamNDC; IComputeCamNDCToLight(perspLiPos, lookAt, camNDC2Li, li2CamNDC); hsScalar minZ, maxZ; hsScalar cotX, cotY; plConst(hsBool) kFixedPersp(true); if( !kFixedPersp ) { hsBounds3Ext bnd(IGetPerspCasterBound(camNDC2Li * world2NDC)); hsBounds3Ext bnd2(IGetPerspCasterBound(world2NDC)); bnd2.Transform(&camNDC2Li); plConst(hsBool) kUseBnd2(false); if( kUseBnd2 ) bnd = bnd2; minZ = bnd.GetMins().fZ; maxZ = bnd.GetMaxs().fZ; // THIS IS WRONG // EAP // This is my hack to get the Nexus age working. The real problem // is probably data-side. I take full responsibility for this // hack-around breaking the entire system, loosing data, causing // unauthorized credit card transactions, etc. if (_isnan(bnd.GetMins().fX) || _isnan(bnd.GetMins().fY)) return false; if (_isnan(bnd.GetMaxs().fX) || _isnan(bnd.GetMaxs().fY)) return false; // THIS IS EVEN MORE WRONG plConst(hsBool) kFakeDepth(false); if( kFakeDepth ) { plConst(hsScalar) kMin(1.f); plConst(hsScalar) kMax(30.f); minZ = kMin; maxZ = kMax; } plConst(hsScalar) kMinMinZ(1.f); if( minZ < kMinMinZ ) minZ = kMinMinZ; if( -bnd.GetMins().fX > bnd.GetMaxs().fX ) { hsAssert(bnd.GetMins().fX < 0, "Empty shadow caster bounds?"); cotX = -minZ / bnd.GetMins().fX; } else { hsAssert(bnd.GetMaxs().fX > 0, "Empty shadow caster bounds?"); cotX = minZ / bnd.GetMaxs().fX; } if( -bnd.GetMins().fY > bnd.GetMaxs().fY ) { hsAssert(bnd.GetMins().fY < 0, "Empty shadow caster bounds?"); cotY = -minZ / bnd.GetMins().fY; } else { hsAssert(bnd.GetMaxs().fY > 0, "Empty shadow caster bounds?"); cotY = minZ / bnd.GetMaxs().fY; } } else { plConst(hsScalar) kHi(1.f); hsBounds3Ext bnd; const hsPoint3 lo(-1.f, -1.f, 0.f); const hsPoint3 hi(1.f, 1.f, kHi); bnd.MakeEmpty(); bnd.Union(&lo); bnd.Union(&hi); bnd.Transform(&camNDC2Li); minZ = bnd.GetMins().fZ; maxZ = bnd.GetMaxs().fZ; // THIS IS WRONG // EAP // This is my hack to get the Nexus age working. The real problem // is probably data-side. I take full responsibility for this // hack-around breaking the entire system, loosing data, causing // unauthorized credit card transactions, etc. if (_isnan(bnd.GetMins().fX) || _isnan(bnd.GetMins().fY)) return false; if (_isnan(bnd.GetMaxs().fX) || _isnan(bnd.GetMaxs().fY)) return false; plConst(hsScalar) kMinMinZ(1.f); if( minZ < kMinMinZ ) minZ = kMinMinZ; if( -bnd.GetMins().fX > bnd.GetMaxs().fX ) { hsAssert(bnd.GetMins().fX < 0, "Empty shadow caster bounds?"); cotX = -minZ / bnd.GetMins().fX; } else { hsAssert(bnd.GetMaxs().fX > 0, "Empty shadow caster bounds?"); cotX = minZ / bnd.GetMaxs().fX; } if( -bnd.GetMins().fY > bnd.GetMaxs().fY ) { hsAssert(bnd.GetMins().fY < 0, "Empty shadow caster bounds?"); cotY = -minZ / bnd.GetMins().fY; } else { hsAssert(bnd.GetMaxs().fY > 0, "Empty shadow caster bounds?"); cotY = minZ / bnd.GetMaxs().fY; } } hsMatrix44 proj; proj.Reset(); proj.NotIdentity(); // LightToTexture // First the LightToTexture, which uses the above pretty much as is. // Note the remapping to range [0.5..width-0.5] etc. Also, the perspective // divide is by the 3rd output (not the fourth), so we make the 3rd // output be W (instead of Z). // This also means that our translate goes into [i][2] instead of [i][3]. proj.fMap[0][0] = cotX * 0.5f; proj.fMap[0][2] = 0.5f * (1.f + 0.5f/fWidth); proj.fMap[1][1] = -cotY * 0.5f; proj.fMap[1][2] = 0.5f * (1.f + 0.5f/fHeight); #if 1 // This computes correct Z, but we really just want W in 3rd component. HACKFISH proj.fMap[2][2] = maxZ / (maxZ - minZ); proj.fMap[2][3] = -minZ * maxZ / (maxZ - minZ); #elif 1 proj.fMap[2][2] = 1.f; proj.fMap[2][3] = 0; #endif proj.fMap[3][2] = 1.f; proj.fMap[3][3] = 0; // fWorldToTexture = proj * camNDC2Li * pipeView.GetWorldToNDC(); fWorldToTexture = proj * camNDC2Li * world2NDC; fWorldToTexture.fMap[2][0] = fWorldToTexture.fMap[3][0]; fWorldToTexture.fMap[2][1] = fWorldToTexture.fMap[3][1]; fWorldToTexture.fMap[2][2] = fWorldToTexture.fMap[3][2]; fWorldToTexture.fMap[2][3] = fWorldToTexture.fMap[3][3]; // Now the LightToNDC. This one's a little trickier, because we want to compensate for // having brought in the viewport to keep our border constant, so we can clamp the // projected texture and not have the edges smear off to infinity. cotX -= cotX / (fWidth * 0.5f); cotY -= cotY / (fHeight * 0.5f); hsScalar tanX = 1.f / cotX; hsScalar tanY = 1.f / cotY; fView.SetScreenSize((UInt16)fWidth, (UInt16)fHeight); fView.SetCameraTransform(pipe->GetViewTransform().GetWorldToCamera(), pipe->GetViewTransform().GetCameraToWorld()); fView.SetPerspective(true); fView.SetViewPort(0, 0, (float)fWidth, (float)fHeight, false); fView.SetView(hsPoint3(-tanX, -tanY, minZ), hsPoint3(tanX, tanY, maxZ)); hsMatrix44 cam2Light = camNDC2Li * pipeView.GetCameraToNDC(); fView.PostMultCameraToNDC(cam2Light); fLightPos = fLightToWorld.GetTranslate(); SetFlag(kPositional, false); return true; } static inline hsVector3 CrossProd(const hsVector3& a, const hsPoint3& b) { return hsVector3(a.fY*b.fZ - a.fZ*b.fY, a.fZ*b.fX - a.fX*b.fZ, a.fX*b.fY - a.fY*b.fX); } static inline void InverseOfPureRotTran(const hsMatrix44& src, hsMatrix44& inv) { inv = src; // We know this is a pure rotation and translation matrix, so // we won't have to do a full inverse. Okay kids, don't try this // at home. inv.fMap[0][1] = src.fMap[1][0]; inv.fMap[0][2] = src.fMap[2][0]; inv.fMap[1][0] = src.fMap[0][1]; inv.fMap[1][2] = src.fMap[2][1]; inv.fMap[2][0] = src.fMap[0][2]; inv.fMap[2][1] = src.fMap[1][2]; hsPoint3 newTran(-src.fMap[0][3], -src.fMap[1][3], -src.fMap[2][3]); inv.fMap[0][3] = newTran.InnerProduct((hsVector3*)&inv.fMap[0][0]); inv.fMap[1][3] = newTran.InnerProduct((hsVector3*)&inv.fMap[1][0]); inv.fMap[2][3] = newTran.InnerProduct((hsVector3*)&inv.fMap[2][0]); } void plPerspDirSlave::IComputeCamNDCToLight(const hsPoint3& from, const hsPoint3& at, hsMatrix44& camNDC2Li, hsMatrix44& li2CamNDC) { hsVector3 atToFrom(&from, &at); hsScalar distSq = atToFrom.MagnitudeSquared(); atToFrom *= hsFastMath::InvSqrtAppr(distSq); const hsScalar kMinMag = 0.5f; hsVector3 up(0,0,1.f); if( CrossProd(up, (at - from)).MagnitudeSquared() < kMinMag ) { up.Set(0, 1.f, 0); } hsMatrix44 w2light; w2light.MakeCamera(&from, &at, &up); hsMatrix44 light2w; InverseOfPureRotTran(w2light, light2w); #ifdef CHECK_INVERSE hsMatrix44 inv; w2light.GetInverse(&inv); #endif // CHECK_INVERSE camNDC2Li = w2light; li2CamNDC = light2w; }