ANSWER AT THE BOTTOM OF THIS QUESTION
I have been reading tutorials, articles, questions on StackExchange and books all with the subject of skeletal rigging. I have boiled my program down to just the essentials and I still experience the same situation.
Currently, I have a root joint, a child bone of the root, and a child of the child. Each child's rotation is relative to its parent. The first one rotates to the proper angle (the second overshoots the goal), but as they rotate, the vectors (vertices) they affect seem to increase in magnitude. They have translated and then rotated it seems.
There are 3 dots. Red is the root joint. Green should represent the end of the first bone. Blue should represent the end of the second bone. The basic pose is to have all bones at an angle of 0 radians, so will lie upon the positive x axis starting from the origin:
As I rotate the first bone, the vertex veers away from the origin, but is at the correct angle of PI/2 Radians (90 Degrees) and the second bone follows:
Now I rotate the last bone, and the corresponding vertex veers even further than the first. However, this one has rotated more than the intended PI/4 Radians (45 Degrees) relative to the parent:
The code I used to calculate the transformation follows.
OpenGL 3.2 Core, Main.cpp:
GLfloat meshVertices[] = // Vertex (X, Y, Z), Color (R, G, B), Joint/Bone Index
{
0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
0.2f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
0.4f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 2.0f
};
GLuint meshElements[] =
{
0, 1, 2
};
glm::mat4 _matRoot = glm::mat4(); // Load Identity
_matRoot = glm::translate(_matRoot, glm::vec3(0.0f, 0.0f, 0.0f)); // Translate to Local Origin (In Relation to MODEL)
_matRoot = glm::rotate(_matRoot, 0.0f, glm::vec3(0.0f, 0.0f, 1.0f)); // Root does not need rotation
_matRoot = glm::translate(_matRoot, glm::vec3(0.0f, 0.0f, 0.0f)); // Translate to Length of Bone (ZERO for root)
glm::mat4 _matBase = _matRoot; // Inherit Parent Transform
_matBase = glm::translate(_matBase, glm::vec3(0.0f, 0.0f, 0.0f)); // Translate to Local Origin (In Relation to PARENT)
_matBase = glm::rotate(_matBase, PI / 2, glm::vec3(0.0f, 0.0f, 1.0f)); // Rotate 90 Degrees About Z Axis
_matBase = glm::translate(_matBase, glm::vec3(0.2f, 0.0f, 0.0f)); // Translate to Length of Bone (x=0.2f is same as mesh vertex)
glm::mat4 _matEnd = _matBase; // Inherit Parent Transform
_matEnd = glm::translate(_matEnd, glm::vec3(0.0f, 0.0f, 0.0f)); // Translate to Local Origin (In Relation to PARENT)
_matEnd = glm::rotate(_matEnd, PI / 4, glm::vec3(0.0f, 0.0f, 1.0f)); // Rotate 45 Degrees About Z Axis
_matEnd = glm::translate(_matEnd, glm::vec3(0.2f, 0.0f, 0.0f)); // Translate to Length of Bone (x=0.2f is same as mesh vertex)
vector<glm::mat4> _matSkeleton(3); // Sent to Shader
_matSkeleton[0] = _matRoot;
_matSkeleton[1] = _matBase;
_matSkeleton[2] = _matEnd;
GLSL 1.50 Core, Vertex Shader:
in vec3 position; // From Mesh - Vertex (XYZ)
in vec3 color; // From Mesh - Color (RGB)
in float bone; // From Mesh - Bone Index
out vec3 Color; // To Fragment Shader
uniform mat4[3] transform; // From glm::mat4 Vector (Bone transforms)
void main()
{
gl_PointSize = 10.0f;
Color = color;
int Bone = int(bone);
gl_Position = transform[Bone] * vec4(position, 1.0);
}
I have tried numerous changes to the translations, attempted different combinations of transforms, added and subtracted transforms. I have recompiled this code a couple of hundred times over the last couple days. This is as close as I have come to any success.
BEGIN EDIT
Here I have provided my method of sending data to the shaders:
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(meshVertices), &meshVertices, GL_STATIC_DRAW);
glGenBuffers(1, &ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(meshElements), &meshElements, GL_STATIC_DRAW);
GLuint shaderAttributePosition = glGetAttribLocation(shaderProgram, "positon");
GLuint shaderAttributeColor = glGetAttribLocation(shaderProgram, "color");
GLuint shaderAttributeBone = glGetAttribLocation(shaderProgram, "bone");
glEnableVertexAttribArray (shaderAttributePosition);
glBindBuffer (GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer (shaderAttributePosition, 3, GL_FLOAT, GL_FALSE,
7 * sizeof(GLfloat), (void*)(0 * sizeof(GLfloat)));
glEnableVertexAttribArray (shaderAttributeColor);
glBindBuffer (GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer (shaderAttributeColor, 3, GL_FLOAT, GL_FALSE,
7 * sizeof(GLfloat), (void*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray (shaderAttributeBone);
glBindBuffer (GL_ARRAY_BUFFER, vbo);
glVertexAttribPointer (shaderAttributeBone, 1, GL_FLOAT, GL_FALSE,
7 * sizeof(GLfloat), (void*)(6 * sizeof(GLfloat)));
And the Uniform:
GLint uniTrans = glGetUniformLocation(shaderProgram(), "transform");
glUniformMatrix4fv(uniTrans, _matSkeleton.size(), GL_FALSE, glm::value_ptr(_matSkeleton[0]));
END EDIT
2nd EDIT (WITH ANSWER) By mapping out the transformation on graph paper, traversing the steps in reverse from result to original point, I was able to figure out the exact steps needed to answer my question. Instead of just building on my matrices with functions, I created several different matrices, each with their own part of the transform (rotation, translation). I then multiplied the matrices together to get the final result.
I re-coded the mathematics in the test source with success. I also removed the root bone and rebased the bones origins. Point one is now at the center and is not transformed in the example. The second point (Green) is transformed by a bone with its origin at the global origin. Finally, the third point (Blue) is transformed by a bone with its origin set to its parent's origin, plus the parent's length along the x axis (x axis being 0 degrees in this example). All further children will follow the pattern as the second bone. I then implemented this back into my main engine and it works flawlessly! Below is the updated math calculations.
The line comments should help anyone else who happens to Google themselves onto this question. Should anyone wish for further explanation, feel free to comment and I will explain what this all means.
// PRECOMPUTED VALUES
float PI = 3.1415926535897932384626433832795f;
float PI2 = PI / 2.0f; // 90 DEGREES
float PI4 = PI / 4.0f; // 45 DEGREES
glm::vec3 RAx = glm::vec3(0.0f, 0.0f, 1.0f); // ROTATIONAL AXIS
glm::mat4 I = glm::mat4(); // IDENTITY MATRIX
// BONE ONE
glm::mat4 R = glm::rotate (I, PI2, RAx); // ROTATION MATRIX (90 DEG AROUND Z)
glm::mat4 O = glm::translate(I, glm::vec3(0.0f, 0.0f, 0.0f)); // MOVE TO LOCAL ORIGIN
glm::mat4 T = glm::translate(I, glm::vec3(0.0f, 0.0f, 0.0f)); // MOVE BACK TO GLOBAL SPACE
glm::mat4 B1 = T * R * O; // LOCAL ORIGIN, ROTATE, GLOBAL SPACE, PARENT TRANSFORM (NON EXISTANT IN THIS CASE)
R = glm::rotate (I, PI4, RAx); // ROTATION MATRIX (45 DEG AROUND Z)
O = glm::translate(I, glm::vec3(-0.2f, 0.0f, 0.0f)); // MOVE TO LOCAL ORIGIN
T = glm::translate(I, glm::vec3(0.2f, 0.0f, 0.0f)); // MOVE BACK TO GLOBAL SPACE
glm::mat4 B2 = B1 * T * R * O; // LOCAL ORIGIN, ROTATE, GLOBAL SPACE, PARENT TRANSFORM
boneOne = B1;
boneTwo = B2;
END 2nd EDIT