You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
393 lines
12 KiB
393 lines
12 KiB
/*==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 <http://www.gnu.org/licenses/>. |
|
|
|
Additional permissions under GNU GPL version 3 section 7 |
|
|
|
If you modify this Program, or any covered work, by linking or |
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, |
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent |
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK |
|
(or a modified version of those libraries), |
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, |
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG |
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the |
|
licensors of this Program grant you additional |
|
permission to convey the resulting work. Corresponding Source for a |
|
non-source form of such a combination shall include the source code for |
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered |
|
work. |
|
|
|
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 "HeadSpin.h" |
|
#include "plPipeline.h" |
|
#include "plTweak.h" |
|
#include "hsFastMath.h" |
|
|
|
#include "plPerspDirSlave.h" |
|
|
|
#include <float.h> |
|
#include <math.h> |
|
|
|
#ifdef _MSC_VER |
|
#define isnan _isnan |
|
#endif |
|
|
|
void plPerspDirSlave::Init() |
|
{ |
|
plShadowSlave::Init(); |
|
fFlags |= kCastInCameraSpace; |
|
} |
|
|
|
hsPoint3 plPerspDirSlave::IProject(const hsMatrix44& world2NDC, const hsPoint3& pos, float 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; |
|
|
|
float 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(float) kYon(100.f); |
|
plConst(float) 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(float) 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); |
|
|
|
float minZ, maxZ; |
|
float 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(float) kMin(1.f); |
|
plConst(float) kMax(30.f); |
|
minZ = kMin; |
|
maxZ = kMax; |
|
} |
|
|
|
plConst(float) 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(float) 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(float) 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); |
|
|
|
float tanX = 1.f / cotX; |
|
float tanY = 1.f / cotY; |
|
fView.SetScreenSize((uint16_t)fWidth, (uint16_t)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); |
|
float distSq = atToFrom.MagnitudeSquared(); |
|
atToFrom *= hsFastMath::InvSqrtAppr(distSq); |
|
|
|
const float 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; |
|
}
|
|
|