Unknown issue while rotating bones - c++

I faced a strange behaviour while was making a test skeleton rotation, here come the code:
for (int i = 0; i < bones.size(); ++i) {
u2long parentlink = bones[i].blenderBone.parent;
bone* b = &bones[i];
if (!parentlink) {
b->parent = nullptr;
b->parentName = "no parent";
glm::mat4 tailtrans = glm::translate(glm::mat4(1.0f), b->local_tail);
glm::mat4 headtrans = glm::translate(glm::mat4(1.0f), b->local_head);
b->tailmatrix = tailtrans * b->bonematrix;
b->headmatrix = headtrans * b->bonematrix;
b->renderedmatrix = b->headmatrix;
}
else {
std::optional<bone> bpar = loader->getBone(parentlink);
std::string parname = bpar->name;
bones[i].parent = getBone(parname);
bones[i].parentName = parname;
glm::mat4 tailtrans = glm::translate(glm::mat4(1.0f), b->local_tail);
glm::mat4 headtrans = glm::translate(glm::mat4(1.0f), b->local_head);
b->headmatrix = b->parent->tailmatrix * headtrans * b->bonematrix;
b->tailmatrix = b->parent->tailmatrix * tailtrans * b->bonematrix;
b->renderedmatrix = b->headmatrix;
};
};
local_tail is bone bindspose start at bone space,
local_tail is bone bindpose end at bone space,
bonematrix is bone bindpose rotation,
headmatrix is used for rendering the bones and is matching bindpose model-space matrix for any bone,
tailmatrix for further transformations
And that gives me result which I find correct:
bindpose
however, if I add a random bone rotation before the cycle:
glm::mat4 r = glm::rotate(glm::mat4(1.0f), 0.89f, glm::vec3(0.0f, 0.0f, 1.0f));
bones[8].bonematrix = bones[8].bonematrix * r;
the rendering is done strange:
Wrong, spine
the following bone above the rotated one is rendered at its bindpose position, BUT the rest of the bones above are stacking all positions and rotations offsets correctly, which is the reason I post the question. The issue applies to any other bone connected to its parent:
Wrong, right arm
But rotating root bone brings correct result this time:
Correct, root
I find this line:
b->tailmatrix = b->parent->tailmatrix * tailtrans * b->bonematrix;
quite suspicious, after having thought all over it seems to me this one:
b->tailmatrix = b->parent->tailmatrix * b->bonematrix * tailtrans;
or better this:
b->tailmatrix = b->headmatrix * tailtrans;
should bring correct results, but swaping to these lines brings chaos, bindpose is a mess, the same mess applies to any bone (except root one) rotation:
Bindpose, wrong
Where am I wrong with that code? I bashed this wall for three weeks but when ist almost through my front is almost dead as well :/
(this is blender file parser, the bones not matching the model is a temporary issue with blender "magical" transform axis, all bones above the torso are parented down)

All right, got it done, the line:
b->tailmatrix = b->parent->tailmatrix * tailtrans * b->bonematrix;
I made this line while I was, errmm...guessing, and it managed to give results but now I have understood why it did, in Blender model offset to head and tail of the next bone is done in parent bone's TAIL local space for both of tail and head, but I thought it was different. The next and the last thing to add - apply rotation offset right into this line, for the bone head matrix has rotated offset but unrotated position (armature[3], bindpose, in my case) and tail matrix receives rotated offset as well as updated position:
b->tailmatrix = b->parent->tailmatrix * tailtrans * (b->bonematrix * b->localrot);
b->localrot is rotation offset matrix.
so the code for now looks this way, and the test rotation is done fine:
bones[8].localrot = glm::rotate(glm::mat4(1.0f), 1.0f, glm::vec3(1.0f, 0.0f, 1.0f));
for (int i = 0; i < bones.size(); ++i) {
bone* b = &bones[i];
if (!b->parent) {
glm::mat4 tailtrans = glm::translate(glm::mat4(1.0f), b->local_tail);
glm::mat4 headtrans = glm::translate(glm::mat4(1.0f), b->local_head);
b->headmatrix = b->bonematrix * b->localrot * headtrans;
b->tailmatrix = b->bonematrix * b->localrot * tailtrans;
b->renderedmatrix = b->headmatrix;
}
else {
glm::mat4 tailtrans = glm::translate(glm::mat4(1.0f), b->local_tail);
glm::mat4 headtrans = glm::translate(glm::mat4(1.0f), b->local_head);
b->headmatrix = b->parent->tailmatrix * headtrans * b->bonematrix;
b->tailmatrix = b->parent->tailmatrix * tailtrans * (b->bonematrix * b->localrot);
b->renderedmatrix = b->headmatrix;
};
};
all good.

Related

How to get non-rigged keyframe animations using assimp and collada?

How do I extract keyframe animations(without bones) from .dae(collada) files using assimp? I followed tutorials on how to achieve this for skeletal animations. But I need to be able to do this for animations that are not rigged and that rely on keyframes instead.
I know how to get the animation and mesh data from .dae files using assimp but how would I use this to animate keyframes and interpolate between them? All tutorials I found only show how to do this with bones. How would assimp represent animations if no bones exist?
[Edit] code for bones
void SkeletalAnimation::Animate(float pDeltaTime, Bone& pSkeleton, Pose& pPose, glm::mat4& pParentTransform) {
Animation& _currentAnimation = mAnimationMap[*mCurrentAnimation];
BoneTransforms& _boneTransforms = _currentAnimation.boneTransforms[pSkeleton.name];
pDeltaTime = fmod(pDeltaTime, _currentAnimation.duration);
//Calculate translations
GetSegment(&mCurrentSegment, _boneTransforms.translationTimestamps, pDeltaTime);
glm::vec3 _translation = glm::mix(
_boneTransforms.translations[mCurrentSegment.segment - 1],
_boneTransforms.translations[mCurrentSegment.segment],
mCurrentSegment.fracture);
//Calculate rotations
GetSegment(&mCurrentSegment, _boneTransforms.rotationTimetamps, pDeltaTime);
glm::quat _rotation = glm::slerp(
_boneTransforms.rotations[mCurrentSegment.segment - 1],
_boneTransforms.rotations[mCurrentSegment.segment],
mCurrentSegment.fracture);
//Calculate scalings
GetSegment(&mCurrentSegment, _boneTransforms.scalingTimetamps, pDeltaTime);
glm::vec3 _scaling = glm::mix(
_boneTransforms.scalings[mCurrentSegment.segment - 1],
_boneTransforms.scalings[mCurrentSegment.segment],
mCurrentSegment.fracture);
glm::mat4 _translationMatrix = glm::translate(glm::mat4(1.0f), _translation);
glm::mat4 _rotationMatrix = glm::toMat4(_rotation);
glm::mat4 _scalingMatrix = glm::scale(glm::mat4(1.0f), _scaling); glm::mat4(1.0f);
glm::mat4 _localTransform = _translationMatrix * _rotationMatrix * _scalingMatrix;
glm::mat4 _globalTransform = pParentTransform * _localTransform;
pPose[pSkeleton.id] = mGlobalInverseTransform * _globalTransform * pSkeleton.offset;
for (Bone& _child : pSkeleton.children) {
Animate(pDeltaTime, _child, pPose, _globalTransform);
}
}

How to change pivot in OpenGL?

I've developed 3D application. I want to change pivot between model center and user position. When I toggle changing pivot option, 3D model is moved to some position. My partial source below. Please help me.
glm::vec2 mouse_delta = mouse_move - mouse_click;
key_yaw = rotate_sensitivity * mouse_delta.x;
key_pitch = rotate_sensitivity * mouse_delta.y;
UpdateRotateMatrix();
if (rotate_mode == CCamera::ROT_CENTER)
UpdateModelMatrix();
else if (rotate_mode == CCamera::ROT_CLICKED)
UpdateModelMatrix(false);
////////////////////////////////////////////////////
void CCamera::UpdateModelMatrix(const bool& bCenter)
{
glm::vec3 pivot(0.0f);
if (bCenter)
pivot = local_position; //50.0, 50.0, 50.0
else
pivot = click_position; //1000.0, 1000.0, 1000.0
glm::mat4 rotate = glm::mat4(1.0f);
rotate = glm::translate(glm::mat4(1.0f), pivot)*rotate_matrix;
rotate = rotate * glm::translate(glm::mat4(1.0f), -pivot);
model_matrix = translate_matrix * rotate;
}
void CCamera::UpdateRotateMatrix()
{
glm::quat key_quat = glm::quat(glm::vec3(key_pitch, key_yaw, key_roll));
key_pitch = key_yaw = key_roll = 0;
camera_quat = key_quat * camera_quat;
camera_quat = glm::normalize(camera_quat);
rotate_matrix = glm::mat4_cast(camera_quat);
}
rotation of model center
rotation of some point
The order of you matrices is messed up.
I you want to rotate around a pivot, then you have to:
glm::mat4 trans_to_pivot = glm::translate(glm::mat4(1.0f), -pivot);
glm::mat4 trans_from_pivot = glm::translate(glm::mat4(1.0f), pivot);
glm::mat4 rotate = trans_from_pivot * rotate_matrix * trans_to_pivot;
If you want to scale the model you have to do it first:
glm::mat4 rotate = rotate * scale_matrix;
Note in your example scale_matrix is in between trans_to_pivot and trans_from_pivot, so the first translation is scaled, but the the second translation is not scaled.
Of course pivot has to be a coordinate in scaled model space rather than model space.

Rotate Object around origin as it faces origin in OpenGL with GLM?

I'm trying to make a simple animation where an object rotates around the world origin in OpenGL using glm lib. My ideia is:
Send object to origin
Rotate it
Send back to original position
Make it look at what I want
Here's my implementation:
// Rotates object around point p
void rotate_about(float deltaTime, glm::vec3 p, bool ended) {
glm::vec3 axis = glm::vec3(0,1,0); //rotation axis
glm::mat4 scale_m = glm::scale(glm::mat4(1.0f), glm::vec3(scale, scale, scale)); //scale matrix
glm::mat4 rotation = getMatrix(Right, Up, Front, Position); //builds rotation matrix
rotation = glm::translate(rotation, p - Position );
rotation = glm::rotate(rotation, ROTATION_SPEED * deltaTime, axis);
rotation = glm::translate(rotation, Position - p );
Matrix = rotation * scale_m;
//look at point P
Front = glm::normalize(p - start_Position);
Right = glm::normalize(glm::cross(WorldUp, Front));
Up = glm::normalize(glm::cross(Right, Front));
if (ended == true) { //if last iteration of my animation: saves position
Position.x = Matrix[3][0];
Position.y = Matrix[3][1];
Position.z = Matrix[3][2];
}
}
getMatrix() simply returns a 4x4 matrix as:
| Right.x Right.y Right.z |
| Up.x Up.y Up.z |
| Front.x Front.y Front.z |
| Pos.x Pos.y Pos.z |
I'm using this image as reference:
As it is my model simply disappears when I start the animation. If I remove lines bellow "//look at point P" it rotates around the origin, but twitches every time my animation restarts. I'm guessing I'm losing or mixing informations I shouldn't somewhere.
How can I store my models Front/Right/Up information so I can rebuild its matrix from scratch?
First edit, this is the effect I'm having when I don't try to make my model look at the point P, in this case the origin. When I do try my model disappears. How can I make it look at where I want, and how can I get my models new Front/Right/Up vectors after I finish rotating it?
This is the code I ran in the gif above
Operations like glm::translate() or glm::roate() build a matrix by its parameters and multiply the input matrix by the new matrix
This means that
rotation = glm::translate(rotation, Position - p );
can be expressed as (pseudo code):
rotation = rotation * translation(Position - p);
Note, that the matrix multiplication has to be "read" from the left to the right. (See GLSL Programming/Vector and Matrix Operations)
The operation translate * rotate causes a rotation around the origin of the object:
The operation rotate * translate causes a rotation around the origin of the world:
The matrix glm::mat4 rotation (in the code of your question) is the current model matrix of your object.
It contains the position (translation) and the orientation of the object.
You want to rotate the object around the origin of the world.
To do so you have to create a matrix which contains the new rotation
glm::mat4 new_rot = glm::rotate(glm::mat4(1.0f), ROTATION_SPEED * deltaTime, axis);
Then you can calculate the final matrix as follows:
Matrix = new_rot * rotation * scale_m;
If you want to rotate an object around the a point p and the object should always face a point p, then all you need is the position of the object (start_position) and the rotation axis.
In your case the rotation axis is the up vector of the world.
glm::vec3 WorldUp( 0.0f, 1.0f, 0.0f );
glm::vec3 start_position = ...;
float scale = ...;
glm::vec3 p = ...;
Calculate the rotation matrix and the new (rotated) position
glm::mat4 rotate = glm::rotate(glm::mat4(1.0f), ROTATION_SPEED * deltaTime, WorldUp);
glm::vec4 pos_rot_h = rotate * glm::vec4( start_position - p, 1.0f );
glm::vec3 pos_rot = glm::vec3( pos_rot_h ) + p;
Calculate the direction in which the object should "look"
glm::vec3 Front = glm::normalize(p - pos_rot);
You can use your function getMatrix to setup the current orientation matrix of the object:
glm::vec3 Right = glm::normalize(glm::cross(WorldUp, Front));
glm::mat4 pos_look = getMatrix(Right, WorldUp, Front, pos_rot);
Calculate the model matrix:
glm::mat4 scale_m = glm::scale(glm::mat4(1.0f), glm::vec3(scale));
Matrix = pos_look * scale_m;
The final code may look like this:
glm::mat4 getMatrix(const glm::vec3 &X, const glm::vec3 &Y, const glm::vec3 &Z, const glm::vec3 &T)
{
return glm::mat4(
glm::vec4( X, 0.0f ),
glm::vec4( Y, 0.0f ),
glm::vec4( Z, 0.0f ),
glm::vec4( T, 1.0f ) );
}
void rotate_about(float deltaTime, glm::vec3 p, bool ended) {
glm::mat4 rotate = glm::rotate(glm::mat4(1.0f), ROTATION_SPEED * deltaTime, WorldUp);
glm::vec4 pos_rot_h = rotate * glm::vec4( start_position - p, 1.0f );
glm::vec3 pos_rot = glm::vec3( pos_rot_h ) + p;
glm::vec3 Front = glm::normalize(p - pos_rot);
glm::vec3 Right = glm::normalize(glm::cross(WorldUp, Front));
glm::mat4 pos_look = getMatrix(Right, WorldUp, Front, pos_rot);
glm::mat4 scale_m = glm::scale(glm::mat4(1.0f), glm::vec3(scale));
Matrix = pos_look * scale_m;
if ( ended == true )
Position = glm::vec3(Matrix[3]);
}
SOLUTION:
The problem was in this part:
rotation = glm::translate(rotation, p - Position );
rotation = glm::rotate(rotation, ROTATION_SPEED * deltaTime, axis);
rotation = glm::translate(rotation, Position - p );
if (ended == true) { //if last iteration of my animation: saves position
Position.x = Matrix[3][0];
Position.y = Matrix[3][1];
Position.z = Matrix[3][2];
}
Note that I was using the distance between world origin and model as the radius of the translation. However, after the animation ends I update the models Position, which changes the result of p - Position, i.e, the orbit radius. When this happen the model "twitches", because it lost rotation information.
I solved it by using a different variable for the orbit radius, and applying the translation on the z-axis of the model. When the translation is applied on the x-axis, the model - which faces the camera initially - will end up sideways to the origin. However, applying the translation on the z-axis will end up with the model either facing or backwards to the origin, depending on the signal.

Skeleton calculation

I'm trying to implement skeletal animation on my 3D Enigne (DX 11). Skinning part is working fine, but there is little problem with bone matrices transformation. I'm using self made binary format which is based on Valve's SMD and already converted to left-handed coordinate system (Y-Z swap). So there is my bone-to-world code:
for(int i = 0; i < Model->numJoints; i++)
{
Bones[i].WorldPos = XMFLOAT3(0.0f, 0.0f, 0.0f);
Joint tmpJoint = Model->joints[i]; //bind-pose bone data (translation + orientation)
//Rotation matrix calculation
XMMATRIX RotX = XMMatrixRotationX(-tmpJoint.orientation.x);
XMMATRIX RotY = XMMatrixRotationY(-tmpJoint.orientation.y);
XMMATRIX RotZ = XMMatrixRotationZ(-tmpJoint.orientation.z);
//Rot * Translation
XMMATRIX Local = RotX * RotY * RotZ * XMMatrixTranslation(tmpJoint.pos.x, tmpJoint.pos.y, tmpJoint.pos.z);
XMMATRIX tmp;
if (Model->Skeleton.parentID[i] != -1)
{
Bones[i].global = Local * Bones[Model->Skeleton.parentID[i]].global;
}
else
Bones[i].global = Local;
XMVECTOR temp;
XMStoreFloat3(&Bones[i].WorldPos, XMVector3Transform(temp, Bones[i].global));
}
//line between 2 connected bones (aka skeleton)
bonePoints[i] = Bones[i].WorldPos; //bone position in world
bonePoints[i+1] = Bones[Model->Skeleton.parentID[i]].WorldPos; //parent bone position in world
as you can see at pictures below, calculated sekeleton looks deformed.
I think something wrong with rotation calculation part, maby some extra transpose is missed. I tryed to change matrix multiplication order but without any success.
Original skeleton (3DS MAX)
Calculated skeleton (Engine)

Emulating gluLookAt with glm::Quat(ernions)

I've been trying to emulate gluLookAt functionality, but with Quaternions. Each of my game object have a TranslationComponent. This component stores the object's position (glm::vec3), rotation (glm::quat) and scale (glm::vec3). The camera calculates its position each tick doing the following:
// UP = glm::vec3(0,1,0);
//FORWARD = glm::vec3(0,0,1);
cameraPosition = playerPosition - (UP * distanceUP) - (FORWARD * distanceAway);
This position code works as expexted, the camera is place 3 metres behind the player and 1 metre up. Now, the camera's Quaternion is set to the follow:
//Looking at the player's feet
cameraRotation = quatFromToRotation(FORWARD, playerPosition);
The rendering engine now takes these values and generates the ViewMatrix (camera) and the ModelMatrix (player) and then renders the scene. The code looks like this:
glm::mat4 viewTranslationMatrix =
glm::translate(glm::mat4(1.0f), cameraTransform->getPosition());
glm::mat4 viewScaleMatrix =
glm::scale(glm::mat4(1.0f), cameraTransform->getScale());
glm::mat4 viewRotationMatrix =
glm::mat4_cast(cameraTransform->getRotation());
viewMatrix = viewTranslationMatrix * viewRotationMatrix * viewScaleMatrix;
quatFromToRotation(glm::vec3 from, glm::vec3 to) is defined as the following:
glm::quat quatFromToRotation(glm::vec3 from, glm::vec3 to)
{
from = glm::normalize(from); to = glm::normalize(to);
float cosTheta = glm::dot(from, to);
glm::vec3 rotationAxis;
if (cosTheta < -1 + 0.001f)
{
rotationAxis = glm::cross(glm::vec3(0.0f, 0.0f, 1.0f), from);
if (glm::length2(rotationAxis) < 0.01f)
rotationAxis = glm::cross(glm::vec3(1.0f, 0.0f, 0.0f), from);
rotationAxis = glm::normalize(rotationAxis);
return glm::angleAxis(180.0f, rotationAxis);
}
rotationAxis = glm::cross(from, to);
float s = sqrt((1.0f + cosTheta) * 2.0f);
float invis = 1.0f / s;
return glm::quat(
s * 0.5f,
rotationAxis.x * invis,
rotationAxis.y * invis,
rotationAxis.z * invis
);
}
What I'm having troubles with is the fact the cameraRotation isn't being set correctly. No matter where the player is, the camera's forward is always (0,0,-1)
Your problem is in the line
//Looking at the player's feet
cameraRotation = quatToFromRotation(FORWARD, playerPosition);
You need to look from the camera position to the player's feet - not from "one meter above the player" (assuming the player is at (0,0,0) when you initially do this). Replace FORWARD with cameraPosition:
cameraRotation = quatToFromRotation(cameraPosition, playerPosition);
EDIT I believe you have an error in your quatToFromRotation function as well. See https://stackoverflow.com/a/11741520/1967396 for a very nice explanation (and some pseudo code) of quaternion rotation.