DrawModel.cpp

#include "StdAfx.h"
#include "DrawModel.h"


#include "AxisOfCoordinate.h"
#include "AxisOfRotation.h"


#include "Model.h"
#include "Link.h"

#include "Joint.h"
#include "Shape.h"

#include "ConnectionRelation.h"
#include "SupportPolygon.h"

#include "Torque.h"


using namespace Core;
using namespace Robotics;

using namespace Microsoft;

using namespace System;
using namespace System::Diagnostics;
using namespace System::Runtime::InteropServices;


// DirectXのライブラリによる const/volatile 修飾子の使用の警告を抑制する
#pragma warning( disable : 4400 )


/// [ Constructor ]
DrawModel::DrawModel( Microsoft::DirectX::Direct3D::Device^ device, System::Object^ obj ) :
    m_device( device ),
    m_model( safe_cast< Model^ >( obj ) )
{
    // モデルの図形を生成する
    CreateModelFigure();


    // 座標軸を生成する
    m_axisOfCoordinate = gcnew AxisOfCoordinate( m_device, 0.05f );

    // 回転軸を生成する
    m_axisOfRotation = gcnew AxisOfRotation( m_device, 0.15f );
}

/// モデルの図形を生成する
void DrawModel::CreateModelFigure()
{
    m_model->CreateFigure( m_device );
}


/// 描画する
void DrawModel::Render()
{
    // リンクを描画する
    DrawLink();

    // 重心を描画する
    m_model->Render( m_device );


    // 支持多角形を取得する
    SupportPolygon^ supportPolygon = m_model->SupportPolygon;

    // ... それの図形を生成する
    supportPolygon->CreateFigure( m_device );

    // ... それを描画する
    supportPolygon->Render( m_device );
}


/// リンクを描画する
void DrawModel::DrawLink()
{
    // トルクを生成する
    Torque^ torque = gcnew Torque( m_model );

    for each( Link^ link in m_model->Links )
    {
        double momentOfForce = Math::Abs( torque[ link ] ); // リンクのトルク

        // ... 最大値に対する割合を求める ( 1.0を超えるならば 1.0に補正する )
        double ratio = Math::Min( momentOfForce / Torque::Maximum, 1.0 );

        const double HueOfBlue = 240.0; // 青の色相
        const double HueOfRed = 0.0;    // 赤の色相

        // 色を HSB色空間から取得する
        Drawing::Color jointColor = GetColorFromHSB(
            HueOfBlue - ( HueOfBlue - HueOfRed ) * ratio,   // 色相
            1.0,                                            // 彩度
            1.0                                             // 明度
            );

        // ... それを関節の色に設定する
        link->Joint->Color = jointColor;


        // リンクの位置へ移動する
        TransferToLinkPosition( link );

        switch( m_expressionMode )
        {
            case ExpressionModel::RealModel:

                // リンクの リアルなモデルを描画する
                link->RenderRealModel( m_device );
                break;

            case ExpressionModel::MechanismModel:

                // リンクの 機構モデルを描画する
                link->RenderMechanismModel( m_device );
                break;

            default:
                Debug::Fail( "表示モードが設定されていない" );
        }

        // ... 選択されているリンクを描画する
        DrawSelectedLink( link );
    }
}

/// 選択されているリンクを描画する
void DrawModel::DrawSelectedLink( Link^ link )
{
    // 選択されているリンクがなければ、何もしない
    if( m_selectedLink == nullptr ) return;


    if( m_selectedLink == link->Joint )
    {
        // 関節が選択されているならば、その位置を取得する
        AxisVector^ position = link->Joint->AxialDirection;

        // ... 回転軸を描画する
        m_axisOfRotation->Render( m_device, position );
    }
    else if( m_selectedLink == link->Shape )
    {
        // 形状が選択されているならば、その位置を取得する
        PositionVector^ position = link->Shape->RelativePosition;


        /// @todo
        /// スクリーンの最前面へ 位置を移動する
        ///
        /// デバイスからカメラの情報を取得して、
        /// そこから求められるかも知れない。


        // ... 座標軸を描画する
        m_axisOfCoordinate->Render( m_device, position );
    }
}


/// リンクを選択する
Distinction^ DrawModel::SelectLink( System::Drawing::Point location )
{
    // 選択されているリンクをクリアする
    m_selectedLink = nullptr;

    // floatへキャストする
    Drawing::PointF locationF(
        safe_cast< float >( location.X ),
        safe_cast< float >( location.Y )
        );

    // 交点までの距離を 最大値で初期化する
    float distanceToIntersection = Single::MaxValue;


    for each( Link^ link in m_model->Links )
    {
        float distance;


        // 関節との交差を調べる
        distance = ComputeDistanceToIntersection( link->Joint, locationF );

        if( distance < distanceToIntersection )
        {
            // 手前にあるならば、交点までの距離を更新する
            distanceToIntersection = distance;

            // 対象とした関節を 選択されているリンクとする
            m_selectedLink = link->Joint;
        }


        // 形状との交差を調べる
        distance = ComputeDistanceToIntersection( link->Shape, locationF );

        if( distance < distanceToIntersection )
        {
            // 手前にあるならば、交点までの距離を更新する
            distanceToIntersection = distance;

            // 対象とした形状を 選択されているリンクとする
            m_selectedLink = link->Shape;
        }
    }
    return m_selectedLink;
}

/// オブジェクトとの交点までの距離を取得する
float DrawModel::ComputeDistanceToIntersection( Distinction^ distinction, System::Drawing::PointF location )
{
    float result = Single::MaxValue;    // 最長距離

    if( distinction != nullptr )
    {
        // 図形を有するか確認する
        if( distinction->Figure != nullptr )
        {
            DirectX::Vector3 near( location.X, location.Y, m_device->Viewport.MinZ );   // 近平面の位置
            DirectX::Vector3 far( location.X, location.Y, m_device->Viewport.MaxZ );    // 遠平面の位置

            // 近平面を オブジェクトのローカル空間に座標変換する
            DirectX::Vector3 vNear = DirectX::Vector3::Unproject(
                near,
                m_device->Viewport,                 // ビューポート
                m_device->Transform->Projection,    // 投影行列
                m_device->Transform->View,          // ビュー行列
                distinction->WorldTransformOfFigure
                );

            // 遠平面を オブジェクトのローカル空間に座標変換する
            DirectX::Vector3 vFar = DirectX::Vector3::Unproject(
                far,
                m_device->Viewport,                 // ビューポート
                m_device->Transform->Projection,    // 投影行列
                m_device->Transform->View,          // ビュー行列
                distinction->WorldTransformOfFigure
                );

            // ... ローカル空間での レイの方向ベクトルを生成する
            DirectX::Vector3 rayDirection = DirectX::Vector3::Normalize( vFar - vNear );


            // 交点を記録するための構造体を生成する
            DirectX::Direct3D::IntersectInformation closestHit;

            // ... 2つの座標を結ぶベクトルと オブジェクトとの交差を調べる
            if( distinction->Figure->Intersect( vNear, rayDirection, closestHit ) )
            {
                // 交差しているならば、その交点までの距離を取得する
                result = closestHit.Dist;
            }
        }
    }
    return result;
}


/// リンクの位置へ移動する
void DrawModel::TransferToLinkPosition( Link^ link )
{
    RotationMatrix^ posture;

    // 胴体か?
    if( link->IsBody() )
    {
        posture = link->Posture;                    // 胴体の姿勢
    }
    else
    {
        posture = link->Connection->Upper->Posture; // 接続元のリンクの姿勢
    }

    // リンクの関節の 絶対位置を取得する
    PositionVector^ position = link->Joint->AbsolutePosition;

    // ... ワールド座標系における位置と姿勢を求める
    m_device->Transform->World
        = ConvertToMatrix( posture )
        * DirectX::Matrix::Translation(
        safe_cast< float >( position->X ),
        safe_cast< float >( position->Y ),
        safe_cast< float >( position->Z )
        );
}


/// 表示モードを設定する [ Property ]
void DrawModel::ExpressionMode::set( ExpressionModel value )
{
    m_expressionMode = value;

    for each( Link^ link in m_model->Links )
    {
        // 形状に 表示モードを設定する
        link->Shape->SetExpressionMode( value );
    }
}


/// 行列に変換する
Microsoft::DirectX::Matrix DrawModel::ConvertToMatrix( Robotics::RotationMatrix^ matrix )
{
    // それぞれの軸周りに回転させる
    DirectX::Matrix result
        = DirectX::Matrix::RotationX( safe_cast< float >( matrix->Roll ) )
        * DirectX::Matrix::RotationY( safe_cast< float >( matrix->Pitch ) )
        * DirectX::Matrix::RotationZ( safe_cast< float >( matrix->Yaw ) );

    return result;


    /// @note
    /// 回転行列クラスが返す ロール・ピッチ・ヨー角にバグがあるため、
    /// Matrix::RotationYawPitchRoll メソッドを使用していない。
}


/// HSB色空間から Colorを取得する
System::Drawing::Color DrawModel::GetColorFromHSB( double hue, double saturation, double brightness )
{
    Debug::Assert( 0.0 <= hue && hue < 360.0, "色相は 0-360の範囲である" );
    Debug::Assert( 0.0 <= saturation && saturation <= 1.0, "彩度は 0.0-1.0の範囲である" );
    Debug::Assert( 0.0 <= brightness && brightness <= 1.0, "明度は 0.0-1.0の範囲である" );


    int red = 0;    // Red
    int green = 0;  // Green
    int blue = 0;   // Blue

    // 明度を 0-255の値に変換する
    int iBrightness = safe_cast< int >( 255 * brightness );


    if( saturation == 0.0 )
    {
        // 彩度が 0.0ならば、モノクロームになる
        red = iBrightness;
        green = iBrightness;
        blue = iBrightness;
    }
    else
    {
        // 色相を 0-5の値に変換する
        int iHue = safe_cast< int >( hue / 60.0 );

        double f = ( hue / 60.0 ) - iHue;

        int p = safe_cast< int >( iBrightness * ( 1.0 - saturation ) );
        int q = safe_cast< int >( iBrightness * ( 1.0 - f * saturation ) );
        int t = safe_cast< int >( iBrightness * ( 1.0 - ( 1.0 - f ) * saturation ) );

        switch( iHue )
        {
            case 0:
                red = iBrightness;
                green = t;
                blue = p;
                break;

            case 1:
                red = q;
                green = iBrightness;
                blue = p;
                break;

            case 2:
                red = p;
                green = iBrightness;
                blue = t;
                break;

            case 3:
                red = p;
                green = q;
                blue = iBrightness;
                break;

            case 4:
                red = t;
                green = p;
                blue = iBrightness;
                break;

            case 5:
                red = iBrightness;
                green = p;
                blue = q;
                break;

            default:
                Debug::Fail( "予期しない値である" );
        };
    }
    // RGBから Colorを生成して返す
    return Drawing::Color::FromArgb( red, green, blue );

    // [ Reference ] HSV色空間 - Wikipedia ( http://ja.wikipedia.org/wiki/HSV%E8%89%B2%E7%A9%BA%E9%96%93 )
}