Graphics/DirectX

Direct X - 19. 모델의 이동, 회전, 확대, 축소

MOLOKINI 2014. 6. 9. 00:41

using System;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using System.Windows.Forms;
using System.Drawing;

namespace MDXSample
{
    /// <summary>
    /// 메인 샘플 클래스
    /// </summary>
    public partial class MainSample : IDisposable
    {
        /// <summary>
        /// 정점 버퍼
        /// </summary>
        private VertexBuffer _xyzLineBuffer = null;

        private Vector3 _scale = new Vector3(1.0f, 1.0f, 1.0f);
        private Vector3 _trans = Vector3.Empty;

        private float _rotate = 0.0f;        

        public bool InitializeApplication(MainForm topLevelForm)
        {
            // 폼의 참조를 보관 유지
            this._form = topLevelForm;
            PresentParameters pp = new PresentParameters();
            try
            {
                // Direct3D 디바이스 작성
                this.CreateDevice(topLevelForm, pp);
                // 폰트의 작성
                this.CreateFont();

            }
            catch (DirectXException ex)
            {
                // 예외 발생
                MessageBox.Show(ex.ToString(), "에러", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }

            LoadXfile("01.x");  // 라이트까지 세팅되어있다.
            createXYZLine();

            this._device.RenderState.NormalizeNormals = true;

            return true;
        }

        /// <summary>
        /// 메인 루프 처리
        /// </summary>
        public void MainLoop()
        {       
            this.SettingCamera();

            if (this._keys[(int)Keys.Left])
            {
                this._rotate -= 5.0f;
            }
            if (this._keys[(int)Keys.Right])
            {
                this._rotate += 5.0f;
            }
            if (this._keys[(int)Keys.Up])
            {
                this._trans += Vector3.TransformCoordinate(new Vector3(0.0f, 0.0f, -0.3f), Matrix.RotationY(Geometry.DegreeToRadian(this._rotate)));
            }
            if (this._keys[(int)Keys.Down])
            {
                this._trans -= Vector3.TransformCoordinate(new Vector3(0.0f, 0.0f, -0.3f), Matrix.RotationY(Geometry.DegreeToRadian(this._rotate)));
            }
            if (this._keys[(int)Keys.A])
            {
                this._scale *= 1.01f;
            }
            if (this._keys[(int)Keys.Z])
            {
                this._scale.X /= 1.01f;
                this._scale.Y /= 1.01f;
                this._scale.Z /= 1.01f;
            }

            Matrix modelTransform = Matrix.Identity;
            modelTransform *= Matrix.Scaling(this._scale);
            modelTransform *= Matrix.RotationY(Geometry.DegreeToRadian(this._rotate));
            modelTransform *= Matrix.Translation(this._trans);
                        
            // 화면을 단색(파랑색)으로 클리어
            this._device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.DarkBlue, 1.0f, 0);

            // 「BeginScene」와「EndScene」의 사이에 화면에 출력할 내용을 코딩
            this._device.BeginScene();

            this.RenderXYZ();           

            this._device.RenderState.Lighting = true;
            this._device.SetTransform(TransformType.World, modelTransform);

            this.RenderMesh();

            this._font.DrawText(null, "θ:" + this._lensPosTheta, 0, 0, Color.White);
            this._font.DrawText(null, "φ:" + this._lensPosPhi, 0, 12, Color.White);
            this._font.DrawText(null, "마우스 위치:" + this._oldMousePoint, 0, 24, Color.White);
            
            // 화면출력은 여기까지
            this._device.EndScene();

            // 실제의 디스플레이에 출력
            this._device.Present();
        }

        private void form_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                this._lensPosTheta -= e.Location.X - this._oldMousePoint.X;
                this._lensPosPhi += e.Location.Y - this._oldMousePoint.Y;
            }
            this._oldMousePoint = e.Location;
        }

        /// <summary>
        /// 자원의 파기
        /// </summary>
        public void Dispose()
        {
            if (this._textures != null)
            {
                foreach (Texture i in this._textures)
                {
                    if (i != null)
                    {
                        i.Dispose();
                    }
                }
            }

            if (this._mesh != null)
            {
                this._mesh.Dispose();
            }

            // 폰트 자원을 해제
            if (this._font != null)
            {
                this._font.Dispose();
            }

            // Direct3D 디바이스의 자원 해제
            if (this._device != null)
            {
                this._device.Dispose();
            }
        }
    }
}


        private Vector3 _scale = new Vector3(1.0f, 1.0f, 1.0f);
        private Vector3 _trans = Vector3.Empty;

        private float _rotate = 0.0f;        
이번에는 확대축소와 위치, 회전(Y축만) 세가지 파라미터야
요 세가지 파라미터를 조합해서 모델의 크기, 위치, 방향을 결정할거야


            if (this._keys[(int)Keys.Up])
            {
                this._trans += Vector3.TransformCoordinate(new Vector3(0.0f, 0.0f, -0.3f), Matrix.RotationY(Geometry.DegreeToRadian(this._rotate)));
            }
            if (this._keys[(int)Keys.Down])
            {
                this._trans -= Vector3.TransformCoordinate(new Vector3(0.0f, 0.0f, -0.3f), Matrix.RotationY(Geometry.DegreeToRadian(this._rotate)));
            }
이동에 관한 부분은 전 강좌(18강)랑은 다르다
여기서 이동하는 양을 결정하는데, "이동할 방향을 회전"시켜 이동하는 것

먼저, 모델이 일절 회전하고 있지 않은 경우는 향하고 있을 방향을 -Z방향으로 한다
 - 그냥 이 모델에 대해서만 그렇게 하자는겁니다 읽는 모델 파일에 따라 상황은 달라질 수 있습니다.


-Z방향을 향하고 있기 때문에 앞으로 나아가는 경우에는 Vector3(0.0f, 0.0f, -0.3f)로 -Z에 0.3을 설정했어, 0.3은 속도니까 알아서 설정하시구요

그렇다면, 여기서 모델을 "오른쪽으로 90도" 회전시켜서 다시 앞으로 나가게되면 똑같이 Vector3(0.0f, 0.0f, -0.3f)를 하면 안됩니다 (모델이 옆걸음을 하게 되겠죠??)

옆으로 돌아갔는데 방향이 안바뀌어서 옆으로 가는 모양새가 나오게 됩니다!

그래서 모델이 회전을 하면 이동하는 방향도 회전을 시키자!


여기서 하구있는건 "회전하고 있지 않은 상태로의 이동방향"으로 "모델의 방향을 변환한 행렬"을 곱하고있습니다


식으로 나타내보면

Y축을 회전축으로 90도 우회전의 회전행렬


초기 이동 벡터에 이 행렬을 곱합니다


결과가 (-0.3, 0, 0, 1)이라서 X방향으로 이동을 하게 되는겁니다

 

 

             if (this._keys[(int)Keys.Left])

            {
                this._rotate -= 5.0f;
            }
            if (this._keys[(int)Keys.Right])
            {
                this._rotate += 5.0f;
            }
모델의 회전


            if (this._keys[(int)Keys.A])
            {
                this._scale *= 1.01f;
            }
            if (this._keys[(int)Keys.Z])
            {
                this._scale.X /= 1.01f;
                this._scale.Y /= 1.01f;
                this._scale.Z /= 1.01f;
            }
확대축소
곱셈은 벡터에 대해 직접 곱할 수 있다
하지만! 나눗셈은 직접 나눌 수 없기 때문에 일일히 하나하나 나누어주는겁니다
만약에 한줄로 하고싶으면 this._scale *= 1.0f / 1.01f 로 하는 방법이 있겠지요?


            Matrix modelTransform = Matrix.Identity;
            modelTransform *= Matrix.Scaling(this._scale);
            modelTransform *= Matrix.RotationY(Geometry.DegreeToRadian(this._rotate));
            modelTransform *= Matrix.Translation(this._trans);
키 조작으로 여러 파라미터를 수정했다
그럼 이걸 매트릭스로 합성시켜야지요
지금까지는 Device.SetTransform 메서드로 값을 직접 수정했지만, 이제 매트릭스화 해서 직접 계산한 좌표 변화치를 넘겨줄겁니다
그래서 따로 Matrix를 선언해서 그에 대한 결과를 곱한 후 수정할겁니다
그냥 세개 한꺼번에 처리한다는 뜻이에요

먼저 Matrix를 Matrix.Identity로 초기화시키고
초기화 매트릭스에 대해서, 매트릭스로 변환한 확대, 축소, 회전, 이동을 곱
곱하는 차례에 따라서 결과가 다를 수 있다
그럼 결과가 어떻게 달라지는지 한번 그림으로 볼까요?


위에 있는게 지금 사용하고있는 처리순서

아래 있는게 이동을 먼저 곱하고 회전을 곱하는 경우

다르지요?


왜그러냐하면, 모든 좌표변화는 원점을 중심으로 생각하고 있기 때문

그래서 이동한 후에 회전하면 그 자리에서 회전해도 결국은 똑같잖아? 하고 생각할 수 있습니다

But! 먼저 이동을 했기 때문에, 모델이 원점으로부터 떨어져있고, 여기에 회전을 걸고 있기 때문에 모델이 회전해버릴 뿐만 아니라 이동한 위치도 회전이 되어버립니다

그래서, 매트릭스를 곱할때는 순서가 중요합니다.

확대축소, 회전, 이동                 (중요)

꼭 이렇게 곱해야합니다!!!!!!!!!!!!!!! 



            this._device.RenderState.Lighting = true;
            this._device.SetTransform(TransformType.World, modelTransform);

            this.RenderMesh();