How can I move bones of a loaded asset programmatically in Away3D?

706 views Asked by At

I'm loading a 3D asset into a Away3D scene and I'd like to move the position of the bones in code.

The asset loading all goes well, I grab a pointer to the Mesh and Skeleton while loading:

private function onAssetComplete(evt:AssetEvent):void
{
    if(evt.asset.assetType == AssetType.SKELETON){
        _skeleton = evt.asset as Skeleton;
    } else if (evt.asset.assetType == AssetType.MESH) {
        _mesh = evt.asset as Mesh;
    }
}

After the asset(s) have finished loading, I have a valid Skeleton and Mesh instance, the model is also visible in my scene. The next thing I tried is the following.

// create a matrix with the desired joint (bone) position
var pos:Matrix3D = new Matrix3D();
pos.position = new Vector3D(60, 0, 0);
pos.invert();

// get the joint I'd like to modifiy. The bone is named "left"
var joint:SkeletonJoint = _skeleton.jointFromName("left");

// assign joint position
joint.inverseBindPose = pos.rawData;

This code runs without error, but the new position isn't being applied to the visible geometry, eg. the position of the bone doesn't change at all.

Is there an additional step I'm missing here? Do I have to re-assign the skeleton to the Mesh somehow? Or do I have to explicitly tell the mesh that the bone positions have changed?

1

There are 1 answers

0
bummzack On BEST ANSWER

This might not be the best way to solve this, but here's what I figured out:

Away3D only applies joint transformations to the geometry when an animation is present. In order to apply your transforms, your geometry must have an animation or you'll have to create an animation in code. Here's how you do that (preferably in your LoaderEvent.RESOURCE_COMPLETE handler method:

// create a new pose for the skeleton
var rootPose:SkeletonPose = new SkeletonPose();

// add all the joints to the pose
// the _skeleton member is being assigned during the loading phase where you
// look for AssetType.SKELETON inside a AssetEvent.ASSET_COMPLETE listener
for each(var joint:SkeletonJoint in _skeleton.joints){
    var m:Matrix3D = new Matrix3D(joint.inverseBindPose);
    m.invert();
    var p:JointPose = new JointPose();
    p.translation = m.transformVector(p.translation);
    p.orientation.fromMatrix(m);
    rootPose.jointPoses.push(p);
}

// create idle animation clip by adding the root pose twice
var clip:SkeletonClipNode = new SkeletonClipNode();
clip.addFrame(rootPose, 1000);
clip.addFrame(rootPose, 1000);
clip.name = "idle";

// build animation set
var animSet:SkeletonAnimationSet = new SkeletonAnimationSet(3);
animSet.addAnimation(clip);

// setup animator with set and skeleton
var animator:SkeletonAnimator = new SkeletonAnimator(animSet, _skeleton);

// assign the newly created animator to your Mesh.
// This example assumes that you grabbed the pointer to _myMesh during the 
// asset loading stage (by looking for AssetType.MESH)
_myMesh.animator = animator;

// run the animation
animator.play("idle");

// it's best to keep a member that points to your pose for
// further modification
_myPose = rootPose;

After that initialization step, you can modify your joint poses dynamically (you alter the position by modifying the translation property and the rotation by altering the orientation property). Example:

_myPose.jointPoses[2].translation.x = 100;

If you don't know the indices of your joints and rather address bones by name, this should work:

var jointIndex:int = _skeleton.jointIndexFromName("myBoneName");
_myPose.jointPoses[jointIndex].translation.y = 10;

If you use the name-lookup frequently (say every frame) and you have a lot of bones in your model, it's advisable to build a Dictionary where you can look up bone indices by name. The reason for this is that the implementation of jointIndexFromName performs a linear search through all joints which is wasteful if you do this multiple times.