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;
}