Rotate 3D

Rotate 3D

2D画面で3次元座標の回転関数をテストします。

前田稔(Maeda Minoru)の超初心者のプログラム入門

プログラムの作成

  1. Win10 Base DirectX Windows10 のベースプロジェクトをベースに3次元座標の回転関数をテストします。
    Win10 Base のプロジェクトを参照して生成して下さい。
    空のウインドウが表示されます。
  2. Draw.h, Draw.cpp を Content\ に格納します。
    App1\Content\ を選択して[追加][既存の項目]からプロジェクトに追加します。
    ソースコードはこのページの後部に掲載しています。
  3. App1Main.h に Draw.h を取り込んで std::unique_ptr<Draw> を定義します。
    自動生成の2D描画の名前が m_fpsTextRenderer になっているのでそのまま使います。
    #include "Content\Draw.h"
    
        //std::unique_ptr<SampleFpsTextRenderer> m_fpsTextRenderer;
        std::unique_ptr<Draw> m_fpsTextRenderer;
    
  4. App1Main.cpp に SampleFpsTextRenderer に代えて Draw を組み込みます。
    【自動生成】
        m_fpsTextRenderer = std::unique_ptr<SampleFpsTextRenderer>(new SampleFpsTextRenderer(m_deviceResources));
    
    【Draw を組み込む】
        m_fpsTextRenderer = std::unique_ptr<Draw>(new Draw(m_deviceResources));
    
    ベースプロジェクトを作成したとき m_fpsTextRenderer が使われている行をコメントアウトしています。
    m_fpsTextRenderer が使われている行のコメントを外して下さい。
    プログラムを実行すると、小さな黄色のマークが円状に移動しながら描画されます。
  5. Draw.h のソースコードです。
    #pragma once
    
    #include <string>
    #include "..\Common\DeviceResources.h"
    #include "..\Common\StepTimer.h"
    struct float3
    {
        float   x;
        float   y;
        float   z;
    };
    
    namespace App1
    {
        class Draw
        {
        public:
            Draw(const std::shared_ptr<DX::DeviceResources>& deviceResources);
            void CreateDeviceDependentResources();
            void ReleaseDeviceDependentResources();
            void Update(DX::StepTimer const& timer);
            void Render();
            float3 f3(float x, float y, float z);
            float3 Rot3D(float3 pos, float rx, float ry, float rz)
            void Debug(float v1, float v2, float v3);
    
        private:
            // デバイス リソースへのキャッシュされたポインター。
            std::shared_ptr<DX::DeviceResources> m_deviceResources;
    
            // レンダリングに関連するリソース。
            Microsoft::WRL::ComPtr<ID2D1SolidColorBrush>    m_whiteBrush;
            Microsoft::WRL::ComPtr<ID2D1DrawingStateBlock>  m_stateBlock;
            D2D1_POINT_2F   m_pos;
        };
    }
    
  6. DirectX9 ではジオメトリ変換を使った3次元座標の回転関数がサポートされていたのですが、ストアアプリでは見つかりません。
    仕方が無いので Rot3D() 関数を自作しました。
    Rot3D() 関数では、座標 pos を rx, ry, rz で回転した結果を float3 でリターンします。
  7. Update() 関数では Rot3D() で回転した結果を、座標(X,Y)にマークします。
  8. Draw.cpp のソースコードです。
    Render() 関数では座標(m_pos)に小さなマーク(円)を描画します。
    #include "pch.h"
    #include "Draw.h"
    
    #include "Common/DirectXHelper.h"
    
    using namespace App1;
    using namespace DirectX;
    void Draw::Debug(float v1, float v2, float v3)
    {
        wchar_t str[80];
        swprintf(str, 80, L"%f, %f, %f\n\n", v1, v2, v3);
        OutputDebugString(str);
    }
    
    Draw::Draw(const std::shared_ptr<DX::DeviceResources>& deviceResources) :
        m_deviceResources(deviceResources)
    {
        DX::ThrowIfFailed(
            m_deviceResources->GetD2DFactory()->CreateDrawingStateBlock(&m_stateBlock)
        );
        CreateDeviceDependentResources();
    }
    
    float3 Draw::f3(float x, float y, float z)
    {
        float3 wf3;
        wf3.x = x;
        wf3.y = y;
        wf3.z = z;
        return wf3;
    }
    
    // 中心点を 0,0,0 として、座標 pos を rx, ry, rz で回転する
    float3 Draw::Rot3D(float3 pos, float rx, float ry, float rz)
    {
        float3  ans, wk;
        ans = pos;
        wk = pos;
        ans.y = (float)(wk.y*cos(rz) - wk.x*sin(rz));
        ans.x = (float)(wk.y*sin(rz) + wk.x*cos(rz));
        wk = ans;
        ans.x = (float)(wk.x*cos(ry) - wk.z*sin(ry));
        ans.z = (float)(wk.x*sin(ry) + wk.z*cos(ry));
        wk = ans;
        ans.z = (float)(wk.z*cos(rx) - wk.y*sin(rx));
        ans.y = (float)(wk.z*sin(rx) + wk.y*cos(rx));
        return ans;
    }
    
    void Draw::Update(DX::StepTimer const& timer)
    {
        //OutputDebugString(L"Draw.cpp Update()\n");
        float radiansPerSecond = XMConvertToRadians(45.0f);
        double totalRotation = timer.GetTotalSeconds() * radiansPerSecond;
        float radians = static_cast<float>(fmod(totalRotation, XM_2PI));
    
        float3  rot;
        rot = Rot3D(f3(0.0f, 200.0f, 0.0f), 0.0f, 0.0f, radians);
        Debug(rot.x, rot.y, radiansPerSecond);
        m_pos.x = rot.x + 400.0f;
        m_pos.y = rot.y + 300.0f;
        //rot = Rot3D(f3(0.0f, 200.0f, 0.0f), radians, 0.0f, 0.0f);
        //m_pos.x = rot.y + 400.0f;
        //m_pos.y = rot.z + 300.0f;
        //rot = Rot3D(f3(200.0f, 0.0f, 0.0f), 0.0f, radians, 0.0f);
        //m_pos.x = rot.z + 400.0f;
        //m_pos.y = rot.x + 300.0f;
    }
    
    // フレームを画面に描画します。
    void Draw::Render()
    {
        Microsoft::WRL::ComPtr<ID2D1SolidColorBrush>    yellowBrush;
        ID2D1DeviceContext* context = m_deviceResources->GetD2DDeviceContext();
        DX::ThrowIfFailed(
            context->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Yellow), &yellowBrush)
            );
    
        context->BeginDraw();
        context->Clear(D2D1::ColorF(D2D1::ColorF::CornflowerBlue));
    
        D2D1_ELLIPSE ellipse = D2D1::Ellipse(m_pos, 5.0f, 5.0f);
        context->FillEllipse(ellipse, yellowBrush.Get());
    
        HRESULT hr = context->EndDraw();
        if (hr != D2DERR_RECREATE_TARGET)
        {   DX::ThrowIfFailed(hr);  }
    }
    
    void Draw::CreateDeviceDependentResources()
    {
        DX::ThrowIfFailed(
            m_deviceResources->GetD2DDeviceContext()->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), &m_whiteBrush)
            );
    }
    void Draw::ReleaseDeviceDependentResources()
    {
        m_whiteBrush.Reset();
    }
    

TransformNormal

  1. TransformNormal() 関数を使って3次元座標を回転します。
    ストアアプリのジオメトリ変換らしい関数を見つけたのですが使い方が解りませんでした。
    かなり苦労しましたが、ようやく動くようになりました。
    キーになるのが XMVECTOR で、次のように宣言されています。
    typedef __m128 XMVECTOR;
    
  2. __m128 は 128Bit(16Byte) を単位とする領域で、64ビットCPUに合わせて設けられたようです。
    __m128 は次のように宣言されています。
    typedef union __declspec(intrin_type) __declspec(align(16)) __m128 {
         float               m128_f32[4];
         unsigned __int64    m128_u64[2];
         __int8              m128_i8[16];
         __int16             m128_i16[8];
         __int32             m128_i32[4];
         __int64             m128_i64[2];
         unsigned __int8     m128_u8[16];
         unsigned __int16    m128_u16[8];
         unsigned __int32    m128_u32[4];
     } __m128;
    
  3. TransformNormal() 関数を使った3次元座標の回転関数 MatRotate() は次のようになります。
    float3 Draw::MatRotate(float3 pos, float3 rt)
    {
        XMMATRIX    mat;
        float3      ans;
        XMVECTOR    vect;
        vect.m128_f32[0] = pos.x;
        vect.m128_f32[1] = pos.y;
        vect.m128_f32[2] = pos.z;
        mat = XMMatrixRotationRollPitchYaw(rt.x, rt.y, rt.z);
        vect = XMVector3TransformNormal(vect, mat);
        ans.x = vect.m128_f32[0];
        ans.y = vect.m128_f32[1];
        ans.z = vect.m128_f32[2];
        return ans;
    }
    
  4. __m128 を直接参照するのは不細工で解りにくいので次のように書きます。
    Rot3D() と比べて回転が逆になるようなので、RollPitchYaw ではサインを反転しています。
    float3 Draw::MatRotate(float3 pos, float3 rt)
    {
        XMMATRIX    mat;
        float3      ans;
        XMVECTOR    vect;
        vect = XMVectorSet(pos.x, pos.y, pos.z, 0.0f);
        mat = XMMatrixRotationRollPitchYaw(-rt.x, -rt.y, -rt.z);
        vect = XMVector3TransformNormal(vect, mat);
        ans.x = XMVectorGetX(vect);
        ans.y = XMVectorGetY(vect);
        ans.z = XMVectorGetZ(vect);
        return ans;
    }
    
    f3() 関数が使えるなら MatRotate() 関数はもっと簡単になります。
    float3 MyCamera::MatRotate(float3 pos, float3 rt)
    {
        XMMATRIX    mat;
        XMVECTOR    vect;
        vect = XMVectorSet(pos.x, pos.y, pos.z, 0.0f);
        mat = XMMatrixRotationRollPitchYaw(-rt.x, -rt.y, -rt.z);
        vect = XMVector3TransformNormal(vect, mat);
        return f3(XMVectorGetX(vect), XMVectorGetY(vect), XMVectorGetZ(vect));
    }
    
  5. Rot3D() を MatRotate() に置き換えて試して下さい。
    良く調べてみないとはっきりしませんが、Rot3D() と MatRotate() の回転結果は一致するようです。
    正常に実行することが確認できたら、以後は MatRotate() 関数を使うことをお勧めします。
        float3  rot;
        rot = MatRotate(f3(0.0f, 200.0f, 0.0f), f3(0.0f, 0.0f, radians));
        m_pos.x = rot.x + 400.0f;
        m_pos.y = rot.y + 300.0f;
    

超初心者のプログラム入門(DirectX Store)