CXO2's picture

Something weird with GL.Scale()

Hi there!
I am facing something weird about GL.Scale..
Currently i am writing my own Sprite and UI classes (like Button, Checkbox, Scrollbar etc (with sprite)) that using Vertex Buffer Object on drawing.
Let's say I have this texture, the size is 162x49 and its a spritesheet (or whatever u call) for Button.

Button Sprite Sheet

Since my Button class is working properly, I can draw it properly, and make it work:

Rendered Correctly! Yay!

And then, I was trying to make some Transformation effects (like Rotation and Scaling)
I didn't find any problem with Rotation, it got work properly, but something weird when I am going to Scale the button with Scale(1.1f, 1f), it appears like:

It's scaled correctly but the position is moved forward! (same case if i scale the height or both too)
I double checked my Vertex Positions and TexCoords, but the value are correct and it drawn properly without Scaling (no position changed, even with rotation).

Here my drawing code:

       /// <summary>
        /// Draw the Sprite With Shader
        /// </summary>
        /// <param name="shader">Shader Effect</param>
        public virtual void Draw(Shader shader)
        {
            try
            {
                if (IsError)
                    return;
 
                if (Texture == null)
                    return;
 
                if (!Visible)
                    return;
 
                // Buffer is Vertex Buffer Object
                Buffer.CreateTexCoords(Texture.Size, TexRect);
                Buffer.CreatePositions(new Rectangle((int)X, (int)Y, Width, Height));
                Buffer.CreateColors(Color);
 
                // Texture Blending
                Renderer.Call(() => GL.Enable(EnableCap.Blend));
                Renderer.Call(() => GL.Color4(Color));
 
                // Translation Origin (this also working properly, I need this function in part of my game lol)
                Renderer.Call(() => GL.Translate(new Vector3((Width * Origin.X), (Height * Origin.Y), 0f)));
 
                // Rotatation
                Renderer.Call(() => GL.Translate(X + Width / 2, Y + Height / 2, 0.0));
                Renderer.Call(() => GL.Rotate(Rotation.Angle, new Vector3(0f, 0f, 1f)));
                Renderer.Call(() => GL.Translate(-(X + Width / 2), -(Y + Height / 2), 0.0));
 
                // Scale (Scaled, but the position changed!)
                Renderer.Call(() => GL.Scale(new Vector3(Scale.X, Scale.Y, 0.0f)));
 
                // Bind texture
                Texture.Bind();
 
                // Bind Shader (Null for no shader)
                Shader.Bind(shader);
 
                // Draw VBO
                Buffer.Draw();
 
                // Restore everything, just in case if anyone want to touch GL function directly.
                // Unbind Shader
                Shader.Bind(null);
 
                // Unbind Texture
                Texture.Unbind();
 
                // Restore Rotation
                Renderer.Call(() => GL.Rotate(-Rotation.Angle, new Vector3(0f, 0f, 1f)));
 
                // Restore Translation Origin
                Renderer.Call(() => GL.Translate(new Vector3(-(Width * Origin.X), -(Height * Origin.Y), 0f)));
 
                // Restore Scalling
                Renderer.Call(() => GL.Scale(new Vector3(1f, 1f, 0.0f)));
 
                // Load Identity
                GL.LoadIdentity();
 
                // Disable blending
                Renderer.Call(() => GL.Disable(EnableCap.Blend));
 
                // Set white color
                Renderer.Call(() => GL.Color4(Color.White));
            }
            catch (Exception ex)
            {
                Log.Instance().WriteLine("Failed to Draw Sprite." +
                    Environment.NewLine + ex.Message +
                    Environment.NewLine + Environment.NewLine + "[Sprite: " + Name + "]", "Error");
 
                Texture.Unbind();
                IsError = true;
            }
        }

Anyway, Renderer.Call() is just something like automatic Error Checking everytime I call GL functions (with no return / void return)
and I am not calling this at GL.Begin() and GL.End() block (I don't even use Immediate mode lol) because calling GL.GetError() will trigger GL_INVALID_OPERATION Error upon the execution.

    public class Renderer
    {
        /// <summary>
        /// Call OpenGL function and check for the error
        /// </summary>
        /// <param name="callback">OpenGL Function to be called</param>
        public static void Call(Action callback)
        {
            callback();
            CheckError();
        }
 
        private static CheckError()
        {
            ErrorCode errorCode = GL.GetError();
 
            if (errorCode != ErrorCode.NoError)
                Log.Instance().WriteLine("Error!", "Fatal Error");
        }
    }

It maybe not proper way to scale the texture (maybe there another better way?)
but I believe there are the way to make this work properly (to be honest i am curious about it even there are better way Lol)

Thank you :D


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Frassle's picture

Calling the GL matrix functions (Translate/Rotate/Scale) creates a new transformation matrix and multiplies it with the current matrix on the top of the matrix stack.
So your call sequence of Translate, Translate, Rotate, Translate, Scale creates a transform matrix by multiplying them all together ((((translate * translate) * rotate) * translate) * scale), conceptually this does the scaling of the vertices after the translates and rotate. So if your vertices have been translated to (100, 200) and then scaled by (1.1, 1.0) then they will be moved to (110, 200). You probably want to scale first, then rotate then translate.

You also want to use Push and Pop Matrix to save and restore the matrix your working on rather than reverting the changes manually at the end of your function.

CXO2's picture

Hi there!
Thanks for the reply

I follow your suggestion, its really helpful and i can figure out how it work now (with transformation matrix)
and I decide to write the classes like SFML one, since i found lot (not really much actually) similarity between my code and SFML code (especially about texture) .

and thanks for Push and Pop Matrix suggestion!

CXO2's picture

Hi, it seem the position still changed, and now i am using Matrix4
like you said, my previous call sequence is Translate * Translate * Rotate * Translate * Scale.
And i changed to Scale * Rotate * Translate * Translate * Translate but the Scaled Sprite still moved..

Here my current drawing (sprite) code now:

        /// <summary>
        /// Draw the Sprite With Shader
        /// </summary>
        /// <param name="shader">Shader Effect</param>
        public virtual void Draw(Shader shader)
        {
            try
            {
                if (IsError)
                    return;
 
                if (Texture == null)
                    return;
 
                if (!Visible)
                    return;
 
                // Buffer is Vertex Buffer Object
                Buffer.CreateTexCoords(Texture.Size, TexRect);
                Buffer.CreatePositions(new Rectangle((int)X, (int)Y, Width, Height));
                Buffer.CreateColors(Color);
 
                // Push Current Matrix
                Renderer.Call(() => GL.PushMatrix());
 
                // Apply Current Matrix
                Renderer.Call(() => GL.MatrixMode(MatrixMode.Modelview));
                Renderer.Call(() => GL.LoadIdentity());
 
                Renderer.Call(() => GL.LoadMatrix(ref Matrix));
 
                // Bind texture
                Texture.Bind();
 
                // Bind Shader (Null for no shader)
                Shader.Bind(shader);
 
                // Draw VBO
                Buffer.Draw();
 
                // Restore the Matrix
                Renderer.Call(() => GL.PopMatrix());
                Renderer.Call(() => GL.LoadIdentity());
 
                // Unbind Shader
                Shader.Bind(null);
 
                // Unbind Texture
                Texture.Unbind();
 
                // Set white color
                Renderer.Call(() => GL.Color4(Color.White));
            }
            catch (Exception ex)
            {
                Log.Instance().WriteLine("Failed to Draw Sprite." +
                    Environment.NewLine + ex.Message +
                    Environment.NewLine + Environment.NewLine + "[Sprite: " + Name + "]", "Error");
 
                Texture.Unbind();
                IsError = true;
            }
        }

and this is how the Matrix calculated, this function is always called when Origin, Rotation and Scaling value is changed.

        /// <summary>
        /// Update Transformation Matrix4
        /// </summary>
        /// <returns></returns>
        public Matrix4 UpdateTransform()
        {
            Matrix4 translation, rotation, scale;
 
            translation = Matrix4.CreateTranslation(new Vector3((Width * Origin.X), (Height * Origin.Y), 0f));
            rotation = Matrix4.CreateFromAxisAngle(new Vector3(0f, 0f, 1f), Rotation);
            scale = Matrix4.CreateScale(new Vector3(Scale.X, Scale.Y, 0.0f));
 
            Matrix = scale * rotation * Matrix4.CreateTranslation(new Vector3(X + Width / 2, Y + Height / 2, 0f)) * Matrix4.CreateTranslation(new Vector3(-(X + Width / 2), -(Y + Height / 2), 0f)) * translation;
            return Matrix;
        }

Thank you

EDIT
After I am using this method, now the rotation is not working (the texture disappear!) LOL
I tested both method and I am trying to pull out the Matrix, and this is what i got:

            // From this code, I got this matrix (which  rotated correctly) :
            // Matrix = { -1,-8.742278E-08,0,0,8.742278E-08,-1,0,0,0,0,1,0,0,0,0,1 }
 
           GL.Rotate(180f, new Vector3(0f, 0f, 1f))

And here the Manual one:

            // From this code, I got this matrix (which  rotated NOT correctly, it make the texture not showing up @__@) :
            // Matrix = { -0.5984601,-0.8011526,0,0,0.8011526,-0.5984601,0,0,0,0,1,0,0,0,0,1 }
            // Note that the Scaling is working (but the position still changed) with this method
 
            Matrix4 rotation = Matrix4.CreateFromAxisAngle(new Vector3(0f, 0f, 1f), 180f);
            GL.LoadMatrix(ref rotation);

And I can guarantee that the rest of other code is same and no GL Error occur during this process..
The problem even get worse @__@

the Fiddler's picture

           Matrix4 rotation = Matrix4.CreateFromAxisAngle(new Vector3(0f, 0f, 1f), 180f);

The last parameter is expected to be in radians, not degrees. Try MathHelper.PiOver2 instead of 180f.

CXO2's picture

Thank you for response Fiddler.

the code:
rotation = Matrix4.CreateFromAxisAngle(new Vector3(0f, 0f, 1f), MathHelper.PiOver2);
It doesn't work, it even make all sprites disappear (not showing).

Here my current code to calculate the Matrix, note that "Rotation" variable is Angle (I set it 180f)

private Matrix4 Matrix;
 
private void UpdateTransform()
{
            Matrix4 translation, rotation, scale;
 
            translation = Matrix4.CreateTranslation(new Vector3((Width * Origin.X), (Height * Origin.Y), 0f));
            rotation = Matrix4.CreateFromAxisAngle(new Vector3(0f, 0f, 1f), MathHelper.DegreesToRadians(Rotation));
            scale = Matrix4.CreateScale(new Vector3(Scale.X, Scale.Y, 0.0f));
 
            Matrix = scale * Matrix4.CreateTranslation(new Vector3(X + Width / 2, Y + Height / 2, 0f)) * rotation * Matrix4.CreateTranslation(new Vector3(-(X + Width / 2), -(Y + Height / 2), 0f)) * translation;
}
 
public Matrix4 GetTransform()
{
            if (!IsTransformUpdated)
                        UpdateTransform();
 
            return Matrix;
}

and here my drawing code:

        /// <summary>
        /// Draw the Sprite With Shader
        /// </summary>
        /// <param name="shader">Shader Effect</param>
        public virtual void Draw(Shader shader)
        {
            try
            {
                if (IsError)
                    return;
 
                if (Texture == null)
                    return;
 
                if (!Visible)
                    return;
 
                // Buffer is Vertex Buffer Object
                Buffer.CreateTexCoords(Texture.Size, TexRect);
                Buffer.CreatePositions(new Rectangle((int)X, (int)Y, Width, Height));
                Buffer.CreateColors(Color);
 
                // Push Current Matrix
                Renderer.Call(() => GL.PushMatrix());
 
                // Blending
                Renderer.Call(() => GL.Color4(Color));
 
                // Bind texture
                Texture.Bind();
 
                // Load the matrix
                Matrix4 Matrix = GetTransform();
                Renderer.Call(() => GL.MatrixMode(MatrixMode.Modelview));
                Renderer.Call(() => GL.LoadIdentity());
 
                Renderer.Call(() => GL.LoadMatrix(ref Matrix));
 
                // Bind Shader (Null for no shader)
                Shader.Bind(shader);
 
                // Draw VBO
                Buffer.Draw();
 
                // Unbind Shader
                Shader.Bind(null);
 
                // Unbind Texture
                Texture.Unbind();
 
                // Restore the Matrix
                Renderer.Call(() => GL.PopMatrix());
                Renderer.Call(() => GL.LoadIdentity());
 
                // Set white color
                Renderer.Call(() => GL.Color4(Color.White));
            }
            catch (Exception ex)
            {
                Log.Instance().WriteLine("Failed to Draw Sprite." +
                    Environment.NewLine + ex.Message +
                    Environment.NewLine + Environment.NewLine + "[Sprite: " + Name + "]", "Error");
 
                Texture.Unbind();
                IsError = true;
            }
        }

Edit
Here the generated Matrix from code above (I get it from GL.GetFloat)
Matrix = {-1,-8.742278E-08,0,0,8.742278E-08,-1,0,0,0,0,0,0,-1134,-1034,0,1}

When I am using GL.Rotate(), it showing properly (i also get this from GL.GetFloat), here the Matrix:
Matrix {-1,-8.742278E-08,0,0,8.742278E-08,-1,0,0,0,0,0,0,1134,1034,0,1}

Edit again
Sorry, i was in wrong formula
I recalculate it with MathHelper, the result is almost same, but why it minus? (-1134,-1034, 0, 1)

Anyway sorry for being so OOT here @__@

CXO2's picture

Okayy so this code solve the Rotation problem:

            Matrix.M41 *= -1;
            Matrix.M42 *= -1;

But I believe there are neat solution about this..
Any suggestion is appreciated!

but my main error still occur now.
the scaling still change the texture position...