Getting Demina to work happily with bone scaling.

May 25, 2012 at 1:36 AM
Edited May 25, 2012 at 11:56 AM

Hey Jesse!  I've been adding some things to Demina lately, and one thing I was really excited to see was that the scaling of individual bones seems to be working in the editor AND the runtime library, though as far as I can tell there was no way to change bone scale values in the editor.  I started out just hand editing the xml, but have since added some scaling options to the editor.

Anyway, the one problem you're probably already aware of, is that as the scaling becomes non-uniform (the scale.X diverges from scale.Y), all hell breaks loose.  It seems the matrix decomposition method stops being able to read a rotation quaternion, and every child of the scaled bone gets all messed up in its rotation.  It seems like this is a common limitation of most 3d animation libraries, as well.  While I don't understand exactly how I could change the matrix calculations in order to support non-uniform scaling, I'll figure it out eventually, but I'm assuming it will involve adding additional operations in order to separate the parent bone's scaling from its rotation.  And this may be why you didn't add bone scaling to the editor in the first place -- because it would slow everything down with extra transformations.

 

Anyway, I'm wondering if this is the case, or if you just got too busy to finish implementing scaling while it was a work in progress, in which case I'd be glad to finish the work and add it as a fork here.

One question which occurs to me looking at the bone transformation code is this: is it actually faster to handle the position/rotation/scaling with 3d methods like matrix.CreateRotation, matrix.Decompose, etc., or would it be faster to just store the position, rotation, and scaling separately, and pass them on from the parent bone one by one.  It would allow the matrix to be eliminated from the BoneTransformation struct altogether, wouldn't it?

I might try this... if you already tried something similar or foresee it being an inferior implementation, I'd definitely appreciate your insight, as always.

 

Thanks Jesse,

 

Alex

Coordinator
May 25, 2012 at 1:36 PM

As with most things related to Demina, I only worked on a feature if I needed it for what I was doing at the time. (Or if someone asked for a feature.) I couldn't easily get scaling working, so I put it on the back burner. I'm not sure if I was running into the same problem you describe or not. It's been quite awhile.

I'd like to get rid of Matrix.Decompose altogether. If you can come up with a good way of doing that, I'm all for it. I've ported the Demina runtime to several platforms (to test them out), and I usually got rid of it, because I wrote SpriteBatch to include a Matrix transform in the Draw call, and I didn't need to bust it out to individual transformations.

May 25, 2012 at 8:49 PM
Edited May 25, 2012 at 9:01 PM

 

Hey Jesse,

so last night I wrote an alternate implementation for the bone transformations which completely avoids using the matrix.  So far I've only tried it in the editor code and the DeminaViewer code, but since DeminaViewer's transform code looks almost identical to the runtime library's transform code, I think it may work for everything:

 

I changed the main loop in DeminaViewer.Animation.GetBoneTransformations() to this:

 

 

     for (int boneIndex = 0; boneIndex < Keyframes[0].UpdateBones.Count; boneIndex++)
     {
          Vector2 position = Vector2.Lerp(currentKeyframe.UpdateBones[boneIndex].Position, nextKeyframe.UpdateBones[boneIndex].Position, t);
          Vector2 scale = Vector2.Lerp(currentKeyframe.UpdateBones[boneIndex].Scale, nextKeyframe.UpdateBones[boneIndex].Scale, t);
          float rotation = MathHelper.Lerp(currentKeyframe.UpdateBones[boneIndex].Rotation, nextKeyframe.UpdateBones[boneIndex].Rotation, t);


          int drawIndex = currentKeyframe.UpdateBones[boneIndex].SelfIndex;


          BoneTransformation bone;

          if (currentKeyframe.UpdateBones[boneIndex].ParentIndex != -1)
               bone = transforms[currentKeyframe.UpdateBones[boneIndex].ParentIndex];
          else
          {
               bone.Position = Vector2.Zero;
               bone.Rotation = 0f;
               bone.Scale = new Vector2(1, 1);
          }

          //rotate bone by its parent's rotation plus its own
          transforms[drawIndex].Rotation = rotation + bone.Rotation;

          //for position, start by scaling the bone's local position (its offset from its parent, in other words) by the parent's scale
          Vector2 scaledPosition = position * bone.Scale;
          //then, transform the scaled position by the parent's rotation.
          scaledPosition = Vector2.Transform(scaledPosition, Matrix.CreateRotationZ(bone.Rotation));
          //finally, translate by the parent's position offset.
          transforms[drawIndex].Position = scaledPosition + bone.Position;

          //scale bone by its parent
          transforms[drawIndex].Scale = scale * bone.Scale;



          }
     }

 

I don't know about copying the whole bone transformation out -- there's probably a faster way, such as directly referencing the array on each line and bypassing the initial copying.  That's just the most concise way I could think to do it.

and I wrote a new method for Demina.Bone:

 

 

        public void UpdateTransform(Bone parentBone)
        {


            TransformedRotation = Rotation + parentBone.TransformedRotation;
            Vector2 scaledPosition = Position * parentBone.TransformedScale;
            TransformedPosition = Vector2.Transform(scaledPosition, Matrix.CreateRotationZ(parentBone.TransformedRotation));
            TransformedPosition += parentBone.TransformedPosition;
            TransformedScale = Scale * parentBone.TransformedScale;

            //update the Transform matrix for the sake of mouse clicking code in Form1.cs
            //(in order to correctly select the bone texture shapes)
            Transform =
                Matrix.CreateScale(TransformedScale.X, TransformedScale.Y, 1) *
                Matrix.CreateRotationZ(TransformedRotation) *
                Matrix.CreateTranslation(TransformedPosition.X, TransformedPosition.Y, 0);
            
        }

I then replaced the call to UpdateTransform(Matrix) to a call to my UpdateTransform(Bone) in Keyframe.UpdateBones().  I only replaced the original method when the bone has a parent bone -- in cases where there is no parent bone, I left in the old method call.

Anyway, this is probably faster, right?  It avoids copying a 4x4 matrix a few times, anyway.  And the transform matrix can be completely eliminated from the runtime code!

Thanks again for Demina, and for all of your help.

 

Alex

Coordinator
May 29, 2012 at 12:35 AM

This is cool. I'll be away on a business trip for the next couple of days, but when I get back, I'll run some tests and profile a bit to see how this changes things. If all goes well, I'll add it to the main repository.

Thanks!

May 31, 2012 at 10:43 AM
Edited May 31, 2012 at 10:43 AM

Glad you like it!  I'm definitely curious to know if it actually improves things.  I'd love to be able to add to Demina, since it's been so incredibly useful to me (and really easy to add on to!)

I've added the code I posted above for DeminaViewer into the DeminaRuntime project, and everything seems to work (the code had to be added to GetBoneTransformations() and GetBoneTransformationsTransition() in the Animation class, and I think one line had to be changed in the second method.   I can post the whole code if you want.  Keep in mind you have to remove the matrix struct from the BoneTransformation struct type, otherwise the whole matrix (all 16 floats, which never get used by the runtime code with these changes) will be initialized every time that BoneTransformation is declared in the method.  Which would affect profiling a lot, I would think.

I've also added in ways to scale (alt + right click and drag) in the editor, as well as a window accessible from the bone menu which allows you to view and edit the numeric values for any selected bone's position, scale, or rotation, and also lets you apply the changes you make to that one bone across all keyframes of an animation (that part is a real timesaver for me).

It didn't take much code to achieve those things, but the alt + right click and drag stuff is a bit scattered throughout the form1 class, so let me know if you want those changes and I can post them or just post my whole project, or whatever.  Thanks for the quick responses, Jesse!  Hope you have a good trip.

 

Alex

Coordinator
May 31, 2012 at 12:11 PM

If you don't mind, go ahead and zip up the code and send it over to me. (Make sure you don't include any binaries, or gmail won't deliver it.)

My email address: jessechounard@gmail.com

Jun 4, 2012 at 9:07 PM

Hey Jesse, I just emailed my working version of Demina, with notes about where to find the changes specifically regarding scaling and transformation code.  Hopefully it's not too cluttered to find the pertinent changes.  

Jun 6, 2012 at 10:11 PM

A quick note:  I've been building animations with the new scaling features for the past week or so, and I'm pretty certain now that it's more trouble than its worth to have scale passed on to child bones.  In other words, unlike position and rotation, the scale of a parent bone shouldn't affect the scale of its child (in the code I sent I was multiplying them to get each bone's transformed scale.)  The main reason this is annoying is that textures may not be aligned:  you may want to scale an arm by the y axis, but the weapon attached to it by the x axis, and not by the y axis.  This leads to (many) situations where you end up scaling down a child bone to offset the scaling of its parent bone, and it just looks and feels messy.

So anyway, scale should still affect the position of child bones, but not their scale, in my opinion.  This leads to skeletons which are the most fun and easy to work with.

If you change the lines where parent bone scale is multiplied by current bone scale (in four different places, I think) that should be the only thing that requires changing.  I can also just resend the source if you agree with my assessment and don't want to play around with it yourself first.

Coordinator
Jun 7, 2012 at 2:50 AM

I haven't had a chance to mess with this just yet. (Or do any programming outside of work, unfortunately.)

I will take a look asap.

Jun 8, 2012 at 2:27 AM

Hey, take your time Jesse.  I'm adding a bunch more stuff in the next couple weeks :P

I'm definitely interested to know if the new transformation code runs faster.  Let me know when you get the time to mess with it.

 

Alex

Jun 21, 2012 at 1:40 AM
Edited Jun 21, 2012 at 1:41 AM

Hey Jesse, I just realized that before profiling the changed bone transformation code, I should really make some of the methods use ref/out versions.  That will speed up a lot of the methods which take structs as parameters and minimize any cost of adding scale to the transformations.  So in case you see this, hold off on testing, I'll send you a new version of the code in a couple days.

 

Alex