//-----------------------------------------------------------------------------
// Verve
// Copyright (C) 2014 - Violent Tulip
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "VPathEditor.h"

#include "console/consoleTypes.h"
#include "gfx/gfxDrawUtil.h"
#include "gfx/primBuilder.h"
#include "gui/worldEditor/worldEditor.h"
#include "math/mathUtils.h"
#include "sim/netConnection.h"

//-----------------------------------------------------------------------------

static F32     gProjectDistance   = 2000.f;
static F32     gSelectionDistance = 2.f;

static ColorI  gPathColor( 255, 255, 255 );
static ColorI  gPathColorSel( 0, 255, 255 );
static ColorI  gNodeLookAtPointColor( 255, 127, 39 );

//-----------------------------------------------------------------------------

// Implement the Edit Mode enum list.
ImplementEnumType( VPathEditorMode, "" )
    { VPathEditor::k_Gizmo,      "GIZMO"      },
    { VPathEditor::k_AddNode,    "ADDNODE"    },
    { VPathEditor::k_DeleteNode, "DELETENODE" },
EndImplementEnumType;

//-----------------------------------------------------------------------------
IMPLEMENT_CONOBJECT( VPathEditor );
//-----------------------------------------------------------------------------

VPathEditor::VPathEditor( void ) :
        mIsDirty( false ),
        mEditMode( k_Gizmo ),
        mEditWeight( false ),
        mEditWeightHandle( -1 )
{
    // Void.
}

bool VPathEditor::onAdd( void )
{
    if ( !Parent::onAdd() )
    {
        return false;
    }

    // Assign Gizmo Name.
    mGizmo->assignName( "VPathEditorGizmo" );

    return true;
}

bool VPathEditor::onWake( void )
{
    // Clear Selection.
    updateSelection( NULL, -1 );

    // Return Parent Value.
    return Parent::onWake();
}

void VPathEditor::initPersistFields( void )
{
    addField( "IsDirty",  TypeBool,           Offset( mIsDirty,  VPathEditor ) );
    addField( "EditMode", TYPEID<EditMode>(), Offset( mEditMode, VPathEditor ) );

    Parent::initPersistFields();
}

//-----------------------------------------------------------------------------
//
// Gui Events
//
//-----------------------------------------------------------------------------

void VPathEditor::on3DMouseDown( const Gui3DMouseEvent &pEvent )
{
    // Using the Gizmo?
    if ( mEditMode != k_Gizmo )
    {
        // No, Quit Now.
        return;
    }

    // Gizmo Event.
    mGizmo->on3DMouseDown( pEvent );

    if ( isValidSelection() )
    {
        // Store Node Information.
        pushNodeEdit();

        switch( mGizmoProfile->mode )
        {
            case MoveMode:
            case RotateMode:
                {
                    if ( mGizmo->getSelection() != Gizmo::None )
                    {
                        // Using Gizmo.
                        return;
                    }

                } break;

            case ScaleMode:
                {
                    if ( isEditingWeight( pEvent ) )
                    {
                        // Editing Weights.
                        return;
                    }

                } break;
        }
    }
    else if ( mSelection.Path )
    {
        // Store Path Information.
        pushPathEdit();

        if ( mGizmo->getSelection() != Gizmo::None )
        {
            // Using Gizmo.
            return;
        }
    }

    // Update Selection.
    if ( !updateSelection( pEvent ) )
    {
        // Clear Selection.
        updateSelection( NULL, -1 );
    }
}

void VPathEditor::on3DMouseUp( const Gui3DMouseEvent &pEvent )
{
    switch ( mEditMode )
    {
        case k_Gizmo :
            {
                // Gizmo Event.
                mGizmo->on3DMouseUp( pEvent );

                // Handle History Actions.
                popPathEdit();
                popNodeEdit();

                // Clear Editing.
                mEditWeight = false;

            } break;

        case k_AddNode :
            {
                if ( mSelection.Path != NULL )
                {
                    // Add New!
                    addNode( pEvent );

                    // Dirty.
                    mIsDirty = true;
                }

            } break;

        case k_DeleteNode :
            {
                // Update Selection.
                if ( updateSelection( pEvent ) )
                {
                    if ( isValidSelection() )
                    {
                        // Delete Node.
                        deleteNode( mSelection.Node );

                        // Dirty.
                        mIsDirty = true;
                    }

                    // Clear Node Selection.
                    updateSelection( mSelection.Path, -1 );
                }

            } break;
    }
}

void VPathEditor::on3DMouseMove( const Gui3DMouseEvent &pEvent )
{
    // Update?
    if ( mEditMode != k_Gizmo || !mSelection.Path )
    {
        return;
    }

    // Update Gizmo?
    if ( mSelection.Node == -1 || mGizmoProfile->mode != ScaleMode )
    {
        // Gizmo Event.
        mGizmo->on3DMouseMove( pEvent );
    }
}

void VPathEditor::on3DMouseDragged( const Gui3DMouseEvent &pEvent )
{
    // Update?
    if ( mEditMode != k_Gizmo || !mSelection.Path )
    {
        return;
    }

    // Update Gizmo?
    if ( mSelection.Node == -1 || mGizmoProfile->mode != ScaleMode )
    {
        // Gizmo Event.
        mGizmo->on3DMouseDragged( pEvent );

        // Handle Gizmo?
        if ( mGizmo->getSelection() == Gizmo::None )
        {
            // Return.
            return;
        }
    }

    // Editing the Path?
    if ( mSelection.Node == -1 )
    {
        switch ( mGizmoProfile->mode )
        {
            case MoveMode :
                {
                    // Fetch Node Position.
                    const Point3F oldPosition = mSelection.Path->getPosition();
                    // Determine New Position.
                    const Point3F newPosition = ( oldPosition + mGizmo->getOffset() );

                    // Apply New Position.
                    setPathPosition( newPosition );

                    // Dirty.
                    mIsDirty        = true;
                    mPathEdit.Dirty = true;

                } break;
            /*
            case RotateMode :
                {

                    // Rotation Delta.
                    MatrixF deltaRotation( EulerF( mGizmo->getDeltaRot() ) );

                    // Fetch Current Transform.
                    MatrixF mat = mSelection.Path->getTransform();
                    mat.mul( deltaRotation );

                    // Apply New Transform.
                    setPathTransform( mat );

                    // Dirty.
                    mIsDirty        = true;
                    mPathEdit.Dirty = true;

                } break;

            case ScaleMode :
                {

                    // Apply New Scale.
                    setPathScale( mGizmo->getScale() );

                    // Dirty.
                    mIsDirty        = true;
                    mPathEdit.Dirty = true;

                } break;
            */
        }
    }

    // No, Editing a Node
    else
    {
        switch ( mGizmoProfile->mode )
        {
            case MoveMode :
                {

                    // Fetch Node.
                    VPathNode *node = mSelection.Path->getNode( mSelection.Node );

                    // Fetch Node Position.
                    const Point3F oldPosition = node->getLocalPosition();

                    // Invert Transform.
                    MatrixF pathTransform = mSelection.Path->getTransform();
                    pathTransform.setPosition( Point3F::Zero );
                    pathTransform.inverse();

                    Point3F deltaPosition = mGizmo->getOffset();
                    pathTransform.mulP( deltaPosition );

                    // Apply New Position.
                    setNodePosition( mSelection.Node, ( oldPosition + deltaPosition ) );

                } break;

            case RotateMode :
                {

                    // Fetch Node.
                    VPathNode *node = mSelection.Path->getNode( mSelection.Node );

                    // Invert Transform.
                    MatrixF pathTransform = mSelection.Path->getTransform();
                    pathTransform.setPosition( Point3F::Zero );
                    pathTransform.inverse();

                    // Rotation Delta.
                    MatrixF deltaRotation( EulerF( mGizmo->getDeltaRot() ) );
                    pathTransform.mul( deltaRotation );

                    // Fetch Current Transform.
                    MatrixF mat = node->getWorldTransform();
                    mat.mul( deltaRotation );

                    // Construct Quat.
                    QuatF newRotation;
                    newRotation.set( mat );

                    // Apply New Rotation.
                    setNodeRotation( mSelection.Node, newRotation );

                } break;

            case ScaleMode :
                {

                    if ( isEditingWeight() )
                    {
                        // Edit Weight.
                        updateWeight( pEvent );
                    }

                } break;
        }
    }
}

//-----------------------------------------------------------------------------
//
// Reference Methods.
//
//-----------------------------------------------------------------------------

VPath *VPathEditor::getClientPath( VPath *pPath )
{
    if ( !pPath )
    {
        return NULL;
    }

    NetConnection *toServer = NetConnection::getConnectionToServer();
    NetConnection *toClient = NetConnection::getLocalClientConnection();
    if ( !toServer || !toClient )
    {
        return NULL;
    }

    const S32 ghostIndex = toClient->getGhostIndex( pPath );
    if ( ghostIndex == -1 )
    {
        return NULL;
    }

    return dynamic_cast<VPath*>( toServer->resolveGhost( ghostIndex ) );
}

//-----------------------------------------------------------------------------
//
// Selection Methods.
//
//-----------------------------------------------------------------------------

bool VPathEditor::updateSelection( const Gui3DMouseEvent &pEvent )
{
    const Point3F pt0 = pEvent.pos;
    const Point3F pt1 = pEvent.pos + pEvent.vec * gProjectDistance;

    RayInfo ri;
    if ( !gServerContainer.collideBox( pt0, pt1, MarkerObjectType, &ri ) )
    {
        // No Object.
        return false;
    }

    VPath *path = dynamic_cast<VPath*>( ri.object );
    if ( !path )
    {
        // No Path Object.
        return false;
    }

    // No Node.
    S32 nodeIndex = -1;

    for ( VPathNodeIterator itr = path->mNodeList.begin(); itr != path->mNodeList.end(); itr++ )
    {
        VPathNode *node = ( *itr );

        Point3F projPosition;
        project( node->getWorldPosition(), &projPosition );

        if ( projPosition.z <= 0.0f )
        {
            continue;
        }

        const Point2I rectHalfSize( 8, 8 );
        const Point2I screenPosition( ( S32 )projPosition.x, ( S32 )projPosition.y );
        const RectI   screenRect( screenPosition - rectHalfSize, 2 * rectHalfSize );

        // Mouse Close Enough?
        if ( screenRect.pointInRect( pEvent.mousePoint ) )
        {
            // Select Node.
            nodeIndex = ( itr - path->mNodeList.begin() );
        }
    }

    // Set Selection.
    updateSelection( path, nodeIndex );

    // Valid Selection.
    return true;
}

void VPathEditor::updateSelection( VPath *pPathObject, const S32 &pNodeIndex )
{
    // Store Selection.
    mSelection.Path = pPathObject;
    mSelection.Node = pNodeIndex;

    // Quick Update.
    updateSelection();

    // Return Buffer.
    char buffer[2][32];
    dSprintf( buffer[0], sizeof( buffer[0] ), "%d", ( pPathObject ) ? pPathObject->getId() : 0 );
    dSprintf( buffer[1], sizeof( buffer[1] ), "%d", pNodeIndex );

    // Callback.
    Con::executef( this, "onUpdateSelection", buffer[0], buffer[1] );
}

void VPathEditor::updateSelection( void )
{
    if ( !isValidSelection() )
    {
        // No Further Updates.
        return;
    }

    // Fetch Node.
    VPathNode *node = mSelection.Path->getNode( mSelection.Node );

    // Fetch Node Rotation Matrix.
    MatrixF mat;
    node->getWorldRotation().setMatrix( &mat );

    // Determine Tangent Axis.
    Point3F pt0(  VPath::gBezierAxis * node->getWeight() );
    Point3F pt1( -VPath::gBezierAxis * node->getWeight() );

    // Rotate Axis.
    mat.mulP( pt0 );
    mat.mulP( pt1 );

    // Offset Points.
    pt0 += node->getWorldPosition();
    pt1 += node->getWorldPosition();

    // Store Points.
    mSelection.TangentHandle[0] = pt0;
    mSelection.TangentHandle[1] = pt1;
}

ConsoleMethod( VPathEditor, clearSelection, void, 2, 2, "( void )" )
{
    // Clear Selection.
    object->updateSelection( NULL, -1 );
}

ConsoleMethod( VPathEditor, setSelection, void, 3, 4, "( pObject, [pNodeIndex] )" )
{
    // Fetch Path.
    VPath *path = dynamic_cast<VPath*>( Sim::findObject( argv[2] ) );
    if ( !path )
    {
        Con::errorf( "VPathEditor::setSelection() - Unable to select target Object." );
        return;
    }

    if ( argc == 3 )
    {
        // Select Path.
        object->updateSelection( path, -1 );
        return;
    }

    // Select Path & Node.
    object->updateSelection( path, dAtoi( argv[3] ) );
}

ConsoleMethod( VPathEditor, isValidSelection, bool, 2, 2, "( void )" )
{
    return object->isValidSelection();
}

ConsoleMethod( VPathEditor, getSelectedPath, S32, 2, 2, "( void )" )
{
    // Fetch Path.
    VPath *path = object->mSelection.Path;

    // Return ID.
    return ( path ) ? path->getId() : 0;
}

ConsoleMethod( VPathEditor, getSelectedNode, S32, 2, 2, "( void )" )
{
    // Return Node Index.
    return ( object->mSelection.Path ) ? object->mSelection.Node : -1;
}

ConsoleMethod( VPathEditor, deleteSelection, void, 2, 2, "( void )" )
{
    // Valid Selection?
    if ( object->isValidSelection() )
    {
        object->deleteNode( object->mSelection.Node );
    }
}

//-----------------------------------------------------------------------------
//
// Weight Editing Methods.
//
//-----------------------------------------------------------------------------

bool VPathEditor::isEditingWeight( const Gui3DMouseEvent &pEvent )
{
    if ( !isValidSelection() || mSelection.Path->mPathType != VPath::k_PathBezier )
    {
        // False.
        mEditWeight = false;

        // Invalid Selection.
        return false;
    }

    const Point3F pt0 = pEvent.pos;
    const Point3F pt1 = pEvent.pos + pEvent.vec * gProjectDistance;

    // Min Index.
    S32 minNode     = -1;
    F32 minDistance = F32_MAX;

    for ( S32 i = 0; i < 2; i++ )
    {
        Point3F pt;
        if ( !Utility::FindNearestPointOnLine( mSelection.TangentHandle[i], pt0, pt1, &pt ) )
        {
            // Skip.
            continue;
        }

        // Distance.
        const F32 ptDistance = ( pt - mSelection.TangentHandle[i] ).len();
        if ( ptDistance < minDistance )
        {
            // Store Index.
            minNode = i;

            // Store Distance.
            minDistance = ptDistance;
        }
    }

    if ( minDistance > gSelectionDistance )
    {
        // False.
        mEditWeight = false;

        // Too Far Away.
        return false;
    }

    // True.
    mEditWeight       = true;
    mEditWeightHandle = minNode;

    return true;
}

void VPathEditor::updateWeight( const Gui3DMouseEvent &pEvent )
{
    if ( !isEditingWeight() )
    {
        // Woops!
        return;
    }

    // Fetch Current Node.
    VPathNode *node    = mSelection.Path->getNode( mSelection.Node );
    Point3F    nodePos = node->getWorldPosition();

    // Fetch Node Transform.
    MatrixF mat = node->getWorldTransform();

    // Fetch the Normal.
    const VectorF planeNormal = mat.getColumn3F( 0 );

    // Construct Plane.
    const PlaneF plane( nodePos, planeNormal );

    Point3F iPt;
    if ( plane.intersect( pEvent.pos, pEvent.vec, &iPt ) )
    {
/*
        // Fetch Edit Vector.
        VectorF tangentVect( mSelection.TangentHandle[mEditWeightHandle] - nodePos );
        tangentVect.normalize();

        // Fetch Mouse Vector.
        VectorF mouseVec( iPt - nodePos );
        F32 mouseDist = mouseVec.len();
        mouseVec.normalize();

        // Find the Angles.
        F32 tangentAngle = mAtan2( -tangentVect.z, tangentVect.x );
        F32 mouseAngle   = mAtan2( -mouseVec.z, mouseVec.x );

        // Determine Sign.
        const S32 sign = ( planeNormal.y > 0.f ) ? -1.f : 1.f;

        // Delta Rotation..
        const QuatF deltaRotation( AngAxisF( planeNormal, sign * ( mouseAngle - tangentAngle ) ) );

        // Calculate New Rotation.
        QuatF newRotation;
        newRotation.mul( nodePos, deltaRotation ); 

        // Apply Rotation.
        setNodeRotation( mSelection.Node, newRotation );
*/
/*
        // Fetch Edit Vector.
        VectorF handleVec( mSelection.TangentHandle[mEditWeightHandle] - nodePos );
        handleVec.normalize();

        // Fetch Mouse Vector.
        VectorF mouseVec( iPt - nodePos );
        mouseVec.normalize();

        // Find the Angles.
        F32 handleAngle = Utility::GetPitch( handleVec ); //mAtan2( -handleVec.z, handleVec.x );
        F32 mouseAngle  = Utility::GetPitch( mouseVec );  //mAtan2( -mouseVec.z, mouseVec.x );

        // Determine Sign.
        const S32 sign = ( planeNormal.y > 0.f ) ? -1.f : 1.f;

        // Delta Rotation.
        MatrixF rotMat;
        AngAxisF::RotateY( sign * ( mouseAngle - handleAngle ), &rotMat );

        // Rotate.
        mat.mul( rotMat );

        QuatF newRotation;
        newRotation.set( mat );

        // Apply Rotation.
        setNodeRotation( mSelection.Node, newRotation );
*/
        // Apply Weight.
        setNodeWeight( mSelection.Node, ( iPt - nodePos ).len() );
    }
}

//-----------------------------------------------------------------------------
//
// Path Editing Methods.
//
//-----------------------------------------------------------------------------

void VPathEditor::setPathPosition( const Point3F &pPosition )
{
    // Fetch Paths.
    VPath *serverPath = mSelection.Path;
    VPath *clientPath = getClientPath( serverPath );

    // Update Position.
    serverPath->setPosition( pPosition );
    clientPath->setPosition( pPosition );

    // Update Selection.
    updateSelection();
}

void VPathEditor::setPathRotation( const QuatF &pRotation )
{
    // Determine the Matrix.
    MatrixF mat;
    pRotation.setMatrix( &mat );
    mat.setPosition( mSelection.Path->getPosition() );

    // Update Transform.
    setPathTransform( mat );
}

void VPathEditor::setPathTransform( const MatrixF &pTransform )
{
    // Fetch Paths.
    VPath *serverPath = mSelection.Path;
    VPath *clientPath = getClientPath( serverPath );

    // Update Transform.
    serverPath->setTransform( pTransform );
    clientPath->setTransform( pTransform );

    // Update Selection.
    updateSelection();
}

void VPathEditor::setPathScale( const VectorF &pScale )
{
    // Fetch Paths.
    VPath *serverPath = mSelection.Path;
    VPath *clientPath = getClientPath( serverPath );

    // Fetch Current Scale.
    VectorF scale = serverPath->getScale();
    scale.convolve( pScale );

    // Update Scale.
    serverPath->setScale( scale );
    clientPath->setScale( scale );

    // Update Selection.
    updateSelection();
}

//-----------------------------------------------------------------------------
//
// Node Editing Methods.
//
//-----------------------------------------------------------------------------

bool VPathEditor::getPointOnPath( VPath *pPath, const Gui3DMouseEvent &pEvent, S32 &pNode, MatrixF &pTransform )
{
    if ( pPath->getNodeCount() < 2 )
    {
        // Start / End Points.
        const Point3F pt0 = pEvent.pos;
        const Point3F pt1 = pEvent.pos + pEvent.vec * gProjectDistance;

        // Create Intersection Plane.
        const PlaneF plane( pPath->getPosition(), VPath::gBezierUp );

        // Intersection Point.
        Point3F intersectionPoint;
        if ( !plane.intersect( pEvent.pos, pEvent.vec, &intersectionPoint ) )
        {
            // No Intersection.
            return false;
        }

        // I'th Node.
        pNode = pPath->getNodeCount();
        // Set Identity.
        pTransform.identity();
        // Set Position.
        pTransform.setPosition( intersectionPoint );

        // Return.
        return true;
    }

    switch ( pPath->mPathType )
    {
        case VPath::k_PathLinear : 
            {

                return getPointOnLinearPath( pPath, pEvent, pNode, pTransform );

            } break;

        case VPath::k_PathBezier :
            {

                return getPointOnBezierPath( pPath, pEvent, pNode, pTransform );

            } break;
    }

    return false;
}

bool VPathEditor::getPointOnLinearPath( VPath *pPath, const Gui3DMouseEvent &pEvent, S32 &pNode, MatrixF &pTransform )
{
    // Start / End Points.
    const Point3F pt0 = pEvent.pos;
    const Point3F pt1 = pEvent.pos + pEvent.vec * gProjectDistance;

    S32 minNode = -1;
    F32 minDistance = F32_MAX;
    Point3F minPoint( 0.f, 0.f, 0.f );
    for ( VPathNodeIterator itr = pPath->mNodeList.begin(); itr != pPath->mNodeList.end(); itr++ )
    {
        // Fetch Nodes.
        VPathNode *srcNode = ( *itr );
        VPathNode *dstNode = ( itr == ( pPath->mNodeList.end() - 1 ) ) ? ( *( pPath->mNodeList.begin() ) ) : ( *( itr + 1 ) );

        // Project to Screen.
        Point3F srcNodeScreenPosition, dstNodeScreenPosition;
        project( srcNode->getWorldPosition(), &srcNodeScreenPosition );
        project( dstNode->getWorldPosition(), &dstNodeScreenPosition );

        // Skip?
        if ( srcNodeScreenPosition.z > 1.f && dstNodeScreenPosition.z > 1.f )
        {
            continue;
        }

        Point3F ptOut0, ptOut1;
        F32 ptOutDistance;
        if ( !Utility::FindNearestDistanceBetweenLines( pt0, pt1,
                                                        srcNode->getWorldPosition(), dstNode->getWorldPosition(),
                                                        &ptOut0, &ptOut1, &ptOutDistance ) )
        {
            continue;
        }

        if ( ptOutDistance < minDistance )
        {
            minDistance = ptOutDistance;
            minPoint = ptOut1;
            minNode = ( itr - pPath->mNodeList.begin() );
        }
    }

    // Distance too Large?
    if ( minDistance > 0.25f )
    {
        // Invalid.
        return false;
    }

    // Setup.
    pTransform.identity();
    pTransform.setPosition( minPoint );

    // Store Node.
    pNode = minNode;

    return true;
}

bool VPathEditor::getPointOnBezierPath( VPath *pPath, const Gui3DMouseEvent &pEvent, S32 &pNode, MatrixF &pTransform )
{
    S32 minNode = -1;
    F32 minInterp = 0.f;
    F32 minDistance = F32_MAX;
    Point3F minPoint( 0.f, 0.f, 0.f );
    for ( VPathNodeIterator itr = pPath->mNodeList.begin(); itr != pPath->mNodeList.end(); itr++ )
    {
        // Fetch Nodes.
        VPathNode *srcNode = ( *itr );
        VPathNode *dstNode = ( itr == ( pPath->mNodeList.end() - 1 ) ) ? ( *( pPath->mNodeList.begin() ) ) : ( *( itr + 1 ) );

        // Project to Screen.
        Point3F srcNodeScreenPosition, dstNodeScreenPosition;
        project( srcNode->getWorldPosition(), &srcNodeScreenPosition );
        project( dstNode->getWorldPosition(), &dstNodeScreenPosition );

        // Skip?
        if ( srcNodeScreenPosition.z > 1.f && dstNodeScreenPosition.z > 1.f )
        {
            continue;
        }

        // Positions.
        const Point3F &pt0 = srcNode->getWorldPosition();
        const Point3F &pt3 = dstNode->getWorldPosition();

        // Fetch Node Rotation Matrices.
        MatrixF mat0, mat1;
        srcNode->getWorldRotation().setMatrix( &mat0 );
        dstNode->getWorldRotation().setMatrix( &mat1 );

        // Determine Tangent Axis.
        Point3F pt1(  VPath::gBezierAxis * srcNode->getWeight() );
        Point3F pt2( -VPath::gBezierAxis * dstNode->getWeight() );

        // Rotate Axis.
        mat0.mulP( pt1 );
        mat1.mulP( pt2 );

        // Offset Points.
        pt1 += pt0;
        pt2 += pt3;

        for ( F32 t = 0.f, it = 1.f; t <= 1.f; t += 0.1f, it = ( 1.f - t ) )
        {
            // Calculate Position.
            Point3F pos = ( pt0 * it * it * it ) + ( 3 * pt1 * it * it * t ) + ( 3 * pt2 * it * t * t ) + ( pt3 * t * t * t );

            // Determine the Screen Position.
            Point3F screenPos;
            project( pos, &screenPos );
            // Behind?
            if ( screenPos.z > 1.f )
            {
                // Skip Point.
                continue;
            }

            // Determine the Distance.
            F32 screenDistance = Point2F( screenPos.x - pEvent.mousePoint.x, screenPos.y - pEvent.mousePoint.y ).lenSquared();
            // Min Distance?
            if ( screenDistance < minDistance )
            {
                // Store.
                minDistance = screenDistance;
                minInterp = t;
                minPoint = pos;
                minNode = ( itr - pPath->mNodeList.begin() );
            }
        }
    }

    // Distance too Large?
    if ( minDistance > 1000.f )
    {
        // Invalid.
        return false;
    }

    // Fetch Orientation.
    const VectorF &orientation = pPath->getPathOrientation( pPath->getNode( minNode ), 
                                                            pPath->getNode( ( minNode + 1 ) % pPath->getNodeCount() ),
                                                            minInterp, true );

    // Z-Axis.
    VectorF zVec = -orientation;
    zVec.normalize();

    // X-Axis.
    VectorF xVec = mCross( VPath::gBezierUp, zVec );
    xVec.normalize();

    // Y-Axis.
    VectorF yVec = mCross( zVec, xVec );
    yVec.normalize();

    // Setup Object Transform.
    pTransform.identity();
    pTransform.setColumn( 0,  xVec );
    pTransform.setColumn( 1, -zVec );
    pTransform.setColumn( 2,  yVec );
    // Set the Position.
    pTransform.setPosition( minPoint );

    // Store Node.
    pNode = minNode;

    return true;
}

void VPathEditor::addNode( const Gui3DMouseEvent &pEvent )
{
    VPath *path = mSelection.Path;
    if ( !path )
    {
        // Woops!
        return;
    }

    // Min Index.
    S32 nodeIndex = -1;
    MatrixF nodeTransform( true );
    if ( !getPointOnPath( path, pEvent, nodeIndex, nodeTransform ) )
    {
        // Can't Add.
        return;
    }

    // Invert Transform.
    MatrixF pathTransform = mSelection.Path->getTransform();
    pathTransform.setPosition( Point3F::Zero );
    pathTransform.inverse();

    Point3F nodePosition = ( nodeTransform.getPosition() - mSelection.Path->getPosition() );
    pathTransform.mulP( nodePosition );

    // Node Rotation.
    nodeTransform.mul( pathTransform );
    QuatF nodeRotation( nodeTransform );

    // Node Weights.
    F32 nodeWeight = 10.f;

    // Add New Node.
    VPathNode *node = path->addNode( nodePosition, nodeRotation, nodeWeight, ++nodeIndex );

    // Valid Node?
    if ( !node )
    {
        return;
    }

    // Update Size.
    path->updateContainer();

    // Calculate Path.
    path->calculatePath();

    UndoManager *historyManager = NULL;
    if ( !Sim::findObject( "EUndoManager", historyManager ) )
    {
        Con::errorf( "VPathEditor::addNode() - EUndoManager not found!" );
        return;           
    }

    // Create Undo Action.
    VPathEditorAddNodeAction *editAction = new VPathEditorAddNodeAction();

    // Store Editor.
    editAction->mEditor       = this;

    // Store Node Details.
    editAction->mPath         = path;
    editAction->mNodeIndex    = nodeIndex;

    editAction->mNodePosition = nodePosition;
    editAction->mNodeRotation = nodeRotation;
    editAction->mNodeWeight   = nodeWeight;

    // Add To Manager.
    historyManager->addAction( editAction );

    // Set World Editor Dirty.
    setWorldEditorDirty();
}

void VPathEditor::deleteNode( const S32 &pNodeIndex )
{
    VPath *path = mSelection.Path;
    if ( !path )
    {
        // Woops!
        return;
    }

    // Fetch Node Properites.
    VPathNode *node        = path->getNode( pNodeIndex );
    const Point3F position = node->getLocalPosition();
    const QuatF   rotation = node->getLocalRotation();
    const F32     weight   = node->getWeight();

    // Delete Node.
    path->deleteNode( pNodeIndex );
    
    // Update Path.
    path->updateContainer();

    // Calculate Path.
    path->calculatePath();

    // Selected Node?
    const S32 _nodeIndex = pNodeIndex;
    if ( pNodeIndex == mSelection.Node )
    {
        // Update Selection.
        updateSelection( mSelection.Path, -1 );
    }

    UndoManager *historyManager = NULL;
    if ( !Sim::findObject( "EUndoManager", historyManager ) )
    {
        Con::errorf( "VPathEditor::deleteNode() - EUndoManager not found!" );
        return;           
    }

    // Create Undo Action.
    VPathEditorDeleteNodeAction *editAction = new VPathEditorDeleteNodeAction();

    // Store Editor.
    editAction->mEditor       = this;

    // Store Node Details.
    editAction->mPath         = path;
    editAction->mNodeIndex    = _nodeIndex;

    editAction->mNodePosition = position;
    editAction->mNodeRotation = rotation;
    editAction->mNodeWeight   = weight;

    // Add To Manager.
    historyManager->addAction( editAction );

    // Set World Editor Dirty.
    setWorldEditorDirty();
}

void VPathEditor::setNodePosition( const S32 &pNodeIndex, const Point3F &pPosition )
{
    // Fetch Paths.
    VPath *serverPath = mSelection.Path;
    VPath *clientPath = getClientPath( serverPath );

    // Sanity!
    if ( !serverPath || !clientPath )
    {
        return;
    }

    // Change?
    if ( serverPath->getNodeLocalPosition( pNodeIndex ) == pPosition )
    {
        return;
    }

    // Set Position.
    serverPath->setNodePosition( pNodeIndex, pPosition );
    clientPath->setNodePosition( pNodeIndex, pPosition );

    // Update Selection.
    updateSelection();
    
    // Dirty.
    mIsDirty    = true;
    mNodeEdit.Dirty = true;

    // Arg Buffer.
    char buffer[3][32];
    dSprintf( buffer[0], sizeof( buffer[0] ), "%d", mSelection.Path->getId() );
    dSprintf( buffer[1], sizeof( buffer[1] ), "%d", pNodeIndex );
    dSprintf( buffer[2], sizeof( buffer[2] ), "%d", ( mSelection.Node == pNodeIndex ) );

    // Callback.
    Con::executef( this, "onUpdateNodePosition", buffer[0], buffer[1], buffer[2] );
}

void VPathEditor::setNodeRotation( const S32 &pNodeIndex, const QuatF &pRotation )
{
    // Fetch Paths.
    VPath *serverPath = mSelection.Path;
    VPath *clientPath = getClientPath( serverPath );

    // Sanity!
    if ( !serverPath || !clientPath )
    {
        return;
    }

    // Change?
    if ( serverPath->getNodeLocalRotation( pNodeIndex ) == pRotation )
    {
        return;
    }

    // Set Position.
    serverPath->setNodeRotation( pNodeIndex, pRotation );
    clientPath->setNodeRotation( pNodeIndex, pRotation );

    // Update Selection.
    updateSelection();
    
    // Dirty.
    mIsDirty    = true;
    mNodeEdit.Dirty = true;

    // Arg Buffer.
    char buffer[3][32];
    dSprintf( buffer[0], sizeof( buffer[0] ), "%d", mSelection.Path->getId() );
    dSprintf( buffer[1], sizeof( buffer[1] ), "%d", pNodeIndex );
    dSprintf( buffer[2], sizeof( buffer[2] ), "%d", ( mSelection.Node == pNodeIndex ) );

    // Callback.
    Con::executef( this, "onUpdateNodeRotation", buffer[0], buffer[1], buffer[2] );
}

void VPathEditor::setNodeWeight( const S32 &pNodeIndex, const F32 &pWeight )
{
    // Fetch Paths.
    VPath *serverPath = mSelection.Path;
    VPath *clientPath = getClientPath( serverPath );

    // Sanity!
    if ( !serverPath || !clientPath )
    {
        return;
    }

    // Change?
    if ( serverPath->getNodeWeight( pNodeIndex ) == pWeight )
    {
        return;
    }

    // Set Weight.
    serverPath->setNodeWeight( pNodeIndex, pWeight );
    clientPath->setNodeWeight( pNodeIndex, pWeight );

    // Update Selection.
    updateSelection();
    
    // Dirty.
    mIsDirty    = true;
    mNodeEdit.Dirty = true;

    // Arg Buffer.
    char buffer[3][32];
    dSprintf( buffer[0], sizeof( buffer[0] ), "%d", mSelection.Path->getId() );
    dSprintf( buffer[1], sizeof( buffer[1] ), "%d", pNodeIndex );
    dSprintf( buffer[2], sizeof( buffer[2] ), "%d", ( mSelection.Node == pNodeIndex ) );

    // Callback.
    Con::executef( this, "onUpdateNodeWeight", buffer[0], buffer[1], buffer[2] );
}

void VPathEditor::setNodeOrientationMode( const S32 &pNodeIndex, const VPathNode::eOrientationType &pType )
{
    // Fetch Paths.
    VPath *serverPath = mSelection.Path;
    VPath *clientPath = getClientPath( serverPath );

    // Sanity!
    if ( !serverPath || !clientPath )
    {
        return;
    }

    // Set Orientation Mode.
    serverPath->setNodeOrientationMode( pNodeIndex, pType );
    clientPath->setNodeOrientationMode( pNodeIndex, pType );
    
    // Dirty.
    mIsDirty    = true;
    mNodeEdit.Dirty = true;

    // Arg Buffer.
    char buffer[3][32];
    dSprintf( buffer[0], sizeof( buffer[0] ), "%d", mSelection.Path->getId() );
    dSprintf( buffer[1], sizeof( buffer[1] ), "%d", pNodeIndex );
    dSprintf( buffer[2], sizeof( buffer[2] ), "%d", ( mSelection.Node == pNodeIndex ) );

    // Callback.
    Con::executef( this, "onUpdateNodeOrientation", buffer[0], buffer[1], buffer[2] );
}

void VPathEditor::setNodeOrientationMode( const S32 &pNodeIndex, const VPathNode::eOrientationType &pType, const Point3F &pPoint )
{
    // Fetch Paths.
    VPath *serverPath = mSelection.Path;
    VPath *clientPath = getClientPath( serverPath );

    // Sanity!
    if ( !serverPath || !clientPath )
    {
        return;
    }

    // Set Orientation Mode.
    serverPath->setNodeOrientationMode( pNodeIndex, pType, pPoint );
    clientPath->setNodeOrientationMode( pNodeIndex, pType, pPoint );
    
    // Dirty.
    mIsDirty    = true;
    mNodeEdit.Dirty = true;

    // Arg Buffer.
    char buffer[3][32];
    dSprintf( buffer[0], sizeof( buffer[0] ), "%d", mSelection.Path->getId() );
    dSprintf( buffer[1], sizeof( buffer[1] ), "%d", pNodeIndex );
    dSprintf( buffer[2], sizeof( buffer[2] ), "%d", ( mSelection.Node == pNodeIndex ) );

    // Callback.
    Con::executef( this, "onUpdateNodeOrientation", buffer[0], buffer[1], buffer[2] );
}

void VPathEditor::pushPathEdit( void )
{
    // Clear Current Edit Dirty.
    mPathEdit.Dirty = false;

    if ( mSelection.Path != NULL )
    {
        // Store Node Details.
        mPathEdit.Transform = mSelection.Path->getTransform();
    }
}

void VPathEditor::popPathEdit( void )
{
    // Did Edit?
    if ( mPathEdit.Dirty && mSelection.Path != NULL )
    {
        UndoManager *historyManager = NULL;
        if ( !Sim::findObject( "EUndoManager", historyManager ) )
        {
            Con::errorf( "VPathEditor - EUndoManager not found!" );
            return;           
        }

        // Create Undo Action.
        VPathEditorEditPathAction *editAction = new VPathEditorEditPathAction( "Edit Path" );

        // Store Editor.
        editAction->mEditor   = this;

        // Store Path Details.
        editAction->mPath      = mSelection.Path;
        editAction->mTransform = mPathEdit.Transform;

        // Add To Manager.
        historyManager->addAction( editAction );

        // Clear Dirty.
        mPathEdit.Dirty = false;

        // Set World Editor Dirty.
        setWorldEditorDirty();
    }
}

void VPathEditor::pushNodeEdit( void )
{
    // Clear Current Edit Dirty.
    mNodeEdit.Dirty = false;

    if ( isValidSelection() )
    {
        // Fetch Node.
        VPathNode *node = mSelection.Path->getNode( mSelection.Node );

        // Store Node Details.
        mNodeEdit.Position = node->getLocalPosition();
        mNodeEdit.Rotation = node->getLocalRotation();
        mNodeEdit.Weight   = node->getWeight();
    }
}

void VPathEditor::popNodeEdit( void )
{
    // Did Edit?
    if ( mNodeEdit.Dirty && isValidSelection() )
    {
        UndoManager *historyManager = NULL;
        if ( !Sim::findObject( "EUndoManager", historyManager ) )
        {
            Con::errorf( "VPathEditor - EUndoManager not found!" );
            return;           
        }

        // Create Undo Action.
        VPathEditorEditNodeAction *editAction = new VPathEditorEditNodeAction( "Edit Node" );

        // Store Editor.
        editAction->mEditor       = this;

        // Store Node Details.
        editAction->mPath         = mSelection.Path;
        editAction->mNodeIndex    = mSelection.Node;

        editAction->mNodePosition = mNodeEdit.Position;
        editAction->mNodeRotation = mNodeEdit.Rotation;
        editAction->mNodeWeight   = mNodeEdit.Weight;

        editAction->mNodeOrientation = mSelection.Path->getNode( mSelection.Node )->getOrientationMode();

        // Add To Manager.
        historyManager->addAction( editAction );

        // Clear Dirty.
        mNodeEdit.Dirty = false;

        // Set World Editor Dirty.
        setWorldEditorDirty();
    }
}

void VPathEditor::setWorldEditorDirty( void )
{
    WorldEditor *worldEditor;
    if ( Sim::findObject( "EWorldEditor", worldEditor ) )
    {
        worldEditor->setDirty();
    }
}

//-----------------------------------------------------------------------------
//
// Render Methods.
//
//-----------------------------------------------------------------------------

void VPathEditor::setStateBlock( void )
{
    // Valid State Block?
    if ( !mStateBlock )
    {
        // Setup Definition.
        GFXStateBlockDesc def;
        def.blendDefined = true;
        def.blendEnable = true;
        def.blendSrc = GFXBlendSrcAlpha;
        def.blendDest = GFXBlendInvSrcAlpha;
        def.zDefined = true;
        def.cullDefined = false;

        // Create State Block.
        mStateBlock = GFX->createStateBlock( def );
    }

    // Set State Block.
    GFX->setStateBlock( mStateBlock );
}

void VPathEditor::renderScene( const RectI &pUpdateRect )
{
    // Setup State Block.
    setStateBlock();

    if ( isValidSelection() )
    {
        // Fetch Current Node.
        VPathNode *node = mSelection.Path->getNode( mSelection.Node );

        // Render Gizmo?
        if ( mEditMode == k_Gizmo && mGizmoProfile->mode != ScaleMode )
        {
            // Fetch Node Transform.
            MatrixF mat= node->getWorldTransform();

            // Move Gizmo.
            mGizmo->set( mat, node->getWorldPosition(), Point3F( 1.0f, 1.0f, 1.0f ) );

            // Render Gizmo.
            mGizmo->renderGizmo( mLastCameraQuery.cameraMatrix );
        }

        // Render Handles?
        if ( mSelection.Path->mPathType == VPath::k_PathBezier )
        {
            // Fetch Tangent Handles.
            const Point3F &pt0 = mSelection.TangentHandle[0];
            const Point3F &pt1 = mSelection.TangentHandle[1];

            // State Block.
            GFXStateBlockDesc desc;
            desc.setZReadWrite( true, true );
            desc.fillMode = GFXFillSolid;

            // Set Color.
            PrimBuild::color( gPathColorSel );

            // Render Line.
            PrimBuild::begin( GFXLineList, 2 );
                PrimBuild::vertex3fv( pt0 );
                PrimBuild::vertex3fv( pt1 );
            PrimBuild::end();

            // Render Handles.
            GFX->getDrawUtil()->drawSphere( desc, 0.1f, pt0, gPathColorSel );
            GFX->getDrawUtil()->drawSphere( desc, 0.1f, pt1, gPathColorSel );
        }

        // ToPoint Node?
        if ( node->getOrientationMode().Type == VPathNode::k_OrientationToPoint )
        {
            PrimBuild::color( gNodeLookAtPointColor );
            PrimBuild::begin( GFXLineStrip, 2 );

            PrimBuild::vertex3fv( node->getWorldPosition() );
            PrimBuild::vertex3fv( node->getOrientationMode().Point );

            PrimBuild::end();
        }
    }
    else if ( mSelection.Path && mEditMode == k_Gizmo )
    {
        switch ( mGizmoProfile->mode )
        {
            case MoveMode:
                {
                    // Fetch Path Transform.
                    const MatrixF &mat = mSelection.Path->getTransform();

                    // Fetch the Path's Box Center.
                    const Point3F &pos = mSelection.Path->getWorldBox().getCenter();

                    // Move Gizmo.
                    mGizmo->set( mat, pos, Point3F( 1.0f, 1.0f, 1.0f ) );

                    // Render Gizmo.
                    mGizmo->renderGizmo( mLastCameraQuery.cameraMatrix );

                } break;
        }
    }

    // Render Path Segments.
    renderPaths( k_RenderSegments );

    // Set Clip Rect.
    GFX->setClipRect( pUpdateRect ); 

    // Render Path Nodes.
    renderPaths( k_RenderNodes );

    if ( isValidSelection() )
    {
        // Fetch Current Node.
        VPathNode *node = mSelection.Path->getNode( mSelection.Node );

        // ToPoint Node?
        if ( node->getOrientationMode().Type == VPathNode::k_OrientationToPoint )
        {
            // Project to Screen.
            Point3F screenPosition;
            project( node->getOrientationMode().Point, &screenPosition );
            if ( screenPosition.z <= 1.0f )
            {
                // Determine the center & size of the node rectangle.
                Point2I nodeCenter = Point2I( screenPosition.x, screenPosition.y );
                Point2I nodeHalfSize = Point2I( 8, 8 );
                // Determine Render Rectangle.
                RectI nodeRect;
                nodeRect.point  = nodeCenter - nodeHalfSize;
                nodeRect.extent = ( 2 * nodeHalfSize );

                // Draw?
                if ( getBounds().overlaps( nodeRect ) )
                {
                    // Render the Point.
                    GFX->getDrawUtil()->drawRectFill( nodeRect, gNodeLookAtPointColor );
                }
            }
        }
    }
}

void VPathEditor::renderPaths( const RenderType &pRenderType )
{
    SimSet *objectSet = VPath::getServerSet();
    for ( SimSetIterator itr( objectSet ); *itr; ++itr )
    {
        VPath *path = dynamic_cast<VPath*>( *itr );
        if ( path )
        {
            // Render Path.
            renderPath( pRenderType, path, ( path == mSelection.Path ) ? gPathColorSel : gPathColor );
        }
    }
}

void VPathEditor::renderPath( const RenderType &pRenderType, VPath *pPath, const ColorI &pColor )
{
    if ( !pPath )
    {
        // Sanity!
        return;
    }

    switch ( pRenderType )
    {
        case k_RenderSegments :
            {
                switch ( pPath->mPathType )
                {
                    case VPath::k_PathLinear : 
                        {
                            renderLinearPath( pPath, pColor );

                        } break;

                    case VPath::k_PathBezier :
                        {
                            renderBezierPath( pPath, pColor );

                        } break;
                }

            } break;

        case k_RenderNodes :
            {
                // Fetch Draw Util.
                GFXDrawUtil *drawUtil = GFX->getDrawUtil();

                // Fetch Bounds.
                RectI bounds = getBounds();

                const Point2I nodeMinHalfSize( 8, 8 );
                for ( VPathNodeIterator itr = pPath->mNodeList.begin(); itr != pPath->mNodeList.end(); itr++ )
                {
                    // Fetch Node.
                    VPathNode *node = ( *itr );

                    // Project to Screen.
                    Point3F screenPosition;
                    project( node->getWorldPosition(), &screenPosition );
                    if ( screenPosition.z > 1.0f )
                    {
                        continue;
                    }

                    // Determine the node text information.
                    const char *nodeText = avar( "%d", ( itr - pPath->mNodeList.begin() ) );
                    const Point2I nodeTextHalfSize = Point2I( 0.5f * (F32)getControlProfile()->mFont->getStrWidth( nodeText ),
                                                              0.5f * (F32)getControlProfile()->mFont->getHeight() );

                    // Determine the center & size of the node rectangle.
                    Point2I nodeCenter = Point2I( screenPosition.x, screenPosition.y );
                    Point2I nodeHalfSize = Point2I( nodeTextHalfSize.x + 3, nodeTextHalfSize.y + 3 );
                    nodeHalfSize.setMax( nodeMinHalfSize );
                    // Determine Render Rectangle.
                    RectI nodeRect;
                    nodeRect.point  = nodeCenter - nodeHalfSize;
                    nodeRect.extent = ( 2 * nodeHalfSize );

                    // Draw?
                    if ( !bounds.overlaps( nodeRect ) )
                    {
                        continue;
                    }

                    // Render the Point.
                    drawUtil->drawRectFill( nodeRect, pColor );

                    // Draw the node index text.
                    drawUtil->setBitmapModulation( getControlProfile()->mFontColor );
                    drawUtil->drawText( getControlProfile()->mFont, nodeCenter - nodeTextHalfSize, nodeText );
                }

            } break;
    }
}

void VPathEditor::renderLinearPath( VPath *pPath, const ColorI &pColor )
{
    if ( pPath->mNodeList.size() < 2 )
    {
        // No Lines.
        return;
    }

    PrimBuild::color( pColor );
    PrimBuild::begin( GFXLineStrip, ( pPath->mNodeList.size() + 1 ) );

    for ( VPathNodeIterator itr = pPath->mNodeList.begin(); itr != pPath->mNodeList.end(); itr++ )
    {
        // Apply Vertex.
        PrimBuild::vertex3fv( ( *itr )->getWorldPosition() );
    }

    // Loop Back.
    PrimBuild::vertex3fv( pPath->mNodeList.front()->getWorldPosition() );

    PrimBuild::end();
}

void VPathEditor::renderBezierPath( VPath *pPath, const ColorI &pColor )
{
    if ( pPath->mNodeList.size() < 2 )
    {
        // No Lines.
        return;
    }

    PrimBuild::color( pColor );
    PrimBuild::begin( GFXLineStrip, U32( ( ( 1.01f / 0.01f ) + 1 ) * pPath->mNodeList.size() ) );

    for ( VPathNodeIterator itr = pPath->mNodeList.begin(); itr != pPath->mNodeList.end(); itr++ )
    {
        // Fetch Nodes.
        VPathNode *srcNode = ( *itr );
        VPathNode *dstNode = ( itr == ( pPath->mNodeList.end() - 1 ) ) ? ( *( pPath->mNodeList.begin() ) ) : ( *( itr + 1 ) );

        // Positions.
        const Point3F &pt0 = srcNode->getWorldPosition();
        const Point3F &pt3 = dstNode->getWorldPosition();

        // Fetch Node Rotation Matrices.
        MatrixF mat0, mat1;
        srcNode->getWorldRotation().setMatrix( &mat0 );
        dstNode->getWorldRotation().setMatrix( &mat1 );

        // Determine Tangent Axis.
        Point3F pt1(  VPath::gBezierAxis * srcNode->getWeight() );
        Point3F pt2( -VPath::gBezierAxis * dstNode->getWeight() );

        // Rotate Axis.
        mat0.mulP( pt1 );
        mat1.mulP( pt2 );

        // Offset Points.
        pt1 += pt0;
        pt2 += pt3;

        for ( F32 t = 0.f, it = 1.f; t <= 1.f; t += 0.01f, it = ( 1.f - t ) )
        {
            // Calculate Position.
            Point3F pos = ( pt0 * it * it * it ) + ( 3 * pt1 * it * it * t ) + ( 3 * pt2 * it * t * t ) + ( pt3 * t * t * t );
            // Apply Vertex.
            PrimBuild::vertex3fv( pos );
        }
    }

    PrimBuild::end();
}

//-----------------------------------------------------------------------------
//
// History Events
//
//-----------------------------------------------------------------------------

void VPathEditor::VPathEditorEditPathAction::undo( void )
{
    const MatrixF oldTransform = mTransform;
    const MatrixF newTransform = mPath->getTransform();

    // Apply Old Values.
    mEditor->setPathTransform( oldTransform );

    // The ol' Switcheroo.
    mTransform = newTransform;

    // Update Selection.
    mEditor->updateSelection();

    if ( mPath == mEditor->mSelection.Path )
    {
        // Arg Buffer.
        char buffer[32];
        dSprintf( buffer, sizeof( buffer ), "%d", mPath->getId() );

        // Callback.
        Con::executef( mEditor, "onUpdatePath", buffer );
    }

    // Set World Editor Dirty.
    mEditor->setWorldEditorDirty();
}

void VPathEditor::VPathEditorEditPathAction::redo( void )
{
    // Undo.
    undo();
}

void VPathEditor::VPathEditorEditNodeAction::undo( void )
{
    // Fetch Properties.
    const Point3F oldPosition = mNodePosition;
    const QuatF   oldRotation = mNodeRotation;
    const F32     oldWeight   = mNodeWeight;
    const VPathNode::sOrientation oldOrientation = mNodeOrientation;

    VPathNode *node           = mPath->getNode( mNodeIndex );
    const Point3F newPosition = node->getLocalPosition();
    const QuatF   newRotation = node->getLocalRotation();
    const F32     newWeight   = node->getWeight();
    const VPathNode::sOrientation newOrientation = node->getOrientationMode();

    // Apply Old Values.
    mPath->setNodePosition( mNodeIndex, oldPosition );
    mPath->setNodeRotation( mNodeIndex, oldRotation );
    mPath->setNodeWeight( mNodeIndex, oldWeight );

    switch( oldOrientation.Type )
    {
    case VPathNode::k_OrientationFree :
        {

            // Orient Free.
            mPath->setNodeOrientationMode( mNodeIndex, oldOrientation.Type );

        } break;

    case VPathNode::k_OrientationToPoint :
        {

            // Orient To Point.
            mPath->setNodeOrientationMode( mNodeIndex, oldOrientation.Type, oldOrientation.Point );

        } break;
    }

    // The ol' Switcheroo.
    mNodePosition    = newPosition;
    mNodeRotation    = newRotation;
    mNodeWeight      = newWeight;
    mNodeOrientation = newOrientation;

    // Update Selection.
    mEditor->updateSelection();

    if ( mPath == mEditor->mSelection.Path )
    {
        // Arg Buffer.
        char buffer[3][32];
        dSprintf( buffer[0], sizeof( buffer[0] ), "%d", mPath->getId() );
        dSprintf( buffer[1], sizeof( buffer[1] ), "%d", mNodeIndex );
        dSprintf( buffer[2], sizeof( buffer[2] ), "%d", ( mEditor->mSelection.Node == mNodeIndex ) );

        // Callback.
        Con::executef( mEditor, "onUpdateNode", buffer[0], buffer[1], buffer[2] );
    }

    // Set World Editor Dirty.
    mEditor->setWorldEditorDirty();
}

void VPathEditor::VPathEditorEditNodeAction::redo( void )
{
    // Undo.
    undo();
}

void VPathEditor::VPathEditorAddNodeAction::undo( void )
{
    // Selected Node?
    if ( mNodeIndex == mEditor->mSelection.Node )
    {
        // Update Selection.
        mEditor->updateSelection( mEditor->mSelection.Path, -1 );
    }

    // Delete Node.
    mPath->deleteNode( mNodeIndex );

    // Update Size.
    mPath->updateContainer();

    // Calculate Path.
    mPath->calculatePath();

    // Set World Editor Dirty.
    mEditor->setWorldEditorDirty();
}

void VPathEditor::VPathEditorAddNodeAction::redo( void )
{
    // Add Node.
    VPathNode *node = mPath->addNode( mNodePosition, mNodeRotation, mNodeWeight, mNodeIndex );

    // Valid Node?
    if ( node )
    {
        // Update Size.
        mPath->updateContainer();

        // Calculate Path.
        mPath->calculatePath();
    }

    // Set World Editor Dirty.
    mEditor->setWorldEditorDirty();
}

void VPathEditor::VPathEditorDeleteNodeAction::undo( void )
{
    // Add Node.
    VPathNode *node = mPath->addNode( mNodePosition, mNodeRotation, mNodeWeight, mNodeIndex );

    // Valid Node?
    if ( node )
    {
        // Update Size.
        mPath->updateContainer();

        // Calculate Path.
        mPath->calculatePath();
    }

    // Set World Editor Dirty.
    mEditor->setWorldEditorDirty();
}

void VPathEditor::VPathEditorDeleteNodeAction::redo( void )
{
    // Delete Node.
    mPath->deleteNode( mNodeIndex );

    // Update Size.
    mPath->updateContainer();

    // Calculate Path.
    mPath->calculatePath();

    // Set World Editor Dirty.
    mEditor->setWorldEditorDirty();
}

//-----------------------------------------------------------------------------
//
// Script Edit Methods
//
//-----------------------------------------------------------------------------

ConsoleMethod( VPathEditor, setNodePosition, void, 3, 3, "( pPosition )" )
{
    // Valid Selection?
    if ( !object->isValidSelection() )
    {
        Con::warnf( "VPathEditor::setNodePosition() - Invalid Node Selection." );
        return;
    }

    // Fetch Position.
    Point3F position;
    dSscanf( argv[2], "%g %g %g", &position.x, &position.y, &position.z );

    // Store.
    object->pushNodeEdit();

    // Apply Update.
    object->setNodePosition( object->mSelection.Node, position );

    // Create Undo Action.
    object->popNodeEdit();
}

ConsoleMethod( VPathEditor, setNodeRotation, void, 3, 3, "( pRotation )" )
{
    // Valid Selection?
    if ( !object->isValidSelection() )
    {
        Con::warnf( "VPathEditor::setNodeRotation() - Invalid Node Selection." );
        return;
    }

    // Fetch Rotation.
    AngAxisF aa;
    QuatF    rotation;
    dSscanf( argv[2], "%g %g %g %g", &aa.axis.x, &aa.axis.y, &aa.axis.z, &aa.angle );

    // Set Rotation.
    rotation.set( aa );

    // Store.
    object->pushNodeEdit();

    // Apply Update.
    object->setNodeRotation( object->mSelection.Node, rotation );

    // Create Undo Action.
    object->popNodeEdit();
}

ConsoleMethod( VPathEditor, setNodeWeight, void, 3, 3, "( pWeight )" )
{
    // Valid Selection?
    if ( !object->isValidSelection() )
    {
        Con::warnf( "VPathEditor::setNodeWeight() - Invalid Node Selection." );
        return;
    }

    // Store.
    object->pushNodeEdit();

    // Apply Update.
    object->setNodeWeight( object->mSelection.Node, dAtof( argv[2] ) );

    // Create Undo Action.
    object->popNodeEdit();
}

ConsoleMethod( VPathEditor, setNodeOrientationMode, void, 3, 4, "( string pOrientationType, [vector pPoint] )" )
{
    // Valid Selection?
    if ( !object->isValidSelection() )
    {
        Con::warnf( "VPathEditor::setNodeOrientationMode() - Invalid Node Selection." );
        return;
    }

    // Store.
    object->pushNodeEdit();

    // Orient?
    const VPathNode::eOrientationType type = VPathNode::getOrientationTypeEnum( argv[2] );

    switch ( type )
    {
        case VPathNode::k_OrientationFree :
            {

                // Apply Mode.
                object->setNodeOrientationMode( object->mSelection.Node, type );

            } break;

        case VPathNode::k_OrientationToPoint:
            {

                // Fetch Point.
                Point3F lookAtPoint( 0.f, 0.f, 0.f );
                dSscanf( argv[3], "%g %g %g", &lookAtPoint.x, &lookAtPoint.y, &lookAtPoint.z );

                // Apply Mode.
                object->setNodeOrientationMode( object->mSelection.Node, type, lookAtPoint );

            } break;
    }

    // Create Undo Action.
    object->popNodeEdit();
}

//-----------------------------------------------------------------------------
//
// Utility
//
//-----------------------------------------------------------------------------

bool Utility::FindNearestDistanceBetweenLines( const Point3F &pA0, const Point3F &pA1, const Point3F &pB0, const Point3F &pB1, Point3F *pOutA, Point3F *pOutB, F32 *pDist )
{
    const Point3F pA1A0 = ( pA1 - pA0 );
    if ( pA1A0.isZero() )
    {
        return false;
    }

    const Point3F pB1B0 = ( pB1 - pB0 );
    if ( pB1B0.isZero() )
    {
        return false;
    }

    const Point3F pA0B0 = ( pA0 - pB0 );

    const F32 &d1343 = pA0B0.x * pB1B0.x + pA0B0.y * pB1B0.y + pA0B0.z * pB1B0.z;
    const F32 &d4321 = pB1B0.x * pA1A0.x + pB1B0.y * pA1A0.y + pB1B0.z * pA1A0.z;
    const F32 &d1321 = pA0B0.x * pA1A0.x + pA0B0.y * pA1A0.y + pA0B0.z * pA1A0.z;
    const F32 &d4343 = pB1B0.x * pB1B0.x + pB1B0.y * pB1B0.y + pB1B0.z * pB1B0.z;
    const F32 &d2121 = pA1A0.x * pA1A0.x + pA1A0.y * pA1A0.y + pA1A0.z * pA1A0.z;

    const F32 &denom = d2121 * d4343 - d4321 * d4321;
    if ( mIsZero( denom ) )
    {
        return false;
    }

    const F32 &mua = ( d1343 * d4321 - d1321 * d4343 ) / denom;
    const F32 &mub = ( d1343 + d4321 * mua ) / d4343;

    *pOutA = pA0 + mua *pA1A0;
    *pOutB = pB0 + mub *pB1B0;

    // Store Distance.
    *pDist = ( ( *pOutA ) - ( *pOutB ) ).len();

    return true;
}

bool Utility::IntersectLineSegment( const Point3F &pA0, const Point3F &pA1, const Point3F &pB0, const Point3F &pB1, const bool pSnap, Point3F *pX )
{
    //
    // Finding the intersection with the following method:
    // We have line a going from P1 to P2:
    //    Pa = P1 + ua( P2 - P1 )
    // and line b going from P3 to P4:
    //    Pb = P3 + ub( P4 - P3 )
    //
    // Solving for Pa = Pb:
    //    x1 + ua( x2 - x1 ) = x3 + ub( x4 - x3 )
    //    y1 + ua( y2 - y1 ) = y3 + ub( y4 - y3 )
    //
    // Solving for ua and ub:
    //    ua = ( ( x4 - x3 )( y1 - y3 ) - ( y4 - y3 )( x1 - x3 ) ) / d
    //    ub = ( ( x2 - x1 )( y1 - y3 ) - ( y2 - y1 )( x1 - x3 ) ) / d
    //    denom = ( y4 - y3 )( x2 - x1 ) - ( x4 - x3 )( y2 - y1 )
    //
    // x = x1 + ua( x2 - x1 )
    // y = y1 + ua( y2 - y1 )
    //

    const F32 d = ( ( pB1.y - pB0.y ) * ( pA1.x - pA0.x ) ) - ( ( pB1.x - pB0.x ) * ( pA1.y - pA0.y ) );

    if ( d == 0.0f )
    {
        // Lines are parallel
        return false;
    }

    // Find the point of intersection
    const F32 uA = ( ( ( pB1.x - pB0.x ) * ( pA0.y - pB0.y ) ) - ( ( pB1.y - pB0.y ) * ( pA0.x - pB0.x ) ) ) / d;
    const F32 uB = ( ( ( pA1.x - pA0.x ) * ( pA0.y - pB0.y ) ) - ( ( pA1.y - pA0.y ) * ( pA0.x - pB0.x ) ) ) / d;

    if ( !pSnap
         && ( ( uA < 0.0f ) || ( uA > 1.0f )
           || ( uB < 0.0f ) || ( uB > 1.0f ) ) )
    {
        return false;
    }

    if ( pX )
    {
        if ( uA < 0.0f )
        {
            *pX = pA0;
        }
        else if ( uA > 1.f )
        {
            *pX = pA1;
        }
        else
        {
            // The path intersects the segment
            *pX = pA0 + uA * ( pA1 - pA0 );
        }
    }

    return true;
}

bool Utility::FindNearestPointOnLine( const Point3F &pSrcPosition, const Point3F &pA0, const Point3F &pA1, Point3F *pDstPosition )
{
    const Point3F up( 0.0f, 0.0f, 1.0f );
    
    Point3F dir = ( pA1 - pA0 );
    dir.normalize();

    Point3F normal = mCross( dir, up );
    normal.normalize();

    // Find the nearest intersection point between the point and the line

    const Point3F b0 = pSrcPosition + ( normal * 100000.0f );
    const Point3F b1 = pSrcPosition - ( normal * 100000.0f );

    return IntersectLineSegment( pA0, pA1, b0, b1, true, pDstPosition );
}

F32 Utility::GetPitch( const VectorF &pVec )
{
    F32 pitch;
    if ( mFabs( pVec.x ) > mFabs( pVec.y ) )
    {
        pitch = mAtan2( mFabs( pVec.z ), mFabs( pVec.x ) );
    }
    else
    {
        pitch = mAtan2( mFabs( pVec.z ), mFabs( pVec.y ) );
    }

    if ( pVec.z < 0.f )
    {
        pitch = -pitch;
    }

    return pitch;
}

F32 Utility::GetYaw( const VectorF &pVec )
{
    F32 yaw = mAtan2( pVec.x, pVec.y );
    if ( yaw < 0.f )
    {
        yaw += M_2PI_F;
    }

    return yaw;
}