// Wild Magic Source Code
// David Eberly
// http://www.geometrictools.com
// Copyright (c) 1998-2007
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2.1 of the License, or (at
// your option) any later version.  The license is available for reading at
// either of the locations:
//     http://www.gnu.org/copyleft/lgpl.html
//     http://www.geometrictools.com/License/WildMagicLicense.pdf
//
// Version: 4.0.0 (2006/06/28)

#include "WSceneBuilder.h"
#include "decomp.h"

const float WSceneBuilder::ATTENUATION = 1e+03f;
const float WSceneBuilder::MIN_DIFFERENCE = 1e-03f;

//----------------------------------------------------------------------------
WSceneBuilder::WSceneBuilder (const TCHAR* acFilename, BOOL bExportSelected, 
    WExportSettings* pkSettings, ExpInterface* pkExport, Interface* pkMax, 
    INode* pkRootNode)
{
    // acFilename:
    //     String containing the filename to which the scene graph should be
    //     saved.
    // bExportSelected:
    //     True if the user chose to export a selected portion of the scene
    //     hierarchy, false if the entire scene is to be exported.
    // pkSettings:
    //     ExportSettings pointer with details of the exporter options
    //     selected by the user.
    // pkExport:
    //     Interface pointer to MAX's exporter specific methods.
    // pkMax:
    //     Interface pointer to MAX's general functionality.
    // pkRootNode:
    //     Top level node in the hierarchy to be exported (not necessarily
    //     the root node of the MAX scene, if the user chose "Export
    //     Selected").

    const size_t uiSize = _tcslen(acFilename) + 1;
    m_acFilename = WM4_NEW TCHAR[uiSize];
#if _MSC_VER >= 1400
    _tcscpy_s(m_acFilename,uiSize,acFilename);
#else
    _tcscpy(m_acFilename,acFilename);
#endif
    m_bExportSelected = bExportSelected;
    m_pkSettings = pkSettings;
    m_pkExport = pkExport;
    m_pkMax = pkMax;
    m_pkRootNode = pkRootNode;  
    m_iTicksPerFrame = GetTicksPerFrame();

    if (m_pkSettings->IncludeCurrentFrame)
    {
        // User has chosen to export the current frame only.  Get the time at
        // which it occurs.
        m_iTimeStart = m_pkMax->GetAnimRange().Start();
        m_iTimeEnd = m_iTimeStart;
    }
    else
    {
        // Calculate the start time in ticks, of the first and last frames.
        m_iTimeStart = m_pkMax->GetAnimRange().Start();
        m_iTimeEnd = m_pkMax->GetAnimRange().End();
        if (m_pkSettings->UseLocalTime)
        {
            m_iTimeOffset = m_iTimeStart;
        }
        else
        {
            m_iTimeOffset = 0;
        }
    }

    // Traverse the Max scene hierarchy three times.  The first pass processes
    // the materials, the second pass builds the node hierarchy, and the third
    // pass applies the modifiers.

    // Construct the world root node of the Magic scene.
    m_spkScene = WM4_NEW Wm4::Node;

    // traverse the Max hierarchy, and process materials
    ConvertMaterials(m_pkRootNode);

    if (!m_bExportSelected)
    {
        // export the entire scene

        // process global ambient light settings
        if (m_pkSettings->IncludeLights)
        {
            BuildAmbientLight();
        }

        // build the Wild Magic node hierarchy
        for (int i = 0; i < m_pkRootNode->NumberOfChildren(); i++ )
        {
            Traverse(m_pkRootNode->GetChildNode(i),m_spkScene);
        }
    }
    else
    {
        // export selection only
        Traverse(m_pkRootNode,m_spkScene);
    }

    if (m_pkSettings->IncludeModifiers)
    {
        m_spkScene->UpdateGS((double)(TicksToSec(0.0f)));
        ApplyModifiers();
    }

    // save the Wild Magic scene to disk
    Wm4::Stream kStream;

    if (m_bExportSelected)
    {
        kStream.Insert(m_spkScene->GetChild(0));
    }
    else
    {
        kStream.Insert(m_spkScene);
    }

    kStream.Save(m_acFilename); 
}
//----------------------------------------------------------------------------
WSceneBuilder::~WSceneBuilder ()
{
    WM4_DELETE[] m_acFilename;
    m_spkScene = 0;
    std::vector<ModifierInfo*>::iterator pkIter;
    for (pkIter = m_kModList.begin(); pkIter < m_kModList.end(); pkIter++)
    {
        WM4_DELETE *pkIter;
    }
}
//----------------------------------------------------------------------------
bool WSceneBuilder::Traverse (INode* pkMaxNode, Wm4::Spatial* pkWm4Node)
{
    // Recursively traverse the Max node hierarchy, constructing and 
    // attaching the corresponding objects to the Wild Magic scene graph.
    // pkMaxNode:
    //     The current node in the Max hierarchy.
    // pkWm4Node:
    //     The parent node of the next node to be attached to the Wild Magic
    //     hierarchy.
    // Returns true if recursion is to continue, false otherwise.

    Wm4::Spatial* pkWm4Child = 0;

    // Get the Max node to evaluate its world state at the time of the start
    // frame.
    ObjectState kObjectState = pkMaxNode->EvalWorldState(m_iTimeStart);
    bool bSupported = true;

    // Determine what kind of object we are dealing with, and process
    // accordingly.  If a node has an associated object (such as a mesh), and
    // the user has decided not to export objects of that type, we preserve
    // the spatial relationship in the hierarchy by linking in a node with
    // the same transform, but no associated object.  Note that this allows
    // us to export animation information only, independently of any
    // geometry, if desired.
    if (kObjectState.obj)
    {
        switch (kObjectState.obj->SuperClassID())
        {
        case GEOMOBJECT_CLASS_ID:
            pkWm4Child = BuildGeometry(pkMaxNode,pkWm4Node);
            break;
        case CAMERA_CLASS_ID:
            // TO DO.  Not yet supported.
            break;
        case LIGHT_CLASS_ID:
            if (m_pkSettings->IncludeLights && !pkMaxNode->IsHidden())
            {
                BuildLight(pkMaxNode,pkWm4Node);
            }
            break;
        case HELPER_CLASS_ID:
            // The "helper" nodes have no associated geometry (e.g. dummy, or
            // bone nodes). Create and link a node with the same transform in
            // the Wild Magic scene graph.
            pkWm4Child = BuildSpatial(pkMaxNode,pkWm4Node);
            break;
        default:
            assert(false);
            bSupported = false;
            return false;
        }
    }

    // attach animation information to the node
    if (pkWm4Child)
    {   
        if (m_pkSettings->IncludeKeyFrames)
        {
            BuildKeyFrameController(pkMaxNode,pkWm4Child);
        }
        else if (m_pkSettings->IncludeAllFrames)
        {
            BuildFrameController(pkMaxNode,pkWm4Child);
        }
    }

    // Accumulate the modifiers.  These are attached to the Wild Magic scene
    // in the WSceneBuilder constructor after the traversals are finished.
    if (m_pkSettings->IncludeModifiers)
    {
        ModifierInfo* pkModInfo = WM4_NEW ModifierInfo;
        FindModifiers(pkMaxNode,pkModInfo->Modifiers);
        if (pkModInfo->Modifiers.empty())
        {
            WM4_DELETE pkModInfo;
        }
        else
        {
            pkModInfo->Node = pkMaxNode;
            m_kModList.push_back(pkModInfo);
        }
    }

    int iNumChildren = pkMaxNode->NumberOfChildren();
    if (iNumChildren == 0) 
    {
        return true;
    }

    for (int i = 0; i < iNumChildren; i++)
    {
        if (!Traverse(pkMaxNode->GetChildNode(i),pkWm4Child) 
        ||  m_pkMax->GetCancel())
        {
            return false;
        }
    }

    return true;
}
//----------------------------------------------------------------------------
Wm4::Spatial* WSceneBuilder::BuildGeometry (INode* pkMaxNode,
    Wm4::Spatial* pkWm4Node)
{
    // Build and link a geometric object into the scene.
    // pkMaxNode:
    //     Pointer node with geometry in Max scene.
    // pkWm4Node:
    //     Parent to which to attach WM4_NEW node(s).

    Wm4::Spatial* pkWm4Child = 0;
    Wm4::Spatial* pkLink = 0;

    // In a Max scene, geometry nodes can have geometry children, whereas
    // with Wild Magic, all geometry nodes must be leaf nodes.  If this Max
    // node has geometry children, then a "link" node has to be created in
    // order to allow the geometry to be attached to the Wild Magic scene as
    // a leaf node.
    for (int i = 0; i < pkMaxNode->NumberOfChildren(); i++)
    {
        ObjectState kObjectState = pkMaxNode->EvalWorldState(m_iTimeStart); 
        if (kObjectState.obj->SuperClassID() == GEOMOBJECT_CLASS_ID)
        {
            pkLink = BuildSpatial(pkMaxNode,pkWm4Node);
            break;
        }
    }

    if (pkLink == 0)
    {
        pkLink = pkWm4Node;
    }

    if (m_pkSettings->IncludeMeshes && !pkMaxNode->IsHidden())
    {
        pkWm4Child = BuildMesh(pkMaxNode,pkLink);
    }
    else
    {
        pkWm4Child = BuildSpatial(pkMaxNode,pkLink);
    }

    if (pkLink != pkWm4Node)
    {
        return pkLink;
    }

    return pkWm4Child;
}
//----------------------------------------------------------------------------
Wm4::Spatial* WSceneBuilder::BuildSpatial (INode* pkMaxNode,
    Wm4::Spatial* pkWm4Node)
{
    Wm4::Node* pkWm4Child = WM4_NEW Wm4::Node;
    pkWm4Child->SetName(pkMaxNode->GetName());

    pkWm4Child->Local = GetLocalTransform(pkMaxNode,m_iTimeStart);

    assert(pkWm4Node->IsDerived(Wm4::Node::TYPE));
    ((Wm4::Node*)pkWm4Node)->AttachChild(pkWm4Child);
    return pkWm4Child;
}
//----------------------------------------------------------------------------
Wm4::Transformation WSceneBuilder::GetLocalTransform (INode* pkNode,
    TimeValue iTime)
{
    // Compute a node's local transform.  Max's node transform access methods
    // all supply the node's world space transform, so we have to perform
    // some manipulation to get the node's local transform.  Also, Max can
    // supply the node's pivot transform via GetNodeTM, or object transform
    // via GetObjectTM(), GetObjTMBeforeWSM(), and GetObjTMAfterWSM(). The
    // "object offset" refers to the transformation of the object associated
    // with the node, relative to the node's pivot, so the node's pivot
    // transform is not in itself sufficient to locate the object in local
    // coordinates.  Note that the object transform access methods return the
    // aggregate of the node's pivot and object offset transformations.

    // evaluate the node's object transform in local coordinates 
    Matrix3 kMaxLocal = pkNode->GetObjTMAfterWSM(iTime) * 
        Inverse(pkNode->GetParentNode()->GetObjTMAfterWSM(iTime));

    // decompose the transform into its affine parts
    AffineParts kAffParts;
    decomp_affine(kMaxLocal,&kAffParts);

    // Determine if the translation is the zero vector.
    bool bTranslationIsZero =
        fabsf(kAffParts.t.x) < MIN_DIFFERENCE &&
        fabsf(kAffParts.t.y) < MIN_DIFFERENCE &&
        fabsf(kAffParts.t.z) < MIN_DIFFERENCE;

    // Determine if the rotation is the identity.
    float fQSign = (kAffParts.q.w >= 0.0f ? 1.0f : -1.0f);
    bool bRotationIsIdentity =
        fabsf(fQSign*kAffParts.q.w - 1.0f) < MIN_DIFFERENCE &&
        fabsf(kAffParts.q.x) < MIN_DIFFERENCE &&
        fabsf(kAffParts.q.y) < MIN_DIFFERENCE &&
        fabsf(kAffParts.q.z) < MIN_DIFFERENCE;

    // Determine if the transformation has a reflection.
    bool bHasReflection = (kAffParts.f < 0.0f);

    // Determine if the scales are uniform.
    bool bScaleIsUniform =
        fabsf(kAffParts.k.x - kAffParts.k.y) < MIN_DIFFERENCE &&
        fabsf(kAffParts.k.y - kAffParts.k.z) < MIN_DIFFERENCE;

    // Determine if the uniform scales are all 1.
    bool bScaleIsUnity = bScaleIsUniform &&
        fabsf(kAffParts.k.x - 1.0f) < MIN_DIFFERENCE;

    // Determine if the scale-orientation is the identity.
    float fUSign = (kAffParts.u.w >= 0.0f ? 1.0f : -1.0f);
    bool bOrientIsIdentity = bScaleIsUniform || (
        fabsf(fUSign*kAffParts.u.w - 1.0f) < MIN_DIFFERENCE &&
        fabsf(kAffParts.u.x) < MIN_DIFFERENCE &&
        fabsf(kAffParts.u.y) < MIN_DIFFERENCE &&
        fabsf(kAffParts.u.z) < MIN_DIFFERENCE);

    // Compute the equivalent Wild Magic transformation.  The careful testing
    // of Max transformation state is designed so that the Wild Magic hints
    // in the Transformation class are properly set.
    Wm4::Transformation kWm4Local;

    if (!bTranslationIsZero)
    {
        kWm4Local.SetTranslate(Wm4::Vector3f(kAffParts.t.x,kAffParts.t.y,
            kAffParts.t.z));
    }

    if (bHasReflection)
    {
        kAffParts.k *= -1.0f;
    }

    if (bScaleIsUniform)
    {
        // matrix of form R*(s*I), where s is uniform scale
        if (!bRotationIsIdentity)
        {
            Wm4::Matrix3f kRot;
            Wm4::Quaternionf(kAffParts.q.w,-kAffParts.q.x,-kAffParts.q.y,
                -kAffParts.q.z).ToRotationMatrix(kRot);
            kWm4Local.SetRotate(kRot);
        }
        if (!bScaleIsUnity)
        {
            kWm4Local.SetUniformScale(kAffParts.k.x);
        }
    }
    else if (bOrientIsIdentity)
    {
        // matrix of form R*S, where S is diagonal with nonuniform scales
        if (!bRotationIsIdentity)
        {
            Wm4::Matrix3f kRot;
            Wm4::Quaternionf(kAffParts.q.w,-kAffParts.q.x,-kAffParts.q.y,
                -kAffParts.q.z).ToRotationMatrix(kRot);
            kWm4Local.SetRotate(kRot);
        }

        kWm4Local.SetScale(Wm4::Vector3f(kAffParts.k.x,kAffParts.k.y,
            kAffParts.k.z));
    }
    else
    {
        // Matrix of form R*U^t*S*U, where U is the orientation matrix and
        // S is diagonal with nonuniform scales.
        Wm4::Matrix3f kMatrix(
            kMaxLocal.GetAddr()[0][0],
            kMaxLocal.GetAddr()[1][0],
            kMaxLocal.GetAddr()[2][0],
            kMaxLocal.GetAddr()[0][1],
            kMaxLocal.GetAddr()[1][1],
            kMaxLocal.GetAddr()[2][1],
            kMaxLocal.GetAddr()[0][2],
            kMaxLocal.GetAddr()[1][2],
            kMaxLocal.GetAddr()[2][2]);

        kWm4Local.SetMatrix(kMatrix);
    }

    return kWm4Local;
}
//----------------------------------------------------------------------------
