// Geometric Tools, LLC
// Copyright (c) 1998-2014
// Distributed under the Boost Software License, Version 1.0.
// http://www.boost.org/LICENSE_1_0.txt
// http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
//
// File Version: 5.0.1 (2013/07/14)

#include "ReflectionsAndShadows.h"

WM5_WINDOW_APPLICATION(ReflectionsAndShadows);

//----------------------------------------------------------------------------
ReflectionsAndShadows::ReflectionsAndShadows ()
    :
    WindowApplication3("SampleGraphics/ReflectionsAndShadows", 0, 0, 640, 480,
        Float4(0.5f, 0.0f, 1.0f, 1.0f)),
        mTextColor(0.0f, 0.0f, 0.0f, 1.0f)
{
    mUpdateTime = 0.0;
}
//----------------------------------------------------------------------------
bool ReflectionsAndShadows::OnInitialize ()
{
    if (!WindowApplication3::OnInitialize())
    {
        return false;
    }

    // Set up the camera.
    mCamera->SetFrustum(60.0f, GetAspectRatio(), 1.0f, 1000.0f);
    APoint camPosition(180.0f, 0.0f, 23.0f);
    AVector camDVector(-1.0f, 0.0f, 0.0f);
    AVector camUVector(0.0f, 0.0f, 1.0f);
    AVector camRVector = camDVector.Cross(camUVector);
    mCamera->SetFrame(camPosition, camDVector, camUVector, camRVector);

    CreateScene ();

    // Initial update of objects.
    mScene->Update();
    mBiped->Update(mUpdateTime);
    CopyNormalToTCoord1(mBiped);

    // Initial culling of scene,
    mSceneCuller.SetCamera(mCamera);
    mSceneCuller.ComputeVisibleSet(mScene);
    mBipedCuller.SetCamera(mCamera);
    mBipedCuller.ComputeVisibleSet(mBiped);

    InitializeCameraMotion(0.1f, 0.01f);
    InitializeObjectMotion(mScene);
    return true;
}
//----------------------------------------------------------------------------
void ReflectionsAndShadows::OnTerminate ()
{
    mScene = 0;
    mBiped = 0;
    mPlane0 = 0;
    mPlane1 = 0;
    mWireState = 0;
    mPSEffect = 0;
    mPREffect = 0;

    WindowApplication3::OnTerminate();
}
//----------------------------------------------------------------------------
void ReflectionsAndShadows::OnIdle ()
{
    MeasureTime();

    if (MoveCamera())
    {
        mSceneCuller.ComputeVisibleSet(mScene);
        mBipedCuller.ComputeVisibleSet(mBiped);
    }

    if (MoveObject())
    {
        mScene->Update();
        mBiped->Update(mUpdateTime);
        CopyNormalToTCoord1(mBiped);
        mSceneCuller.ComputeVisibleSet(mScene);
        mBipedCuller.ComputeVisibleSet(mBiped);
    }

    if (mRenderer->PreDraw())
    {
        mRenderer->ClearBuffers();
        mRenderer->Draw(mSceneCuller.GetVisibleSet());
        mRenderer->Draw(mBipedCuller.GetVisibleSet(), mPSEffect);
        mRenderer->Draw(mBipedCuller.GetVisibleSet(), mPREffect);
        DrawFrameRate(8, GetHeight()-8, mTextColor);
        mRenderer->PostDraw();
        mRenderer->DisplayColorBuffer();
    }

    UpdateFrameCount();
}
//----------------------------------------------------------------------------
bool ReflectionsAndShadows::OnKeyDown (unsigned char key, int x, int y)
{
    if (WindowApplication3::OnKeyDown(key, x, y))
    {
        return true;
    }

    switch (key)
    {
    case 'w':
    case 'W':
        mWireState->Enabled = !mWireState->Enabled;
        return true;

    case 'g':
        mUpdateTime += 0.01;
        mBiped->Update(mUpdateTime);
        CopyNormalToTCoord1(mBiped);
        return true;
    case 'G':
        mUpdateTime = 0.0;
        mBiped->Update(mUpdateTime);
        CopyNormalToTCoord1(mBiped);
        return true;
    }

    return false;
}
//----------------------------------------------------------------------------
void ReflectionsAndShadows::CreateScene ()
{
    mScene = new0 Node();
    mWireState = new0 WireState();
    mRenderer->SetOverrideWireState(mWireState);

    LoadBiped();
    CreatePlanes();
    CreatePlanarShadow();
    CreatePlanarReflection();
    mScene->LocalTransform.SetRotate(HMatrix(AVector::UNIT_Z, 0.75f));
}
//----------------------------------------------------------------------------
void ReflectionsAndShadows::LoadBiped ()
{
#ifdef WM5_LITTLE_ENDIAN
    std::string path = Environment::GetPathR("SkinnedBipedPNTC1.wmof");
#else
    std::string path = Environment::GetPathR("SkinnedBipedPN.be.wmof");
#endif
    InStream source;
    source.Load(path);
    mBiped = (Node*)source.GetObjectAt(0);

    // This guarantees that the reflection is drawn, even when the biped is
    // not visible to the camera.
    mBiped->Culling = Spatial::CULL_NEVER;
}
//----------------------------------------------------------------------------
void ReflectionsAndShadows::CreatePlanes ()
{
    VertexFormat* vformat = VertexFormat::Create(2,
        VertexFormat::AU_POSITION, VertexFormat::AT_FLOAT3, 0,
        VertexFormat::AU_TEXCOORD, VertexFormat::AT_FLOAT2, 0);
    int vstride = vformat->GetStride();

    // Create the floor mesh.
    VertexBuffer* vbuffer = new0 VertexBuffer(4, vstride);
    VertexBufferAccessor floor(vformat, vbuffer);

    float xValue = 128.0f;
    float yValue = 256.0f;
    float zValue = 0.0f;
    floor.Position<Float3>(0) = Float3(-xValue, -yValue, zValue);
    floor.Position<Float3>(1) = Float3(+xValue, -yValue, zValue);
    floor.Position<Float3>(2) = Float3(+xValue, +yValue, zValue);
    floor.Position<Float3>(3) = Float3(-xValue, +yValue, zValue);
    floor.TCoord<Float2>(0, 0) = Float2(0.0f, 0.0f);
    floor.TCoord<Float2>(0, 1) = Float2(1.0f, 0.0f);
    floor.TCoord<Float2>(0, 2) = Float2(1.0f, 1.0f);
    floor.TCoord<Float2>(0, 3) = Float2(0.0f, 1.0f);

    IndexBuffer* ibuffer = new0 IndexBuffer(6, sizeof(int));
    int* indices = (int*)ibuffer->GetData();
    indices[0] = 0;  indices[1] = 1;  indices[2] = 2;
    indices[3] = 0;  indices[4] = 2;  indices[5] = 3;

    mPlane0 = new0 TriMesh(vformat, vbuffer, ibuffer);

    Texture2DEffect* effect = new0 Texture2DEffect(Shader::SF_LINEAR_LINEAR,
        Shader::SC_REPEAT, Shader::SC_REPEAT);
    std::string path = Environment::GetPathR("Sand.wmtf");
    Texture2D* texture = Texture2D::LoadWMTF(path);
    mPlane0->SetEffectInstance(effect->CreateInstance(texture));

    mScene->AttachChild(mPlane0);

    // Create the wall mesh.
    vbuffer = new0 VertexBuffer(4, vstride);
    VertexBufferAccessor wall(vformat, vbuffer);

    xValue = -128.0f;
    yValue = 256.0f;
    zValue = 128.0f;
    wall.Position<Float3>(0) = Float3(xValue, -yValue, 0.0f);
    wall.Position<Float3>(1) = Float3(xValue, +yValue, 0.0f);
    wall.Position<Float3>(2) = Float3(xValue, +yValue, zValue);
    wall.Position<Float3>(3) = Float3(xValue, -yValue, zValue);
    wall.TCoord<Float2>(0, 0) = Float2(0.0f, 0.0f);
    wall.TCoord<Float2>(0, 1) = Float2(1.0f, 0.0f);
    wall.TCoord<Float2>(0, 2) = Float2(1.0f, 1.0f);
    wall.TCoord<Float2>(0, 3) = Float2(0.0f, 1.0f);

    mPlane1 = new0 TriMesh(vformat, vbuffer, ibuffer);

    path = Environment::GetPathR("Stone.wmtf");
    texture = Texture2D::LoadWMTF(path);
    mPlane1->SetEffectInstance(effect->CreateInstance(texture));

    mScene->AttachChild(mPlane1);
}
//----------------------------------------------------------------------------
void ReflectionsAndShadows::CreatePlanarShadow ()
{
    Light* projector = new0 Light(Light::LT_POINT);
    projector->Position = APoint(30.0f, -50.0f, 80.0f);
    LightNode* projectorNode = new0 LightNode(projector);
    mScene->AttachChild(projectorNode);

    mPSEffect = new0 PlanarShadowEffect(1, mBiped);
    Float4 shadowColor(0.0f, 0.0f, 0.0f, 0.5f);
    mPSEffect->SetPlane(0, mPlane0);
    mPSEffect->SetProjector(0, projector);
    mPSEffect->SetShadowColor(0, shadowColor);
}
//----------------------------------------------------------------------------
void ReflectionsAndShadows::CreatePlanarReflection ()
{
    Light* projector = new0 Light(Light::LT_DIRECTIONAL);
    projector->DVector = -AVector::UNIT_X;
    LightNode* projectorNode = new0 LightNode(projector);
    mScene->AttachChild(projectorNode);

    mPREffect = new0 PlanarReflectionEffect(1);
    mPREffect->SetPlane(0, mPlane1);
    mPREffect->SetReflectance(0, 0.25f);
}
//----------------------------------------------------------------------------
void ReflectionsAndShadows::CopyNormalToTCoord1 (Object* object)
{
    TriMesh* mesh = DynamicCast<TriMesh>(object);
    if (mesh)
    {
        VertexBufferAccessor vba(mesh);
        for (int i = 0; i < vba.GetNumVertices(); ++i)
        {
            vba.TCoord<Vector3f>(1, i) = vba.Normal<Vector3f>(i);
        }
        mRenderer->Update(mesh->GetVertexBuffer());
    }

    Node* node = DynamicCast<Node>(object);
    if (node)
    {
        for (int i = 0; i < node->GetNumChildren(); ++i)
        {
            Spatial* child = node->GetChild(i);
            if (child)
            {
                CopyNormalToTCoord1(child);
            }
        }
    }
}
//----------------------------------------------------------------------------
