Sign in to follow this  
bsharp

Stuck on rotation problem for days and need help

Recommended Posts

This is an example of TrackBall rotation using Quaternions. It is written in C# using managed directX 9 in VS 2013.

With this you can figure out how to rotate things just like in 3DS Max or any other CAD software using C# and .NET.


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

namespace DX9Sample
{
    public delegate void RotationChangedHandler(Object sender, Matrix newRotation);
    public class DX9Form : System.Windows.Forms.Form
    {
        private Device d3dDevice = null;
        private VertexBuffer vertexBuffer = null;
        Texture texture = null;

        public static Quaternion WorldRotation;
        public static Matrix WorldMatrix;

        static private float elapsedTime;
        static private DateTime currentTime;
        static private DateTime lastTime;
        private int clicked = 0;


        const float TRACKBALLSIZE = 0.8f;
        const int RENORMCOUNT = 97;

        public Control m_Target;
        private float m_MouseDownX;
        private float m_MouseDownY;
        private float m_HalfWidth;  // half the width of m_Target.ClientRectangle
        private float m_HalfHeight; // half the height of m_Target.ClientRectangle
        private Quaternion m_CurrentRotQuat = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
        private long m_QuatAddCount;

        public event RotationChangedHandler RotationChanged;

        private float xrot = 0.0f;
        private float yrot = 0.0f;
        private float zrot = 0.0f;

        struct Vertex
        {
            float x, y, z;
            float tu, tv;

            public Vertex( float _x, float _y, float _z, float _tu, float _tv )
            {
                x = _x; y = _y; z = _z;
                tu = _tu; tv = _tv;
            }

            public static readonly VertexFormats FVF_Flags = VertexFormats.Position | VertexFormats.Texture1;
        };

        Vertex[] cubeVertices =
        {
            new Vertex(-1.0f, 1.0f,-1.0f,  0.0f,0.0f ),
            new Vertex( 1.0f, 1.0f,-1.0f,  1.0f,0.0f ),
            new Vertex(-1.0f,-1.0f,-1.0f,  0.0f,1.0f ),
            new Vertex( 1.0f,-1.0f,-1.0f,  1.0f,1.0f ),

            new Vertex(-1.0f, 1.0f, 1.0f,  1.0f,0.0f ),
            new Vertex(-1.0f,-1.0f, 1.0f,  1.0f,1.0f ),
            new Vertex( 1.0f, 1.0f, 1.0f,  0.0f,0.0f ),
            new Vertex( 1.0f,-1.0f, 1.0f,  0.0f,1.0f ),

            new Vertex(-1.0f, 1.0f, 1.0f,  0.0f,0.0f ),
            new Vertex( 1.0f, 1.0f, 1.0f,  1.0f,0.0f ),
            new Vertex(-1.0f, 1.0f,-1.0f,  0.0f,1.0f ),
            new Vertex( 1.0f, 1.0f,-1.0f,  1.0f,1.0f ),

            new Vertex(-1.0f,-1.0f, 1.0f,  0.0f,0.0f ),
            new Vertex(-1.0f,-1.0f,-1.0f,  1.0f,0.0f ),
            new Vertex( 1.0f,-1.0f, 1.0f,  0.0f,1.0f ),
            new Vertex( 1.0f,-1.0f,-1.0f,  1.0f,1.0f ),

            new Vertex( 1.0f, 1.0f,-1.0f,  0.0f,0.0f ),
            new Vertex( 1.0f, 1.0f, 1.0f,  1.0f,0.0f ),
            new Vertex( 1.0f,-1.0f,-1.0f,  0.0f,1.0f ),
            new Vertex( 1.0f,-1.0f, 1.0f,  1.0f,1.0f ),

            new Vertex(-1.0f, 1.0f,-1.0f,  1.0f,0.0f ),
            new Vertex(-1.0f,-1.0f,-1.0f,  1.0f,1.0f ),
            new Vertex(-1.0f, 1.0f, 1.0f,  0.0f,0.0f ),
            new Vertex(-1.0f,-1.0f, 1.0f,  0.0f,1.0f )
        };

        public DX9Form()
        {
            CheckForIllegalCrossThreadCalls = false;
            m_Target = DX9Form.ActiveForm;
            this.ClientSize = new Size( 640, 480 );
            this.Text = "Direct3D (DX9/C#) - Vertex Data";
            this.SetStyle( ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true );
        }

        protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
        {
            this.Render();
            this.Invalidate();
        }

        protected override void OnKeyDown(System.Windows.Forms.KeyEventArgs e)
        {
            switch( e.KeyCode )
            {
                case System.Windows.Forms.Keys.Escape:
                    this.Dispose();
                    break;
            }
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose( disposing );
        }

        static void Main()
        {
            using( DX9Form frm = new DX9Form() )
            {
                frm.InitializeComponent();
                frm.Show();
                frm.Init();
                Application.Run( frm );
            }
        }

        private void LoadTexture()
        {
            try
            {
                Bitmap testImage = (Bitmap)Bitmap.FromFile( "test.bmp" );
                texture = Texture.FromBitmap( d3dDevice, testImage, 0, Pool.Managed );
            }
            catch
            {
                // We must be running from within Visual Studio. Relocate the
                // current directory and try again.
                System.IO.Directory.SetCurrentDirectory(
                    System.Windows.Forms.Application.StartupPath +  @"\..\..\");

                Bitmap testImage = (Bitmap)Bitmap.FromFile( "test.bmp" );
                texture = Texture.FromBitmap( d3dDevice, testImage, 0, Pool.Managed );
            }

            d3dDevice.SamplerState[0].MinFilter = TextureFilter.Linear;
            d3dDevice.SamplerState[0].MagFilter = TextureFilter.Linear;
        }

        /// <summary>
        /// This method basically creates and initialize the Direct3D device and
        /// anything else that doens't need to be recreated after a device
        /// reset.
        /// </summary>
        private void Init()
        {
            //
            // Do we support hardware vertex processing? If so, use it.
            // If not, downgrade to software.
            //

            Caps caps = Manager.GetDeviceCaps( Manager.Adapters.Default.Adapter, DeviceType.Hardware );
            CreateFlags flags;

            if( caps.DeviceCaps.SupportsHardwareTransformAndLight )
                flags = CreateFlags.HardwareVertexProcessing;
            else
                flags = CreateFlags.SoftwareVertexProcessing;

            //
            // Everything checks out - create a simple, windowed device.
            //

            PresentParameters d3dpp = new PresentParameters();

            d3dpp.BackBufferFormat       = Format.Unknown;
            d3dpp.SwapEffect             = SwapEffect.Discard;
            d3dpp.Windowed               = true;
            d3dpp.EnableAutoDepthStencil = true;
            d3dpp.AutoDepthStencilFormat = DepthFormat.D16;
            d3dpp.PresentationInterval   = PresentInterval.Immediate;

            d3dDevice = new Device( 0, DeviceType.Hardware, this, flags, d3dpp );

            // Register an event-handler for DeviceReset and call it to continue
            // our setup.
            d3dDevice.DeviceReset += new System.EventHandler( this.OnResetDevice );
            OnResetDevice( d3dDevice, null );
        }

        /// <summary>
        /// This event-handler is a good place to create and initialize any
        /// Direct3D related objects, which may become invalid during a
        /// device reset.
        /// </summary>
        public void OnResetDevice(object sender, EventArgs e)
        {
            Device device = (Device)sender;
            

            device.RenderState.ZBufferEnable = true;
            device.RenderState.Lighting = false;

            vertexBuffer = new VertexBuffer( typeof(Vertex),
                                             cubeVertices.Length, device,
                                             Usage.Dynamic | Usage.WriteOnly,
                                             Vertex.FVF_Flags,
                                             Pool.Default );

            GraphicsStream gStream = vertexBuffer.Lock( 0, 0, LockFlags.None );

            // Now, copy the vertex data into the vertex buffer
            gStream.Write( cubeVertices );

            vertexBuffer.Unlock();

            LoadTexture();
        }

        /// <summary>
        /// This method is dedicated completely to rendering our 3D scene and is
        /// is called by the OnPaint() event-handler.
        /// </summary>
        private void Render()
        {
            d3dDevice.Clear( ClearFlags.Target | ClearFlags.ZBuffer,
                             Color.FromArgb(255, 0, 0, 0), 1.0f, 0 );

            d3dDevice.BeginScene();

            if (clicked == 0)
            {
                d3dDevice.Transform.World = Matrix.Translation(0.0f, 0.0f, 5.0f);
            }
            else
            {
                d3dDevice.Transform.World = WorldMatrix * Matrix.Translation(0.0f, 0.0f, 5.0f);
            }
            d3dDevice.Transform.Projection = Matrix.PerspectiveFovLH(Geometry.DegreeToRadian(45.0f), (float)this.ClientSize.Width / this.ClientSize.Height, 0.1f, 100.0f);
            
            d3dDevice.VertexFormat = Vertex.FVF_Flags;
            d3dDevice.SetStreamSource( 0, vertexBuffer, 0 );

            d3dDevice.SetTexture( 0, texture );

            d3dDevice.DrawPrimitives( PrimitiveType.TriangleStrip,  0, 2 );
            d3dDevice.DrawPrimitives( PrimitiveType.TriangleStrip,  4, 2 );
            d3dDevice.DrawPrimitives( PrimitiveType.TriangleStrip,  8, 2 );
            d3dDevice.DrawPrimitives( PrimitiveType.TriangleStrip, 12, 2 );
            d3dDevice.DrawPrimitives( PrimitiveType.TriangleStrip, 16, 2 );
            d3dDevice.DrawPrimitives( PrimitiveType.TriangleStrip, 20, 2 );

            d3dDevice.EndScene();

            d3dDevice.Present();
        }

        private void InitializeComponent()
        {
          //  this.SuspendLayout();
            //
            // DX9Form
            //
            this.ClientSize = new System.Drawing.Size(284, 261);
            this.Name = "DX9Form";
            this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.DX9Form_MouseDown);
            this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.DX9Form_MouseMove);
            this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.DX9Form_MouseUp);
            this.ResumeLayout(false);

        }

        private void DX9Form_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                this.m_Target.Capture = true;

                //    System.Diagnostics.Debug.Assert( m_Target.ClientRectangle.Left == 0 );
                //    System.Diagnostics.Debug.Assert( m_Target.ClientRectangle.Top == 0 );

                m_HalfWidth = m_Target.ClientRectangle.Width / 2.0f;
                m_HalfHeight = m_Target.ClientRectangle.Height / 2.0f;

                m_MouseDownX = (e.X - m_HalfWidth) / m_HalfWidth;
                m_MouseDownY = (m_HalfHeight - e.Y) / m_HalfHeight;
            }
        }

        private void DX9Form_MouseUp(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                m_Target.Capture = false;

                float curX = (e.X - m_HalfWidth) / m_HalfWidth;
                float curY = (m_HalfHeight - e.Y) / m_HalfHeight;

                Quaternion curRot = ComputeRotation(m_MouseDownX, m_MouseDownY, curX, curY);
                m_CurrentRotQuat = AddQuats(m_CurrentRotQuat, curRot);

                if (m_QuatAddCount++ > RENORMCOUNT)
                {
                    m_CurrentRotQuat.Normalize();
                }

                Matrix m = Matrix.RotationQuaternion(m_CurrentRotQuat);

                WorldMatrix = m;
            }
        }

        private void DX9Form_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                float curX = (e.X - m_HalfWidth) / m_HalfWidth;
                float curY = (m_HalfHeight - e.Y) / m_HalfHeight;

                Quaternion curRot = ComputeRotation(m_MouseDownX, m_MouseDownY, curX, curY);

                Matrix m = Matrix.RotationQuaternion(AddQuats(m_CurrentRotQuat, curRot));

                WorldMatrix = m; clicked = 1;
            }
        }

        public Control Target
        {
            get { return m_Target; }
            set
            {
                m_Target = value;
            }
        }

        /// <summary>
        /// This function computes a rotation quat for two points representing the
        /// beginning and end points of a drag on the virtual trackball.
        /// </summary>
        /// <param name="p1x">Beginning point's x value (specified in units where -1.0 to 1.0 represents client width).</param>
        /// <param name="p1y">Beginning point's y value (specified in units where -1.0 to 1.0 represents client height).</param>
        /// <param name="p2x">Ending point's x value (specified in units where -1.0 to 1.0 represents client width).</param>
        /// <param name="p2y">Ending point's y value (specified in units where -1.0 to 1.0 represents client height).</param>
        /// <returns>a quarternion representing the rotation.</returns>
        public static Quaternion ComputeRotation(float p1x, float p1y, float p2x, float p2y)
        {
            Vector3 axis;  // axis of rotation
            Vector3 p1;
            Vector3 p2;
            Vector3 d;
            float phi;     // how much to rotate about the axis
            float t;

            if ((p1x == p2x) && (p1y == p2y))
            {
                // Zero rotation
                return new Quaternion(0, 0, 0, 1.0f);
            }

            //
            // First, figure out z-coordinates for projection of P1 and P2 to
            // deformed sphere
            //
            p1 = new Vector3(p1x, p1y, p_ProjectToSphere(TRACKBALLSIZE, p1x, p1y));
            p2 = new Vector3(p2x, p2y, p_ProjectToSphere(TRACKBALLSIZE, p2x, p2y));

            //            
            // Now, we want the cross product of P1 and P2
            //            
            axis = Vector3.Cross(p1, p2);

            //
            // Figure out how much to rotate around that axis.
            //            
            d = p1 - p2;
            t = d.Length() / (2.0f * TRACKBALLSIZE);

            //
            // Avoid problems with out-of-control values...
            //
            if (t > 1.0f) t = 1.0f;
            if (t < -1.0f) t = -1.0f;
            phi = 2.0f * (float)System.Math.Asin(t);

            return Quaternion.RotationAxis(axis, phi);
        }

        public Quaternion AddQuats(Quaternion q1, Quaternion q2)
        {
            Vector3 q1v3 = new Vector3(q1.X, q1.Y, q1.Z);
            Vector3 q2v3 = new Vector3(q2.X, q2.Y, q2.Z);

            Vector3 t1 = new Vector3(q1.X, q1.Y, q1.Z);
            t1.Scale(q2.W);

            Vector3 t2 = new Vector3(q2.X, q2.Y, q2.Z);
            t2.Scale(q1.W);

            Vector3 t3 = Vector3.Cross(new Vector3(q2.X, q2.Y, q2.Z), new Vector3(q1.X, q1.Y, q1.Z));
            Vector3 tf = t1 + t2;
            tf = tf + t3;
            float tfW = q1.W * q2.W - Vector3.Dot(q1v3, q2v3);

            return new Quaternion(tf.X, tf.Y, tf.Z, tfW);
        }

        private static float p_ProjectToSphere(float r, float x, float y)
        {
            float d, t, z;

            d = (float)System.Math.Sqrt(x * x + y * y);

            if (d < r * 0.70710678118654752440)
            {
                // Inside sphere
                z = (float)System.Math.Sqrt(r * r - d * d);
            }
            else
            {
                // On hyperbola
                t = (float)(r / 1.41421356237309504880);
                z = t * t / d;
            }

            return -z;
        }
    }
}




Edited by bsharp

Share this post


Link to post
Share on other sites

Figured it out!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

 

Yay me!

 

I updated the code so if there is some other poor smuck that runs into this hopefully they wont have to spend a few days like I did trying to figure out how to do this.

 

It just does the rotation but adding translation "pan" and scaling "zoom" will be a walk in the park after fighting with the rotation.

Thanks to Kevin Harris at codesampler.com for providing the simple Vertex data demo and also Jonathan Leonard for the TrackBall code I found on SourceForge.

 

PS Screw You macosxnerd101 on Dreamincode.net! I didnt need your crappy site or flaming help anyway.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this