Change to Bone User Transformation

In my current project I have a model which rotates its body to face the direction of the mouse (i.e. the direction he is facing). The problem I ran into was that using ‘setUserTransforms’ on the bone stopped the idle ‘breathing’ animation.

My solution was to have a ‘Bone’ multiply the user transformations with the animation transformations when ‘userControl’ was set to false. Instead of the usual functionality of throwing an exception.

Therefore, the new functionality is: when userControl is set to true it overrides the animation transformation, when it is set to false it combines the transformations instead.

What is everyone’s thoughts on this? Is there a more elegant way of doing it? My other idea was to replace userControl with an enum that has something to the effect of ‘FullUser’, ‘Blend’ and ‘FullAnimation’. To make it more obvious to the user.

Anyway here is the patch for my current modification.

[patch]

Index: core/com/jme3/animation/Bone.java

===================================================================

— core/com/jme3/animation/Bone.java (revision 7830)

+++ core/com/jme3/animation/Bone.java (working copy)

@@ -59,11 +59,6 @@

private Bone parent;

private final ArrayList<Bone> children = new ArrayList<Bone>();

/**

  • * If enabled, user can control bone transform with setUserTransforms.<br />
    
  • * Animation transforms are not applied to this bone when enabled.<br />
    
  • */<br />
    
  • private boolean userControl = false;
  • /**
  • The attachment node.

    */

    private Node attachNode;

    @@ -82,12 +77,18 @@

    private Quaternion worldBindInverseRot;

    private Vector3f worldBindInverseScale;

    /**
  • * The local animated transform combined with the local bind transform and parent world transform<br />
    
  • * The local transform which defines the bones position<br />
    

*/

private Vector3f localPos = new Vector3f();

private Quaternion localRot = new Quaternion();

private Vector3f localScale = new Vector3f(1.0f, 1.0f, 1.0f);

/**

  • * The user specified transformation (see {@link Mode})<br />
    
  • */<br />
    
  • private Vector3f userPos = new Vector3f();
  • private Quaternion userRot = new Quaternion();
  • private Vector3f userScale = new Vector3f(1.0f, 1.0f, 1.0f);
  • /**
  • MODEL SPACE -> BONE SPACE (in animated state)

    */

    private Vector3f worldPos = new Vector3f();

    @@ -96,6 +97,23 @@

    //used for getCombinedTransform

    private Transform tmpTransform = new Transform();


  • private Mode boneMode = Mode.ANIMATION;

    +
  • public enum Mode
  • {
  •   // Only user transformations are used, bind and animation are ignored<br />
    
  •   USER,<br />
    

+

  •   // Combines user transformations and the bind transform<br />
    
  •   USER_AND_BIND,<br />
    

+

  •   // Combines the bind, animation and user transformations<br />
    
  •   USER_AND_ANIMATION,<br />
    

+

  •   // Combines the bind and animation transformation<br />
    
  •   ANIMATION<br />
    
  • }

    +

    /**
  • Creates a new bone with the given name.

    *

    @@ -130,7 +148,7 @@

    Bone(Bone source) {

    this.name = source.name;


  •    userControl = source.userControl;<br />
    
  •    boneMode = source.boneMode;<br />
    

initialPos = source.initialPos;
initialRot = source.initialRot;
@@ -305,10 +323,10 @@
* If enabled, user can control bone transform with setUserTransforms.
* Animation transforms are not applied to this bone when enabled.
*/
- public void setUserControl(boolean enable) {
- userControl = enable;
+ public void setBoneMode( Mode mode ) {
+ boneMode = mode;
}
-
+
/**
* Add a new child to this bone. Shouldn't be used by user code.
* Can corrupt skeleton.
@@ -399,12 +417,21 @@
* Reset the bone and it's children to bind pose.
*/
final void reset() {
- if (!userControl) {
- localPos.set(initialPos);
- localRot.set(initialRot);
- localScale.set(initialScale);
+
+ if( boneMode == Mode.USER )
+ return;
+
+ localPos.set(initialPos);
+ localRot.set(initialRot);
+ localScale.set(initialScale);
+
+ if( boneMode == Mode.USER_AND_BIND )
+ {
+ localPos.addLocal( userPos );
+ localRot.multLocal( userRot );
+ localScale.multLocal( userScale );
}
-
+
for (int i = children.size() - 1; i >= 0; i--) {
children.get(i).reset();
}
@@ -437,17 +464,28 @@
* Sets user transform.
*/
public void setUserTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) {
- if (!userControl) {
- throw new IllegalStateException("User control must be on bone to allow user transforms");
- }
-
- localPos.set(initialPos);
- localRot.set(initialRot);
- localScale.set(initialScale);
-
- localPos.addLocal(translation);
- localRot = localRot.mult(rotation);
- localScale.multLocal(scale);
+
+ userPos.set(translation);
+ userRot.set(rotation);
+ userScale.set(scale);
+
+ if( boneMode == Mode.ANIMATION )
+ return;
+
+ if( boneMode == Mode.USER )
+ {
+ localPos.set(userPos);
+ localRot.set(userRot);
+ localScale.set(userScale);
+ }
+ else
+ {
+ // With Mode.USER_AND_ANIMATION this will be overrided with calls to setAnimTransoforms
+
+ localPos.set(initialPos).addLocal( userPos );
+ localRot.set(initialRot).multLocal( userRot );
+ localScale.set(initialScale).multLocal( userScale );
+ }
}

/**
@@ -456,10 +494,10 @@
* @param rotation
*/
public void setUserTransformsWorld(Vector3f translation, Quaternion rotation) {
- if (!userControl) {
- throw new IllegalStateException("User control must be on bone to allow user transforms");
- }

+ if( boneMode == Mode.ANIMATION )
+ return;
+
// TODO: add scale here ???
worldPos.set(translation);
worldRot.set(rotation);
@@ -502,27 +540,32 @@
* Bone is assumed to be in bind pose when this is called.
*/
void setAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) {
- if (userControl) {
- return;
- }

-// localPos.addLocal(translation);
-// localRot.multLocal(rotation);
- //localRot = localRot.mult(rotation);
+ if( boneMode == Mode.USER || boneMode == Mode.USER_AND_BIND )
+ return;
+
+ if( translation != null )
+ localPos.set(initialPos).addLocal(translation);
+
+ if( rotation != null)
+ localRot.set(initialRot).multLocal(rotation);

- localPos.set(initialPos).addLocal(translation);
- localRot.set(initialRot).multLocal(rotation);
-
- if (scale != null) {
+ if (scale != null)
localScale.set(initialScale).multLocal(scale);
+
+ if( boneMode == Mode.USER_AND_ANIMATION )
+ {
+ localPos.addLocal( userPos );
+ localRot.multLocal( userRot );
+ localScale.multLocal( userScale );
}
}

void blendAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale, float weight) {
- if (userControl) {
- return;
- }

+ if( boneMode == Mode.USER || boneMode == Mode.USER_AND_BIND )
+ return;
+
TempVars vars = TempVars.get();
// assert vars.lock();

@@ -544,8 +587,14 @@
localScale.interpolate(tmpV2, weight);
}

-
vars.release();
+
+ if( boneMode == Mode.USER_AND_ANIMATION )
+ {
+ localPos.addLocal( userPos );
+ localRot.multLocal( userRot );
+ localScale.multLocal( userScale );
+ }
}

/**
Index: jbullet/com/jme3/bullet/control/KinematicRagdollControl.java
===================================================================
--- jbullet/com/jme3/bullet/control/KinematicRagdollControl.java (revision 7830)
+++ jbullet/com/jme3/bullet/control/KinematicRagdollControl.java (working copy)
@@ -241,10 +241,10 @@
//updating bones transforms
if (boneList.isEmpty()) {
//we ensure we have the control to update the bone
- link.bone.setUserControl(true);
+ //link.bone.setBoneMode( Bone.Mode.USER );
link.bone.setUserTransformsWorld(position, tmpRot1);
//we give control back to the key framed animation.
- link.bone.setUserControl(false);
+ //link.bone.setUserControl(false);
} else {
RagdollUtils.setTransform(link.bone, position, tmpRot1, true, boneList);
}
@@ -696,7 +696,7 @@
vars.release();

for (Bone bone : skeleton.getRoots()) {
- RagdollUtils.setUserControl(bone, mode == Mode.Ragdoll);
+ RagdollUtils.setBoneMode(bone, (mode == Mode.Ragdoll) ? Bone.Mode.USER : Bone.Mode.USER_AND_ANIMATION );
}
}

@@ -738,7 +738,7 @@
vars.release();

for (Bone bone : skeleton.getRoots()) {
- RagdollUtils.setUserControl(bone, false);
+ RagdollUtils.setBoneMode(bone, Bone.Mode.USER_AND_ANIMATION);
}

blendStart = 0;
Index: jbullet/com/jme3/bullet/control/ragdoll/RagdollUtils.java
===================================================================
--- jbullet/com/jme3/bullet/control/ragdoll/RagdollUtils.java (revision 7830)
+++ jbullet/com/jme3/bullet/control/ragdoll/RagdollUtils.java (working copy)
@@ -247,7 +247,7 @@
public static void setTransform(Bone bone, Vector3f pos, Quaternion rot, boolean restoreBoneControl, Set<String> boneList) {
//we ensure that we have the control
if (restoreBoneControl) {
- bone.setUserControl(true);
+ bone.setBoneMode( Bone.Mode.USER );
}
//we set te user transforms of the bone
bone.setUserTransformsWorld(pos, rot);
@@ -260,14 +260,14 @@
}
//we give back the control to the keyframed animation
if (restoreBoneControl) {
- bone.setUserControl(false);
+ bone.setBoneMode( Bone.Mode.USER_AND_ANIMATION );
}
}

- public static void setUserControl(Bone bone, boolean bool) {
- bone.setUserControl(bool);
+ public static void setBoneMode(Bone bone, Bone.Mode mode ) {
+ bone.setBoneMode(mode);
for (Bone child : bone.getChildren()) {
- setUserControl(child, bool);
+ setBoneMode(child, mode);
}
}
}
[/patch]
EDIT: Updated patch to set user transforms in 'blendAnimTransforms' as well.
EDIT AGAIN: Updated patch to add support for the following situations USER, USER_AND_BIND, USER_AND_ANIMATION and ANIMATION
1 Like

I would just have a root bone that I leave unanimated in Blender (or whatever). If you don’t have access to the model that would be a problem though. I like your idea of combining the transforms.

Sorry, I’m not sure I follow. My model’s legs stay stationary but the upper torso rotates to face the mouse. I also rotate 3 separate spine bones so there is a nice twist in the mesh as well.



Are you suggesting I can achieve this with an un-animated root bone?

I think supporting all 3 modes would be best. In some cases people might want to avoid applying the bind pose transforms also.

Momoko_Fan



Good point, I’ll edit my patch so it can support all 4 situations.