Draw.cpp

#include "StdAfx.h"
#include "Draw.h"


#include "DrawModel.h"
#include "DrawScene.h"


using namespace Core;
using namespace Microsoft;

using namespace System;
using namespace System::Diagnostics;


/// [ Constructor ]
Draw::Draw() :
    m_viewPoint( 0.0f, 0.0f, 0.2f ),
    m_lightColor( Drawing::Color::White )
{
}

/// [ Finalizer ]
Draw::!Draw()
{
    // デバイスを破棄する
    delete m_device;
}


/// 初期化する
bool Draw::Initialize( System::Windows::Forms::Control^ control, System::Object^ obj )
{
    Debug::WriteLine( "描画クラスを初期化する" );

    try
    {
        // プレゼンテーション パラメータを生成する
        DirectX::Direct3D::PresentParameters^ parameters
            = gcnew DirectX::Direct3D::PresentParameters();

        parameters->Windowed = true;     // 画面モード
        parameters->SwapEffect = DirectX::Direct3D::SwapEffect::Discard;    // スワップ・エフェクト

        parameters->EnableAutoDepthStencil = true;                                  // 深度ステンシルバッファ
        parameters->AutoDepthStencilFormat = DirectX::Direct3D::DepthFormat::D16;   // 自動深度ステンシル サーフェイスのフォーマット


        // デバイス作成を制御するフラグを生成する
        DirectX::Direct3D::CreateFlags flags = DirectX::Direct3D::CreateFlags::FpuPreserve;

        /// @note
        /// Direct3Dのデバイス生成時には、デフォルトで FPUの演算がで単精度に変更される。
        /// これを回避するためには、CreateFlags::FpuPreserve フラグを指定して、
        /// 倍精度を使用するように指示する必要がある。
        ///
        /// http://d.hatena.ne.jp/NyaRuRu/20040820/p1
        /// http://blogs.msdn.com/tmiller/archive/2004/06/01/145596.aspx
        /// http://www.microsoft.com/japan/msdn/community/gdn/ShowPost-20454.htm


        // デフォルトのアダプタを保存する
        int adapterOrdinal = DirectX::Direct3D::Manager::Adapters->Default->Adapter;

        // ... デバイスの能力を取得する
        DirectX::Direct3D::Caps caps
            = DirectX::Direct3D::Manager::GetDeviceCaps( adapterOrdinal, DirectX::Direct3D::DeviceType::Hardware );

        // ハードウェアによる頂点処理をサポートしているか?
        if( caps.DeviceCaps.SupportsHardwareTransformAndLight )
        {
            flags = flags | DirectX::Direct3D::CreateFlags::HardwareVertexProcessing;  // ハードウェア頂点処理
        }
        else
        {
            flags = flags | DirectX::Direct3D::CreateFlags::SoftwareVertexProcessing;  // ソフトウェア頂点処理
        }

        /// @note
        /// PureDeviceを使用することで、パフォーマンスを向上できるが、
        /// 頂点情報を取得できなくなるため、使用していない。
        ///
        /// Microsoft DirectX 9 開発者向け FAQ ( D3DCREATE_PUREDEVICEの項目 )
        /// http://msdn.microsoft.com/library/ja/default.asp?url=/library/ja/jpdndxgen/htm/directx9devfaq.asp


        // デバイスを生成する
        m_device = gcnew DirectX::Direct3D::Device(
            0,                                       // ディスプレイ・アダプタを示す序数
            DirectX::Direct3D::DeviceType::Hardware, // デバイス・タイプ
            control,                                 // フォーカスを設定する ウィンドウ・ハンドル
            flags,                                   // デバイス作成を制御するフラグ
            parameters                               // デバイスの動作
            );

        // [ Reference ]「Managed DirectX9」Tom Miller(Sams) P81


        // モデルの描画を生成する
        m_drawModel = gcnew DrawModel( m_device, obj );

        // 背景の描画を生成する
        m_drawScene = gcnew DrawScene( m_device );


        // デバイスをリセットした後のイベントに登録する
        m_device->DeviceReset += gcnew System::EventHandler( this, &Draw::ResetDevice );

        // ... デバイスをリセットする
        ResetDevice( m_device, nullptr );
    }
    catch( DirectX::DirectXException^ e )
    {
        // 生成に失敗した
        Debug::WriteLine( e );

        // ... 下位のパフォーマンスで生成を試みる

        return false;
    }
    return true;
}


/// 画面サイズを設定する
void Draw::SetupScreenSize( int width, int height)
{
    // デバイスの動作を設定するクラスを取得する
    DirectX::Direct3D::PresentParameters^ parameters = m_device->PresentationParameters;

    // ... バックバッファのサイズを設定する
    parameters->BackBufferWidth = width;
    parameters->BackBufferHeight = height;


    // ビューポートを設定する
    DirectX::Direct3D::Viewport viewport;
    viewport.Width = width;
    viewport.Height = height;

    // ... それをデバイスに設定する
    m_device->Viewport = viewport;
}


/// デバイスがリセットされた (リソースを再生成する)
void Draw::ResetDevice( System::Object^ sender, System::EventArgs^ )
{
    // 引数から目的とする型を取得する
    DirectX::Direct3D::Device^ device = safe_cast< DirectX::Direct3D::Device^ >( sender );

    device->RenderState->CullMode = DirectX::Direct3D::Cull::Clockwise; // カリングを有効
    device->RenderState->ZBufferEnable = true;  // Zバッファを有効


    // ビューを設定する
    SetupView();

    // カメラを設定する
    SetupCamera();

    // ライトを設定する
    SetupLights();
}


/// 描画する
void Draw::Render()
{
    // デバイスが動作可能か確認する
    if( IsDeviceEnable() )
    {
        // ライトの方向を設定する (視点からの方向とする)
        m_device->Lights[ 0 ]->Direction = -Viewpoint;

        // ... 更新する
        m_device->Lights[ 0 ]->Update();


        // 画面をクリアする (色はメタセコイアの背景色と同一)
        Clear( Drawing::Color::FromArgb( 30, 65, 90 ) );


        // 描画を開始する
        m_device->BeginScene();

        // ... 背景を描画する
        m_drawScene->Render();

        // ... モデルを描画する
        m_drawModel->Render();


        // ... 描画を終了する
        m_device->EndScene();

        // シーンを表示する
        m_device->Present();
    }
}

/// デバイスは動作可能か?
bool Draw::IsDeviceEnable()
{
    if( m_device == nullptr )
    {
        // デバイスが生成されていない
        return false;
    }

    // デバイスの現在の協調レベル ステータスを取得して、デバイスが動作可能か調べる
    int resultCode;
    if( !m_device->CheckCooperativeLevel( resultCode ) )
    {
        // デバイスが消失しているので、デバイスの復元を行う
        switch( safe_cast< DirectX::Direct3D::ResultCode >( resultCode ) )
        {
            case DirectX::Direct3D::ResultCode::DeviceLost:
            {
                // まだリセットできる状態ではないので少し待つ
                const int WaitTime = 10;    // [ msec ]
                Threading::Thread::Sleep( WaitTime );
            }
            return false;

            case DirectX::Direct3D::ResultCode::DeviceNotReset:

                // デバイスをリセットする
                m_device->Reset( m_device->PresentationParameters );
                break;

            default:

                // 例外を投げる
                throw gcnew System::Exception( "デバイスの消失後、復元ができない" );
        }


        /// @note
        /// デバイスの消失は、ウィンドウのサイズ変更時などに発生する。
    }
    return true;
}


/// ライトを設定する
void Draw::SetupLights()
{
    DirectX::Direct3D::Caps caps = m_device->DeviceCaps;    // デバイスの能力

    Debug::Assert( caps.VertexProcessingCaps.SupportsDirectionalLights, "平行光線のライトはサポートされている" );
    Debug::Assert( 1 <= caps.MaxActiveLights, "1つ以上のライトが存在する" );


    /// @todo
    /// ライトがサポートされていない場合にも対応させる。


    DirectX::Direct3D::Light^ light = m_device->Lights[ 0 ];    // ライト

    light->Type = DirectX::Direct3D::LightType::Directional;    // 種類
    light->Diffuse = m_lightColor;                              // 色
    light->Enabled = true;                                      // 有効

    // 更新する
    light->Update();
}

/// ビューを設定する
void Draw::SetupView()
{
    // ビュー変換行列を 右手座標系で設定する
    m_device->Transform->View = DirectX::Matrix::LookAtRH(
        Viewpoint,                              // 視点
        m_viewPoint,                            // 注視対象 [ m ]
        DirectX::Vector3( 0.0f, 0.0f, 1.0f)     // ワールド座標の上方
        );
}

/// カメラを設定する
void Draw::SetupCamera()
{
    float viewAngle = m_scale;   // 視野角(画角) [deg]

    // アスペクト比を求める
    float aspectRatio
        = safe_cast< float >( m_device->Viewport.Width )
        / safe_cast< float >( m_device->Viewport.Height );

    // 射影変換行列を 右手座標系で設定する
    m_device->Transform->Projection = DirectX::Matrix::PerspectiveFovRH(
        DirectX::Direct3D::Geometry::DegreeToRadian( viewAngle ),    // 視野角
        aspectRatio,    // アスペクト比(空間の高さを幅で割った値)
        0.5f,           // 近クリップ面の距離 [ m ]
        2.5f            // 遠クリップ面の距離 [ m ]
        );
}

/// 視点を取得する [ Property ]
Microsoft::DirectX::Vector3 Draw::Viewpoint::get()
{
    // 角度から 注視対象との視点の位置を求める
    DirectX::Vector3 result(
        safe_cast< float >( ( Math::Sin( m_zenithAngle ) * Math::Cos( m_azimuthAngle ) ) ),
        safe_cast< float >( ( Math::Sin( m_zenithAngle ) * Math::Sin( m_azimuthAngle ) ) ),
        safe_cast< float >( ( Math::Cos( m_zenithAngle ) ) )
        );

    // ... 視点を加算して返す
    return result + m_viewPoint;

    // [ Reference ]「工業力学」入江敏博(理工学社) P113
}

/// 縮尺を設定する [ Property ]
void Draw::Scale::set( float value )
{
    m_scale = value;


    // 縮尺を制限する
    const float Minimum = 5.0f;
    const float Maximum = 90.0f;
    m_scale = Math::Max( m_scale, Minimum );
    m_scale = Math::Min( m_scale, Maximum );

    /// @note 値は適当


    // カメラを設定する
    SetupCamera();

    // ... 描画する
    Render();
}


/// 視点を回転する
void Draw::RotateViewpoint( float azimuthAngle, float zenithAngle )
{
    // 方位角に加算する
    m_azimuthAngle += azimuthAngle;

    // 天頂角に加算する
    m_zenithAngle += zenithAngle;


    const float Little = 0.001f;    // 微少量

    // 天頂角を制限する
    if( m_zenithAngle <= 0.0f )
    {
        m_zenithAngle = Little;
    }
    else if( Math::PI <= m_zenithAngle )
    {
        m_zenithAngle = safe_cast< float >( Math::PI - Little );
    }


    // ビューを設定する
    SetupView();

    // ... 描画する
    Render();
}

/// 視点を移動する
void Draw::TranslateViewpoint( float x, float y )
{
    double zenithCos = Math::Cos( m_zenithAngle );
    double zenithSin = Math::Sin( m_zenithAngle );

    double azimuthCos = Math::Cos( m_azimuthAngle );
    double azimuthSin = Math::Sin( m_azimuthAngle );

    // 視点に加算する
    m_viewPoint.X -= safe_cast< float >( ( zenithCos * azimuthCos ) * y - azimuthSin * x );
    m_viewPoint.Y -= safe_cast< float >( ( zenithCos * azimuthSin ) * y + azimuthCos * x );
    m_viewPoint.Z += safe_cast< float >( zenithSin * y );


    /// @todo 移動位置を制限する。


    // ビューを設定する
    SetupView();

    // ... 描画する
    Render();
}


/// 画面をクリアする
void Draw::Clear( System::Drawing::Color color )
{
    m_device->Clear(
        DirectX::Direct3D::ClearFlags::Target | // 消去する対象
        DirectX::Direct3D::ClearFlags::ZBuffer,
        color,                                  // 塗りつぶす色
        1.0f,                                   // Z深度
        0                                       //
        );
}


/// オブジェクトを選択する
bool Draw::SelectObject( System::Drawing::Point positionToSelect )
{
    // 現在 選択されているリンクを保存する
    Distinction^ selectedLink = m_drawModel->SelectedLink;

    // リンクを選択する
    if( selectedLink == m_drawModel->SelectLink( positionToSelect ) )
    {
        // 選択が変更されなかった
        return false;
    }

    // 再描画する
    Render();

    return true;
}