You’re welcome!
By the way, in the code snippet, I posted on that topic, the “convert to in-place” part is dirty code. Better to use the function I posted here which is also integrated into the Wes library.
Edit:
Here it is updated to use the new “in-place animation” method:
Note that you must rename “root.x” to the root bone name on your model. In my case, I am using Auto Rig Pro addon to rig all my characters and the root bone name is “root.x” that is why I have hardcoded it in code.
import com.jme3.anim.*;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.util.SafeArrayList;
/**
* @author Ali-RS
*/
public class AnimUtils {
public static SkinningControl findSkinningControl(Spatial model) {
Spatial s = getAnimRoot(model);
SkinningControl ac = s == null ? null : s.getControl(SkinningControl.class);
return ac;
}
public static AnimComposer findAnimComposer(Spatial model) {
Spatial s = getAnimRoot(model);
AnimComposer ac = s == null ? null : s.getControl(AnimComposer.class);
return ac;
}
protected static Spatial getAnimRoot(Spatial model) {
// Find spatial with the composer
// For the moment, we'll guess
// Spatial animRoot = ((Node) model).getChild("root");
// if (animRoot == null) {
// // Have to find it the hard way
// animRoot = findAnimRoot(model);
// }
return findAnimRoot(model);
}
protected static Spatial findAnimRoot(Spatial s) {
if (s.getControl(AnimComposer.class) != null) {
return s;
}
if (s instanceof Node) {
for (Spatial child : ((Node) s).getChildren()) {
Spatial result = findAnimRoot(child);
if (result != null) {
return result;
}
}
}
return null;
}
public static AnimClip retargetClip(AnimClip sourceClip, Spatial target, boolean inPlace, String newClipName) {
Spatial animRoot = getAnimRoot(target);
if (animRoot == null) {
System.err.println("Anim root is null!");
return null;
}
SkinningControl sc = animRoot.getControl(SkinningControl.class);
if (target.getUserData("hasInitialPose") == null) {
sc.getArmature().applyBindPose();
sc.getArmature().saveInitialPose();
target.setUserData("hasInitialPose", Boolean.TRUE);
}
SafeArrayList<AnimTrack> tracks = new SafeArrayList<>(AnimTrack.class);
for (AnimTrack animTrack : sourceClip.getTracks()) {
if (animTrack instanceof TransformTrack) {
TransformTrack sourceTrack = (TransformTrack) animTrack;
Joint sourceJoint = (Joint) sourceTrack.getTarget();
// I am using a standard humanoid rig for all my characters so joint
// names are the same on all rigs
Joint targetJoint = sc.getArmature().getJoint(sourceJoint.getName());
if (targetJoint == null) {
System.err.println( "Joint with name " + sourceJoint.getName() + " not fount on target");
continue;
}
// Only Rotations are cloned
TransformTrack targetTrack = new TransformTrack(targetJoint, sourceTrack.getTimes(), null, sourceTrack.getRotations(), null);//sourceTrack.jmeClone();
targetTrack.setTarget(targetJoint);
if (targetJoint.getName().equals("root.x")) {
System.out.println("Transferring root motion...");
// Modify translations to fit in target rig
if (sourceTrack.getTranslations() != null) {
Vector3f[] translations = new Vector3f[sourceTrack.getTimes().length];
//boolean convertToInPlace = false;
/*Vector3f start = new Vector3f(sourceTrack.getTranslations()[0]);
Vector3f end = new Vector3f(sourceTrack.getTranslations()[sourceTrack.getTranslations().length - 1]);
start.y = 0;
end.y = 0;
if (start.distance(end) > 0.30f) {
convertToInPlace = true;
System.out.println("Converting to InPlace:" + sourceClip.getName());
}*/
float scale = targetJoint.getInitialTransform().getTranslation().getY() / sourceJoint.getInitialTransform().getTranslation().getY();
System.out.println("Scale factor= " + scale);
for (int i = 0; i < sourceTrack.getTranslations().length; i++) {
// delta translation = track translation - bind translation
Vector3f deltaTranslation = sourceTrack.getTranslations()[i].subtract(sourceJoint.getLocalTranslation());
deltaTranslation.multLocal(scale);
/*if (convertToInPlace) {
deltaTranslation.x = 0f;
deltaTranslation.z = 0f;
}*/
Vector3f targetTranslation = new Vector3f(targetJoint.getInitialTransform().getTranslation());
targetTranslation.addLocal(deltaTranslation);
translations[i] = targetTranslation;
}
if (inPlace) {
convertToInPlace(translations);
}
targetTrack.setKeyframesTranslation(translations);
}
}
tracks.add(targetTrack);
}
}
AnimClip targetClip = new AnimClip(newClipName);
targetClip.setTracks(tracks.getArray());
return targetClip;
}
private static void convertToInPlace(Vector3f[] translations) {
Vector3f start = translations[0];
Vector3f end = translations[translations.length - 1];
Vector3f delta = end.subtract(start);
Vector3f factor = delta.divide(translations.length - 1);
Vector3f vTemp = new Vector3f();
for (int index = 0; index < translations.length; index++) {
Vector3f translation = translations[index];
vTemp.set(factor).multLocal(index);
translation.subtractLocal(vTemp);
}
}
}
and to use it:
AnimClip newClip = AnimUtils.retargetClip(clip, targetModel, inPlace, newClipName);
AnimComposer ac = AnimUtils.findAnimComposer(targetModel);
ac.addAnimClip(newClip);