Model.cpp

#include "StdAfx.h"
#include "Model.h"


#include "Pose.h"

#include "Link.h"
#include "Joint.h"

#include "Shape.h"
#include "SupportPolygon.h"

#include "ForwardKinematics.h"
#include "InverseKinematics.h"


using namespace Core;
using namespace Robotics;

using namespace Microsoft;

using namespace System;
using namespace System::Diagnostics;


/// [ Constructor ]
Model::Model( Core::Links^ links ) :
    m_links( links )
{
    Core::Link::Body = m_links[ 0 ];    // 胴体


    // リンクの 位置と姿勢の変更イベントに登録する
    Core::Link::PositionAndPostureChanged
        += gcnew PositionAndPostureChangeEventHandler( this, &Model::LinkPositionAndPostureChanged );

    // 関節の構造変化イベントに登録する
    Joint::MechanicsChanged += gcnew System::EventHandler( this, &Model::Recompute );
}

/// [ Finalizer ]
Model::!Model()
{
    delete m_centerOfGravityFigure;
}


/// 状態を初期化する
void Model::InitializeState()
{
    for each( Core::Link^ link in m_links )
    {
        // 全ての関節角度を初期値にする
        link->Joint->InitializeAngle();
    }


    // 胴体を 原点位置に強制的に設定する
    Core::Link::Body->SetPositionAndPostureForcibly(
        gcnew PositionVector(),
        gcnew RotationMatrix()
    );

    // ... リンクの位置と姿勢を求める
    ForwardKinematics::CalculatePositionAndPostureOfLinks( Core::Link::Body );

    // ... 地面との衝突を回避する
    EvadeCollisionWithGround();


    /// @note
    /// イベントを発生させないために、再計算メソッドを呼んで処理させていない。


    Debug::WriteLine( "モデルを初期化した" );
}



/// 再計算する
void Model::Recompute( System::Object^ sender, System::EventArgs^ )
{
    // 引数から目的とする型を取得する
    Joint^ joint = safe_cast< Joint^ >( sender );


    // リンクの位置と姿勢を求める
    ForwardKinematics::CalculatePositionAndPostureOfLinks( Link[ joint->Name ] );

    // ... 状態変化イベントを発生する
    StateChanged( this, nullptr );
}


/// 地面との衝突を回避する
bool Model::EvadeCollisionWithGround()
{
    const double ThresholdHeight = 0.001;    // 衝突しているとみなす高さ[ m ]


    /// @note
    /// ここではリンクの位置ではなく、形状の頂点位置について考えている。


    // 最低高さのリンクを取得する
    Core::Link^ link = GetLinkInLowestPosition();

    // ... そのリンクの形状の最下部を取得する
    PositionVector^ bottom = link->GetBottomOfShape();

    // 衝突しているか?
    if( bottom->Z < ThresholdHeight )
    {
        PositionVector^ position = link->AbsolutePosition;  // リンクの絶対位置
        RotationMatrix^ posture = link->Posture;            // リンクの姿勢

        // 地面に接地させる
        position->Z = 0.0;

        // 水平にする
        posture->Horizontal();


        // 衝突しているリンクを それを回避するように強制的に設定する
        link->SetPositionAndPostureForcibly( position, posture );

        // ... それを基準に 再計算する
        Recompute( link->Joint, nullptr );


        /// @todo
        /// 姿勢の修正によって 他のリンクが衝突する可能性があるので、
        /// 衝突の確認をくり返す。


        Debug::WriteLine( String::Format( "地面との衝突を回避した ( 上方に {0:0.000}[ m ] )", -bottom->Z ) );
        Debug::Assert( Math::Abs( link->GetBottomOfShape()->Z ) < ThresholdHeight, "衝突を回避したリンクは 地面に接している" );

        return true;
    }
    return false;
}

/// 最低高さのリンクを取得する
Core::Link^ Model::GetLinkInLowestPosition()
{
    Core::Link^ result;
    double bottom = Double::MaxValue;   // 最下部

    for each( Core::Link^ target in m_links )
    {
        // リンクの形状の最下部を取得する
        PositionVector^ position = target->GetBottomOfShape();

        // ... それの高さと比較する
        if( position->Z < bottom )
        {
            bottom = position->Z;
            result = target;
        }
    }
    Debug::Assert( result != nullptr, "最下部のリンクは存在する" );

    return result;
}


/// リンクの位置と姿勢が変更された
void Model::LinkPositionAndPostureChanged( System::Object^ sender, PositionAndPostureChangeEventArgs^ e )
{
    // 引数から目的とする型を取得する
    Core::Link^ targetLink = safe_cast< Core::Link^ >( sender );


    /// @todo
    /// 直前のポーズに戻してから、逆運動学の演算を実行するようにする。
    /// それにより、より適する解を得られると思われる。


    // 対象となるリンクと それの位置と姿勢を格納するためのリストを生成する
    LinkList^ targetLinks = gcnew LinkList();
    Collections::Generic::List< PositionVector^ >^ targetPositions = gcnew Collections::Generic::List< PositionVector^ >();
    Collections::Generic::List< RotationMatrix^ >^ targetPostures = gcnew Collections::Generic::List< RotationMatrix^ >();

    // ... 指定されたリンクと その位置と姿勢を格納する
    targetLinks->Add( targetLink );
    targetPositions->Add( e->Position );
    targetPostures->Add( e->Posture );


    for each( Core::Link^ link in m_links )
    {
        if( !link->IsStandard() && link->IsSupportingPhase() )
        {
            // 基準リンクではなく支持相ならば、それを保存する
            targetLinks->Add( link );
            targetPositions->Add( gcnew PositionVector( link->AbsolutePosition ) );
            targetPostures->Add( gcnew RotationMatrix( link->Posture ) );
        }
    }


    // 逆運動学クラスを生成する
    InverseKinematics^ inverseKinematics = gcnew InverseKinematics( m_links );

    for( int i = 0; i < targetLinks->Count ; i++ )
    {
        // 全ての関節の回転角を求める
        bool success = inverseKinematics->CalculateAngleOfAllJoints(
            targetLinks[ i ],
            targetPositions[ i ],
            targetPostures[ i ]
        );

        // 求まらなかったならば、処理を中止する
        if( !success ) return;


        // 対象が胴体でなければ、処理を終える
        if( !targetLink->IsBody() ) break;

        /// @note
        /// 胴体以外ならば、支持相のリンクが移動させられることはないので、
        /// 複数のリンクについて、演算を繰り返す必要はない。
    }

    // 状態変化イベントを発生する
    StateChanged( this, nullptr );
}


/// リンクを取得する [ Property ]
Core::Link^ Model::Link::get( System::String^ name )
{
    for each( Core::Link^ link in m_links )
    {
        if( link->Name == name )
        {
            // 名称の一致するリンクを返す
            return link;
        }
    }

    Debug::Fail( "一致するリンクが存在しない" );
    return nullptr;
}

/// リンク番号を取得する
int Model::GetLinkIndex( Core::Link^ link )
{
    for( int index = 0; index < m_links->Length; index++ )
    {
        if( m_links[ index ] == link )
        {
            return index;
        }
    }
    Debug::Fail( "一致するリンクが存在しない" );
    return 0;
}


/// 支持多角形を取得する [ Property ]
Core::SupportPolygon^ Model::SupportPolygon::get()
{
    // リンクから支持多角形を生成して返す
    return Core::SupportPolygon::Create( m_links );
}


/// 重心を取得する [ Property ]
Robotics::PositionVector^ Model::CenterOfGravity::get()
{
    // 全てのリンクの 重心を求める
    return CalculateCenterOfGravity( LinkLists );
}

/// 質量を取得する [ Property ]
double Model::Mass::get()
{
    // リンクの質量を求める
    return CalculateMass( LinkLists );
}


/// ポーズをとる
void Model::DoPose( Pose^ pose )
{
    // ポーズをとる
    pose->PosedModel( this );

    // ... 状態変化イベントを発生する
    StateChanged( this, nullptr );
}


/// リンクを地面に接地させる
void Model::LinkGrounded( Distinction^ distinction )
{
    if( distinction != nullptr )
    {
        // リンクを取得する
        Core::Link^ link = Link[ distinction->Name ];

        // リンクが形状であり、基準リンクではないことを確認する
        if( ( distinction->GetType() == Shape::typeid ) && ( !link->IsStandard() ) )
        {
            // 支持相でなければ、地面に接地させる
            if( !link->IsSupportingPhase() )
            {
                PositionVector^ position = link->AbsolutePosition;  // リンクの絶対位置
                RotationMatrix^ posture = link->Posture;            // リンクの姿勢

                // 地面に接地させる
                position->Z = 0.0;

                // 水平にする
                posture->Horizontal();


                // リンクの位置と姿勢を設定する
                link->SetPositionAndPosture( position, posture );
            }


            if( link->IsSupportingPhase() )
            {
                // 支持相に設定されたならば、基準リンクにする
                Core::Link::Standard = link;
            }
        }
    }
}


/// 重心を求める
Robotics::PositionVector^ Model::CalculateCenterOfGravity( LinkList^ links )
{
    PositionVector^ result = gcnew PositionVector( 0.0, 0.0, 0.0 );

    // 指定されたリンクの ワールド座標系における 重心ベクトルを合計する
    for each( Core::Link^ link in links )
    {
        result += link->GetCenterOfGravityInWorldPosition();
    }
    // ... 得られたモーメントを それらの全質量で割って返す
    return( result / CalculateMass( links ) );


    // [ Reference ]「ヒューマノイドロボット」梶田秀司(オーム社) P90
}

/// 質量を求める
double Model::CalculateMass( LinkList^ links )
{
    double result = 0.0;

    // 指定されたリンクの 質量を合計する
    for each( Core::Link^ link in links )
    {
        result += link->Mass;
    }
    return result;
}


/// 図形を生成する
void Model::CreateFigure( Microsoft::DirectX::Direct3D::Device^ device )
{
    m_centerOfGravityFigure = DirectX::Direct3D::Mesh::Sphere(
        device,
        0.01f,  // 球の半径[ m ]
        8,      // Z軸に平行な分割数
        8       // Z軸に垂直な分割数
        );


    Debug::WriteLine( "リンクの図形を生成する" );

    for each( Core::Link^ link in m_links )
    {
        // リンクの図形を生成する
        link->CreateFigure( device );
    }
}

/// 描画する
void Model::Render( Microsoft::DirectX::Direct3D::Device^ device )
{
    // ライトを有効にする
    device->RenderState->Lighting = true;


    // マテリアルを生成する
    DirectX::Direct3D::Material material;

    material.Ambient = Drawing::Color::Yellow;  // 環境光に対する反射係数
    material.Diffuse = Drawing::Color::Yellow;  // 拡散反射係数

    // ... それをマテリアルに設定する
    device->Material = material;


    PositionVector^ position = CenterOfGravity; // 重心

    // ... ワールド座標に それを設定する
    device->Transform->World
        = DirectX::Matrix::Translation(
            safe_cast< float >( position->X ),
            safe_cast< float >( position->Y ),
            safe_cast< float >( position->Z )
            );

    // レンダリングする
    m_centerOfGravityFigure->DrawSubset( 0 );
}