Rotating Solid Cube Using VB.NET and GDI+

In my last tutorial I have shown how to make a wireframe cube using VB.NET and GDI+. Today, we will build from the code from the last tutorial in order to make a rotating solid cube. The video below shows what we will achieve after finishing the tutorial.

THE FULL SOURCE CODE IS HERE.

The Code

The original code was divided in 2 files [Point3D.vb and Main.vb]. The Point3D.vb file  defined the Point3D class which represents points in 3D space. The file Main.vb defined the Window form where the simulation runs.

The class Point3D remains unchanged and is presented below.

'
' Defines the Point3D class that represents points in 3D space.
' Developed by leonelmachava <leonelmachava@gmail.com>
' http://codentronix.com
'
' Copyright (c) 2011 Leonel Machava
'
Option Explicit On

Public Class Point3D
    Protected m_x As Double, m_y As Double, m_z As Double

    Public Sub New(ByVal x As Double, ByVal y As Double, ByVal z As Double)
        Me.X = x
        Me.Y = y
        Me.Z = z
    End Sub

    Public Property X() As Double
        Get
            Return m_x
        End Get
        Set(ByVal value As Double)
            m_x = value
        End Set
    End Property

    Public Property Y() As Double
        Get
            Return m_y
        End Get
        Set(ByVal value As Double)
            m_y = value
        End Set
    End Property

    Public Property Z() As Double
        Get
            Return m_z
        End Get
        Set(ByVal value As Double)
            m_z = value
        End Set
    End Property

    Public Function RotateX(ByVal angle As Integer) As Point3D
        Dim rad As Double, cosa As Double, sina As Double, yn As Double, zn As Double

        rad = angle * Math.PI / 180
        cosa = Math.Cos(rad)
        sina = Math.Sin(rad)
        yn = Me.Y * cosa - Me.Z * sina
        zn = Me.Y * sina + Me.Z * cosa
        Return New Point3D(Me.X, yn, zn)
    End Function

    Public Function RotateY(ByVal angle As Integer) As Point3D
        Dim rad As Double, cosa As Double, sina As Double, Xn As Double, Zn As Double

        rad = angle * Math.PI / 180
        cosa = Math.Cos(rad)
        sina = Math.Sin(rad)
        Zn = Me.Z * cosa - Me.X * sina
        Xn = Me.Z * sina + Me.X * cosa

        Return New Point3D(Xn, Me.Y, Zn)
    End Function

    Public Function RotateZ(ByVal angle As Integer) As Point3D
        Dim rad As Double, cosa As Double, sina As Double, Xn As Double, Yn As Double

        rad = angle * Math.PI / 180
        cosa = Math.Cos(rad)
        sina = Math.Sin(rad)
        Xn = Me.X * cosa - Me.Y * sina
        Yn = Me.X * sina + Me.Y * cosa
        Return New Point3D(Xn, Yn, Me.Z)
    End Function

    Public Function Project(ByVal viewWidth, ByVal viewHeight, ByVal fov, ByVal viewDistance)
        Dim factor As Double, Xn As Double, Yn As Double
        factor = fov / (viewDistance + Me.Z)
        Xn = Me.X * factor + viewWidth / 2
        Yn = Me.Y * factor + viewHeight / 2
        Return New Point3D(Xn, Yn, Me.Z)
    End Function
End Class

The file Main.vb has suffered some changes. See below its code.

'
' Simulation of a Rotating Cube using GDI+
' Developed by leonelmachava <leonelmachava@gmail.com>
' http://codentronix.com
'
' Copyright (c) 2011 Leonel Machava
'
Imports System.Drawing.Graphics
Imports System.Drawing.Pen
Imports System.Drawing.Color
Imports System.Drawing.Brush
Imports System.Drawing.Point
Imports System.Drawing.Bitmap

Public Class Main
    Protected m_timer As Timer
    Protected m_vertices(8) As Point3D
    Protected m_faces(6, 4) As Integer
    Protected m_colors(6) As Color
    Protected m_brushes(6) As Brush
    Protected m_angle As Integer

    Private Sub Main_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ' Enable double-buffering to eliminate flickering.
        Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
        Me.SetStyle(ControlStyles.OptimizedDoubleBuffer, True)

        InitCube()

        ' Create the timer.
        m_timer = New Timer()

        ' Set the timer interval to 25 milliseconds. This will give us 1000/25 ~ 40 frames per second.
        m_timer.Interval = 25

        ' Set the callback for the timer.
        AddHandler m_timer.Tick, AddressOf AnimationLoop

        ' Start the timer.
        m_timer.Start()
    End Sub

    Private Sub InitCube()
        ' Create the cube vertices.
        m_vertices = New Point3D() {
                     New Point3D(-1, 1, -1),
                     New Point3D(1, 1, -1),
                     New Point3D(1, -1, -1),
                     New Point3D(-1, -1, -1),
                     New Point3D(-1, 1, 1),
                     New Point3D(1, 1, 1),
                     New Point3D(1, -1, 1),
                     New Point3D(-1, -1, 1)}

        ' Create an array representing the 6 faces of a cube. Each face is composed by indices to the vertex array
        ' above.
        m_faces = New Integer(,) {{0, 1, 2, 3}, {1, 5, 6, 2}, {5, 4, 7, 6}, {4, 0, 3, 7}, {0, 4, 5, 1}, {3, 2, 6, 7}}

        ' Define the colors of each face.
        m_colors = New Color() {Color.BlueViolet, Color.Cyan, Color.Green, Color.Yellow, Color.Violet, Color.LightSkyBlue}

        ' Create the brushes to draw each face. Brushes are used to draw filled polygons.
        For i = 0 To 5
            m_brushes(i) = New SolidBrush(m_colors(i))
        Next
    End Sub

    Private Sub AnimationLoop()
        ' Forces the Paint event to be called.
        Me.Invalidate()

        ' Update the variable after each frame.
        m_angle += 1
    End Sub

    Private Sub Main_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
        Dim t(8) As Point3D
        Dim f(4) As Integer
        Dim v As Point3D
        Dim avgZ(6) As Double
        Dim order(6) As Integer
        Dim tmp As Double
        Dim iMax As Integer

        ' Clear the window
        e.Graphics.Clear(Color.LightBlue)

        ' Transform all the points and store them on the "t" array.
        For i = 0 To 7
            Dim b As Brush = New SolidBrush(Color.White)
            v = m_vertices(i)
            t(i) = v.RotateX(m_angle).RotateY(m_angle).RotateZ(Me.m_angle)
            t(i) = t(i).Project(Me.ClientSize.Width, Me.ClientSize.Height, 256, 4)
        Next

        ' Compute the average Z value of each face.
        For i = 0 To 5
            avgZ(i) = (t(m_faces(i, 0)).Z + t(m_faces(i, 1)).Z + t(m_faces(i, 2)).Z + t(m_faces(i, 3)).Z) / 4.0
            order(i) = i
        Next

        ' Next we sort the faces in descending order based on the Z value.
        ' The objective is to draw distant faces first. This is called
        ' the PAINTERS ALGORITHM. So, the visible faces will hide the invisible ones.
        ' The sorting algorithm used is the SELECTION SORT.
        For i = 0 To 4
            iMax = i
            For j = i + 1 To 5
                If avgZ(j) > avgZ(iMax) Then
                    iMax = j
                End If
            Next
            If iMax <> i Then
                tmp = avgZ(i)
                avgZ(i) = avgZ(iMax)
                avgZ(iMax) = tmp

                tmp = order(i)
                order(i) = order(iMax)
                order(iMax) = tmp
            End If
        Next

        ' Draw the faces using the PAINTERS ALGORITHM (distant faces first, closer faces last).
        For i = 0 To 5
            Dim points() As Point
            Dim index As Integer = order(i)
            points = New Point() {
                New Point(CInt(t(m_faces(index, 0)).X), CInt(t(m_faces(index, 0)).Y)),
                New Point(CInt(t(m_faces(index, 1)).X), CInt(t(m_faces(index, 1)).Y)),
                New Point(CInt(t(m_faces(index, 2)).X), CInt(t(m_faces(index, 2)).Y)),
                New Point(CInt(t(m_faces(index, 3)).X), CInt(t(m_faces(index, 3)).Y))
            }
            e.Graphics.FillPolygon(m_brushes(index), points)
        Next
    End Sub
End Class

The code is pretty much self-explanatory. Below I will just list the key changes.

In the Load event I enable double buffering. This is fundamental to eliminate flickering in the animation. Try to remove the first 2 lines of code, and run the application. You will certainly see the flickering that results.

Basically I have changed the code to draw filled faces instead of lines. However, now we must make sure to draw distant faces first, and closer ones last (Painters Algorithm).

THE FULL SOURCE CODE IS HERE.

Conclusion

As I have promised in the last tutorial, today I have shown how to make a rotating solid cube. In my next VB.NET tutorial, I will show you how to make a cool game in pretty simple steps.
If you liked this article, please consider leaving a comment, or sharing this post using one of the buttons below, or even subscribing to the blog.

Leave a comment ?

11 Comments.

  1. Parabéns bacana seu projeto

  2. wiiii, several years ago i droped a personal project (in vb6) because i couldnt create a function that would let me rotate the axis in r3, i cant wait to modify this >w<

  3. hello again lefam, im sorry that i didnt post anything up to now, i was really busy because i got a new job just a few weeks ago =3
    well, i didnt spent too much time on this but i got some strange results xd, let me explain it (and please forgive my bad english, its not my native language)
    i always like to know how things work, so i keep “discovering the circle again” over and over on diferent things that i see
    some years ago i was playing with some equations and i wondered how they would look like on a 3D space, and made myself a rough but extremely precise (and slow) 3D drawing software, based on explicit 3 var functions, painting one dot at a time xd
    i made some changes and painted some beatiful functions (adding colors based on the function itself makes them alive) but my software had some trouble, it was only capable of “looking” from one angle, so i wanted to rotate the functions, but i got some personal issues and droped the proyect before i could design an algorith to do so
    a week ago i was surfing the net looking how to manage built in drawing functions from vb.net (because it doesnt let me know the hDC of the form anymore <.<) and found your example, it reminded me of that project and i remade it (for some reason it was really easy this time xd) and added 2 rows from your code, after some fixing it worked lovely, but for some reason, when i try to rotate more than 1 axis at a time the functions suffer horrible disforming xD
    ill work on this a little more, thanks again for you example n_n
    oh, and… im sorry if i wrote too much xd
    see you later =3

    • First of all, let me tell you that I am glad to know that my articles and codes were useful to you.
      The “disformation” may happen due to an error in the rotation formula (or matrix composition, if you are using matrices). Other probable cause can be the order of the transformations (rotation followed by translation may have a different result from translation followed by rotation).
      Anyway, I would need to see your code to better help you.

  4. here’s a visual example of whats happening with the points, i made this in a rush so, no back buffer = lots of flickering xD
    also, i used a low dot density, that way it redraws fast even with a high refresh rate (Form_MouseMove) but it doesnt look like a solid object
    the example its done with a function that creates half a sphere, that way you can see the rotation easily
    well, here it is:
    http://www.megaupload.com/?d=0X2ZVIB3

  5. the entire process its done with 2 for in this way:

    start FOR rows
    Start FOR columns
    calculate profundity for the 3d point
    rotate in column axis
    rotate in row axis
    translate 3d point into 2d
    paint it
    NEXT column
    NEXT row

    • Nice simulation (“despite the disformation”).
      Probably, you are not doing correctly the composition of the transformations (I mean rotations).

Leave a Comment

Notify me of followup comments via e-mail. You can also subscribe without commenting.