
com.jme3.bullet.animation.BoneLink Maven / Gradle / Ivy
Show all versions of Minie Show documentation
/*
* Copyright (c) 2018-2023 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.bullet.animation;
import com.jme3.anim.Joint;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.bullet.RotationOrder;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.joints.Constraint;
import com.jme3.bullet.joints.New6Dof;
import com.jme3.bullet.joints.PhysicsJoint;
import com.jme3.bullet.joints.SixDofJoint;
import com.jme3.bullet.objects.PhysicsRigidBody;
import com.jme3.bullet.util.DebugShapeFactory;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.math.Matrix3f;
import com.jme3.math.Quaternion;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import com.jme3.util.clone.Cloner;
import java.io.IOException;
import java.util.logging.Logger;
import jme3utilities.Heart;
import jme3utilities.MySkeleton;
import jme3utilities.Validate;
import jme3utilities.math.MyMath;
import jme3utilities.math.MyQuaternion;
/**
* Link an animated bone in an Armature/Skeleton to a jointed rigid body in a
* ragdoll.
*
* @author Stephen Gold [email protected]
*
* Based on KinematicRagdollControl by Normen Hansen and Rémy Bouquet (Nehon).
*/
public class BoneLink extends PhysicsLink {
// *************************************************************************
// constants and loggers
/**
* message logger for this class
*/
final public static Logger logger2
= Logger.getLogger(BoneLink.class.getName());
/**
* local copy of {@link com.jme3.math.Matrix3f#IDENTITY}
*/
final private static Matrix3f matrixIdentity = new Matrix3f();
/**
* field names for serialization
*/
final private static String tagEndBoneTransforms = "endBoneTransforms";
final private static String tagManagedArmatureJoints
= "managedArmatureJoints";
final private static String tagManagedBones = "managedBones";
final private static String tagPrevBoneTransforms = "prevBoneTransforms";
final private static String tagStartBoneTransforms = "startBoneTransforms";
final private static String tagSubmode = "submode";
/**
* local copy of {@link com.jme3.math.Vector3f#ZERO}
*/
final private static Vector3f translateIdentity = new Vector3f(0f, 0f, 0f);
// *************************************************************************
// fields
/**
* skeleton bones managed by this link, in a pre-order, depth-first
* traversal of the Skeleton, starting with the linked bone, or null for an
* Armature
*/
private Bone[] managedBones = null;
/**
* armature joints managed by this link, in a pre-order, depth-first
* traversal of the Armature, starting with the linked bone, or null for a
* Skeleton
*/
private Joint[] managedArmatureJoints = null;
/**
* submode when kinematic
*/
private KinematicSubmode submode = KinematicSubmode.Animated;
/**
* reusable temporary storage for a 3x3 matrix
*/
private Matrix3f tmpMatrix = new Matrix3f();
/**
* local transform for each managed bone at the end of a blend to
* {@code Reset} submode, or null if not specified
*/
private Transform[] endBoneTransforms = null;
/**
* local transform of each managed bone from the previous update
*/
private Transform[] prevBoneTransforms = null;
/**
* local transform of each managed bone at the start of the most recent
* blend interval
*/
private Transform[] startBoneTransforms = null;
// *************************************************************************
// constructors
/**
* No-argument constructor needed by SavableClassUtil.
*/
protected BoneLink() {
// do nothing
}
/**
* Instantiate a purely kinematic link between the specified skeleton bone
* and the specified rigid body.
*
* @param control the Control that will manage this link (not null, alias
* created)
* @param bone the linked skeleton bone (not null, alias created)
* @param collisionShape the desired shape (not null, alias created)
* @param linkConfig the link configuration (not null)
* @param localOffset the location of the body's center (in the bone's local
* coordinates, not null, unaffected)
*/
BoneLink(DacLinks control, Bone bone, CollisionShape collisionShape,
LinkConfig linkConfig, Vector3f localOffset) {
super(control, bone, collisionShape, linkConfig, localOffset);
}
/**
* Instantiate a purely kinematic link between the specified armature joint
* and the specified rigid body.
*
* @param control the Control that will manage this link (not null, alias
* created)
* @param joint the linked armature joint (not null, alias created)
* @param collisionShape the desired shape (not null, alias created)
* @param linkConfig the link configuration (not null)
* @param localOffset the location of the body's center (in the joint's
* local coordinates, not null, unaffected)
*/
BoneLink(DacLinks control, Joint joint, CollisionShape collisionShape,
LinkConfig linkConfig, Vector3f localOffset) {
super(control, joint, collisionShape, linkConfig, localOffset);
}
// *************************************************************************
// new methods exposed
/**
* Add a PhysicsJoint to this link and configure its range of motion. Also
* initialize the link's parent and its array of managed bones (or armature
* joints).
*
* The new joint will be either a SixDofJoint or a New6Dof, depending on how
* the bone is configured. Its "A" end will be the parent's rigid body, and
* its "B" end will be this link's rigid body.
*
* @param parentLink (not null, alias created)
*/
void addJoint(PhysicsLink parentLink) {
assert parentLink != null;
assert getJoint() == null;
setParent(parentLink);
Transform parentToWorld = parentLink.physicsTransform(null);
parentToWorld.setScale(1f);
Transform worldToParent = parentToWorld.invert();
Transform childToWorld = physicsTransform(null);
childToWorld.setScale(1f);
Transform childToParent
= MyMath.combine(childToWorld, worldToParent, null);
Spatial transformer = getControl().getTransformer();
Vector3f pivotMesh;
Bone bone = getBone();
if (bone != null) { // old animation system
pivotMesh = bone.getModelSpacePosition();
} else { // new animation system
Joint armatureJoint = getArmatureJoint();
Transform t = armatureJoint.getModelTransform();
pivotMesh = t.getTranslation(); // alias
}
Vector3f pivotWorld = transformer.localToWorld(pivotMesh, null);
PhysicsRigidBody parentBody = parentLink.getRigidBody();
PhysicsRigidBody childBody = getRigidBody();
Vector3f pivotParent
= MyMath.transformInverse(parentToWorld, pivotWorld, null);
Vector3f pivotChild
= MyMath.transformInverse(childToWorld, pivotWorld, null);
childToParent.getRotation().toRotationMatrix(tmpMatrix);
Matrix3f rotParent = tmpMatrix; // alias
Matrix3f rotChild = matrixIdentity; // alias
Constraint constraint;
String name = boneName();
RotationOrder rotationOrder = getControl().config(name).rotationOrder();
if (rotationOrder == null) {
// TODO try HingeJoint or ConeJoint
constraint = new SixDofJoint(parentBody, childBody, pivotParent,
pivotChild, rotParent, rotChild, true);
} else {
constraint = new New6Dof(parentBody, childBody, pivotParent,
pivotChild, rotParent, rotChild, rotationOrder);
}
setJoint(constraint);
RangeOfMotion rangeOfMotion = getControl().getJointLimits(name);
rangeOfMotion.setup(constraint, false, false, false);
assert managedBones == null;
assert managedArmatureJoints == null;
int numManaged;
if (bone != null) { // old animation system
this.managedBones = getControl().listManagedBones(name);
numManaged = managedBones.length;
} else { // new animation system
this.managedArmatureJoints
= getControl().listManagedArmatureJoints(name);
numManaged = managedArmatureJoints.length;
}
this.startBoneTransforms = new Transform[numManaged];
for (int managedIndex = 0; managedIndex < numManaged; ++managedIndex) {
this.startBoneTransforms[managedIndex] = new Transform();
}
}
/**
* Begin blending this link to a purely kinematic mode.
*
* @param submode enum value (not null)
* @param blendInterval the duration of the blend interval (in seconds,
* ≥0)
*/
public void blendToKinematicMode(
KinematicSubmode submode, float blendInterval) {
Validate.nonNull(submode, "submode");
Validate.nonNegative(blendInterval, "blend interval");
blendToKinematicMode(blendInterval);
this.submode = submode;
// Save initial bone transforms for blending.
int numManaged = countManaged();
for (int managedIndex = 0; managedIndex < numManaged; ++managedIndex) {
Transform transform;
if (prevBoneTransforms == null) { // this link not updated yet
transform = copyManagedTransform(managedIndex, null);
} else {
transform = prevBoneTransforms[managedIndex];
}
startBoneTransforms[managedIndex].set(transform);
}
// Take or release control of the managed bones.
if (submode == KinematicSubmode.Animated) {
setUserControl(false);
} else {
setUserControl(true);
}
}
/**
* Determine the index in the Armature/Skeleton of the indexed managed bone.
*
* @param managedIndex which managed bone (0 = the linked bone, ≥0,
* <numManaged)
* @return the index in the Armature or Skeleton (≥0)
*/
public int boneIndex(int managedIndex) {
int numManaged = countManaged();
Validate.inRange(managedIndex, "managed index", 0, numManaged - 1);
int result;
if (managedBones != null) { // old animation system
Bone managed = managedBones[managedIndex];
Skeleton skeleton = getControl().getSkeleton();
result = skeleton.getBoneIndex(managed);
} else { // new animation system
Joint managed = managedArmatureJoints[managedIndex];
result = managed.getId();
}
assert result >= 0 : result;
return result;
}
/**
* Determine the number of managed skeleton bones or armature joints.
*
* @return the count (≥1)
*/
public int countManaged() {
int result;
if (managedBones != null) {
result = managedBones.length;
} else {
result = managedArmatureJoints.length;
}
assert result >= 1 : result;
return result;
}
/**
* Estimate the footprint of this link.
*
* @return the corner locations of a rectangle (in world coordinates)
*/
public Vector3f[] footprint() {
CollisionShape shape = getRigidBody().getCollisionShape();
assert shape.isConvex();
Transform localToWorld = physicsTransform(null);
localToWorld.setScale(1f);
Vector3f[] result = DebugShapeFactory.footprint(
shape, localToWorld, DebugShapeFactory.lowResolution);
return result;
}
/**
* Copy animation data from the specified link, which must have the same
* name and the same managed bones.
*
* @param oldLink the link to copy from (not null, unaffected)
*/
void postRebuild(BoneLink oldLink) {
int numManaged = countManaged();
assert oldLink.countManaged() == numManaged;
postRebuildLink(oldLink);
if (oldLink.isKinematic()) {
this.submode = oldLink.submode;
} else {
this.submode = KinematicSubmode.Frozen;
}
this.endBoneTransforms = Heart.deepCopy(oldLink.endBoneTransforms);
this.prevBoneTransforms = Heart.deepCopy(oldLink.prevBoneTransforms);
for (int managedIndex = 0; managedIndex < numManaged; ++managedIndex) {
Transform transform = oldLink.startBoneTransforms[managedIndex];
startBoneTransforms[managedIndex].set(transform);
}
}
/**
* Immediately put this link into dynamic mode and update the range of
* motion of its joint.
*
* @param uniformAcceleration the uniform acceleration vector (in
* physics-space coordinates, not null, unaffected)
* @param lockX true to lock the joint's X-axis
* @param lockY true to lock the joint's Y-axis
* @param lockZ true to lock the joint's Z-axis
*/
public void setDynamic(Vector3f uniformAcceleration, boolean lockX,
boolean lockY, boolean lockZ) {
Validate.finite(uniformAcceleration, "uniform acceleration");
String desiredAction = "put " + name() + " into dynamic mode";
getControl().verifyReadyForDynamicMode(desiredAction);
super.setDynamic(uniformAcceleration);
String name = boneName();
RangeOfMotion preset = getControl().getJointLimits(name);
preset.setup(getJoint(), lockX, lockY, lockZ);
setUserControl(true);
}
/**
* Immediately put this link into dynamic mode and lock its PhysicsJoint at
* the specified rotation.
*
* The control must be "ready" for dynamic mode.
*
* @param uniformAcceleration the uniform acceleration vector (in
* physics-space coordinates, not null, unaffected)
* @param userRotation the desired rotation relative to the bind rotation of
* the linked bone (not null, unaffected)
*/
public void setDynamic(
Vector3f uniformAcceleration, Quaternion userRotation) {
Validate.finite(uniformAcceleration, "uniform acceleration");
String desiredAction = "put " + name() + " into dynamic mode";
getControl().verifyReadyForDynamicMode(desiredAction);
super.setDynamic(uniformAcceleration);
PhysicsJoint joint = getJoint();
RotationOrder rotOrder;
if (joint instanceof SixDofJoint) {
rotOrder = RotationOrder.XYZ;
} else {
rotOrder = ((New6Dof) joint).getRotationOrder();
}
userRotation.toRotationMatrix(tmpMatrix);
Vector3f eulerAngles
= rotOrder.matrixToEuler(tmpMatrix, null); // garbage
RangeOfMotion rom = new RangeOfMotion(eulerAngles);
rom.setup(joint, false, false, false);
setUserControl(true);
}
/**
* Alter the local transform for each managed bone at the end of a blend to
* {@code Reset} submode.
*
* @param transforms (not null, one element for each managed bone, no null
* elements, alias created)
*/
public void setEndBoneTransforms(Transform[] transforms) {
Validate.nonNull(transforms, "transforms");
int numManaged = countManaged();
Validate.require(transforms.length == numManaged,
"one element for each managed bone");
this.endBoneTransforms = transforms;
}
/**
* Alter the local transform of the indexed managed bone. Use this method to
* animate managed bones other than the linked one. Effective only once the
* link has been updated.
*
* @param mbIndex the index of the managed bone (≥1, <numManaged)
* @param localTransform the desired Transform (not null, unaffected)
*/
public void setLocalTransform(int mbIndex, Transform localTransform) {
int numManaged = countManaged();
Validate.inRange(mbIndex, "index", 1, numManaged - 1);
if (prevBoneTransforms != null) {
prevBoneTransforms[mbIndex].set(localTransform);
}
}
// *************************************************************************
// PhysicsLink methods
/**
* Callback from {@link com.jme3.util.clone.Cloner} to convert this
* shallow-cloned link into a deep-cloned one, using the specified Cloner
* and original to resolve copied fields.
*
* @param cloner the Cloner that's cloning this link (not null)
* @param original the instance from which this link was shallow-cloned (not
* null, unaffected)
*/
@Override
public void cloneFields(Cloner cloner, Object original) {
super.cloneFields(cloner, original);
this.managedBones = cloner.clone(managedBones);
this.managedArmatureJoints = cloner.clone(managedArmatureJoints);
this.tmpMatrix = cloner.clone(tmpMatrix);
this.endBoneTransforms = cloner.clone(endBoneTransforms);
this.prevBoneTransforms = cloner.clone(prevBoneTransforms);
this.startBoneTransforms = cloner.clone(startBoneTransforms);
}
/**
* Update this link in Dynamic mode, setting the linked bone's transform
* based on the transform of the rigid body.
*/
@Override
protected void dynamicUpdate() {
assert !getRigidBody().isKinematic();
// Disable bone animations, if any.
int numManaged = countManaged();
for (int managedIndex = 1; managedIndex < numManaged; ++managedIndex) {
Transform t = prevBoneTransforms[managedIndex]; // alias
setManagedTransform(managedIndex, t);
}
// Override the local transform of the linked bone and update.
Transform transform = localBoneTransform(null); // TODO garbage
if (managedBones != null) {
MySkeleton.setLocalTransform(getBone(), transform);
for (Bone managed : managedBones) {
managed.updateModelTransforms();
}
} else {
getArmatureJoint().setLocalTransform(transform);
for (Joint managed : managedArmatureJoints) {
managed.updateModelTransforms();
}
}
}
/**
* Immediately freeze this link.
*
* @param forceKinematic true→force to kinematic mode,
* false→preserve mode
*/
@Override
public void freeze(boolean forceKinematic) {
if (forceKinematic || isKinematic()) {
blendToKinematicMode(KinematicSubmode.Frozen, 0f);
} else {
setDynamic(translateIdentity, true, true, true);
}
}
/**
* Update this link in blended Kinematic mode.
*
* @param tpf the time interval between frames (in seconds, ≥0)
*/
@Override
protected void kinematicUpdate(float tpf) {
assert tpf >= 0f : tpf;
assert getRigidBody().isKinematic();
Transform transform = new Transform();
int numManaged = countManaged();
for (int managedIndex = 0; managedIndex < numManaged; ++managedIndex) {
switch (submode) {
case Amputated:
int boneIndex = boneIndex(managedIndex);
getControl().copyBindTransform(boneIndex, transform);
transform.setScale(0.001f);
break;
case Animated:
copyManagedTransform(managedIndex, transform);
break;
case Bound:
boneIndex = boneIndex(managedIndex);
getControl().copyBindTransform(boneIndex, transform);
break;
case Frozen:
transform.set(prevBoneTransforms[managedIndex]);
break;
case Reset:
transform.set(endBoneTransforms[managedIndex]);
break;
default:
throw new IllegalStateException(submode.toString());
}
if (kinematicWeight() < 1f) { // not purely kinematic yet
/*
* For a smooth transition, blend the saved bone transform
* (from the start of the blend interval)
* into the goal transform.
*/
Transform start = startBoneTransforms[managedIndex]; // alias
Quaternion startQuat = start.getRotation(); // alias
MyQuaternion.normalizeLocal(startQuat);
Quaternion endQuat = transform.getRotation(); // alias
if (startQuat.dot(endQuat) < 0f) {
endQuat.multLocal(-1f);
} // TODO smarter sign flipping
MyQuaternion.normalizeLocal(endQuat);
MyMath.slerp(kinematicWeight(), start, transform, transform);
}
// Update the managed bone.
setManagedTransform(managedIndex, transform);
}
super.kinematicUpdate(tpf);
}
/**
* Unambiguously identify this link by name, within its DynamicAnimControl.
*
* @return a brief textual description (not null, not empty)
*/
@Override
public String name() {
String result = "Bone:" + boneName();
return result;
}
/**
* De-serialize this link from the specified importer, for example when
* loading from a J3O file.
*
* @param importer (not null)
* @throws IOException from the importer
*/
@Override
public void read(JmeImporter importer) throws IOException {
super.read(importer);
InputCapsule capsule = importer.getCapsule(this);
Savable[] tmp
= capsule.readSavableArray(tagManagedArmatureJoints, null);
if (tmp != null) {
this.managedArmatureJoints = new Joint[tmp.length];
for (int managedI = 0; managedI < tmp.length; ++managedI) {
this.managedArmatureJoints[managedI] = (Joint) tmp[managedI];
}
}
tmp = capsule.readSavableArray(tagManagedBones, null);
if (tmp != null) {
this.managedBones = new Bone[tmp.length];
for (int managedI = 0; managedI < tmp.length; ++managedI) {
this.managedBones[managedI] = (Bone) tmp[managedI];
}
}
this.submode = capsule.readEnum(
tagSubmode, KinematicSubmode.class, KinematicSubmode.Animated);
this.endBoneTransforms
= RagUtils.readTransformArray(capsule, tagEndBoneTransforms);
this.prevBoneTransforms
= RagUtils.readTransformArray(capsule, tagPrevBoneTransforms);
this.startBoneTransforms
= RagUtils.readTransformArray(capsule, tagStartBoneTransforms);
}
/**
* Immediately put this link into dynamic mode. The control must be "ready".
*
* @param uniformAcceleration the uniform acceleration vector to apply (in
* physics-space coordinates, not null, unaffected)
*/
@Override
public void setDynamic(Vector3f uniformAcceleration) {
Validate.finite(uniformAcceleration, "uniform acceleration");
String desiredAction = "put " + name() + " into dynamic mode";
getControl().verifyReadyForDynamicMode(desiredAction);
super.setDynamic(uniformAcceleration);
setUserControl(true);
}
/**
* Immediately put this link into ragdoll mode. The control must be ready
* for dynamic mode.
*/
@Override
public void setRagdollMode() {
String desiredAction = "put " + name() + " into ragdoll mode";
getControl().verifyReadyForDynamicMode(desiredAction);
Vector3f gravity = getControl().gravity(null);
setDynamic(gravity, false, false, false);
super.setRagdollMode();
}
/**
* Internal callback, invoked once per frame during the logical-state
* update, provided the control is added to a scene.
*
* @param tpf the time interval between frames (in seconds, ≥0)
*/
@Override
void update(float tpf) {
assert tpf >= 0f : tpf;
int numManaged = countManaged();
if (prevBoneTransforms == null) {
/*
* On the first update, allocate and initialize
* the array of previous bone transforms, if it wasn't
* allocated in blendToKinematicMode().
*/
this.prevBoneTransforms = new Transform[numManaged];
for (int managedI = 0; managedI < numManaged; ++managedI) {
Transform boneTransform = copyManagedTransform(managedI, null);
this.prevBoneTransforms[managedI] = boneTransform;
}
}
super.update(tpf);
// Save copies of the latest managed-bone transforms.
for (int managedIndex = 0; managedIndex < numManaged; ++managedIndex) {
Transform lastTransform = prevBoneTransforms[managedIndex]; // alias
copyManagedTransform(managedIndex, lastTransform);
}
}
/**
* Serialize this link to the specified exporter, for example when saving to
* a J3O file.
*
* @param exporter (not null)
* @throws IOException from the exporter
*/
@Override
public void write(JmeExporter exporter) throws IOException {
super.write(exporter);
OutputCapsule capsule = exporter.getCapsule(this);
capsule.write(managedArmatureJoints, tagManagedArmatureJoints, null);
capsule.write(managedBones, tagManagedBones, null);
capsule.write(submode, tagSubmode, KinematicSubmode.Animated);
// tmpMatrix is never written.
capsule.write(endBoneTransforms, tagEndBoneTransforms, null);
capsule.write(
prevBoneTransforms, tagPrevBoneTransforms, new Transform[0]);
capsule.write(
startBoneTransforms, tagStartBoneTransforms, new Transform[0]);
}
// *************************************************************************
// private methods
/**
* Copy the local transform of the indexed managed bone in this link.
*
* @param managedIndex which managed bone (≥0, <numManaged)
* @param storeResult storage for the result (modified if not null)
* @return the Transform (either storeResult or a new instance, not null)
*/
private Transform copyManagedTransform(
int managedIndex, Transform storeResult) {
Transform result
= (storeResult == null) ? new Transform() : storeResult;
if (managedBones != null) { // old animation system
Bone managed = managedBones[managedIndex];
MySkeleton.copyLocalTransform(managed, result);
} else { // new animation system
Joint managed = managedArmatureJoints[managedIndex];
Transform local = managed.getLocalTransform(); // alias
result.set(local);
}
return result;
}
/**
* Calculate the local bone transform to match the physics transform of the
* rigid body.
*
* @param storeResult storage for the result (modified if not null)
* @return the calculated bone transform (in local coordinates, either
* storeResult or a new transform, not null)
*/
private Transform localBoneTransform(Transform storeResult) {
Transform result
= (storeResult == null) ? new Transform() : storeResult;
Vector3f location = result.getTranslation(); // alias
Quaternion orientation = result.getRotation(); // alias
Vector3f scale = result.getScale(); // alias
// Start with the rigid body's transform in physics/world coordinates.
getRigidBody().getTransform(result);
// Convert to mesh coordinates.
Transform worldToMesh = getControl().meshTransform(null).invert();
MyMath.combine(result, worldToMesh, result);
/*
* Convert to the bone's local coordinate system by factoring out the
* parent bone's transform.
*/
if (managedBones != null) { // old animation system
Bone parent = getBone().getParent();
RagUtils.meshToLocal(parent, result);
} else { // new animation system
Joint parent = getArmatureJoint().getParent();
RagUtils.meshToLocal(parent, result);
}
// Subtract the body's local offset, rotated and scaled.
Vector3f parentOffset = localOffset(null);
parentOffset.multLocal(scale);
MyQuaternion.rotate(orientation, parentOffset, parentOffset);
location.subtractLocal(parentOffset);
return result;
}
/**
* Alter the local transform of the indexed managed bone in this link.
*
* @param managedIndex which managed bone (≥0, <numManaged)
* @param transform the desired Transform (not null, unaffected)
*/
private void setManagedTransform(int managedIndex, Transform transform) {
if (managedBones != null) { // old animation system
Bone managed = managedBones[managedIndex];
MySkeleton.setLocalTransform(managed, transform);
managed.updateModelTransforms();
} else { // new animation system
Joint managed = managedArmatureJoints[managedIndex];
managed.setLocalTransform(transform);
managed.updateModelTransforms();
}
}
/**
* Alter the user-control flags of all skeleton bones managed by this link.
*
* @param wantUserControl the desired setting
*/
private void setUserControl(boolean wantUserControl) {
if (managedBones != null) { // old animation system
for (Bone managed : managedBones) {
managed.setUserControl(wantUserControl);
}
}
}
}