Math’d

Once I managed to get an entirely unimaginative red 2D triangle or three onto the screen in a few different ways, I knew that I had to expand into the 3rd dimension.  To that end, I dropped back to a single 3D triangle, left it red, and prepared myself for what should be on the screen.  A couple seconds of compiling and firing up the program revealed that while the triangle was in fact on my screen, it looked no different from my 2D version.

This is simply because by default OpenGL happily ignores the Z-Axis entirely.  In the old fixed pipeline days, you could probably fix that by simply setting some configuration value or other to one that makes 3D stuff happen.  In the fancy new shader pipeline you need to tell the card how to do that.  That means passing a few matrices down with all of your fun geometry.  In English, that means you need to tell the card how to use the Z-axis.

In one of the books I’m reading it was mentioned that OpenGL “is not a math library.”  This is especially true with LWJGL.  In C/C++ you can use the library mentioned by the book called vmath.  In Java you are on your own.  This fact has sent me down the multi-week long detour of crafting a math package of my own and making sure the tests for it prove it behaves the way math should.

The current list of things in my math library are as follows:

  • For graphics:
    • Vector2 – Vector of 2 floating points.
    • Vector3 – Vector of 3 floating points.
    • Vector4 – Vector of 4 floating points.
  • For pixel level precision:
    • IntVector2 – Vector of 2 integers.
    • IntVector3 – Vector of 3 integers.
    • IntVector4 – Vector of 4 integers.
  • For transforms:
    • Matrix33 – A 3×3 matrix of floating points.
    • Matrix44 – A 4×4 matrix of floating points.
    • Quaternion – Numbers in the form (a + bi + cj + dk) using floating point values.
  • For fractals:
    • ComplexNumber – Numbers in the form (a + bi) using floating point values.

This list is perhaps a bit larger than I needed with the goal of simply producing a a proper perspective projection matrix.  I was however on a roll and my test cases provided actual evidence of progress which is nice to have sometimes when you feel a bit stalled in a project.  I also created a little test program to see how well these things perform.  Specifically how quickly I can multiply a Matrix44 by another Matrix44, and how fast multiplying a Matrix44 by a Vector4 was.  I was satisfied with the numbers I was seeing so hopefully it will be enough.

As of right now, my test coverage tool tells me that around 89% of my math package is covered with tests.  I am shooting for a perfect 100% for this package because if I can’t trust my math library, how can I know when I’ve built things that use it and have problems whether it’s the math that’s wrong or the thing I built on top of it?

Tricky Tricky…

So there I was, wrestling with an unending supply of “what the heck is going on” while trying to figure out LWJGL and OpenGL.  Everything looked right, everything compiled, logically the code made sense, but nothing would show up on my screen.  I double checked everything, I read more parts of my various books, I looked things up in online tutorials.  I triple checked and quadruple checked.  Nothing seemed wrong at all except nothing was showing up.

Over the course of over a week I systematically added more calls to System.out.println() (that’s Java’s console output command) than there were functional lines.  If there was something that some part of the code was doing and I didn’t already know everything about what it was doing, I’d add more output.  Still with dozens of lines of output with everything from confirmation that my configuration file was being found and properly loaded, to the exact contents of the shaders that it was using, to the ID that OpenGL assigned my vertex buffer, I just couldn’t find a problem.

After days of messing around with this, a friend gave it a shot from scratch and his worked.  This was obviously frustrating for me, but he gave me his code to look over and compare to mine.  After an hour or two of picking it over nothing seemed out of place except that he was using the “BufferUtils” that is built in to LWJGL.  I switched mine over to use that and what do you know, suddenly I could see things on my screen.

Now I’m not one to look a gift horse in the mouth here, but after all my attempts and with my goal being to learn how to do all of this for myself, I really had to know what was different.  I was using something to the effect of:

FloatBuffer vertexBuffer = ByteBuffer.allocateDirect( 12 )
                             .asFloatBuffer();

He was doing:

FloatBuffer vertexBuffer = BufferUtils.createFloatBuffer( 3 );

My knowledge of the various buffers comes from use of Java’s NIO package.  I had no idea there was a fancy utility class within LWJGL.  If I had bumped into that knowledge, I would have had a pretty good idea about what I was doing wrong.  The documentation provided for BufferUtils is pretty detailed about what is going on, if only I’d known to look.  In my defense, I’m primarily learning OpenGL and then mentally translating it to LWJGL.

The secret comes down to byte order.  In computers there are two ways of representing multi-byte values.  They are referred to as little endian and big endian.  In English, they mean that the low-order byte is at the end (on the right) or the high-order byte is at the end respectively.

This is an example of how to represent a two byte value containing the number 17,117 in both ways:

Little Endian:  01000010 11011101
Big Endian:     11011101 01000010

The reason this is important is because my computer (Intel architecture) uses little endian and the Java Virtual Machine (JVM) uses big endian.  In other words, Java sees everything as correct, all the right numbers show up in my console output, and then when I hand it to the video card everything is in the wrong order.  This means my fancy triangle had its vertexes somewhere very different than I was expecting (and likely not in the view of the camera at all).

As it turns out, the tiny part I was missing from my code was this:

FloatBuffer vertexBuffer = ByteBuffer.allocateDirect( 12 )
                             .order( ByteOrder.nativeOrder() )
                             .asFloatBuffer();

If that were there, Java would know that it should be using the buffer data in the opposite order it was expecting to use for itself (because it would be setting them up for the real computer instead of the JVM).  This is not to say I won’t be using the BufferUtils version, because I will, but now I know how what I had was different from what it needed to be and why it had to be that way.