Skip to content

Tutorial – Drawing Shapes

Christopher Chamberlain edited this page May 13, 2020 · 1 revision

In this tutorial we will draw shapes.

Ensure you've covered 'Getting Started' before following this guide.

These tutorials are written assuming that the program is running on NET Core 3.0. The libraries themselves are NET Standard 2.1 compliant. If you don't have .NET Core 3.0 (or newer), then you can download it here.

Shapes

In Heirloom, there are various shape types. They are Circle, Triangle, Rectangle and Polygon. In addition to these types, there exist a Mesh type for rendering complex geometry.

Circle

The following example code will act as a basis for this article. It draws a simple white circle on a window.

using Heirloom.Desktop;
using Heirloom;

namespace Tutorial
{
    public class Program
    {
        public static float Time;

        private static void Main(string[] args)
        {
            Application.Run(() =>
            {
                // Construct a window with 4x multisampling
                var window = new Window("Tutorial", (800, 480), MultisampleQuality.Medium);
                window.MoveToCenter();

                // Begin the update loop
                var loop = GameLoop.Create(window.Graphics, OnUpdate);
                loop.Start();
            });
        }

        private static void OnUpdate(GraphicsContext gfx, float dt)
        {
            gfx.Clear(Color.DarkGray);

            Time += dt;

            // Compute the center of the screen
            var center = new Vector
            {
                X = gfx.Surface.Width / 2F,
                Y = gfx.Surface.Height / 2F
            };

            // Draw the circle
            gfx.Color = Color.White;
            gfx.DrawCircle(center, 200);
        }
    }
}

When the code above is ran, it should look like the following:

In addition we can use the circle struct to draw as well, change the code with the following. We will replace DrawCircle with DrawCircleOutline and set a line width of 3.

// We can create a circle struct too
var circle = new Circle(center, 200);

// Draw the circle
gfx.Color = Color.White;
gfx.DrawCircleOutline(circle, 3);

If you run the application now, it should look like the image below.

Each primitive drawing function in Heirloom comes in pairs for the solid and outline version (ie, DrawTriangle and DrawTriangleOutline). With that said, let's move onto triangles.

Triangles

Continuing with the code drawing the circle outline, add the following to your source code.

// Compute an animated triangle
var triangle = new Triangle
{
    A = center + Vector.FromAngle(Time + Calc.TwoPi * (0 / 3F)) * 200,
    B = center + Vector.FromAngle(Time + Calc.TwoPi * (1 / 3F)) * 200,
    C = center + Vector.FromAngle(Time + Calc.TwoPi * (2 / 3F)) * 200,
};

// Draw a triangle
gfx.Color = Color.Violet;
gfx.DrawTriangle(triangle);

gfx.Color = Color.White;
gfx.DrawTriangleOutline(triangle, 3);

Again, when ran it should look like the follow (but you know, actually rotating).

It is difficult for me to come up with clever or fun ways to introduce different primitive shapes supported by Heirloom. The remainder of this article will largely just be code snippets and associated images. While that might not be exciting, hopefully it will be useful.

Regular Polygon

Here we draw a 5 sided regular polygon (also known as a pentagon).

// Draw a pentagon
gfx.Color = Color.White;
gfx.DrawPolygon(center, 5, 200);

Simple Polygon

In this snippet, we draw a simple polygon defined by the points. They define a blocky C shape.

var points = new Vector[]
{
    center + new Vector(-200, -200),
    center + new Vector(+200, -200),
    center + new Vector(+200, -100),
    center + new Vector(-100, -100),
    center + new Vector(-100, +100),
    center + new Vector(+200, +100),
    center + new Vector(+200, +200),
    center + new Vector(-200, +200),
};

// Draw the polygon
gfx.Color = Color.White;
gfx.DrawPolygon(points);

In addition to drawing from a set of points, a Polygon type exists. This type provides extra information about the polygon such as edge normals, area and centroid. Heirloom also contains the tools to triangulate or decompose simple concave polygons into convex partitions.

Mesh

To demonstrate the Mesh object, we will decompose the previous polygon into triangles and map an image onto it.

The first thing to do is add the following static field to our program.

public static Mesh Mesh;

Second, add the following before the code that creates the game loop:

// Construct a new polygon
var polygon = new Polygon(new Vector[]
{
    new Vector(-200, -200),
    new Vector(+200, -200),
    new Vector(+200, -100),
    new Vector(-100, -100),
    new Vector(-100, +100),
    new Vector(+200, +100),
    new Vector(+200, +200),
    new Vector(-200, +200),
});

Next we need to take advantage of the aformentioned triangulation feature. Add the following immediately after the previous snippet:

// Construct a new mesh
Mesh = new Mesh();

// Triangulate and add the triangles to the mesh instance
foreach (var (a, b, c) in polygon.Triangulate())
{
    var i = Mesh.Vertices.Count;

    // Add the vertices of this face
    Mesh.AddVertex(new Vertex(a, (0F, 0F)));
    Mesh.AddVertex(new Vertex(b, (1F, 0F)));
    Mesh.AddVertex(new Vertex(c, (1F, 1F)));

    // Define a face with these vertices
    Mesh.AddTriangle(i + 0, i + 1, i + 2);
}

This piece of code takes the simple polygon, triangulates it and appends triangle faces to a mesh object. If we run the code now, we will see the following purple shape.

Yikes, the image is stretched funny on the triangles. Can we do any better? The answer is yes, we need to compute improved UV coordintes for each vertex. The values in the previous snippet were just a quick and dirty choice to get something on the screen.

Lets compute some improved UV coordinates based on the bounding rectangle of this polygon.

var bounds = polygon.Bounds;

// Triangulate and add the triangles to the mesh instance
foreach (var (a, b, c) in polygon.Triangulate())
{
    var i = Mesh.Vertices.Count;

    // Add the vertices of this face
    Mesh.AddVertex(new Vertex(a, (a - bounds.Min) / (Vector) bounds.Size));
    Mesh.AddVertex(new Vertex(b, (b - bounds.Min) / (Vector) bounds.Size));
    Mesh.AddVertex(new Vertex(c, (c - bounds.Min) / (Vector) bounds.Size));

    // Define a face with these vertices
    Mesh.AddTriangle(i + 0, i + 1, i + 2);
}

And voila!

A Mesh with an image mapped to it. Obviously more sophisticated UV mappings are possible.

Clone this wiki locally