3
\$\begingroup\$

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:

Basic Pose

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:

enter image description here

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:

enter image description here

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

\$\endgroup\$
  • \$\begingroup\$ Could you post how you send your data to the shader? Seeing the mat4[3] reminds me of a similar problem I had. \$\endgroup\$ – Soapy Jul 9 '14 at 23:15
  • \$\begingroup\$ I added an edit to the bottom of the question for you. It should provide all of the relevant information you requestion. I am going to write an even smaller test project that has no more code than necessary to replicate this scenario. If I still experience the same issues, I will just post the entire script in the question, if needed, to simplify analysis. \$\endgroup\$ – BenGearig Jul 10 '14 at 2:22
0
\$\begingroup\$

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. I will also edit my question to include the answer. 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;
\$\endgroup\$
-1
\$\begingroup\$

Depending on your compiler, when you divide PI in your calculations then the result could be interpreted as either a float or an int. From what I recall, the C++ standard demands that the result of an arithmetic operation on basic data types will always have the same signature as the left hand operand. However, not all compilers follow the standard. Because of this, the result could be rounded up in the case of an int, causing your scale to increase as well. Try writing "PI / 2.0f" and "PI / 4.0f" instead.

\$\endgroup\$
  • \$\begingroup\$ "PI" is not a standard constant in C++. Presupposing it is defined as a floating point value, the result of the expression "PI / 4" is a floating-point value due to type conversion/promotion rules. Any remotely sane compiler implements that part of the standard correctly. \$\endgroup\$ – Josh Jul 9 '14 at 5:56
  • \$\begingroup\$ @JoshPetrie I never said PI was part of the standard. Im going off the assumption that it is implemented as a float in the OP's code. Either way, I just realized my answer is horribly wrong. The angles of the joints are rotated correctly, and as expected, based off the angles he wrote his code. A floating point error in the angle of rotation would not affect translation as was shown in the screenshots. \$\endgroup\$ – icdae Jul 9 '14 at 6:09
  • \$\begingroup\$ I actually use a variable _PI_4 in my code (I changed it to the actual arithmetic for the sake of understanding). float _PI = 3.1415926535897932384626433832795f; float _PI_4 = _PI / 4.0f; \$\endgroup\$ – BenGearig Jul 9 '14 at 16:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.