OpenGL: More on Transforms

In OpenGL- Rotate, Scale and Translate I took a very simple example of transforming some text but what happens if you want to do something more complex. For example you want to have a pair of strings that you can transform around the screen together (ie not have to worry about transforming individually. To do this we can use a form of layering in our rendering where each layer can contain any number of items that will be rendered together. Each layer can be transformed with the resulting transform being applied to each item in the layer.

We’re going to start off by defining an abstract class called ScreenWidget. I’ve used widget because I couldn’t think of a more “correct” name to call items that are going to be displayed on the screen. If there is a better name, do tell me as I’m keen to stick with a vocabulary that is well understood.

abstract class ScreenWidget
    {
        public Vector3f Position { get; set; }
        public Vector3f Anchor { get; set; }
        public Vector3f Scale { get; set; }
        public float Angle { get; set; }
        public Vector3f RotationAxis { get; set; }

        public ScreenWidget()
        {
            Scale = new Vector3f(1, 1, 1);
            RotationAxis = new Vector3f(0,0,1);
        }

        public unsafe void Draw()
        {

            gl.PushMatrix();
            gl.Translatef(Position.X, Position.Y, Position.Z);
            gl.Scalef(Scale.X, Scale.Y, Scale.Z);
            gl.Rotatef(Angle, RotationAxis.X, RotationAxis.Y, RotationAxis.Z);
            gl.Translatef(-Anchor.X, -Anchor.Y, -Anchor.Z);

            DrawComponents();
            gl.PopMatrix();
        }

        protected abstract void DrawComponents();

        public virtual void Setup() {}

        public virtual void Update(float secondsSinceLastUpdate){}
    }

In the ScreenWidget class we can see that it tracks values for position, anchor, scale, angle and rotation axis. It’s worth noting that the position is actually the position of the anchor point within the containing widget. If the widget isn’t nested within another widget then the position will be the position of the anchor point on the screen.

The Draw method on the ScreenWidget is called in order to render all items contained within the widget to the screen. Before doing so it applies a sequence of transforms to scale, rotate and position the widget correctly with respect to its containing widget (and thus subsequently the screen).

What’s interesting is that all these transforms and the call to DrawComponents (which by the way is what an overriding class has to implement in order to actually render items to the screen) are wrapped in a pair of PushMatrix and PopMatrix method calls.  Essentially this ensures that the OpenGL model matrix (ie the matrix that is used to transform items being rendered) is the same when the method ends as when the method started – failure to do this may lead to unexpected results with items being incorrectly positioned, scaled or rotated further on during rendering. PushMatrix pushes the current matrix onto a temporary stack, whilst PopMatrix pops the top matrix on the stack off and makes it the current matrix.

We’ll start by creating the simplest of widgets, the TextWidget:

class TextWidget:ScreenWidget
{
    OpenGLFont font;
    GlyphRun title;
    public override void Setup()
    {
        font = new OpenGLFont(new Font(FontFamily.GenericSerif, 12, FontStyle.Regular));
        title = new GlyphRun(font, "Hello World!", new Size(int.MaxValue, int.MaxValue), OpenGLTextAlignment.Left, true);
        this.Anchor = new Vector3f(title.Size.Width / 2, title.Size.Height / 2, 0);
    }
    protected override void DrawComponents()
    {
        title.Draw();
    }
}

As you can see the TextWidget simply creates a GlyphRun object that can be draw during the DrawComponents method. Also note that the Anchor property has been set based on the size of the title. This will ensure that if this widget is rotated or scaled it will be done based on the centre of the text, rather than a corner.

Next is a simple container widget, funnily enough called ContainerWidget:

class ContainerWidget : ScreenWidget
    {

        TextWidget text;
        TextWidget text2;
        public override void Setup()
        {
            text = new TextWidget();
            text.Position = new Vector3f(10,10, 0);
            text.Angle = 30;
            text.Setup();

            text2 = new TextWidget();
            text2.Position = new Vector3f(20, 20, 0);
            text2.Angle = 50;
            text2.Setup();
        }
        protected override void DrawComponents()
        {
            text.Draw();
            text2.Draw();
        }

    }

You can see that the two TextWidget instances have been position and rotated by different amounts. Before we can see this we’ll need to go back to our MainForm and create an instance of the ContainerWidget.  The Draw method will then be called on this instance in order to draw the two nested GlyphRuns.

public partial class MainForm : ApplicationForm
{

    public MainForm()
    {
        InitializeComponent(); 
    }

    ContainerWidget widget;
    protected override void SetupScene()
    {
        base.SetupScene();

        widget = new ContainerWidget();
        widget.Position = new Vector3f(0, 0, 0);
        widget.Setup();
    }

    protected override void DrawScene()
    {
        base.DrawScene();

        widget.Draw();

    }

    protected override void UpdateScene(float secondsSinceLastUpdate)
    {
        base.UpdateScene(secondsSinceLastUpdate);

        widget.Update(secondsSinceLastUpdate);
    }
}

As you can see this keeps both the MainForm and the ContainerWidget very clean in terms of the code than needs to be written to display elements.

image

This image isn’t particularly great as the two pieces of text are mostly off screen. So lets modify just the position of the ContainerWidget in the DrawScene method of the MainForm:

gl.PushMatrix();
gl.Translatef(100.0f, 100.0f, 0);
widget.Draw();
gl.PopMatrix();

This gives a much clearer picture of the two pieces of text but note that we only needed to translate the container widget.

image

Leave a comment