I've been reverse engineering a MineCraft application in C, built with OpenGL. If you are unaware, the game involves the generation of cubes, UV Maps textures to the cubes, and then they are placed in the world that you (generally) add to, and remove to build and destroy (and a whole lot more).
The game is fully functional. It works. Everything renders. All functionality is okay. But I am having a LOT of trouble (for days), reconstructing the UV Mapping on a cube from a texture atlas. The problem specifically is that I do not understand how this system is mapping the textures onto the cube with UV Mapping. I've reverse engineered, reconstructed, and manually calculated the function spoken of in this post, and yet from all the resources I've read, I'm falling short. If anyone would be gracious enough to help me see what I've understood incorrectly, and / or how this function is doing what it is doing, I'd be overjoyed. This is the final piece, the rest of the application I understand inside and out. It's now just this.
One function is being utilized to create the vertices buffer data, the normal data, and the texture data to push down the GL Pipeline after the cubes are constructed within it.
The vertices data in the function I am reverse engineering is self-explanatory, as it is simply using a cartesian coordinate system. The normal data, also simple. But the UV Data, I've been spinning on this for three days. ONE reason I am jammed up is all of the tutorials for UV Mapping either involve a single square texture used on all 6 sides of the square, or an unraveled cube texture. This Minecraft does not use either of these. It uses a 3x1 rectangle map. Bottom, middle, top. The middle is used as all the side (left, right, front, back), and the top and bottom are standalone.
The other reason is when I've done the math (and I have, over and over again to check my calculations), I cannot come up with the same answer the program does. And the program is running fine, it works. So I know I'm missing something.
I've drawn the UV coordinates on my whiteboard, researched / read UV Mapping from many sources (I'll cite below), and I just cannot figure out how this formula works. The main reason is the non-existent documentation and relatively ambiguous naming conventions the person used to create these functions. But nevertheless, I'm so close to finishing everything. This is the last piece I need to understand in order for the whole puzzle to snap into place.
From my understanding as to how this works, with with UV Texture Mapping, the UV or ST coordinates map the same as Cartesian, except that they are to be from [0..1]. But from what I see as the algorithm in this application (that works), I must be missing something.
The way I was looking at it, or at least what I had read in multiple places, is that regardless of where your vertices are, wherever you want to render them, wherever you want the bottom left of the texture to be, you place it where you would consider your bottom left vertices to be. Top right of the texture, top right vertices. And so on. And when I did the math on some of these, 4 / 6 of the equations panned out when I substituted the proper [0..1] coordinates in place of the <x,y,z> vectors.
FYI, the texture atlas has 16 "rows", or textures, from left to right. But each cube consists of 3 texture squares. There are no "left", "right", or "back", as the "front" texture is used for each of those sides. Meaning, left = front = back = left... Ultimately, TOP, MIDDLE x 4, BOTTOM. Each texture block is 0.0625
wide, 0.0625
tall. That's 16 in total.
float *vector
is an array of floats that will be used as the vertices for the shader.float *normal
is the array of normal data to be used for the shader.float *texture
is the UV data, which likewise is later added to the shader.int left, int right, int top, int bottom, int front, int back
are all booleans. They specify which faces to render. If all are1
, then all vertex data for that cube will be rendered. If all of those were0
, then none would be rendered.float x, float y, float z
are the coordinates of the block to be rendered.int n
is the offset of the coordinates supplied next, to build the cube (the width and height)int w
is the texture to be used. (from1 - 16
)
When the function begins, it subtracts 1
from w
to start the texture coordinates at 0
, instead of 1
. It then runs through, setting the coordinates for each point, going to the next memory space and assigning it the following coordinates. It does that over and over, for the *vector_data
which points to *vector
, and *normal_data
which points to *normal
. This is all intuitive.
But coming to *texture_data
which points to "*texture", is where it all goes to hell and a hand-basket for me.
I used values (0, 0, 0)
as the initial (x, y, z)
values, w = 1
, n = 0.5
, and all faces, int left, int right, int top, int bottom, int front, int back = 1, 1, 1, 1, 1, 1
, to try to dissect it, as could not find clear-cut information in the resources I cited below, not at least which could help me resolve this, that I could understand.
Just to let you know I've tried everything I think I can, I'll also give you the data that I ran through this application, that didn't match up with my equations.
THE PROBLEMATIC FUNCTION.
make_cube(float *vector, float *normal, float *texture, int left, int right, int top, int bottom, int front, int back,
float x, float y, float z, float n, int w) {
float *vector_data = vector;
float *normal_data = normal;
float *texture_data = texture;
float s = 1.0f / 16; // 0.0625f
float a = 0;
float b = 1.0f / 16; // 0.0625
// float b = s; // 0.0625
float du, dv;
float ou, ov;
w--;
ou = (w % 16) * s; // w % 16 * 0.0625 width offset... the position of the texture times the width percentage of textures texture_pos * (1 / 16)
ov = (w / 16 * 3) * s; // height offset
// 108 data points, or 36 (3D) vector_data for the cube
// 108 data points, or 36 (3D) normal_data for the cube
// 72 data points, or 36 (2D) texture_data for the cube
if (left) {
du = ou; dv = ov + s;
*(vector_data++) = x - n; *(vector_data++) = y - n; *(vector_data++) = z - n;
*(vector_data++) = x - n; *(vector_data++) = y + n; *(vector_data++) = z + n;
*(vector_data++) = x - n; *(vector_data++) = y + n; *(vector_data++) = z - n;
*(vector_data++) = x - n; *(vector_data++) = y - n; *(vector_data++) = z - n;
*(vector_data++) = x - n; *(vector_data++) = y - n; *(vector_data++) = z + n;
*(vector_data++) = x - n; *(vector_data++) = y + n; *(vector_data++) = z + n;
*(normal_data++) = -1; *(normal_data++) = 0; *(normal_data++) = 0;
*(normal_data++) = -1; *(normal_data++) = 0; *(normal_data++) = 0;
*(normal_data++) = -1; *(normal_data++) = 0; *(normal_data++) = 0;
*(normal_data++) = -1; *(normal_data++) = 0; *(normal_data++) = 0;
*(normal_data++) = -1; *(normal_data++) = 0; *(normal_data++) = 0;
*(normal_data++) = -1; *(normal_data++) = 0; *(normal_data++) = 0;
// hint: z appears to be interchanged with y in pattern
// and x's pattern doesn't matter.
*(texture_data++) = a + du; *(texture_data++) = a + dv;
*(texture_data++) = b + du; *(texture_data++) = b + dv;
*(texture_data++) = a + du; *(texture_data++) = b + dv;
*(texture_data++) = a + du; *(texture_data++) = a + dv;
*(texture_data++) = b + du; *(texture_data++) = a + dv;
*(texture_data++) = b + du; *(texture_data++) = b + dv;
}
if (right) {
du = ou; dv = ov + s;
*(vector_data++) = x + n; *(vector_data++) = y - n; *(vector_data++) = z - n;
*(vector_data++) = x + n; *(vector_data++) = y + n; *(vector_data++) = z + n;
*(vector_data++) = x + n; *(vector_data++) = y - n; *(vector_data++) = z + n;
*(vector_data++) = x + n; *(vector_data++) = y - n; *(vector_data++) = z - n;
*(vector_data++) = x + n; *(vector_data++) = y + n; *(vector_data++) = z - n;
*(vector_data++) = x + n; *(vector_data++) = y + n; *(vector_data++) = z + n;
*(normal_data++) = 1; *(normal_data++) = 0; *(normal_data++) = 0;
*(normal_data++) = 1; *(normal_data++) = 0; *(normal_data++) = 0;
*(normal_data++) = 1; *(normal_data++) = 0; *(normal_data++) = 0;
*(normal_data++) = 1; *(normal_data++) = 0; *(normal_data++) = 0;
*(normal_data++) = 1; *(normal_data++) = 0; *(normal_data++) = 0;
*(normal_data++) = 1; *(normal_data++) = 0; *(normal_data++) = 0;
// hint: z appears to be interchanged with y in pattern
// and x's pattern doesn't matter.
*(texture_data++) = b + du; *(texture_data++) = a + dv;
*(texture_data++) = a + du; *(texture_data++) = b + dv;
*(texture_data++) = a + du; *(texture_data++) = a + dv;
*(texture_data++) = b + du; *(texture_data++) = a + dv;
*(texture_data++) = b + du; *(texture_data++) = b + dv;
*(texture_data++) = a + du; *(texture_data++) = b + dv;
}
if (top) {
du = ou; dv = ov + s + s;
*(vector_data++) = x - n; *(vector_data++) = y + n; *(vector_data++) = z - n;
*(vector_data++) = x - n; *(vector_data++) = y + n; *(vector_data++) = z + n;
*(vector_data++) = x + n; *(vector_data++) = y + n; *(vector_data++) = z + n;
*(vector_data++) = x - n; *(vector_data++) = y + n; *(vector_data++) = z - n;
*(vector_data++) = x + n; *(vector_data++) = y + n; *(vector_data++) = z + n;
*(vector_data++) = x + n; *(vector_data++) = y + n; *(vector_data++) = z - n;
*(normal_data++) = 0; *(normal_data++) = 1; *(normal_data++) = 0;
*(normal_data++) = 0; *(normal_data++) = 1; *(normal_data++) = 0;
*(normal_data++) = 0; *(normal_data++) = 1; *(normal_data++) = 0;
*(normal_data++) = 0; *(normal_data++) = 1; *(normal_data++) = 0;
*(normal_data++) = 0; *(normal_data++) = 1; *(normal_data++) = 0;
*(normal_data++) = 0; *(normal_data++) = 1; *(normal_data++) = 0;
// hint: the pattern seems to be with x and z, (not y) because it remains the same
*(texture_data++) = a + du; *(texture_data++) = b + dv;
*(texture_data++) = a + du; *(texture_data++) = a + dv;
*(texture_data++) = b + du; *(texture_data++) = a + dv;
*(texture_data++) = a + du; *(texture_data++) = b + dv;
*(texture_data++) = b + du; *(texture_data++) = a + dv;
*(texture_data++) = b + du; *(texture_data++) = b + dv;
}
if (bottom) {
du = ou; dv = ov + 0;
*(vector_data++) = x - n; *(vector_data++) = y - n; *(vector_data++) = z - n;
*(vector_data++) = x + n; *(vector_data++) = y - n; *(vector_data++) = z - n;
*(vector_data++) = x + n; *(vector_data++) = y - n; *(vector_data++) = z + n;
*(vector_data++) = x - n; *(vector_data++) = y - n; *(vector_data++) = z - n;
*(vector_data++) = x + n; *(vector_data++) = y - n; *(vector_data++) = z + n;
*(vector_data++) = x - n; *(vector_data++) = y - n; *(vector_data++) = z + n;
*(normal_data++) = 0; *(normal_data++) = -1; *(normal_data++) = 0;
*(normal_data++) = 0; *(normal_data++) = -1; *(normal_data++) = 0;
*(normal_data++) = 0; *(normal_data++) = -1; *(normal_data++) = 0;
*(normal_data++) = 0; *(normal_data++) = -1; *(normal_data++) = 0;
*(normal_data++) = 0; *(normal_data++) = -1; *(normal_data++) = 0;
*(normal_data++) = 0; *(normal_data++) = -1; *(normal_data++) = 0;
// hint: the pattern seems to fit with x, z and negate y
*(texture_data++) = a + du; *(texture_data++) = a + dv;
*(texture_data++) = b + du; *(texture_data++) = a + dv;
*(texture_data++) = b + du; *(texture_data++) = b + dv;
*(texture_data++) = a + du; *(texture_data++) = a + dv;
*(texture_data++) = b + du; *(texture_data++) = b + dv;
*(texture_data++) = a + du; *(texture_data++) = b + dv;
}
if (front) {
du = ou; dv = ov + s;
*(vector_data++) = x - n; *(vector_data++) = y - n; *(vector_data++) = z + n;
*(vector_data++) = x + n; *(vector_data++) = y - n; *(vector_data++) = z + n;
*(vector_data++) = x + n; *(vector_data++) = y + n; *(vector_data++) = z + n;
*(vector_data++) = x - n; *(vector_data++) = y - n; *(vector_data++) = z + n;
*(vector_data++) = x + n; *(vector_data++) = y + n; *(vector_data++) = z + n;
*(vector_data++) = x - n; *(vector_data++) = y + n; *(vector_data++) = z + n;
*(normal_data++) = 0; *(normal_data++) = 0; *(normal_data++) = -1;
*(normal_data++) = 0; *(normal_data++) = 0; *(normal_data++) = -1;
*(normal_data++) = 0; *(normal_data++) = 0; *(normal_data++) = -1;
*(normal_data++) = 0; *(normal_data++) = 0; *(normal_data++) = -1;
*(normal_data++) = 0; *(normal_data++) = 0; *(normal_data++) = -1;
*(normal_data++) = 0; *(normal_data++) = 0; *(normal_data++) = -1;
// hint: u seems to link with x, and v with y (but the opposite)
*(texture_data++) = b + du; *(texture_data++) = a + dv;
*(texture_data++) = a + du; *(texture_data++) = a + dv;
*(texture_data++) = a + du; *(texture_data++) = b + dv;
*(texture_data++) = b + du; *(texture_data++) = a + dv;
*(texture_data++) = a + du; *(texture_data++) = b + dv;
*(texture_data++) = b + du; *(texture_data++) = b + dv;
}
if (back) {
du = ou; dv = ov + s;
*(vector_data++) = x - n; *(vector_data++) = y - n; *(vector_data++) = z - n;
*(vector_data++) = x + n; *(vector_data++) = y + n; *(vector_data++) = z - n;
*(vector_data++) = x + n; *(vector_data++) = y - n; *(vector_data++) = z - n;
*(vector_data++) = x - n; *(vector_data++) = y - n; *(vector_data++) = z - n;
*(vector_data++) = x - n; *(vector_data++) = y + n; *(vector_data++) = z - n;
*(vector_data++) = x + n; *(vector_data++) = y + n; *(vector_data++) = z - n;
*(normal_data++) = 0; *(normal_data++) = 0; *(normal_data++) = 1;
*(normal_data++) = 0; *(normal_data++) = 0; *(normal_data++) = 1;
*(normal_data++) = 0; *(normal_data++) = 0; *(normal_data++) = 1;
*(normal_data++) = 0; *(normal_data++) = 0; *(normal_data++) = 1;
*(normal_data++) = 0; *(normal_data++) = 0; *(normal_data++) = 1;
*(normal_data++) = 0; *(normal_data++) = 0; *(normal_data++) = 1;
// hint: the texture u seems to link with x, and v with y
*(texture_data++) = a + du; *(texture_data++) = a + dv;
*(texture_data++) = b + du; *(texture_data++) = b + dv;
*(texture_data++) = b + du; *(texture_data++) = a + dv;
*(texture_data++) = a + du; *(texture_data++) = a + dv;
*(texture_data++) = a + du; *(texture_data++) = b + dv;
*(texture_data++) = b + du; *(texture_data++) = b + dv;
*(texture_data++) = b + du; *(texture_data++) = a + dv;
*(texture_data++) = a + du; *(texture_data++) = a + dv;
*(texture_data++) = a + du; *(texture_data++) = b + dv;
*(texture_data++) = b + du; *(texture_data++) = a + dv;
*(texture_data++) = a + du; *(texture_data++) = b + dv;
*(texture_data++) = b + du; *(texture_data++) = b + dv;
}
}
When I got the results for the left
, from the program itself, this is what I got for the vertices:
x = 0.500000, y = 0.500000, z = 0.500000
x = 0.500000, y = 1.500000, z = 1.500000
x = 0.500000, y = 1.500000, z = 0.500000
x = 0.500000, y = 0.500000, z = 0.500000
x = 0.500000, y = 0.500000, z = 1.500000
x = 0.500000, y = 1.500000, z = 1.500000
And the texture data... (remember that because the left
cube, is the second one up (v = 0.0625
) and the x starting position is u = 0
), so the origin in UV is 0, 0.0625
.
x = 0.000000, y = 0.062500
x = 0.062500, y = 0.125000
x = 0.000000, y = 0.125000
x = 0.000000, y = 0.062500
x = 0.062500, y = 0.062500
x = 0.062500, y = 0.125000
But my calculations by hand, when I applied the coordinates to the UV Map Positioning, I actually got... The way I did this was every time I got a
x = 0.000000, y = 0.062500
x = 0.062500, y = 0.125000
x = 0.062500, y = 0.062500
x = 0.000000, y = 0.062500
x = 0.000000, y = 0.125000
x = 0.062500, y = 0.125000
I don't think it's by accident that I got the ALMOST correct values. The cubes also do not have identically rendered textures. Some of them are reversed, to make the texture seem organically connecting one to another. Here are some screenshots of the 4 sides of the cube when rotating around it, counter-clockwise.
Does anyone have any idea how this works? Can someone point me in the direction? I feel like I've been looking at this so long I can't see the forest through the trees. I need to understand how these UV Coordinates are mapped to each of these specific sides of this cube.
If anyone else is interested, here is the rest of the output of the vertices and texture data (respectively in the same block of code).
Right
x = 1.500000, y = 0.500000, z = 0.500000
x = 1.500000, y = 1.500000, z = 1.500000
x = 1.500000, y = 0.500000, z = 1.500000
x = 1.500000, y = 0.500000, z = 0.500000
x = 1.500000, y = 1.500000, z = 0.500000
x = 1.500000, y = 1.500000, z = 1.500000
x = 0.062500, y = 0.062500
x = 0.000000, y = 0.125000
x = 0.000000, y = 0.062500
x = 0.062500, y = 0.062500
x = 0.062500, y = 0.125000
x = 0.000000, y = 0.125000
Top
x = 0.500000, y = 1.500000, z = 0.500000
x = 0.500000, y = 1.500000, z = 1.500000
x = 1.500000, y = 1.500000, z = 1.500000
x = 0.500000, y = 1.500000, z = 0.500000
x = 1.500000, y = 1.500000, z = 1.500000
x = 1.500000, y = 1.500000, z = 0.500000
x = 0.000000, y = 0.187500
x = 0.000000, y = 0.125000
x = 0.062500, y = 0.125000
x = 0.000000, y = 0.187500
x = 0.062500, y = 0.125000
x = 0.062500, y = 0.187500
Bottom
x = 0.500000, y = 0.500000, z = 0.500000
x = 1.500000, y = 0.500000, z = 0.500000
x = 1.500000, y = 0.500000, z = 1.500000
x = 0.500000, y = 0.500000, z = 0.500000
x = 1.500000, y = 0.500000, z = 1.500000
x = 0.500000, y = 0.500000, z = 1.500000
x = 0.000000, y = 0.000000
x = 0.062500, y = 0.000000
x = 0.062500, y = 0.062500
x = 0.000000, y = 0.000000
x = 0.062500, y = 0.062500
x = 0.000000, y = 0.062500
Front
x = 0.500000, y = 0.500000, z = 1.500000
x = 1.500000, y = 0.500000, z = 1.500000
x = 1.500000, y = 1.500000, z = 1.500000
x = 0.500000, y = 0.500000, z = 1.500000
x = 1.500000, y = 1.500000, z = 1.500000
x = 0.500000, y = 1.500000, z = 1.500000
x = 0.062500, y = 0.062500
x = 0.000000, y = 0.062500
x = 0.000000, y = 0.125000
x = 0.062500, y = 0.062500
x = 0.000000, y = 0.125000
x = 0.062500, y = 0.125000
Back
x = 0.500000, y = 0.500000, z = 0.500000
x = 1.500000, y = 1.500000, z = 0.500000
x = 1.500000, y = 0.500000, z = 0.500000
x = 0.500000, y = 0.500000, z = 0.500000
x = 0.500000, y = 1.500000, z = 0.500000
x = 1.500000, y = 1.500000, z = 0.500000
x = 0.000000, y = 0.062500
x = 0.062500, y = 0.125000
x = 0.062500, y = 0.062500
x = 0.000000, y = 0.062500
x = 0.000000, y = 0.125000
x = 0.062500, y = 0.125000
I'd be so grateful if anyone could figure this out. Simply to put my mind at peace. I've been called a Jack-Rustle Terrier -- unable to let go.
I'd love to see the simple thing I'm missing! If you've gone this far, THANK YOU!
Here are SOME of the references I've used to try to figure this out.
http://www.opengl-tutorial.org/beginners-tutorials/tutorial-5-a-textured-cube/ https://gamedev.stackexchange.com/questions/152991/how-can-i-calculate-normals-using-a-vertex-and-index-buffer https://gamedev.stackexchange.com/questions/172352/finding-texture-coordinates-for-plane https://gamedev.net/forums/topic/626790-uv-mapping-where-do-the-u-v-coordinates-correspond-to-on-a-square-texture/4951976/ https://github.com/Hopson97/MineCraft-One-Week-Challenge/blob/master/Source/Texture/TextureAtlas.cpp https://en.wikipedia.org/wiki/UV_mapping https://gamedev.stackexchange.com/questions/25057/help-a-2d-image-onto-a-3d-cube OpenGL 4 - UV Coordinates for Triangle Strip Cube https://conceptartempire.com/uv-mapping-unwrapping/ Beginning OpenGL Game Programming by Dave Astle page 151 - 158 (Texture Mapping)