Click here to Skip to main content
15,867,141 members
Articles / Multimedia / GDI+

Quaternion Mathematics and 3D Library with C# and GDI+

Rate me:
Please Sign up or sign in to vote.
4.91/5 (42 votes)
1 Jun 2009CPOL3 min read 117.3K   7.3K   96   21
Rotating 3D objects using Quaternion and Drawing 3D objects with GDI+ Graphics
Image 1

Introduction

The 3D Library, written using C# and GDI+, allows you to draw pictures in a 3-dimensional space and rotate or/and move 3D objects. The 3D Library includes: Point3d.cs, Vector3d.cs, Shape3d.cs, Cuboid3d.cs, Quaternion.cs and Camera.cs. This library uses the right handed rectangular coordinate system and has the same X, Y coordinates as GDI+:

Image 2

About the Program

To illustrate how this libray works, I wrote this program. When you run this program, a colored cube is loaded, and its center is shown with the camera's position in pixels. Their rotated degrees are set as zero. This program is quite intuitive. You can have pictures on each of the cube surfaces by clicking button Load Image. You can also reset this program by button Reset. Just a reminder: because rotation sequence is incommutable, it's possible that the cube is not in its initial state when all the rotated degrees are returned to zeros.

About the Code

Quaternion

In mathematics, quaternions are a non-commutative number system that extend the complex numbers. They provide a convenient mathematical notation for representing orientations and rotations of objects in three dimensions. Quaternions have 4 dimensions (each quaternion consists of 4 scalar numbers), one real dimension w and 3 imaginary dimensions xi + yj + zk that can describe an axis of rotation and an angle. Quaternions are often used in 3D engines to rotate points in space quickly.

Define the quaternion:

q = w + xi + yj + zk = w + (x, y, z) = cos(a/2) + usin(a/2)

where u is a unit vector and a is rotated angle around the u axis.

Let also v be an ordinary vector of the 3 dimensional space, considered as a quaternion with a real coordinate equal(w) to zero. Then the quaternion product:

qvq<sup>-1</sup>

yields the vector v rotated by an angle a around the u axis. The rotation is clockwise if our line of sight points in the direction pointed by u. This operation is known as conjugation by q. The quaternion multiplication is composition of rotations, for if p and q are quaternions representing rotations, then rotation (conjugation) by pq is:

pqv(pq)<sup>-1</sup> = pqvp<sup>-1</sup>p<sup>-1</sup> = p(qvq<sup>-1</sup>)p<sup>-1</sup>

Let's look at the construction of Quaternion.cs:

C#
public struct Quaternion
{
    public double X, Y, Z, W;

    public Quaternion(double w, double x, double y, double z)
    {
        W = w; X = x; Y = y; Z = z;
    }

    public Quaternion(double w, Vector3d v)
    {
        W = w; X = v.X; Y = v.Y; Z = v.Z;
    }

    public void FromAxisAngle(Vector3d axis, double angleRadian)
    {
        double m = axis.Magnitude;
        if (m > 0.0001)
        {
            double ca = Math.Cos(angleRadian / 2);
            double sa = Math.Sin(angleRadian / 2);
            X = axis.X / m * sa;
            Y = axis.Y / m * sa;
            Z = axis.Z / m * sa;
            W = ca;
        }
        else
        {
            W = 1; X = 0; Y = 0; Z = 0;
        }
    }
... ...

multiplication of quaternion q1 with q2 is:

(w1 + x1i + y1j + z1k)*(w2 + x2i + y2j + z2k) 

and

i*j = k, j*k = i, k*i = j; i*i = -1, j*j = -1, k*k = -1 

So we can easily obtain this code based on the above information:

C#
public static Quaternion operator *(Quaternion q1, Quaternion q2)
{
    double nw = q1.W * q2.W - q1.X * q2.X - q1.Y * q2.Y - q1.Z * q2.Z;
    double nx = q1.W * q2.X + q1.X * q2.W + q1.Y * q2.Z - q1.Z * q2.Y;
    double ny = q1.W * q2.Y + q1.Y * q2.W + q1.Z * q2.X - q1.X * q2.Z;
    double nz = q1.W * q2.Z + q1.Z * q2.W + q1.X * q2.Y - q1.Y * q2.X;
    return new Quaternion(nw, nx, ny, nz);
} 

The code for the rotation by quaternion follows:

C#
public void Rotate(Point3d pt)
{
    this.Normalise();
    Quaternion q1 = this.Copy();
    q1.Conjugate();
    
    Quaternion qNode = new Quaternion(0, pt.X, pt.Y, pt.Z);
    qNode = this * qNode * q1;
    pt.X = qNode.X;
    pt.Y = qNode.Y;
    pt.Z = qNode.Z;
}

public void Rotate(Point3d[] nodes)
{
    this.Normalise();
    Quaternion q1 = this.Copy();
    q1.Conjugate();
    for (int i = 0; i < nodes.Length; i++)
    {
        Quaternion qNode = new Quaternion(0, nodes[i].X, nodes[i].Y, nodes[i].Z);
        qNode = this * qNode * q1;
        nodes[i].X = qNode.X;
        nodes[i].Y = qNode.Y;
        nodes[i].Z = qNode.Z;
    }
}

Camera

Since the computer monitor is limited to displaying 2D images, we have to flatten the 3D objects on to the 2D plane using projection. Camera.cs is used to transform the 3D point to 2D point on the plane. Camera.cs has three properties:

C#
public class Camera
{
    Point3d loc = new Point3d(0, 0, 0);
    double _d = 500.0;
    Quaternion quan = new Quaternion(1, 0, 0, 0);

public Point3d Location
{
    set { loc = value; }
    get { return loc; }
}

public double FocalDistance
{
    set { _d = value; }
    get { return _d; }
}

public Quaternion Quaternion
{
    set { quan = value; }
    get { return quan; }
}
... ...

When the camera moves, its Location is changed:

C#
public void MoveRight(double d)
{
    loc.X += d;
}

public void MoveLeft(double d)
{
    loc.X -= d;
}

public void MoveUp(double d)
{
    loc.Y -= d;
}

public void MoveDown(double d)
{
    loc.Y += d;
}

public void MoveIn(double d)
{
    loc.Z += d;
}

public void MoveOut(double d)
{
    loc.Z -= d;
}

And when camera turns, the Quaternion records the rotation of camera:

C#
public void Roll(int degree) // rotate around Z axis
{
    Quaternion q = new Quaternion();
    q.FromAxisAngle(new Vector3d(0, 0, 1), degree * Math.PI / 180.0);
    quan = q * quan;
}

public void Yaw(int degree)  // rotate around Y axis
{
    Quaternion q = new Quaternion();
    q.FromAxisAngle(new Vector3d(0, 1, 0), degree * Math.PI / 180.0);
    quan = q * quan;
}

public void Pitch(int degree) // rotate around X axis
{
    Quaternion q = new Quaternion();
    q.FromAxisAngle(new Vector3d(1, 0, 0), degree * Math.PI / 180.0);
    quan = q * quan;
}

public void TurnUp(int degree)
{
    Pitch(-degree);
}

public void TurnDown(int degree)
{
    Pitch(degree);
}

public void TurnLeft(int degree)
{
    Yaw(degree);
}

public void TurnRight(int degree)
{
    Yaw(-degree);
}

To project the 3D point to 2D, the first step is to translate the origin to the camera's Location and use camera's Quaternion to rotate the 3D objects in relation to camera's rotation. The second step is projection:

C#
public PointF[] GetProjection(Point3d[] pts)
{
    PointF[] pt2ds = new PointF[pts.Length];

    // transform to new coordinates system which origin is camera location
    Point3d[] pts1 = Point3d.Copy(pts);
    Point3d.Offset(pts1, -loc.X, -loc.Y, -loc.Z);

    // rotate
    quan.Rotate(pts1);

    //project
    for (int i = 0; i < pts.Length; i++)
    {
       if (pts1[i].Z > 0.1)
       {
           pt2ds[i] = new PointF((float)(loc.X + pts1[i].X * _d / pts1[i].Z),
           (float)(loc.Y + pts1[i].Y * _d / pts1[i].Z));
       }
       else
       {
           pt2ds[i] = new PointF(float.MaxValue, float.MaxValue);
       }
   }
   return pt2ds;
}
}

3D Object and Cube

Shape3d.cs is a primitive base class that records the key 3D points for drawing shape and provides the abilities to draw 3D shapes:

C#
public class Shape3d
{
    protected Point3d[] pts = new Point3d[8];
    public Point3d[] Point3dArray
    {
        get { return pts; }
    }

    protected Point3d center = new Point3d(0, 0, 0);
    public Point3d Center
    {
        set
        {
            double dx = value.X - center.X;
            double dy = value.Y - center.Y;
            double dz = value.Z - center.Z;
            Point3d.Offset(pts, dx, dy, dz);
            center = value;
       }
       get { return center; }
    }

    protected Color lineColor = Color.Black;
    public Color LineColor
    {
        set { lineColor = value; }
        get { return lineColor; }
    }

    public void RotateAt(Point3d pt, Quaternion q)
    {
        // transform origin to pt
        Point3d[] copy = Point3d.Copy(pts);
        Point3d.Offset(copy,  - pt.X,  - pt.Y,  - pt.Z);
        
        // rotate
        q.Rotate(copy);
        q.Rotate(center);

        // transform to original origin
        Point3d.Offset(copy, pt.X, pt.Y, pt.Z);
        pts = copy;
    }
        
    public virtual void Draw(Graphics g,Camera cam)
    {
    }
}

Now it's possible to construct any 3D object. Here's a simple example, Cuboid derived from Shape3d.cs:

C#
public Cuboid(double a, double b, double c)
{
     center = new Point3d(a / 2, b / 2, c / 2);
     pts[0] = new Point3d(0, 0, 0);
     pts[1] = new Point3d(a, 0, 0);
     pts[2] = new Point3d(a, b, 0);
     pts[3] = new Point3d(0, b, 0);
     pts[4] = new Point3d(0, 0, c);
     pts[5] = new Point3d(a, 0, c);
     pts[6] = new Point3d(a, b, c);
     pts[7] = new Point3d(0, b, c);
}

To draw the 3D cube on screen, we have to pass its 3D points to Camera.cs and get the 2D points for drawing:

C#
public override void Draw(Graphics g, Camera cam)
{
     PointF[] pts2d = cam.GetProjection(pts);
     ... ...

You can also fill the cube face; however, you can only fill the fore face and not the back face. So we have to identify which is the fore face:

C#
if (YLScsDrawing.Geometry.Vector.IsClockwise(face[i][0], face[i][1], face[i][2]))
// the face can be seen by camera
{
   if (fillingFace) g.FillPolygon(new SolidBrush(faceColor[i]), face[i]);
   ... ...

To have some fun, I placed the pictures on the fore face by Free Image Transformation:

if (drawingImage && bmp[i] != null)
{
    filters[i].FourCorners = face[i];
    g.DrawImage(filters[i].Bitmap, filters[i].ImageLocation);
}

Thanks

Thanks a lot for reading and have fun trying out my 3D Library.

History

  • 1st June, 2009: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Unknown
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Question@YLS CS Pin
Maurice Marinus25-Feb-23 20:55
Maurice Marinus25-Feb-23 20:55 
QuestionI used this program as the basis for work I did on coordinate conversion Pin
johnmott5931-Jul-17 3:56
johnmott5931-Jul-17 3:56 
QuestionIs this a bug when use struct as input paramter for rotate point? Pin
Kevin Chen12-Jun-15 17:35
Kevin Chen12-Jun-15 17:35 
GeneralIt was really whet I needed Pin
Danushka127-Jun-14 2:06
Danushka127-Jun-14 2:06 
QuestionHere's a more complete Quaternion class... Pin
Warren C Harding6-Apr-13 14:31
Warren C Harding6-Apr-13 14:31 
AnswerFinal Conclusion Pin
kburman64-Apr-13 5:27
professionalkburman64-Apr-13 5:27 
GeneralMy vote of 1 Pin
RenniePet19-Mar-13 16:42
RenniePet19-Mar-13 16:42 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey20-Feb-12 20:52
professionalManoj Kumar Choubey20-Feb-12 20:52 
QuestionAdding more de 1 cube Pin
maximchris12-Oct-11 23:10
maximchris12-Oct-11 23:10 
AnswerRe: Adding more de 1 cube Pin
kburman64-Apr-13 5:13
professionalkburman64-Apr-13 5:13 
Questionproblem regarding moving,rotating etc Pin
nbhagya28-Jul-11 23:55
nbhagya28-Jul-11 23:55 
QuestionRotation Order Matters? Pin
Jeffrey A Grunschel22-Feb-11 6:06
Jeffrey A Grunschel22-Feb-11 6:06 
GeneralMethod Rotate can't work correctly Pin
eugenxxx8-Feb-11 1:42
eugenxxx8-Feb-11 1:42 
GeneralPlotting functions Pin
yavor_todorov1819-Oct-10 2:24
yavor_todorov1819-Oct-10 2:24 
GeneralMy vote of 5 Pin
Hamid faragardi23-Aug-10 10:09
Hamid faragardi23-Aug-10 10:09 
QuestionArgumentNullException when trying to use any bitmaps [modified] Pin
Member 41691593-Jan-10 18:38
Member 41691593-Jan-10 18:38 
SuggestionRe: ArgumentNullException when trying to use any bitmaps [modified] Pin
kburman64-Apr-13 5:08
professionalkburman64-Apr-13 5:08 
QuestionProblem? Pin
Member 403088910-Sep-09 23:36
Member 403088910-Sep-09 23:36 
AnswerRe: Problem? Pin
yna_productions3-Jan-10 22:36
yna_productions3-Jan-10 22:36 
QuestionNice Pin
tptshepo1-Jun-09 6:28
tptshepo1-Jun-09 6:28 
AnswerSome thoughs Pin
tobywf8-Jun-09 11:42
tobywf8-Jun-09 11:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.