/*==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 "plShadowSlave.h"
#include "plTweak.h"

#include <cfloat>
#include <cmath>

static const float kMinMinZ = 1.f; // totally random arbitrary number (has to be > 0).

bool plShadowSlave::ISetupOrthoViewTransform()
{
    hsBounds3Ext bnd = fCasterWorldBounds;

    bnd.Transform(&fWorldToLight);

    float minZ = bnd.GetMins().fZ;
    float maxZ = bnd.GetCenter().fZ + fAttenDist;

    float minX = bnd.GetMins().fX;
    float maxX = bnd.GetMaxs().fX;

    float minY = bnd.GetMins().fY;
    float maxY = bnd.GetMaxs().fY;


    hsMatrix44 proj;
    proj.Reset();
    proj.NotIdentity();

    // First the LightToTexture, which uses the above pretty much as is.
    // Note the remapping to range [0.5..width-0.5] etc. 
    // About this kAdjustBias. According to the docs, it should be 0.5,
    // and on the perspective projection 0.5 works great. But on the
    // directional (ortho) projection, it just makes the mapping wrong,
    // and an offset of zero works great. This could be a driver bug, or
    // hardware "dependency" (read IHV bug), but whatever, zero is working
    // now. Might need to adjust for new drivers or other hardware.
    const float kAdjustBias = 0.0f;
    proj.fMap[0][0] = 1.f / (maxX - minX);
    proj.fMap[0][3] = -minX / (maxX - minX) + kAdjustBias / fWidth;
    proj.fMap[1][1] = -1.f / (maxY - minY);
    proj.fMap[1][3] = -minY / (maxY - minY) + kAdjustBias / fHeight;
    proj.fMap[2][2] = 1.f;
    proj.fMap[3][3] = 1.f;

    fWorldToTexture = proj * fWorldToLight;

    // 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.
    // Like the adjust bias above, this part is correct in theory, but only
    // screws things up (increases Z-acne).
#if 0
    float delX = maxX - minX;
    minX += delX / (fWidth * 0.5f);
    maxX -= delX / (fWidth * 0.5f);
    float delY = maxY - minY;
    minY += delY / (fHeight * 0.5f);
    maxY -= delY / (fHeight * 0.5f);
#endif 


    fView.SetView(hsPoint3(minX, minY, minZ), hsPoint3(maxX, maxY, maxZ));
    fView.SetScreenSize((uint16_t)fWidth, (uint16_t)fHeight);
    fView.SetCameraTransform(fWorldToLight, fLightToWorld);
    fView.SetPerspective(false);
    fView.SetViewPort(0, 0, float(fWidth), float(fHeight), false);

    fLightDir = fLightToWorld.GetAxis(hsMatrix44::kUp);
    SetFlag(kPositional, false);
    SetFlag(kReverseCull, true);
    
    return true;
}

bool plShadowSlave::ISetupPerspViewTransform()
{
    hsBounds3Ext bnd = fCasterWorldBounds;

    bnd.Transform(&fWorldToLight);

    float minZ = bnd.GetMins().fZ;
    float maxZ = bnd.GetCenter().fZ + fAttenDist;

    if( minZ < kMinMinZ )
        minZ = kMinMinZ;

    // 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 (std::isnan(bnd.GetMins().fX) || std::isnan(bnd.GetMins().fY))
        return false;
    if (std::isnan(bnd.GetMaxs().fX) || std::isnan(bnd.GetMaxs().fY))
        return false;

    float cotX, cotY;
    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();

    // 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].
#if 0
    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);
#else
    plConst(float) kBiasScale(1.f);
    plConst(float) kBiasTrans(1.f);
    proj.fMap[0][0] = cotX * 0.5f * ( float(fWidth-2.f) / float(fWidth) ) * kBiasScale;
    proj.fMap[0][2] = 0.5f * (1.f - kBiasTrans * 0.5f/fWidth);
    proj.fMap[1][1] = -cotY * 0.5f * ( float(fHeight-2.f) / float(fHeight) ) * kBiasScale;
    proj.fMap[1][2] = 0.5f * (1.f - kBiasTrans * 0.5f/fHeight);
#endif

#if 0 // 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 * fWorldToLight;

    // 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.SetView(hsPoint3(-tanX, -tanY, minZ), hsPoint3(tanX, tanY, maxZ));
    fView.SetScreenSize((uint16_t)fWidth, (uint16_t)fHeight);
    fView.SetCameraTransform(fWorldToLight, fLightToWorld);
    fView.SetPerspective(true);
    fView.SetViewPort(0, 0, float(fWidth), float(fHeight), false);

    fLightPos = fLightToWorld.GetTranslate();
    SetFlag(kPositional, true);

    return true;
}
