parent
59af265398
commit
235b9db2ca
@ -0,0 +1,493 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2018-2019 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.bullet.collision.shapes.CollisionShape; |
||||||
|
import com.jme3.bullet.joints.SixDofJoint; |
||||||
|
import com.jme3.bullet.objects.PhysicsRigidBody; |
||||||
|
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; |
||||||
|
|
||||||
|
/** |
||||||
|
* Link an animated bone in a skeleton to a jointed rigid body in a ragdoll. |
||||||
|
* <p> |
||||||
|
* This class is shared between JBullet and Native Bullet. |
||||||
|
* |
||||||
|
* @author Stephen Gold sgold@sonic.net |
||||||
|
* |
||||||
|
* 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(); |
||||||
|
// *************************************************************************
|
||||||
|
// fields
|
||||||
|
|
||||||
|
/** |
||||||
|
* bones managed by this link, in a pre-order, depth-first traversal of the |
||||||
|
* skeleton, starting with the linked bone |
||||||
|
*/ |
||||||
|
private Joint[] managedBones = null; |
||||||
|
/** |
||||||
|
* submode when kinematic |
||||||
|
*/ |
||||||
|
private KinematicSubmode submode = KinematicSubmode.Animated; |
||||||
|
/** |
||||||
|
* 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. Do not invoke |
||||||
|
* directly! |
||||||
|
*/ |
||||||
|
public BoneLink() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Instantiate a purely kinematic link between the named skeleton bone and |
||||||
|
* the specified rigid body. |
||||||
|
* |
||||||
|
* @param control the control that will manage this link (not null, alias |
||||||
|
* created) |
||||||
|
* @param bone the linked 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, Joint bone, CollisionShape collisionShape, |
||||||
|
float mass, Vector3f localOffset) { |
||||||
|
super(control, bone, collisionShape, mass, localOffset); |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// new methods exposed
|
||||||
|
|
||||||
|
/** |
||||||
|
* Add a physics joint to this link and configure its range of motion. Also |
||||||
|
* initialize the link's parent and its array of managed bones. |
||||||
|
* |
||||||
|
* @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 = childToWorld.clone(); |
||||||
|
childToParent.combineWithParent(worldToParent); |
||||||
|
|
||||||
|
Spatial transformer = getControl().getTransformer(); |
||||||
|
Vector3f pivotMesh = getBone().getModelTransform().getTranslation(); |
||||||
|
Vector3f pivotWorld = transformer.localToWorld(pivotMesh, null); |
||||||
|
|
||||||
|
PhysicsRigidBody parentBody = parentLink.getRigidBody(); |
||||||
|
PhysicsRigidBody childBody = getRigidBody(); |
||||||
|
Vector3f pivotParent |
||||||
|
= parentToWorld.transformInverseVector(pivotWorld, null); |
||||||
|
Vector3f pivotChild |
||||||
|
= childToWorld.transformInverseVector(pivotWorld, null); |
||||||
|
Matrix3f rotParent = childToParent.getRotation().toRotationMatrix(); |
||||||
|
Matrix3f rotChild = matrixIdentity; |
||||||
|
// TODO try HingeJoint or ConeJoint
|
||||||
|
SixDofJoint joint = new SixDofJoint(parentBody, childBody, pivotParent, |
||||||
|
pivotChild, rotParent, rotChild, true); |
||||||
|
super.setJoint(joint); |
||||||
|
|
||||||
|
String name = boneName(); |
||||||
|
RangeOfMotion rangeOfMotion = getControl().getJointLimits(name); |
||||||
|
rangeOfMotion.setupJoint(joint); |
||||||
|
|
||||||
|
joint.setCollisionBetweenLinkedBodys(false); |
||||||
|
|
||||||
|
assert managedBones == null; |
||||||
|
managedBones = getControl().listManagedBones(name); |
||||||
|
|
||||||
|
int numManagedBones = managedBones.length; |
||||||
|
startBoneTransforms = new Transform[numManagedBones]; |
||||||
|
for (int i = 0; i < numManagedBones; ++i) { |
||||||
|
startBoneTransforms[i] = 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) { |
||||||
|
super.blendToKinematicMode(blendInterval); |
||||||
|
|
||||||
|
this.submode = submode; |
||||||
|
/* |
||||||
|
* Save initial bone transforms for blending. |
||||||
|
*/ |
||||||
|
int numManagedBones = managedBones.length; |
||||||
|
for (int mbIndex = 0; mbIndex < numManagedBones; ++mbIndex) { |
||||||
|
Transform transform; |
||||||
|
if (prevBoneTransforms == null) { // this link not updated yet
|
||||||
|
Joint managedBone = managedBones[mbIndex]; |
||||||
|
transform = managedBone.getLocalTransform().clone(); |
||||||
|
} else { |
||||||
|
transform = prevBoneTransforms[mbIndex]; |
||||||
|
} |
||||||
|
startBoneTransforms[mbIndex].set(transform); |
||||||
|
} |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// 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 |
||||||
|
* (unused) |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void cloneFields(Cloner cloner, Object original) { |
||||||
|
super.cloneFields(cloner, original); |
||||||
|
|
||||||
|
managedBones = cloner.clone(managedBones); |
||||||
|
prevBoneTransforms = cloner.clone(prevBoneTransforms); |
||||||
|
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(); |
||||||
|
|
||||||
|
Transform transform = localBoneTransform(null); |
||||||
|
getBone().setLocalTransform(transform); |
||||||
|
|
||||||
|
for (Joint managedBone : managedBones) { |
||||||
|
managedBone.updateModelTransforms(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a shallow clone for the JME cloner. |
||||||
|
* |
||||||
|
* @return a new instance |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public BoneLink jmeClone() { |
||||||
|
try { |
||||||
|
BoneLink clone = (BoneLink) super.clone(); |
||||||
|
return clone; |
||||||
|
} catch (CloneNotSupportedException exception) { |
||||||
|
throw new RuntimeException(exception); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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(); |
||||||
|
for (int mbIndex = 0; mbIndex < managedBones.length; ++mbIndex) { |
||||||
|
Joint managedBone = managedBones[mbIndex]; |
||||||
|
switch (submode) { |
||||||
|
case Animated: |
||||||
|
transform.set(managedBone.getLocalTransform()); |
||||||
|
break; |
||||||
|
case Frozen: |
||||||
|
transform.set(prevBoneTransforms[mbIndex]); |
||||||
|
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[mbIndex]; |
||||||
|
Quaternion startQuat = start.getRotation(); |
||||||
|
Quaternion endQuat = transform.getRotation(); |
||||||
|
if (startQuat.dot(endQuat) < 0f) { |
||||||
|
endQuat.multLocal(-1f); |
||||||
|
} |
||||||
|
transform.interpolateTransforms( |
||||||
|
startBoneTransforms[mbIndex].clone(), transform, |
||||||
|
kinematicWeight()); |
||||||
|
} |
||||||
|
/* |
||||||
|
* Update the managed bone. |
||||||
|
*/ |
||||||
|
managedBone.setLocalTransform(transform); |
||||||
|
managedBone.updateModelTransforms(); |
||||||
|
} |
||||||
|
|
||||||
|
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; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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 numManagedBones = managedBones.length; |
||||||
|
assert oldLink.managedBones.length == numManagedBones; |
||||||
|
|
||||||
|
super.postRebuild(oldLink); |
||||||
|
if (oldLink.isKinematic()) { |
||||||
|
submode = oldLink.submode; |
||||||
|
} else { |
||||||
|
submode = KinematicSubmode.Frozen; |
||||||
|
} |
||||||
|
|
||||||
|
if (prevBoneTransforms == null) { |
||||||
|
prevBoneTransforms = new Transform[numManagedBones]; |
||||||
|
for (int i = 0; i < numManagedBones; ++i) { |
||||||
|
prevBoneTransforms[i] = new Transform(); |
||||||
|
} |
||||||
|
} |
||||||
|
for (int i = 0; i < numManagedBones; ++i) { |
||||||
|
prevBoneTransforms[i].set(oldLink.prevBoneTransforms[i]); |
||||||
|
startBoneTransforms[i].set(oldLink.startBoneTransforms[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* De-serialize this link, for example when loading from a J3O file. |
||||||
|
* |
||||||
|
* @param im importer (not null) |
||||||
|
* @throws IOException from importer |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void read(JmeImporter im) throws IOException { |
||||||
|
super.read(im); |
||||||
|
InputCapsule ic = im.getCapsule(this); |
||||||
|
|
||||||
|
Savable[] tmp = ic.readSavableArray("managedBones", null); |
||||||
|
if (tmp == null) { |
||||||
|
managedBones = null; |
||||||
|
} else { |
||||||
|
managedBones = new Joint[tmp.length]; |
||||||
|
for (int i = 0; i < tmp.length; ++i) { |
||||||
|
managedBones[i] = (Joint) tmp[i]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
submode = ic.readEnum("submode", KinematicSubmode.class, |
||||||
|
KinematicSubmode.Animated); |
||||||
|
prevBoneTransforms = RagUtils.readTransformArray(ic, |
||||||
|
"prevBoneTransforms"); |
||||||
|
startBoneTransforms = RagUtils.readTransformArray(ic, |
||||||
|
"startBoneTransforms"); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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) |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void setDynamic(Vector3f uniformAcceleration) { |
||||||
|
getControl().verifyReadyForDynamicMode("put link into dynamic mode"); |
||||||
|
|
||||||
|
super.setDynamic(uniformAcceleration); |
||||||
|
|
||||||
|
String name = boneName(); |
||||||
|
RangeOfMotion preset = getControl().getJointLimits(name); |
||||||
|
preset.setupJoint((SixDofJoint) getJoint()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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; |
||||||
|
|
||||||
|
if (prevBoneTransforms == null) { |
||||||
|
/* |
||||||
|
* On the first update, allocate and initialize |
||||||
|
* the array of previous bone transforms, if it wasn't |
||||||
|
* allocated in blendToKinematicMode(). |
||||||
|
*/ |
||||||
|
int numManagedBones = managedBones.length; |
||||||
|
prevBoneTransforms = new Transform[numManagedBones]; |
||||||
|
for (int mbIndex = 0; mbIndex < numManagedBones; ++mbIndex) { |
||||||
|
Joint managedBone = managedBones[mbIndex]; |
||||||
|
Transform boneTransform |
||||||
|
= managedBone.getLocalTransform().clone(); |
||||||
|
prevBoneTransforms[mbIndex] = boneTransform; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
super.update(tpf); |
||||||
|
/* |
||||||
|
* Save copies of the latest bone transforms. |
||||||
|
*/ |
||||||
|
for (int mbIndex = 0; mbIndex < managedBones.length; ++mbIndex) { |
||||||
|
Transform lastTransform = prevBoneTransforms[mbIndex]; |
||||||
|
Joint managedBone = managedBones[mbIndex]; |
||||||
|
lastTransform.set(managedBone.getLocalTransform()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Serialize this link, for example when saving to a J3O file. |
||||||
|
* |
||||||
|
* @param ex exporter (not null) |
||||||
|
* @throws IOException from exporter |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void write(JmeExporter ex) throws IOException { |
||||||
|
super.write(ex); |
||||||
|
OutputCapsule oc = ex.getCapsule(this); |
||||||
|
|
||||||
|
oc.write(managedBones, "managedBones", null); |
||||||
|
oc.write(submode, "submode", KinematicSubmode.Animated); |
||||||
|
oc.write(prevBoneTransforms, "prevBoneTransforms", new Transform[0]); |
||||||
|
oc.write(startBoneTransforms, "startBoneTransforms", new Transform[0]); |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// private methods
|
||||||
|
|
||||||
|
/** |
||||||
|
* 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(); |
||||||
|
Quaternion orientation = result.getRotation(); |
||||||
|
Vector3f scale = result.getScale(); |
||||||
|
/* |
||||||
|
* Start with the rigid body's transform in physics/world coordinates. |
||||||
|
*/ |
||||||
|
PhysicsRigidBody body = getRigidBody(); |
||||||
|
body.getPhysicsLocation(result.getTranslation()); |
||||||
|
body.getPhysicsRotation(result.getRotation()); |
||||||
|
result.setScale(body.getCollisionShape().getScale()); |
||||||
|
/* |
||||||
|
* Convert to mesh coordinates. |
||||||
|
*/ |
||||||
|
Transform worldToMesh = getControl().meshTransform(null).invert(); |
||||||
|
result.combineWithParent(worldToMesh); |
||||||
|
/* |
||||||
|
* Convert to the bone's local coordinate system by factoring out the |
||||||
|
* parent bone's transform. |
||||||
|
*/ |
||||||
|
Joint parentBone = getBone().getParent(); |
||||||
|
RagUtils.meshToLocal(parentBone, result); |
||||||
|
/* |
||||||
|
* Subtract the body's local offset, rotated and scaled. |
||||||
|
*/ |
||||||
|
Vector3f parentOffset = localOffset(null); |
||||||
|
parentOffset.multLocal(scale); |
||||||
|
orientation.mult(parentOffset, parentOffset); |
||||||
|
location.subtractLocal(parentOffset); |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,566 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2018-2019 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.Armature; |
||||||
|
import com.jme3.anim.Joint; |
||||||
|
import com.jme3.bullet.control.AbstractPhysicsControl; |
||||||
|
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.Vector3f; |
||||||
|
import com.jme3.renderer.RenderManager; |
||||||
|
import com.jme3.renderer.ViewPort; |
||||||
|
import com.jme3.scene.Spatial; |
||||||
|
import com.jme3.util.clone.Cloner; |
||||||
|
import java.io.IOException; |
||||||
|
import java.util.Collection; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
/** |
||||||
|
* Configure a DynamicAnimControl and access its configuration. |
||||||
|
* <p> |
||||||
|
* This class is shared between JBullet and Native Bullet. |
||||||
|
* |
||||||
|
* @author Stephen Gold sgold@sonic.net |
||||||
|
* |
||||||
|
* Based on KinematicRagdollControl by Normen Hansen and Rémy Bouquet (Nehon). |
||||||
|
*/ |
||||||
|
abstract public class DacConfiguration extends AbstractPhysicsControl { |
||||||
|
// *************************************************************************
|
||||||
|
// constants and loggers
|
||||||
|
|
||||||
|
/** |
||||||
|
* message logger for this class
|
||||||
|
*/ |
||||||
|
final public static Logger logger2 |
||||||
|
= Logger.getLogger(DacConfiguration.class.getName()); |
||||||
|
/** |
||||||
|
* name for the ragdoll's torso, must not be used for any bone |
||||||
|
*/ |
||||||
|
final public static String torsoName = ""; |
||||||
|
// *************************************************************************
|
||||||
|
// fields
|
||||||
|
|
||||||
|
/** |
||||||
|
* viscous damping ratio for new rigid bodies (0→no damping, |
||||||
|
* 1→critically damped, default=0.6) |
||||||
|
*/ |
||||||
|
private float damping = 0.6f; |
||||||
|
/** |
||||||
|
* minimum applied impulse for a collision event to be dispatched to |
||||||
|
* listeners (default=0) |
||||||
|
*/ |
||||||
|
private float eventDispatchImpulseThreshold = 0f; |
||||||
|
/** |
||||||
|
* mass for the torso |
||||||
|
*/ |
||||||
|
private float torsoMass = 1f; |
||||||
|
/** |
||||||
|
* map linked bone names to masses |
||||||
|
*/ |
||||||
|
private Map<String, Float> blConfigMap = new HashMap<>(50); |
||||||
|
/** |
||||||
|
* map linked bone names to ranges of motion for createSpatialData() |
||||||
|
*/ |
||||||
|
private Map<String, RangeOfMotion> jointMap = new HashMap<>(50); |
||||||
|
/** |
||||||
|
* gravitational acceleration vector for ragdolls (default is 9.8 in the -Y |
||||||
|
* direction, approximating Earth-normal in MKS units) |
||||||
|
*/ |
||||||
|
private Vector3f gravityVector = new Vector3f(0f, -9.8f, 0f); |
||||||
|
// *************************************************************************
|
||||||
|
// constructors
|
||||||
|
|
||||||
|
/** |
||||||
|
* Instantiate an enabled control without any attachments or linked bones |
||||||
|
* (torso only). |
||||||
|
*/ |
||||||
|
DacConfiguration() { |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// new methods exposed
|
||||||
|
|
||||||
|
/** |
||||||
|
* Count the linked bones. |
||||||
|
* |
||||||
|
* @return count (≥0) |
||||||
|
*/ |
||||||
|
public int countLinkedBones() { |
||||||
|
int count = blConfigMap.size(); |
||||||
|
|
||||||
|
assert count == jointMap.size(); |
||||||
|
assert count >= 0 : count; |
||||||
|
return count; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Count the links. |
||||||
|
* |
||||||
|
* @return count (≥0) |
||||||
|
*/ |
||||||
|
public int countLinks() { |
||||||
|
int result = countLinkedBones() + 1; |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Read the damping ratio for new rigid bodies. |
||||||
|
* |
||||||
|
* @return the viscous damping ratio (0→no damping, 1→critically |
||||||
|
* damped) |
||||||
|
*/ |
||||||
|
public float damping() { |
||||||
|
assert damping >= 0f : damping; |
||||||
|
return damping; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Read the event-dispatch impulse threshold of this control. |
||||||
|
* |
||||||
|
* @return the threshold value (≥0) |
||||||
|
*/ |
||||||
|
public float eventDispatchImpulseThreshold() { |
||||||
|
assert eventDispatchImpulseThreshold >= 0f; |
||||||
|
return eventDispatchImpulseThreshold; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Access the nominal range of motion for the joint connecting the named |
||||||
|
* linked bone to its parent in the hierarchy. |
||||||
|
* |
||||||
|
* @param boneName the name of the linked bone (not null, not empty) |
||||||
|
* @return the pre-existing instance (not null) |
||||||
|
*/ |
||||||
|
public RangeOfMotion getJointLimits(String boneName) { |
||||||
|
if (!hasBoneLink(boneName)) { |
||||||
|
String msg = "No linked bone named " + boneName; |
||||||
|
throw new IllegalArgumentException(msg); |
||||||
|
} |
||||||
|
RangeOfMotion result = jointMap.get(boneName); |
||||||
|
|
||||||
|
assert result != null; |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Copy this control's gravitational acceleration for Ragdoll mode. |
||||||
|
* |
||||||
|
* @param storeResult storage for the result (modified if not null) |
||||||
|
* @return an acceleration vector (in physics-space coordinates, either |
||||||
|
* storeResult or a new vector, not null) |
||||||
|
*/ |
||||||
|
public Vector3f gravity(Vector3f storeResult) { |
||||||
|
Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; |
||||||
|
result.set(gravityVector); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Test whether a BoneLink exists for the named bone. |
||||||
|
* |
||||||
|
* @param boneName the name of the bone (may be null) |
||||||
|
* @return true if found, otherwise false |
||||||
|
*/ |
||||||
|
public boolean hasBoneLink(String boneName) { |
||||||
|
boolean result; |
||||||
|
if (boneName == null) { |
||||||
|
result = false; |
||||||
|
} else { |
||||||
|
result = blConfigMap.containsKey(boneName); |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Link the named bone using the specified mass and range of motion. |
||||||
|
* <p> |
||||||
|
* Allowed only when the control is NOT added to a spatial. |
||||||
|
* |
||||||
|
* @param boneName the name of the bone to link (not null, not empty) |
||||||
|
* @param mass the desired mass of the bone (>0) |
||||||
|
* @param rom the desired range of motion (not null) |
||||||
|
* @see #setJointLimits(java.lang.String, |
||||||
|
* com.jme3.bullet.animation.RangeOfMotion) |
||||||
|
*/ |
||||||
|
public void link(String boneName, float mass, RangeOfMotion rom) { |
||||||
|
verifyNotAddedToSpatial("link a bone"); |
||||||
|
if (hasBoneLink(boneName)) { |
||||||
|
logger2.log(Level.WARNING, "Bone {0} is already linked.", boneName); |
||||||
|
} |
||||||
|
|
||||||
|
jointMap.put(boneName, rom); |
||||||
|
blConfigMap.put(boneName, mass); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Enumerate all bones with bone links. |
||||||
|
* |
||||||
|
* @return a new array of bone names (not null, may be empty) |
||||||
|
*/ |
||||||
|
public String[] listLinkedBoneNames() { |
||||||
|
int size = countLinkedBones(); |
||||||
|
String[] result = new String[size]; |
||||||
|
Collection<String> names = blConfigMap.keySet(); |
||||||
|
names.toArray(result); |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Read the mass of the named bone/torso. |
||||||
|
* |
||||||
|
* @param boneName the name of the bone/torso (not null) |
||||||
|
* @return the mass (in physics units, >0) |
||||||
|
*/ |
||||||
|
public float mass(String boneName) { |
||||||
|
float mass; |
||||||
|
if (torsoName.equals(boneName)) { |
||||||
|
mass = torsoMass; |
||||||
|
} else { |
||||||
|
mass = blConfigMap.get(boneName); |
||||||
|
} |
||||||
|
return mass; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Alter the viscous damping ratio for new rigid bodies. |
||||||
|
* |
||||||
|
* @param dampingRatio the desired damping ratio (non-negative, 0→no |
||||||
|
* damping, 1→critically damped, default=0.6) |
||||||
|
*/ |
||||||
|
public void setDamping(float dampingRatio) { |
||||||
|
damping = dampingRatio; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Alter the event-dispatch impulse threshold of this control. |
||||||
|
* |
||||||
|
* @param threshold the desired threshold (≥0) |
||||||
|
*/ |
||||||
|
public void setEventDispatchImpulseThreshold(float threshold) { |
||||||
|
eventDispatchImpulseThreshold = threshold; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Alter this control's gravitational acceleration for Ragdoll mode. |
||||||
|
* |
||||||
|
* @param gravity the desired acceleration vector (in physics-space |
||||||
|
* coordinates, not null, unaffected, default=0,-9.8,0) |
||||||
|
*/ |
||||||
|
public void setGravity(Vector3f gravity) { |
||||||
|
gravityVector.set(gravity); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Alter the range of motion of the joint connecting the named BoneLink to |
||||||
|
* its parent in the link hierarchy. |
||||||
|
* |
||||||
|
* @param boneName the name of the BoneLink (not null, not empty) |
||||||
|
* @param rom the desired range of motion (not null) |
||||||
|
*/ |
||||||
|
public void setJointLimits(String boneName, RangeOfMotion rom) { |
||||||
|
if (!hasBoneLink(boneName)) { |
||||||
|
String msg = "No linked bone named " + boneName; |
||||||
|
throw new IllegalArgumentException(msg); |
||||||
|
} |
||||||
|
|
||||||
|
jointMap.put(boneName, rom); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Alter the mass of the named bone/torso. |
||||||
|
* |
||||||
|
* @param boneName the name of the bone, or torsoName (not null) |
||||||
|
* @param mass the desired mass (>0) |
||||||
|
*/ |
||||||
|
public void setMass(String boneName, float mass) { |
||||||
|
if (torsoName.equals(boneName)) { |
||||||
|
torsoMass = mass; |
||||||
|
} else if (hasBoneLink(boneName)) { |
||||||
|
blConfigMap.put(boneName, mass); |
||||||
|
} else { |
||||||
|
String msg = "No bone/torso named " + boneName; |
||||||
|
throw new IllegalArgumentException(msg); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Calculate the ragdoll's total mass. |
||||||
|
* |
||||||
|
* @return the total mass (>0) |
||||||
|
*/ |
||||||
|
public float totalMass() { |
||||||
|
float totalMass = torsoMass; |
||||||
|
for (float mass : blConfigMap.values()) { |
||||||
|
totalMass += mass; |
||||||
|
} |
||||||
|
|
||||||
|
return totalMass; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Unlink the BoneLink of the named bone. |
||||||
|
* <p> |
||||||
|
* Allowed only when the control is NOT added to a spatial. |
||||||
|
* |
||||||
|
* @param boneName the name of the bone to unlink (not null, not empty) |
||||||
|
*/ |
||||||
|
public void unlinkBone(String boneName) { |
||||||
|
if (!hasBoneLink(boneName)) { |
||||||
|
String msg = "No linked bone named " + boneName; |
||||||
|
throw new IllegalArgumentException(msg); |
||||||
|
} |
||||||
|
verifyNotAddedToSpatial("unlink a bone"); |
||||||
|
|
||||||
|
jointMap.remove(boneName); |
||||||
|
blConfigMap.remove(boneName); |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// new protected methods
|
||||||
|
|
||||||
|
/** |
||||||
|
* Add unlinked descendants of the specified bone to the specified |
||||||
|
* collection. Note: recursive. |
||||||
|
* |
||||||
|
* @param startBone the starting bone (not null, unaffected) |
||||||
|
* @param addResult the collection of bone names to append to (not null, |
||||||
|
* modified) |
||||||
|
*/ |
||||||
|
protected void addUnlinkedDescendants(Joint startBone, |
||||||
|
Collection<Joint> addResult) { |
||||||
|
for (Joint childBone : startBone.getChildren()) { |
||||||
|
String childName = childBone.getName(); |
||||||
|
if (!hasBoneLink(childName)) { |
||||||
|
addResult.add(childBone); |
||||||
|
addUnlinkedDescendants(childBone, addResult); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Find the manager of the specified bone. |
||||||
|
* |
||||||
|
* @param startBone the bone (not null, unaffected) |
||||||
|
* @return a bone/torso name (not null) |
||||||
|
*/ |
||||||
|
protected String findManager(Joint startBone) { |
||||||
|
String managerName; |
||||||
|
Joint bone = startBone; |
||||||
|
while (true) { |
||||||
|
String boneName = bone.getName(); |
||||||
|
if (hasBoneLink(boneName)) { |
||||||
|
managerName = boneName; |
||||||
|
break; |
||||||
|
} |
||||||
|
bone = bone.getParent(); |
||||||
|
if (bone == null) { |
||||||
|
managerName = torsoName; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
assert managerName != null; |
||||||
|
return managerName; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a map from bone indices to the names of the bones that manage |
||||||
|
* them. |
||||||
|
* |
||||||
|
* @param skeleton (not null, unaffected) |
||||||
|
* @return a new array of bone/torso names (not null) |
||||||
|
*/ |
||||||
|
protected String[] managerMap(Armature skeleton) { |
||||||
|
int numBones = skeleton.getJointCount(); |
||||||
|
String[] managerMap = new String[numBones]; |
||||||
|
for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { |
||||||
|
Joint bone = skeleton.getJoint(boneIndex); |
||||||
|
managerMap[boneIndex] = findManager(bone); |
||||||
|
} |
||||||
|
|
||||||
|
return managerMap; |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// AbstractPhysicsControl methods
|
||||||
|
|
||||||
|
/** |
||||||
|
* Callback from {@link com.jme3.util.clone.Cloner} to convert this |
||||||
|
* shallow-cloned control into a deep-cloned one, using the specified cloner |
||||||
|
* and original to resolve copied fields. |
||||||
|
* |
||||||
|
* @param cloner the cloner that's cloning this control (not null, modified) |
||||||
|
* @param original the control from which this control was shallow-cloned |
||||||
|
* (not null, unaffected) |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void cloneFields(Cloner cloner, Object original) { |
||||||
|
super.cloneFields(cloner, original); |
||||||
|
|
||||||
|
blConfigMap = cloner.clone(blConfigMap); |
||||||
|
jointMap = cloner.clone(jointMap); |
||||||
|
gravityVector = cloner.clone(gravityVector); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a shallow clone for the JME cloner. |
||||||
|
* |
||||||
|
* @return a new instance |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public DacConfiguration jmeClone() { |
||||||
|
try { |
||||||
|
DacConfiguration clone |
||||||
|
= (DacConfiguration) super.clone(); |
||||||
|
return clone; |
||||||
|
} catch (CloneNotSupportedException exception) { |
||||||
|
throw new RuntimeException(exception); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* De-serialize this control, for example when loading from a J3O file. |
||||||
|
* |
||||||
|
* @param im importer (not null) |
||||||
|
* @throws IOException from importer |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void read(JmeImporter im) throws IOException { |
||||||
|
super.read(im); |
||||||
|
InputCapsule ic = im.getCapsule(this); |
||||||
|
|
||||||
|
damping = ic.readFloat("damping", 0.6f); |
||||||
|
eventDispatchImpulseThreshold |
||||||
|
= ic.readFloat("eventDispatchImpulseThreshold", 0f); |
||||||
|
|
||||||
|
jointMap.clear(); |
||||||
|
blConfigMap.clear(); |
||||||
|
String[] linkedBoneNames = ic.readStringArray("linkedBoneNames", null); |
||||||
|
Savable[] linkedBoneJoints |
||||||
|
= ic.readSavableArray("linkedBoneJoints", null); |
||||||
|
float[] blConfigs = ic.readFloatArray("blConfigs", null); |
||||||
|
for (int i = 0; i < linkedBoneNames.length; ++i) { |
||||||
|
String boneName = linkedBoneNames[i]; |
||||||
|
RangeOfMotion rom = (RangeOfMotion) linkedBoneJoints[i]; |
||||||
|
jointMap.put(boneName, rom); |
||||||
|
blConfigMap.put(boneName, blConfigs[i]); |
||||||
|
} |
||||||
|
|
||||||
|
torsoMass = ic.readFloat("torsoMass", 1f); |
||||||
|
gravityVector = (Vector3f) ic.readSavable("gravity", null); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Render this control. Invoked once per view port per frame, provided the |
||||||
|
* control is added to a scene. Should be invoked only by a subclass or by |
||||||
|
* the RenderManager. |
||||||
|
* |
||||||
|
* @param rm the render manager (not null) |
||||||
|
* @param vp the view port to render (not null) |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void render(RenderManager rm, ViewPort vp) { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Alter whether physics-space coordinates should match the spatial's local |
||||||
|
* coordinates. |
||||||
|
* |
||||||
|
* @param applyPhysicsLocal true→match local coordinates, |
||||||
|
* false→match world coordinates (default=false) |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void setApplyPhysicsLocal(boolean applyPhysicsLocal) { |
||||||
|
if (applyPhysicsLocal) { |
||||||
|
throw new UnsupportedOperationException( |
||||||
|
"DynamicAnimControl does not support local physics."); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Serialize this control, for example when saving to a J3O file. |
||||||
|
* |
||||||
|
* @param ex exporter (not null) |
||||||
|
* @throws IOException from exporter |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void write(JmeExporter ex) throws IOException { |
||||||
|
super.write(ex); |
||||||
|
OutputCapsule oc = ex.getCapsule(this); |
||||||
|
|
||||||
|
oc.write(damping, "damping", 0.6f); |
||||||
|
oc.write(eventDispatchImpulseThreshold, "eventDispatchImpulseThreshold", |
||||||
|
0f); |
||||||
|
|
||||||
|
int count = countLinkedBones(); |
||||||
|
String[] linkedBoneNames = new String[count]; |
||||||
|
RangeOfMotion[] roms = new RangeOfMotion[count]; |
||||||
|
float[] blConfigs = new float[count]; |
||||||
|
int i = 0; |
||||||
|
for (Map.Entry<String, Float> entry : blConfigMap.entrySet()) { |
||||||
|
linkedBoneNames[i] = entry.getKey(); |
||||||
|
roms[i] = jointMap.get(entry.getKey()); |
||||||
|
blConfigs[i] = entry.getValue(); |
||||||
|
++i; |
||||||
|
} |
||||||
|
oc.write(linkedBoneNames, "linkedBoneNames", null); |
||||||
|
oc.write(roms, "linkedBoneJoints", null); |
||||||
|
oc.write(blConfigs, "blConfigs", null); |
||||||
|
|
||||||
|
oc.write(torsoMass, "torsoMass", 1f); |
||||||
|
oc.write(gravityVector, "gravity", null); |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// private methods
|
||||||
|
|
||||||
|
/** |
||||||
|
* Verify that this control is NOT added to a Spatial. |
||||||
|
* |
||||||
|
* @param desiredAction (not null, not empty) |
||||||
|
*/ |
||||||
|
private void verifyNotAddedToSpatial(String desiredAction) { |
||||||
|
assert desiredAction != null; |
||||||
|
|
||||||
|
Spatial controlledSpatial = getSpatial(); |
||||||
|
if (controlledSpatial != null) { |
||||||
|
String message = "Cannot " + desiredAction |
||||||
|
+ " while the Control is added to a Spatial."; |
||||||
|
throw new IllegalStateException(message); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,537 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2018-2019 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.bullet.PhysicsSpace; |
||||||
|
import com.jme3.bullet.collision.PhysicsCollisionEvent; |
||||||
|
import com.jme3.bullet.collision.PhysicsCollisionListener; |
||||||
|
import com.jme3.bullet.collision.PhysicsCollisionObject; |
||||||
|
import com.jme3.bullet.objects.PhysicsRigidBody; |
||||||
|
import com.jme3.export.InputCapsule; |
||||||
|
import com.jme3.export.JmeExporter; |
||||||
|
import com.jme3.export.JmeImporter; |
||||||
|
import com.jme3.export.OutputCapsule; |
||||||
|
import com.jme3.math.Transform; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.util.SafeArrayList; |
||||||
|
import com.jme3.util.clone.Cloner; |
||||||
|
import java.io.IOException; |
||||||
|
import java.util.List; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
/** |
||||||
|
* Before adding this control to a spatial, configure it by invoking |
||||||
|
* {@link #link(java.lang.String, float, com.jme3.bullet.animation.RangeOfMotion)} |
||||||
|
* for each bone that should have its own rigid body. Leave unlinked bones near |
||||||
|
* the root of the skeleton to form the torso of the ragdoll. |
||||||
|
* <p> |
||||||
|
* When you add the control to a spatial, it generates a ragdoll consisting of a |
||||||
|
* rigid body for the torso and another for each linked bone. It also creates a |
||||||
|
* SixDofJoint connecting each rigid body to its parent in the link hierarchy. |
||||||
|
* The mass of each rigid body and the range-of-motion of each joint can be |
||||||
|
* reconfigured on the fly. |
||||||
|
* <p> |
||||||
|
* Each link is either dynamic (driven by forces and torques) or kinematic |
||||||
|
* (unperturbed by forces and torques). Transitions from dynamic to kinematic |
||||||
|
* can be immediate or gradual. |
||||||
|
* <p> |
||||||
|
* This class is shared between JBullet and Native Bullet. |
||||||
|
* |
||||||
|
* @author Stephen Gold sgold@sonic.net |
||||||
|
* |
||||||
|
* Based on KinematicRagdollControl by Normen Hansen and Rémy Bouquet (Nehon). |
||||||
|
*/ |
||||||
|
public class DynamicAnimControl |
||||||
|
extends DacLinks |
||||||
|
implements PhysicsCollisionListener { |
||||||
|
// *************************************************************************
|
||||||
|
// constants and loggers
|
||||||
|
|
||||||
|
/** |
||||||
|
* message logger for this class
|
||||||
|
*/ |
||||||
|
final public static Logger logger35 |
||||||
|
= Logger.getLogger(DynamicAnimControl.class.getName()); |
||||||
|
// *************************************************************************
|
||||||
|
// fields
|
||||||
|
|
||||||
|
/** |
||||||
|
* calculated total mass, not including released attachments |
||||||
|
*/ |
||||||
|
private float ragdollMass = 0f; |
||||||
|
/** |
||||||
|
* list of registered collision listeners |
||||||
|
*/ |
||||||
|
private List<RagdollCollisionListener> collisionListeners |
||||||
|
= new SafeArrayList<>(RagdollCollisionListener.class); |
||||||
|
/* |
||||||
|
* center-of-mass actual location (in physics-space coordinates) |
||||||
|
*/ |
||||||
|
private Vector3f centerLocation = new Vector3f(); |
||||||
|
/* |
||||||
|
* center-of-mass estimated velocity (psu/second in physics-space coordinates) |
||||||
|
*/ |
||||||
|
private Vector3f centerVelocity = new Vector3f(); |
||||||
|
// *************************************************************************
|
||||||
|
// constructors
|
||||||
|
|
||||||
|
/** |
||||||
|
* Instantiate an enabled control without any linked bones or attachments |
||||||
|
* (torso only). |
||||||
|
*/ |
||||||
|
public DynamicAnimControl() { |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// new methods exposed
|
||||||
|
|
||||||
|
/** |
||||||
|
* Add a collision listener to this control. |
||||||
|
* |
||||||
|
* @param listener (not null, alias created) |
||||||
|
*/ |
||||||
|
public void addCollisionListener(RagdollCollisionListener listener) { |
||||||
|
collisionListeners.add(listener); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Begin blending the specified link and all its descendants to kinematic |
||||||
|
* animation. |
||||||
|
* |
||||||
|
* @param rootLink the root of the subtree to bind (not null) |
||||||
|
* @param blendInterval the duration of the blend interval (in seconds, |
||||||
|
* ≥0) |
||||||
|
*/ |
||||||
|
public void animateSubtree(PhysicsLink rootLink, float blendInterval) { |
||||||
|
verifyAddedToSpatial("change modes"); |
||||||
|
blendSubtree(rootLink, KinematicSubmode.Animated, blendInterval); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Begin blending all links to purely kinematic mode, driven by animation. |
||||||
|
* TODO callback when the transition completes |
||||||
|
* <p> |
||||||
|
* Allowed only when the control IS added to a spatial. |
||||||
|
* |
||||||
|
* @param blendInterval the duration of the blend interval (in seconds, |
||||||
|
* ≥0) |
||||||
|
* @param endModelTransform the desired local transform for the controlled |
||||||
|
* spatial when the transition completes or null for no change to local |
||||||
|
* transform (unaffected) |
||||||
|
*/ |
||||||
|
public void blendToKinematicMode(float blendInterval, |
||||||
|
Transform endModelTransform) { |
||||||
|
verifyAddedToSpatial("change modes"); |
||||||
|
|
||||||
|
getTorsoLink().blendToKinematicMode(KinematicSubmode.Animated, blendInterval, |
||||||
|
endModelTransform); |
||||||
|
for (BoneLink boneLink : getBoneLinks()) { |
||||||
|
boneLink.blendToKinematicMode(KinematicSubmode.Animated, |
||||||
|
blendInterval); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Calculate the ragdoll's total mass and center of mass, excluding released |
||||||
|
* attachments. |
||||||
|
* <p> |
||||||
|
* Allowed only when the control IS added to a spatial. |
||||||
|
* |
||||||
|
* @param storeLocation storage for the location of the center (in |
||||||
|
* physics-space coordinates, modified if not null) |
||||||
|
* @param storeVelocity storage for the velocity of the center (psu/second |
||||||
|
* in physics-space coordinates, modified if not null) |
||||||
|
* @return the total mass (>0) |
||||||
|
*/ |
||||||
|
public float centerOfMass(Vector3f storeLocation, Vector3f storeVelocity) { |
||||||
|
verifyReadyForDynamicMode("calculate the center of mass"); |
||||||
|
|
||||||
|
recalculateCenter(); |
||||||
|
if (storeLocation != null) { |
||||||
|
storeLocation.set(centerLocation); |
||||||
|
} |
||||||
|
if (storeVelocity != null) { |
||||||
|
storeVelocity.set(centerVelocity); |
||||||
|
} |
||||||
|
|
||||||
|
return ragdollMass; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Alter the contact-response setting of the specified link and all its |
||||||
|
* descendants (excluding released attachments). Note: recursive! |
||||||
|
* <p> |
||||||
|
* Allowed only when the control IS added to a spatial. |
||||||
|
* |
||||||
|
* @param rootLink the root of the subtree to modify (not null) |
||||||
|
* @param desiredResponse true for the usual rigid-body response, false for |
||||||
|
* ghost-like response |
||||||
|
*/ |
||||||
|
public void setContactResponseSubtree(PhysicsLink rootLink, |
||||||
|
boolean desiredResponse) { |
||||||
|
verifyAddedToSpatial("change modes"); |
||||||
|
|
||||||
|
if (!rootLink.isReleased()) { |
||||||
|
PhysicsRigidBody rigidBody = rootLink.getRigidBody(); |
||||||
|
rigidBody.setContactResponse(desiredResponse); |
||||||
|
|
||||||
|
PhysicsLink[] children = rootLink.listChildren(); |
||||||
|
for (PhysicsLink child : children) { |
||||||
|
setContactResponseSubtree(child, desiredResponse); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Immediately put the specified link and all its ancestors (excluding the |
||||||
|
* torso) into dynamic mode. Note: recursive! |
||||||
|
* <p> |
||||||
|
* Allowed only when the control IS added to a spatial. |
||||||
|
* |
||||||
|
* @param startLink the start of the chain to modify (not null) |
||||||
|
* @param chainLength the maximum number of links to modify (≥0) |
||||||
|
* @param uniformAcceleration the uniform acceleration vector (in |
||||||
|
* physics-space coordinates, not null, unaffected) |
||||||
|
*/ |
||||||
|
public void setDynamicChain(PhysicsLink startLink, int chainLength, |
||||||
|
Vector3f uniformAcceleration) { |
||||||
|
if (chainLength == 0) { |
||||||
|
return; |
||||||
|
} |
||||||
|
verifyAddedToSpatial("change modes"); |
||||||
|
|
||||||
|
if (startLink instanceof BoneLink) { |
||||||
|
BoneLink boneLink = (BoneLink) startLink; |
||||||
|
boneLink.setDynamic(uniformAcceleration); |
||||||
|
} |
||||||
|
|
||||||
|
PhysicsLink parent = startLink.getParent(); |
||||||
|
if (parent != null && chainLength > 1) { |
||||||
|
setDynamicChain(parent, chainLength - 1, uniformAcceleration); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Immediately put the specified link and all its descendants (excluding |
||||||
|
* released attachments) into dynamic mode. Note: recursive! |
||||||
|
* <p> |
||||||
|
* Allowed only when the control IS added to a spatial. |
||||||
|
* |
||||||
|
* @param rootLink the root of the subtree to modify (not null) |
||||||
|
* @param uniformAcceleration the uniform acceleration vector (in |
||||||
|
* physics-space coordinates, not null, unaffected) |
||||||
|
*/ |
||||||
|
public void setDynamicSubtree(PhysicsLink rootLink, |
||||||
|
Vector3f uniformAcceleration) { |
||||||
|
verifyAddedToSpatial("change modes"); |
||||||
|
|
||||||
|
if (rootLink == getTorsoLink()) { |
||||||
|
getTorsoLink().setDynamic(uniformAcceleration); |
||||||
|
} else if (rootLink instanceof BoneLink) { |
||||||
|
BoneLink boneLink = (BoneLink) rootLink; |
||||||
|
boneLink.setDynamic(uniformAcceleration); |
||||||
|
} |
||||||
|
|
||||||
|
PhysicsLink[] children = rootLink.listChildren(); |
||||||
|
for (PhysicsLink child : children) { |
||||||
|
setDynamicSubtree(child, uniformAcceleration); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Immediately put all links into purely kinematic mode. |
||||||
|
* <p> |
||||||
|
* Allowed only when the control IS added to a spatial. |
||||||
|
*/ |
||||||
|
public void setKinematicMode() { |
||||||
|
verifyAddedToSpatial("set kinematic mode"); |
||||||
|
|
||||||
|
Transform localTransform = getSpatial().getLocalTransform(); |
||||||
|
blendToKinematicMode(0f, localTransform); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Immediately put this control into ragdoll mode. |
||||||
|
* <p> |
||||||
|
* Allowed only when the control IS added to a spatial and all links are |
||||||
|
* "ready". |
||||||
|
*/ |
||||||
|
public void setRagdollMode() { |
||||||
|
verifyReadyForDynamicMode("set ragdoll mode"); |
||||||
|
|
||||||
|
TorsoLink torsoLink = getTorsoLink(); |
||||||
|
Vector3f acceleration = gravity(null); |
||||||
|
torsoLink.setDynamic(acceleration); |
||||||
|
for (BoneLink boneLink : getBoneLinks()) { |
||||||
|
boneLink.setDynamic(acceleration); |
||||||
|
} |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// DacPhysicsLinks methods
|
||||||
|
|
||||||
|
/** |
||||||
|
* Add all managed physics objects to the PhysicsSpace. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
protected void addPhysics(PhysicsSpace space) { |
||||||
|
super.addPhysics(space); |
||||||
|
|
||||||
|
space.addCollisionListener(this); |
||||||
|
space.addTickListener(this); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Callback from {@link com.jme3.util.clone.Cloner} to convert this |
||||||
|
* shallow-cloned control into a deep-cloned one, using the specified cloner |
||||||
|
* and original to resolve copied fields. |
||||||
|
* |
||||||
|
* @param cloner the cloner that's cloning this control (not null, modified) |
||||||
|
* @param original the control from which this control was shallow-cloned |
||||||
|
* (not null, unaffected) |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void cloneFields(Cloner cloner, Object original) { |
||||||
|
super.cloneFields(cloner, original); |
||||||
|
|
||||||
|
collisionListeners = cloner.clone(collisionListeners); |
||||||
|
centerLocation = cloner.clone(centerLocation); |
||||||
|
centerVelocity = cloner.clone(centerVelocity); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a shallow clone for the JME cloner. |
||||||
|
* |
||||||
|
* @return a new instance |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public DynamicAnimControl jmeClone() { |
||||||
|
try { |
||||||
|
DynamicAnimControl clone = (DynamicAnimControl) super.clone(); |
||||||
|
return clone; |
||||||
|
} catch (CloneNotSupportedException exception) { |
||||||
|
throw new RuntimeException(exception); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* De-serialize this control, for example when loading from a J3O file. |
||||||
|
* |
||||||
|
* @param im the importer (not null) |
||||||
|
* @throws IOException from the importer |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public void read(JmeImporter im) throws IOException { |
||||||
|
super.read(im); |
||||||
|
InputCapsule ic = im.getCapsule(this); |
||||||
|
|
||||||
|
// isReady and collisionListeners not read
|
||||||
|
ragdollMass = ic.readFloat("ragdollMass", 1f); |
||||||
|
centerLocation |
||||||
|
= (Vector3f) ic.readSavable("centerLocation", new Vector3f()); |
||||||
|
centerVelocity |
||||||
|
= (Vector3f) ic.readSavable("centerVelocity", new Vector3f()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Remove all managed physics objects from the PhysicsSpace. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
protected void removePhysics(PhysicsSpace space) { |
||||||
|
super.removePhysics(space); |
||||||
|
|
||||||
|
space.removeCollisionListener(this); |
||||||
|
space.removeTickListener(this); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Serialize this control, for example when saving to a J3O file. |
||||||
|
* |
||||||
|
* @param ex the exporter (not null) |
||||||
|
* @throws IOException from the exporter |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void write(JmeExporter ex) throws IOException { |
||||||
|
super.write(ex); |
||||||
|
OutputCapsule oc = ex.getCapsule(this); |
||||||
|
|
||||||
|
// isReady and collisionListeners not written
|
||||||
|
oc.write(ragdollMass, "ragdollMass", 1f); |
||||||
|
oc.write(centerLocation, "centerLocation", null); |
||||||
|
oc.write(centerVelocity, "centerVelocity", null); |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// PhysicsCollisionListener methods
|
||||||
|
|
||||||
|
/** |
||||||
|
* For internal use only: callback for collision events. |
||||||
|
* |
||||||
|
* @param event (not null) |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void collision(PhysicsCollisionEvent event) { |
||||||
|
if (event.getNodeA() == null && event.getNodeB() == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
/* |
||||||
|
* Determine which bone was involved (if any) and also the |
||||||
|
* other collision object involved. |
||||||
|
*/ |
||||||
|
boolean isThisControlInvolved = false; |
||||||
|
PhysicsLink physicsLink = null; |
||||||
|
PhysicsCollisionObject otherPco = null; |
||||||
|
PhysicsCollisionObject pcoA = event.getObjectA(); |
||||||
|
PhysicsCollisionObject pcoB = event.getObjectB(); |
||||||
|
|
||||||
|
Object userA = pcoA.getUserObject(); |
||||||
|
Object userB = pcoB.getUserObject(); |
||||||
|
if (userA instanceof PhysicsLink) { |
||||||
|
physicsLink = (PhysicsLink) userA; |
||||||
|
DacLinks control = physicsLink.getControl(); |
||||||
|
if (control == this) { |
||||||
|
isThisControlInvolved = true; |
||||||
|
} |
||||||
|
otherPco = pcoB; |
||||||
|
} |
||||||
|
if (userB instanceof PhysicsLink) { |
||||||
|
physicsLink = (PhysicsLink) userB; |
||||||
|
DacLinks control = physicsLink.getControl(); |
||||||
|
if (control == this) { |
||||||
|
isThisControlInvolved = true; |
||||||
|
} |
||||||
|
otherPco = pcoA; |
||||||
|
} |
||||||
|
/* |
||||||
|
* Discard collisions that don't involve this control. |
||||||
|
*/ |
||||||
|
if (!isThisControlInvolved) { |
||||||
|
return; |
||||||
|
} |
||||||
|
/* |
||||||
|
* Discard low-impulse collisions. |
||||||
|
*/ |
||||||
|
float impulseThreshold = eventDispatchImpulseThreshold(); |
||||||
|
if (event.getAppliedImpulse() < impulseThreshold) { |
||||||
|
return; |
||||||
|
} |
||||||
|
/* |
||||||
|
* Dispatch an event. |
||||||
|
*/ |
||||||
|
for (RagdollCollisionListener listener : collisionListeners) { |
||||||
|
listener.collide(physicsLink, otherPco, event); |
||||||
|
} |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// private methods
|
||||||
|
|
||||||
|
/** |
||||||
|
* Begin blending the descendents of the specified link to the specified |
||||||
|
* kinematic submode. Note: recursive! |
||||||
|
* |
||||||
|
* @param rootLink the root of the subtree to blend (not null) |
||||||
|
* @param submode an enum value (not null) |
||||||
|
* @param blendInterval the duration of the blend interval (in seconds, |
||||||
|
* ≥0) |
||||||
|
*/ |
||||||
|
private void blendDescendants(PhysicsLink rootLink, |
||||||
|
KinematicSubmode submode, float blendInterval) { |
||||||
|
assert rootLink != null; |
||||||
|
assert submode != null; |
||||||
|
assert blendInterval >= 0f : blendInterval; |
||||||
|
|
||||||
|
PhysicsLink[] children = rootLink.listChildren(); |
||||||
|
for (PhysicsLink child : children) { |
||||||
|
if (child instanceof BoneLink) { |
||||||
|
BoneLink boneLink = (BoneLink) child; |
||||||
|
boneLink.blendToKinematicMode(submode, blendInterval); |
||||||
|
} |
||||||
|
blendDescendants(child, submode, blendInterval); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Begin blending the specified link and all its descendants to the |
||||||
|
* specified kinematic submode. |
||||||
|
* |
||||||
|
* @param rootLink the root of the subtree to blend (not null) |
||||||
|
* @param submode the desired submode (not null) |
||||||
|
* @param blendInterval the duration of the blend interval (in seconds, |
||||||
|
* ≥0) |
||||||
|
*/ |
||||||
|
private void blendSubtree(PhysicsLink rootLink, KinematicSubmode submode, |
||||||
|
float blendInterval) { |
||||||
|
assert rootLink != null; |
||||||
|
assert submode != null; |
||||||
|
assert blendInterval >= 0f : blendInterval; |
||||||
|
|
||||||
|
blendDescendants(rootLink, submode, blendInterval); |
||||||
|
|
||||||
|
if (rootLink == getTorsoLink()) { |
||||||
|
getTorsoLink().blendToKinematicMode(submode, blendInterval, null); |
||||||
|
} else if (rootLink instanceof BoneLink) { |
||||||
|
BoneLink boneLink = (BoneLink) rootLink; |
||||||
|
boneLink.blendToKinematicMode(submode, blendInterval); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Recalculate the total mass of the ragdoll, not including released |
||||||
|
* attachments. Also updates the location and estimated velocity of the |
||||||
|
* center of mass. |
||||||
|
*/ |
||||||
|
private void recalculateCenter() { |
||||||
|
double massSum = 0.0; |
||||||
|
Vector3f locationSum = new Vector3f(); |
||||||
|
Vector3f velocitySum = new Vector3f(); |
||||||
|
Vector3f tmpVector = new Vector3f(); |
||||||
|
List<PhysicsLink> links = listLinks(PhysicsLink.class); |
||||||
|
for (PhysicsLink link : links) { |
||||||
|
if (!link.isReleased()) { |
||||||
|
PhysicsRigidBody rigidBody = link.getRigidBody(); |
||||||
|
float mass = rigidBody.getMass(); |
||||||
|
massSum += mass; |
||||||
|
|
||||||
|
rigidBody.getPhysicsLocation(tmpVector); |
||||||
|
tmpVector.multLocal(mass); |
||||||
|
locationSum.addLocal(tmpVector); |
||||||
|
|
||||||
|
link.velocity(tmpVector); |
||||||
|
tmpVector.multLocal(mass); |
||||||
|
velocitySum.addLocal(tmpVector); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
float invMass = (float) (1.0 / massSum); |
||||||
|
locationSum.mult(invMass, centerLocation); |
||||||
|
velocitySum.mult(invMass, centerVelocity); |
||||||
|
ragdollMass = (float) massSum; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2018-2019 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; |
||||||
|
|
||||||
|
/** |
||||||
|
* Enumerate submodes for a link in kinematic mode. |
||||||
|
* <p> |
||||||
|
* This class is shared between JBullet and Native Bullet. |
||||||
|
* |
||||||
|
* @author Stephen Gold sgold@sonic.net |
||||||
|
*/ |
||||||
|
public enum KinematicSubmode { |
||||||
|
/** |
||||||
|
* driven by animation (if any) |
||||||
|
*/ |
||||||
|
Animated, |
||||||
|
/** |
||||||
|
* frozen in the transform it had when blending started |
||||||
|
*/ |
||||||
|
Frozen; |
||||||
|
} |
@ -0,0 +1,643 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2018-2019 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.bullet.collision.shapes.CollisionShape; |
||||||
|
import com.jme3.bullet.joints.PhysicsJoint; |
||||||
|
import com.jme3.bullet.objects.PhysicsRigidBody; |
||||||
|
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.Transform; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.util.clone.Cloner; |
||||||
|
import com.jme3.util.clone.JmeCloneable; |
||||||
|
import java.io.IOException; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
/** |
||||||
|
* The abstract base class used by DynamicAnimControl to link pieces of a JME |
||||||
|
* model to their corresponding collision objects in a ragdoll. Subclasses |
||||||
|
* include: AttachmentLink, BoneLink, and TorsoLink. The links in each |
||||||
|
* DynamicAnimControl form a hierarchy with the TorsoLink at its root. |
||||||
|
* <p> |
||||||
|
* This class is shared between JBullet and Native Bullet. |
||||||
|
* |
||||||
|
* @author Stephen Gold sgold@sonic.net |
||||||
|
* |
||||||
|
* Based on KinematicRagdollControl by Normen Hansen and Rémy Bouquet (Nehon). |
||||||
|
*/ |
||||||
|
abstract public class PhysicsLink |
||||||
|
implements JmeCloneable, Savable { |
||||||
|
// *************************************************************************
|
||||||
|
// constants and loggers
|
||||||
|
|
||||||
|
/** |
||||||
|
* message logger for this class
|
||||||
|
*/ |
||||||
|
final public static Logger logger |
||||||
|
= Logger.getLogger(PhysicsLink.class.getName()); |
||||||
|
// *************************************************************************
|
||||||
|
// fields
|
||||||
|
|
||||||
|
/** |
||||||
|
* immediate children in the link hierarchy (not null) |
||||||
|
*/ |
||||||
|
private ArrayList<PhysicsLink> children = new ArrayList<>(8); |
||||||
|
/** |
||||||
|
* corresponding bone in the skeleton (not null) |
||||||
|
*/ |
||||||
|
private Joint bone; |
||||||
|
/** |
||||||
|
* scene-graph control that manages this link (not null) |
||||||
|
*/ |
||||||
|
private DacLinks control; |
||||||
|
/** |
||||||
|
* duration of the most recent blend interval (in seconds, ≥0) |
||||||
|
*/ |
||||||
|
private float blendInterval = 1f; |
||||||
|
/** |
||||||
|
* weighting of kinematic movement (≥0, ≤1, 0=purely dynamic, 1=purely |
||||||
|
* kinematic, default=1, progresses from 0 to 1 during the blend interval) |
||||||
|
*/ |
||||||
|
private float kinematicWeight = 1f; |
||||||
|
/** |
||||||
|
* joint between the rigid body and the parent's rigid body, or null if not |
||||||
|
* yet created |
||||||
|
*/ |
||||||
|
private PhysicsJoint joint = null; |
||||||
|
/** |
||||||
|
* parent/manager in the link hierarchy, or null if none |
||||||
|
*/ |
||||||
|
private PhysicsLink parent = null; |
||||||
|
/** |
||||||
|
* linked rigid body in the ragdoll (not null) |
||||||
|
*/ |
||||||
|
private PhysicsRigidBody rigidBody; |
||||||
|
/** |
||||||
|
* transform of the rigid body as of the most recent update (in |
||||||
|
* physics-space coordinates, updated in kinematic mode only) |
||||||
|
*/ |
||||||
|
private Transform kpTransform = new Transform(); |
||||||
|
/** |
||||||
|
* estimate of the body's linear velocity as of the most recent update |
||||||
|
* (psu/sec in physics-space coordinates, kinematic mode only) |
||||||
|
*/ |
||||||
|
private Vector3f kpVelocity = new Vector3f(); |
||||||
|
/** |
||||||
|
* location of the rigid body's center (in the skeleton bone's local |
||||||
|
* coordinates) |
||||||
|
*/ |
||||||
|
private Vector3f localOffset; |
||||||
|
// *************************************************************************
|
||||||
|
// constructors
|
||||||
|
|
||||||
|
/** |
||||||
|
* No-argument constructor needed by SavableClassUtil. Do not invoke |
||||||
|
* directly! |
||||||
|
*/ |
||||||
|
public PhysicsLink() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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 corresponding bone (not null, alias created) |
||||||
|
* @param collisionShape the desired shape (not null, alias created) |
||||||
|
* @param mass the mass (in physics-mass units) |
||||||
|
* @param localOffset the location of the body's center (in the bone's local |
||||||
|
* coordinates, not null, unaffected) |
||||||
|
*/ |
||||||
|
PhysicsLink(DacLinks control, Joint bone, CollisionShape collisionShape, |
||||||
|
float mass, Vector3f localOffset) { |
||||||
|
assert control != null; |
||||||
|
assert bone != null; |
||||||
|
assert collisionShape != null; |
||||||
|
assert localOffset != null; |
||||||
|
|
||||||
|
this.control = control; |
||||||
|
this.bone = bone; |
||||||
|
rigidBody = createRigidBody(mass, collisionShape); |
||||||
|
|
||||||
|
logger.log(Level.FINE, "Creating link for bone {0} with mass={1}", |
||||||
|
new Object[]{bone.getName(), rigidBody.getMass()}); |
||||||
|
|
||||||
|
this.localOffset = localOffset.clone(); |
||||||
|
updateKPTransform(); |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// new methods exposed
|
||||||
|
|
||||||
|
/** |
||||||
|
* Read the name of the corresponding bone. |
||||||
|
* |
||||||
|
* @return the bone name (not null) |
||||||
|
*/ |
||||||
|
public String boneName() { |
||||||
|
String boneName = bone.getName(); |
||||||
|
|
||||||
|
assert boneName != null; |
||||||
|
return boneName; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Count this link's immediate children in the link hierarchy. |
||||||
|
* |
||||||
|
* @return the count (≥0) |
||||||
|
*/ |
||||||
|
public int countChildren() { |
||||||
|
int numChildren = children.size(); |
||||||
|
return numChildren; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Access the corresponding bone. |
||||||
|
* |
||||||
|
* @return the pre-existing instance (not null) |
||||||
|
*/ |
||||||
|
final public Joint getBone() { |
||||||
|
assert bone != null; |
||||||
|
return bone; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Access the control that manages this link. |
||||||
|
* |
||||||
|
* @return the pre-existing instance (not null) |
||||||
|
*/ |
||||||
|
public DacLinks getControl() { |
||||||
|
assert control != null; |
||||||
|
return control; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Access the joint between this link's rigid body and that of its parent. |
||||||
|
* |
||||||
|
* @return the pre-existing instance, or null for the torso |
||||||
|
*/ |
||||||
|
public PhysicsJoint getJoint() { |
||||||
|
return joint; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Access this link's parent/manager in the link hierarchy. |
||||||
|
* |
||||||
|
* @return the link, or null if none |
||||||
|
*/ |
||||||
|
public PhysicsLink getParent() { |
||||||
|
return parent; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Access the linked rigid body. |
||||||
|
* |
||||||
|
* @return the pre-existing instance (not null) |
||||||
|
*/ |
||||||
|
public PhysicsRigidBody getRigidBody() { |
||||||
|
assert rigidBody != null; |
||||||
|
return rigidBody; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Test whether the link is in kinematic mode. |
||||||
|
* |
||||||
|
* @return true if kinematic, or false if purely dynamic |
||||||
|
*/ |
||||||
|
public boolean isKinematic() { |
||||||
|
if (kinematicWeight > 0f) { |
||||||
|
return true; |
||||||
|
} else { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Test whether the attached model (if any) has been released. |
||||||
|
* |
||||||
|
* @return false unless this is an AttachmentLink |
||||||
|
*/ |
||||||
|
public boolean isReleased() { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Read the kinematic weight of this link. |
||||||
|
* |
||||||
|
* @return 0 if purely dynamic, 1 if purely kinematic |
||||||
|
*/ |
||||||
|
public float kinematicWeight() { |
||||||
|
assert kinematicWeight >= 0f : kinematicWeight; |
||||||
|
assert kinematicWeight <= 1f : kinematicWeight; |
||||||
|
return kinematicWeight; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Enumerate this link's immediate children in the link hierarchy. |
||||||
|
* |
||||||
|
* @return a new array (not null) |
||||||
|
*/ |
||||||
|
public PhysicsLink[] listChildren() { |
||||||
|
int numChildren = children.size(); |
||||||
|
PhysicsLink[] result = new PhysicsLink[numChildren]; |
||||||
|
children.toArray(result); |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Unambiguously identify this link by name, within its DynamicAnimControl. |
||||||
|
* |
||||||
|
* @return a text string (not null, not empty) |
||||||
|
*/ |
||||||
|
abstract public String name(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Calculate a physics transform for the rigid body (to match the skeleton |
||||||
|
* bone). |
||||||
|
* |
||||||
|
* @param storeResult storage for the result (modified if not null) |
||||||
|
* @return the calculated transform (in physics-space coordinates, either |
||||||
|
* storeResult or a new transform, not null) |
||||||
|
*/ |
||||||
|
public Transform physicsTransform(Transform storeResult) { |
||||||
|
Transform result |
||||||
|
= (storeResult == null) ? new Transform() : storeResult; |
||||||
|
if (isKinematic()) { |
||||||
|
result.set(kpTransform); |
||||||
|
} else { |
||||||
|
rigidBody.getPhysicsLocation(result.getTranslation()); |
||||||
|
rigidBody.getPhysicsRotation(result.getRotation()); |
||||||
|
result.setScale(rigidBody.getCollisionShape().getScale()); |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Copy animation data from the specified link, which must correspond to the |
||||||
|
* same bone. |
||||||
|
* |
||||||
|
* @param oldLink the link to copy from (not null, unaffected) |
||||||
|
*/ |
||||||
|
void postRebuild(PhysicsLink oldLink) { |
||||||
|
assert oldLink != null; |
||||||
|
assert oldLink.bone == bone; |
||||||
|
|
||||||
|
if (oldLink.isKinematic()) { |
||||||
|
blendInterval = oldLink.blendInterval; |
||||||
|
kinematicWeight = oldLink.kinematicWeight; |
||||||
|
} else { |
||||||
|
blendInterval = 0f; |
||||||
|
kinematicWeight = 1f; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Internal callback, invoked just AFTER the physics is stepped. |
||||||
|
*/ |
||||||
|
void postTick() { |
||||||
|
if (!isKinematic()) { |
||||||
|
rigidBody.activate(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Internal callback, invoked just BEFORE the physics is stepped. |
||||||
|
* |
||||||
|
* @param timeStep the physics time step (in seconds, ≥0) |
||||||
|
*/ |
||||||
|
void preTick(float timeStep) { |
||||||
|
if (isKinematic()) { |
||||||
|
rigidBody.setPhysicsLocation(kpTransform.getTranslation()); |
||||||
|
rigidBody.setPhysicsRotation(kpTransform.getRotation()); |
||||||
|
rigidBody.getCollisionShape().setScale(kpTransform.getScale()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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) |
||||||
|
*/ |
||||||
|
public void setDynamic(Vector3f uniformAcceleration) { |
||||||
|
control.verifyReadyForDynamicMode("put link into dynamic mode"); |
||||||
|
|
||||||
|
setKinematicWeight(0f); |
||||||
|
rigidBody.setGravity(uniformAcceleration); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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) |
||||||
|
*/ |
||||||
|
void update(float tpf) { |
||||||
|
assert tpf >= 0f : tpf; |
||||||
|
|
||||||
|
if (kinematicWeight > 0f) { |
||||||
|
kinematicUpdate(tpf); |
||||||
|
} else { |
||||||
|
dynamicUpdate(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Copy the body's linear velocity, or an estimate thereof. |
||||||
|
* |
||||||
|
* @param storeResult (modified if not null) |
||||||
|
* @return a new velocity vector (psu/sec in physics-space coordinates) |
||||||
|
*/ |
||||||
|
Vector3f velocity(Vector3f storeResult) { |
||||||
|
Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; |
||||||
|
if (isKinematic()) { |
||||||
|
result.set(kpVelocity); |
||||||
|
} else { |
||||||
|
assert !rigidBody.isKinematic(); |
||||||
|
rigidBody.getLinearVelocity(result); |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// new protected methods
|
||||||
|
|
||||||
|
/** |
||||||
|
* Begin blending this link to a purely kinematic mode. |
||||||
|
* |
||||||
|
* @param blendInterval the duration of the blend interval (in seconds, |
||||||
|
* ≥0) |
||||||
|
*/ |
||||||
|
protected void blendToKinematicMode(float blendInterval) { |
||||||
|
assert blendInterval >= 0f : blendInterval; |
||||||
|
|
||||||
|
this.blendInterval = blendInterval; |
||||||
|
setKinematicWeight(Float.MIN_VALUE); // non-zero to trigger blending
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Update this link in Dynamic mode, setting the linked bone's transform |
||||||
|
* based on the transform of the rigid body. |
||||||
|
*/ |
||||||
|
abstract protected void dynamicUpdate(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Update this link in blended Kinematic mode. |
||||||
|
* |
||||||
|
* @param tpf the time interval between frames (in seconds, ≥0) |
||||||
|
*/ |
||||||
|
protected void kinematicUpdate(float tpf) { |
||||||
|
assert tpf >= 0f : tpf; |
||||||
|
assert rigidBody.isKinematic(); |
||||||
|
/* |
||||||
|
* If blending, increase the kinematic weight. |
||||||
|
*/ |
||||||
|
if (blendInterval == 0f) { |
||||||
|
setKinematicWeight(1f); // done blending
|
||||||
|
} else { |
||||||
|
setKinematicWeight(kinematicWeight + tpf / blendInterval); |
||||||
|
} |
||||||
|
/* |
||||||
|
* If we didn't need kpVelocity, we could defer this |
||||||
|
* calculation until the preTick(). |
||||||
|
*/ |
||||||
|
Vector3f previousLocation = kpTransform.getTranslation(null); |
||||||
|
updateKPTransform(); |
||||||
|
if (tpf > 0f) { |
||||||
|
kpTransform.getTranslation().subtract(previousLocation, kpVelocity); |
||||||
|
kpVelocity.divideLocal(tpf); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Copy the local offset of this link. |
||||||
|
* |
||||||
|
* @param storeResult storage for the result (modified if not null) |
||||||
|
* @return the offset (in bone local coordinates, either storeResult or a |
||||||
|
* new vector, not null) |
||||||
|
*/ |
||||||
|
protected Vector3f localOffset(Vector3f storeResult) { |
||||||
|
Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; |
||||||
|
result.set(localOffset); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Assign a physics joint to this link, or cancel the assigned joint. |
||||||
|
* |
||||||
|
* @param joint (may be null, alias created) |
||||||
|
*/ |
||||||
|
final protected void setJoint(PhysicsJoint joint) { |
||||||
|
this.joint = joint; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Assign a parent/manager for this link. |
||||||
|
* |
||||||
|
* @param parent (not null, alias created) |
||||||
|
*/ |
||||||
|
final protected void setParent(PhysicsLink parent) { |
||||||
|
assert parent != null; |
||||||
|
assert this.parent == null; |
||||||
|
this.parent = parent; |
||||||
|
parent.children.add(this); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Alter the rigid body for this link. |
||||||
|
* |
||||||
|
* @param body the desired rigid body (not null, alias created) |
||||||
|
*/ |
||||||
|
protected void setRigidBody(PhysicsRigidBody body) { |
||||||
|
assert body != null; |
||||||
|
assert rigidBody != null; |
||||||
|
rigidBody = body; |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// JmeCloneable 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 |
||||||
|
* (unused) |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void cloneFields(Cloner cloner, Object original) { |
||||||
|
bone = cloner.clone(bone); |
||||||
|
control = cloner.clone(control); |
||||||
|
children = cloner.clone(children); |
||||||
|
joint = cloner.clone(joint); |
||||||
|
parent = cloner.clone(parent); |
||||||
|
rigidBody = cloner.clone(rigidBody); |
||||||
|
kpTransform = cloner.clone(kpTransform); |
||||||
|
kpVelocity = cloner.clone(kpVelocity); |
||||||
|
localOffset = cloner.clone(localOffset); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a shallow clone for the JME cloner. |
||||||
|
* |
||||||
|
* @return a new instance |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public PhysicsLink jmeClone() { |
||||||
|
try { |
||||||
|
PhysicsLink clone = (PhysicsLink) super.clone(); |
||||||
|
return clone; |
||||||
|
} catch (CloneNotSupportedException exception) { |
||||||
|
throw new RuntimeException(exception); |
||||||
|
} |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// Savable methods
|
||||||
|
|
||||||
|
/** |
||||||
|
* De-serialize this link, for example when loading from a J3O file. |
||||||
|
* |
||||||
|
* @param im importer (not null) |
||||||
|
* @throws IOException from importer |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public void read(JmeImporter im) throws IOException { |
||||||
|
InputCapsule ic = im.getCapsule(this); |
||||||
|
|
||||||
|
children = ic.readSavableArrayList("children", new ArrayList(1)); |
||||||
|
bone = (Joint) ic.readSavable("bone", null); |
||||||
|
control = (DacLinks) ic.readSavable("control", null); |
||||||
|
blendInterval = ic.readFloat("blendInterval", 1f); |
||||||
|
kinematicWeight = ic.readFloat("kinematicWeight", 1f); |
||||||
|
joint = (PhysicsJoint) ic.readSavable("joint", null); |
||||||
|
parent = (PhysicsLink) ic.readSavable("parent", null); |
||||||
|
rigidBody = (PhysicsRigidBody) ic.readSavable("rigidBody", null); |
||||||
|
kpTransform |
||||||
|
= (Transform) ic.readSavable("kpTransform", new Transform()); |
||||||
|
kpVelocity = (Vector3f) ic.readSavable("kpVelocity", new Vector3f()); |
||||||
|
localOffset = (Vector3f) ic.readSavable("offset", new Vector3f()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Serialize this link, for example when saving to a J3O file. |
||||||
|
* |
||||||
|
* @param ex exporter (not null) |
||||||
|
* @throws IOException from exporter |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void write(JmeExporter ex) throws IOException { |
||||||
|
OutputCapsule oc = ex.getCapsule(this); |
||||||
|
|
||||||
|
oc.writeSavableArrayList(children, "children", null); |
||||||
|
oc.write(bone, "bone", null); |
||||||
|
oc.write(control, "control", null); |
||||||
|
oc.write(blendInterval, "blendInterval", 1f); |
||||||
|
oc.write(kinematicWeight, "kinematicWeight", 1f); |
||||||
|
oc.write(joint, "joint", null); |
||||||
|
oc.write(parent, "parent", null); |
||||||
|
oc.write(rigidBody, "rigidBody", null); |
||||||
|
oc.write(kpTransform, "kpTransform", null); |
||||||
|
oc.write(kpVelocity, "kpVelocity", null); |
||||||
|
oc.write(localOffset, "offset", null); |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// private methods
|
||||||
|
|
||||||
|
/** |
||||||
|
* Create and configure a rigid body for this link. |
||||||
|
* |
||||||
|
* @param linkConfig the link configuration (not null) |
||||||
|
* @param collisionShape the desired shape (not null, alias created) |
||||||
|
* @return a new instance, not in any PhysicsSpace |
||||||
|
*/ |
||||||
|
private PhysicsRigidBody createRigidBody(float mass, |
||||||
|
CollisionShape collisionShape) { |
||||||
|
assert collisionShape != null; |
||||||
|
|
||||||
|
PhysicsRigidBody body = new PhysicsRigidBody(collisionShape, mass); |
||||||
|
|
||||||
|
float viscousDamping = control.damping(); |
||||||
|
body.setDamping(viscousDamping, viscousDamping); |
||||||
|
|
||||||
|
body.setKinematic(true); |
||||||
|
body.setUserObject(this); |
||||||
|
|
||||||
|
return body; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Alter the kinematic weight and copy the physics transform and velocity |
||||||
|
* info as needed. |
||||||
|
* |
||||||
|
* @param weight (≥0) |
||||||
|
*/ |
||||||
|
private void setKinematicWeight(float weight) { |
||||||
|
assert weight >= 0f : weight; |
||||||
|
|
||||||
|
boolean wasKinematic = (kinematicWeight > 0f); |
||||||
|
kinematicWeight = (weight > 1f) ? 1f : weight; |
||||||
|
boolean isKinematic = (kinematicWeight > 0f); |
||||||
|
|
||||||
|
if (wasKinematic && !isKinematic) { |
||||||
|
rigidBody.setKinematic(false); |
||||||
|
rigidBody.setPhysicsLocation(kpTransform.getTranslation()); |
||||||
|
rigidBody.setPhysicsRotation(kpTransform.getRotation()); |
||||||
|
rigidBody.getCollisionShape().setScale(kpTransform.getScale()); |
||||||
|
rigidBody.setLinearVelocity(kpVelocity); |
||||||
|
} else if (isKinematic && !wasKinematic) { |
||||||
|
rigidBody.getPhysicsLocation(kpTransform.getTranslation()); |
||||||
|
rigidBody.getPhysicsRotation(kpTransform.getRotation()); |
||||||
|
Vector3f scale = rigidBody.getCollisionShape().getScale(); |
||||||
|
kpTransform.setScale(scale); |
||||||
|
rigidBody.getLinearVelocity(kpVelocity); |
||||||
|
rigidBody.setKinematic(true); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Update the kinematic physics transform. |
||||||
|
*/ |
||||||
|
private void updateKPTransform() { |
||||||
|
control.physicsTransform(bone, localOffset, kpTransform); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,707 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2018-2019 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.Armature; |
||||||
|
import com.jme3.anim.Joint; |
||||||
|
import com.jme3.export.InputCapsule; |
||||||
|
import com.jme3.export.Savable; |
||||||
|
import com.jme3.math.FastMath; |
||||||
|
import com.jme3.math.Quaternion; |
||||||
|
import com.jme3.math.Transform; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.scene.Geometry; |
||||||
|
import com.jme3.scene.Mesh; |
||||||
|
import com.jme3.scene.Node; |
||||||
|
import com.jme3.scene.Spatial; |
||||||
|
import com.jme3.scene.VertexBuffer; |
||||||
|
import com.jme3.scene.control.Control; |
||||||
|
import java.io.IOException; |
||||||
|
import java.nio.Buffer; |
||||||
|
import java.nio.ByteBuffer; |
||||||
|
import java.nio.FloatBuffer; |
||||||
|
import java.nio.ShortBuffer; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.TreeSet; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
/** |
||||||
|
* Utility methods used by DynamicAnimControl and associated classes. |
||||||
|
* <p> |
||||||
|
* This class is shared between JBullet and Native Bullet. |
||||||
|
* |
||||||
|
* @author Stephen Gold sgold@sonic.net |
||||||
|
* |
||||||
|
* Based on KinematicRagdollControl by Normen Hansen and Rémy Bouquet (Nehon). |
||||||
|
*/ |
||||||
|
public class RagUtils { |
||||||
|
// *************************************************************************
|
||||||
|
// constants and loggers
|
||||||
|
|
||||||
|
/** |
||||||
|
* message logger for this class
|
||||||
|
*/ |
||||||
|
final private static Logger logger |
||||||
|
= Logger.getLogger(RagUtils.class.getName()); |
||||||
|
// *************************************************************************
|
||||||
|
// constructors
|
||||||
|
|
||||||
|
/** |
||||||
|
* A private constructor to inhibit instantiation of this class. |
||||||
|
*/ |
||||||
|
private RagUtils() { |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// new methods exposed
|
||||||
|
|
||||||
|
/** |
||||||
|
* Assign each mesh vertex to a bone/torso link and add its location (mesh |
||||||
|
* coordinates in bind pose) to that link's list. |
||||||
|
* |
||||||
|
* @param meshes array of animated meshes to use (not null, unaffected) |
||||||
|
* @param managerMap a map from bone indices to managing link names (not |
||||||
|
* null, unaffected) |
||||||
|
* @return a new map from bone/torso names to sets of vertex coordinates |
||||||
|
*/ |
||||||
|
static Map<String, VectorSet> coordsMap(Mesh[] meshes, |
||||||
|
String[] managerMap) { |
||||||
|
float[] wArray = new float[4]; |
||||||
|
int[] iArray = new int[4]; |
||||||
|
Vector3f bindPosition = new Vector3f(); |
||||||
|
Map<String, VectorSet> coordsMap = new HashMap<>(32); |
||||||
|
for (Mesh mesh : meshes) { |
||||||
|
int numVertices = mesh.getVertexCount(); |
||||||
|
for (int vertexI = 0; vertexI < numVertices; ++vertexI) { |
||||||
|
String managerName = findManager(mesh, vertexI, iArray, wArray, |
||||||
|
managerMap); |
||||||
|
VectorSet set = coordsMap.get(managerName); |
||||||
|
if (set == null) { |
||||||
|
set = new VectorSet(1); |
||||||
|
coordsMap.put(managerName, set); |
||||||
|
} |
||||||
|
vertexVector3f(mesh, VertexBuffer.Type.BindPosePosition, |
||||||
|
vertexI, bindPosition); |
||||||
|
set.add(bindPosition); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return coordsMap; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Find an animated geometry in the specified subtree of the scene graph. |
||||||
|
* Note: recursive! |
||||||
|
* |
||||||
|
* @param subtree where to search (not null, unaffected) |
||||||
|
* @return a pre-existing instance, or null if none |
||||||
|
*/ |
||||||
|
static Geometry findAnimatedGeometry(Spatial subtree) { |
||||||
|
Geometry result = null; |
||||||
|
if (subtree instanceof Geometry) { |
||||||
|
Geometry geometry = (Geometry) subtree; |
||||||
|
Mesh mesh = geometry.getMesh(); |
||||||
|
VertexBuffer indices = mesh.getBuffer(VertexBuffer.Type.BoneIndex); |
||||||
|
boolean hasIndices = indices != null; |
||||||
|
VertexBuffer weights = mesh.getBuffer(VertexBuffer.Type.BoneWeight); |
||||||
|
boolean hasWeights = weights != null; |
||||||
|
if (hasIndices && hasWeights) { |
||||||
|
result = geometry; |
||||||
|
} |
||||||
|
|
||||||
|
} else if (subtree instanceof Node) { |
||||||
|
Node node = (Node) subtree; |
||||||
|
List<Spatial> children = node.getChildren(); |
||||||
|
for (Spatial child : children) { |
||||||
|
result = findAnimatedGeometry(child); |
||||||
|
if (result != null) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Find the index of the specified scene-graph control in the specified |
||||||
|
* spatial. |
||||||
|
* |
||||||
|
* @param spatial the spatial to search (not null, unaffected) |
||||||
|
* @param sgc the control to search for (not null, unaffected) |
||||||
|
* @return the index (≥0) or -1 if not found |
||||||
|
*/ |
||||||
|
static int findIndex(Spatial spatial, Control sgc) { |
||||||
|
int numControls = spatial.getNumControls(); |
||||||
|
int result = -1; |
||||||
|
for (int controlIndex = 0; controlIndex < numControls; ++controlIndex) { |
||||||
|
Control control = spatial.getControl(controlIndex); |
||||||
|
if (control == sgc) { |
||||||
|
result = controlIndex; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Find the main root bone of a skeleton, based on its total bone weight. |
||||||
|
* |
||||||
|
* @param skeleton the skeleton (not null, unaffected) |
||||||
|
* @param targetMeshes an array of animated meshes to provide bone weights |
||||||
|
* (not null) |
||||||
|
* @return a root bone, or null if none found |
||||||
|
*/ |
||||||
|
static Joint findMainBone(Armature skeleton, Mesh[] targetMeshes) { |
||||||
|
Joint[] rootBones = skeleton.getRoots(); |
||||||
|
|
||||||
|
Joint result; |
||||||
|
if (rootBones.length == 1) { |
||||||
|
result = rootBones[0]; |
||||||
|
} else { |
||||||
|
result = null; |
||||||
|
float[] totalWeights = totalWeights(targetMeshes, skeleton); |
||||||
|
float greatestTotalWeight = Float.NEGATIVE_INFINITY; |
||||||
|
for (Joint rootBone : rootBones) { |
||||||
|
int boneIndex = skeleton.getJointIndex(rootBone); |
||||||
|
float weight = totalWeights[boneIndex]; |
||||||
|
if (weight > greatestTotalWeight) { |
||||||
|
result = rootBone; |
||||||
|
greatestTotalWeight = weight; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Enumerate all animated meshes in the specified subtree of a scene graph. |
||||||
|
* Note: recursive! |
||||||
|
* |
||||||
|
* @param subtree which subtree (aliases created) |
||||||
|
* @param storeResult (added to if not null) |
||||||
|
* @return an expanded list (either storeResult or a new instance) |
||||||
|
*/ |
||||||
|
static List<Mesh> listAnimatedMeshes(Spatial subtree, |
||||||
|
List<Mesh> storeResult) { |
||||||
|
if (storeResult == null) { |
||||||
|
storeResult = new ArrayList<>(10); |
||||||
|
} |
||||||
|
|
||||||
|
if (subtree instanceof Geometry) { |
||||||
|
Geometry geometry = (Geometry) subtree; |
||||||
|
Mesh mesh = geometry.getMesh(); |
||||||
|
VertexBuffer indices = mesh.getBuffer(VertexBuffer.Type.BoneIndex); |
||||||
|
boolean hasIndices = indices != null; |
||||||
|
VertexBuffer weights = mesh.getBuffer(VertexBuffer.Type.BoneWeight); |
||||||
|
boolean hasWeights = weights != null; |
||||||
|
if (hasIndices && hasWeights && !storeResult.contains(mesh)) { |
||||||
|
storeResult.add(mesh); |
||||||
|
} |
||||||
|
|
||||||
|
} else if (subtree instanceof Node) { |
||||||
|
Node node = (Node) subtree; |
||||||
|
List<Spatial> children = node.getChildren(); |
||||||
|
for (Spatial child : children) { |
||||||
|
listAnimatedMeshes(child, storeResult); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return storeResult; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Convert a transform from the mesh coordinate system to the local |
||||||
|
* coordinate system of the specified bone. |
||||||
|
* |
||||||
|
* @param parentBone (not null) |
||||||
|
* @param transform the transform to convert (not null, modified) |
||||||
|
*/ |
||||||
|
static void meshToLocal(Joint parentBone, Transform transform) { |
||||||
|
Vector3f location = transform.getTranslation(); |
||||||
|
Quaternion orientation = transform.getRotation(); |
||||||
|
Vector3f scale = transform.getScale(); |
||||||
|
|
||||||
|
Transform pmx = parentBone.getModelTransform(); |
||||||
|
Vector3f pmTranslate = pmx.getTranslation(); |
||||||
|
Quaternion pmRotInv = pmx.getRotation().inverse(); |
||||||
|
Vector3f pmScale = pmx.getScale(); |
||||||
|
|
||||||
|
location.subtractLocal(pmTranslate); |
||||||
|
location.divideLocal(pmScale); |
||||||
|
pmRotInv.mult(location, location); |
||||||
|
scale.divideLocal(pmScale); |
||||||
|
pmRotInv.mult(orientation, orientation); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Read an array of transforms from an input capsule. |
||||||
|
* |
||||||
|
* @param capsule the input capsule (not null) |
||||||
|
* @param fieldName the name of the field to read (not null) |
||||||
|
* @return a new array or null |
||||||
|
* @throws IOException from capsule |
||||||
|
*/ |
||||||
|
static Transform[] readTransformArray(InputCapsule capsule, |
||||||
|
String fieldName) throws IOException { |
||||||
|
Savable[] tmp = capsule.readSavableArray(fieldName, null); |
||||||
|
Transform[] result; |
||||||
|
if (tmp == null) { |
||||||
|
result = null; |
||||||
|
} else { |
||||||
|
result = new Transform[tmp.length]; |
||||||
|
for (int i = 0; i < tmp.length; ++i) { |
||||||
|
result[i] = (Transform) tmp[i]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Calculate a coordinate transform for the specified spatial relative to a |
||||||
|
* specified ancestor node. The result incorporates the transform of the |
||||||
|
* starting spatial, but not that of the ancestor. |
||||||
|
* |
||||||
|
* @param startSpatial the starting spatial (not null, unaffected) |
||||||
|
* @param ancestorNode the ancestor node (not null, unaffected) |
||||||
|
* @param storeResult storage for the result (modified if not null) |
||||||
|
* @return a coordinate transform (either storeResult or a new vector, not |
||||||
|
* null) |
||||||
|
*/ |
||||||
|
static Transform relativeTransform(Spatial startSpatial, |
||||||
|
Node ancestorNode, Transform storeResult) { |
||||||
|
assert startSpatial.hasAncestor(ancestorNode); |
||||||
|
Transform result |
||||||
|
= (storeResult == null) ? new Transform() : storeResult; |
||||||
|
|
||||||
|
result.loadIdentity(); |
||||||
|
Spatial loopSpatial = startSpatial; |
||||||
|
while (loopSpatial != ancestorNode) { |
||||||
|
Transform localTransform = loopSpatial.getLocalTransform(); |
||||||
|
result.combineWithParent(localTransform); |
||||||
|
loopSpatial = loopSpatial.getParent(); |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Validate a skeleton for use with DynamicAnimControl. |
||||||
|
* |
||||||
|
* @param skeleton the skeleton to validate (not null, unaffected) |
||||||
|
*/ |
||||||
|
static void validate(Armature skeleton) { |
||||||
|
int numBones = skeleton.getJointCount(); |
||||||
|
if (numBones < 0) { |
||||||
|
throw new IllegalArgumentException("Bone count is negative!"); |
||||||
|
} |
||||||
|
|
||||||
|
Set<String> nameSet = new TreeSet<>(); |
||||||
|
for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) { |
||||||
|
Joint bone = skeleton.getJoint(boneIndex); |
||||||
|
if (bone == null) { |
||||||
|
String msg = String.format("Bone %d in skeleton is null!", |
||||||
|
boneIndex); |
||||||
|
throw new IllegalArgumentException(msg); |
||||||
|
} |
||||||
|
String boneName = bone.getName(); |
||||||
|
if (boneName == null) { |
||||||
|
String msg = String.format("Bone %d in skeleton has null name!", |
||||||
|
boneIndex); |
||||||
|
throw new IllegalArgumentException(msg); |
||||||
|
} else if (boneName.equals(DynamicAnimControl.torsoName)) { |
||||||
|
String msg = String.format( |
||||||
|
"Bone %d in skeleton has a reserved name!", |
||||||
|
boneIndex); |
||||||
|
throw new IllegalArgumentException(msg); |
||||||
|
} else if (nameSet.contains(boneName)) { |
||||||
|
String msg = "Duplicate bone name in skeleton: " + boneName; |
||||||
|
throw new IllegalArgumentException(msg); |
||||||
|
} |
||||||
|
nameSet.add(boneName); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Validate a model for use with DynamicAnimControl. |
||||||
|
* |
||||||
|
* @param model the model to validate (not null, unaffected) |
||||||
|
*/ |
||||||
|
static void validate(Spatial model) { |
||||||
|
List<Geometry> geometries = listGeometries(model, null); |
||||||
|
if (geometries.isEmpty()) { |
||||||
|
throw new IllegalArgumentException("No meshes in the model."); |
||||||
|
} |
||||||
|
for (Geometry geometry : geometries) { |
||||||
|
if (geometry.isIgnoreTransform()) { |
||||||
|
throw new IllegalArgumentException( |
||||||
|
"A model geometry ignores transforms."); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// private methods
|
||||||
|
|
||||||
|
private static void addPreOrderJoints(Joint bone, List<Joint> addResult) { |
||||||
|
assert bone != null; |
||||||
|
addResult.add(bone); |
||||||
|
List<Joint> children = bone.getChildren(); |
||||||
|
for (Joint child : children) { |
||||||
|
addPreOrderJoints(child, addResult); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Add the vertex weights of each bone in the specified mesh to an array of |
||||||
|
* total weights. |
||||||
|
* |
||||||
|
* @param mesh animated mesh to analyze (not null, unaffected) |
||||||
|
* @param totalWeights (not null, modified) |
||||||
|
*/ |
||||||
|
private static void addWeights(Mesh mesh, float[] totalWeights) { |
||||||
|
assert totalWeights != null; |
||||||
|
|
||||||
|
int maxWeightsPerVert = mesh.getMaxNumWeights(); |
||||||
|
if (maxWeightsPerVert <= 0) { |
||||||
|
maxWeightsPerVert = 1; |
||||||
|
} |
||||||
|
assert maxWeightsPerVert > 0 : maxWeightsPerVert; |
||||||
|
assert maxWeightsPerVert <= 4 : maxWeightsPerVert; |
||||||
|
|
||||||
|
VertexBuffer biBuf = mesh.getBuffer(VertexBuffer.Type.BoneIndex); |
||||||
|
Buffer boneIndexBuffer = biBuf.getDataReadOnly(); |
||||||
|
boneIndexBuffer.rewind(); |
||||||
|
int numBoneIndices = boneIndexBuffer.remaining(); |
||||||
|
assert numBoneIndices % 4 == 0 : numBoneIndices; |
||||||
|
int numVertices = boneIndexBuffer.remaining() / 4; |
||||||
|
|
||||||
|
VertexBuffer wBuf = mesh.getBuffer(VertexBuffer.Type.BoneWeight); |
||||||
|
FloatBuffer weightBuffer = (FloatBuffer) wBuf.getDataReadOnly(); |
||||||
|
weightBuffer.rewind(); |
||||||
|
int numWeights = weightBuffer.remaining(); |
||||||
|
assert numWeights == numVertices * 4 : numWeights; |
||||||
|
|
||||||
|
for (int vIndex = 0; vIndex < numVertices; ++vIndex) { |
||||||
|
for (int wIndex = 0; wIndex < 4; ++wIndex) { |
||||||
|
float weight = weightBuffer.get(); |
||||||
|
int boneIndex = readIndex(boneIndexBuffer); |
||||||
|
if (wIndex < maxWeightsPerVert) { |
||||||
|
totalWeights[boneIndex] += FastMath.abs(weight); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Determine which physics link should manage the specified mesh vertex. |
||||||
|
* |
||||||
|
* @param mesh the mesh containing the vertex (not null, unaffected) |
||||||
|
* @param vertexIndex the vertex index in the mesh (≥0) |
||||||
|
* @param iArray temporary storage for bone indices (not null, modified) |
||||||
|
* @param wArray temporary storage for bone weights (not null, modified) |
||||||
|
* @param managerMap a map from bone indices to bone/torso names (not null, |
||||||
|
* unaffected) |
||||||
|
* @return a bone/torso name |
||||||
|
*/ |
||||||
|
private static String findManager(Mesh mesh, int vertexIndex, int[] iArray, |
||||||
|
float[] wArray, String[] managerMap) { |
||||||
|
vertexBoneIndices(mesh, vertexIndex, iArray); |
||||||
|
vertexBoneWeights(mesh, vertexIndex, wArray); |
||||||
|
Map<String, Float> weightMap = weightMap(iArray, wArray, managerMap); |
||||||
|
|
||||||
|
float bestTotalWeight = Float.NEGATIVE_INFINITY; |
||||||
|
String bestName = null; |
||||||
|
for (Map.Entry<String, Float> entry : weightMap.entrySet()) { |
||||||
|
float totalWeight = entry.getValue(); |
||||||
|
if (totalWeight >= bestTotalWeight) { |
||||||
|
bestTotalWeight = totalWeight; |
||||||
|
bestName = entry.getKey(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return bestName; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Enumerate all geometries in the specified subtree of a scene graph. Note: |
||||||
|
* recursive! |
||||||
|
* |
||||||
|
* @param subtree (not null, aliases created) |
||||||
|
* @param addResult (added to if not null) |
||||||
|
* @return an expanded list (either storeResult or a new instance) |
||||||
|
*/ |
||||||
|
private static List<Geometry> listGeometries(Spatial subtree, |
||||||
|
List<Geometry> addResult) { |
||||||
|
List<Geometry> result = (addResult == null) ? new ArrayList<Geometry>(50) : addResult; |
||||||
|
|
||||||
|
if (subtree instanceof Geometry) { |
||||||
|
Geometry geometry = (Geometry) subtree; |
||||||
|
if (!result.contains(geometry)) { |
||||||
|
result.add(geometry); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (subtree instanceof Node) { |
||||||
|
Node node = (Node) subtree; |
||||||
|
List<Spatial> children = node.getChildren(); |
||||||
|
for (Spatial child : children) { |
||||||
|
listGeometries(child, result); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Enumerate all bones in a pre-order, depth-first traversal of the |
||||||
|
* skeleton, such that child bones never precede their ancestors. |
||||||
|
* |
||||||
|
* @param skeleton the skeleton to traverse (not null, unaffected) |
||||||
|
* @return a new list of bones |
||||||
|
*/ |
||||||
|
private static List<Joint> preOrderJoints(Armature skeleton) { |
||||||
|
int numBones = skeleton.getJointCount(); |
||||||
|
List<Joint> result = new ArrayList<>(numBones); |
||||||
|
Joint[] rootBones = skeleton.getRoots(); |
||||||
|
for (Joint rootBone : rootBones) { |
||||||
|
addPreOrderJoints(rootBone, result); |
||||||
|
} |
||||||
|
|
||||||
|
assert result.size() == numBones : result.size(); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Read an index from a buffer. |
||||||
|
* |
||||||
|
* @param buffer a buffer of bytes or shorts (not null) |
||||||
|
* @return index (≥0) |
||||||
|
*/ |
||||||
|
private static int readIndex(Buffer buffer) { |
||||||
|
int result; |
||||||
|
if (buffer instanceof ByteBuffer) { |
||||||
|
ByteBuffer byteBuffer = (ByteBuffer) buffer; |
||||||
|
byte b = byteBuffer.get(); |
||||||
|
result = 0xff & b; |
||||||
|
} else if (buffer instanceof ShortBuffer) { |
||||||
|
ShortBuffer shortBuffer = (ShortBuffer) buffer; |
||||||
|
short s = shortBuffer.get(); |
||||||
|
result = 0xffff & s; |
||||||
|
} else { |
||||||
|
throw new IllegalArgumentException(); |
||||||
|
} |
||||||
|
|
||||||
|
assert result >= 0 : result; |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Calculate the total bone weight animated by each bone in the specified |
||||||
|
* meshes. |
||||||
|
* |
||||||
|
* @param meshes the animated meshes to analyze (not null, unaffected) |
||||||
|
* @param skeleton (not null, unaffected) |
||||||
|
* @return a map from bone indices to total bone weight |
||||||
|
*/ |
||||||
|
private static float[] totalWeights(Mesh[] meshes, Armature skeleton) { |
||||||
|
int numBones = skeleton.getJointCount(); |
||||||
|
float[] result = new float[numBones]; |
||||||
|
for (Mesh mesh : meshes) { |
||||||
|
RagUtils.addWeights(mesh, result); |
||||||
|
} |
||||||
|
|
||||||
|
List<Joint> bones = preOrderJoints(skeleton); |
||||||
|
Collections.reverse(bones); |
||||||
|
for (Joint childBone : bones) { |
||||||
|
int childIndex = skeleton.getJointIndex(childBone); |
||||||
|
Joint parent = childBone.getParent(); |
||||||
|
if (parent != null) { |
||||||
|
int parentIndex = skeleton.getJointIndex(parent); |
||||||
|
result[parentIndex] += result[childIndex]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Copy the bone indices for the indexed vertex. |
||||||
|
* |
||||||
|
* @param mesh subject mesh (not null) |
||||||
|
* @param vertexIndex index into the mesh's vertices (≥0) |
||||||
|
* @param storeResult (modified if not null) |
||||||
|
* @return the data vector (either storeResult or a new instance) |
||||||
|
*/ |
||||||
|
private static int[] vertexBoneIndices(Mesh mesh, |
||||||
|
int vertexIndex, int[] storeResult) { |
||||||
|
if (storeResult == null) { |
||||||
|
storeResult = new int[4]; |
||||||
|
} else { |
||||||
|
assert storeResult.length >= 4 : storeResult.length; |
||||||
|
} |
||||||
|
|
||||||
|
int maxWeightsPerVert = mesh.getMaxNumWeights(); |
||||||
|
if (maxWeightsPerVert <= 0) { |
||||||
|
maxWeightsPerVert = 1; |
||||||
|
} |
||||||
|
|
||||||
|
VertexBuffer biBuf = mesh.getBuffer(VertexBuffer.Type.BoneIndex); |
||||||
|
Buffer boneIndexBuffer = biBuf.getDataReadOnly(); |
||||||
|
boneIndexBuffer.position(4 * vertexIndex); |
||||||
|
for (int wIndex = 0; wIndex < maxWeightsPerVert; ++wIndex) { |
||||||
|
int boneIndex = readIndex(boneIndexBuffer); |
||||||
|
storeResult[wIndex] = boneIndex; |
||||||
|
} |
||||||
|
/* |
||||||
|
* Fill with -1s. |
||||||
|
*/ |
||||||
|
int length = storeResult.length; |
||||||
|
for (int wIndex = maxWeightsPerVert; wIndex < length; ++wIndex) { |
||||||
|
storeResult[wIndex] = -1; |
||||||
|
} |
||||||
|
|
||||||
|
return storeResult; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Copy the bone weights for the indexed vertex. |
||||||
|
* |
||||||
|
* @param mesh subject mesh (not null) |
||||||
|
* @param vertexIndex index into the mesh's vertices (≥0) |
||||||
|
* @param storeResult (modified if not null) |
||||||
|
* @return the data vector (either storeResult or a new instance) |
||||||
|
*/ |
||||||
|
private static float[] vertexBoneWeights(Mesh mesh, |
||||||
|
int vertexIndex, float[] storeResult) { |
||||||
|
if (storeResult == null) { |
||||||
|
storeResult = new float[4]; |
||||||
|
} else { |
||||||
|
assert storeResult.length >= 4 : storeResult.length; |
||||||
|
} |
||||||
|
|
||||||
|
int maxWeightsPerVert = mesh.getMaxNumWeights(); |
||||||
|
if (maxWeightsPerVert <= 0) { |
||||||
|
maxWeightsPerVert = 1; |
||||||
|
} |
||||||
|
|
||||||
|
VertexBuffer wBuf = mesh.getBuffer(VertexBuffer.Type.BoneWeight); |
||||||
|
FloatBuffer weightBuffer = (FloatBuffer) wBuf.getDataReadOnly(); |
||||||
|
weightBuffer.position(4 * vertexIndex); |
||||||
|
for (int wIndex = 0; wIndex < maxWeightsPerVert; ++wIndex) { |
||||||
|
storeResult[wIndex] = weightBuffer.get(); |
||||||
|
} |
||||||
|
/* |
||||||
|
* Fill with 0s. |
||||||
|
*/ |
||||||
|
int length = storeResult.length; |
||||||
|
for (int wIndex = maxWeightsPerVert; wIndex < length; ++wIndex) { |
||||||
|
storeResult[wIndex] = 0f; |
||||||
|
} |
||||||
|
|
||||||
|
return storeResult; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Copy Vector3f data for the indexed vertex from the specified vertex |
||||||
|
* buffer. |
||||||
|
* <p> |
||||||
|
* A software skin update is required BEFORE reading vertex |
||||||
|
* positions/normals/tangents from an animated mesh |
||||||
|
* |
||||||
|
* @param mesh subject mesh (not null) |
||||||
|
* @param bufferType which buffer to read (5 legal values) |
||||||
|
* @param vertexIndex index into the mesh's vertices (≥0) |
||||||
|
* @param storeResult (modified if not null) |
||||||
|
* @return the data vector (either storeResult or a new instance) |
||||||
|
*/ |
||||||
|
private static Vector3f vertexVector3f(Mesh mesh, |
||||||
|
VertexBuffer.Type bufferType, int vertexIndex, |
||||||
|
Vector3f storeResult) { |
||||||
|
assert bufferType == VertexBuffer.Type.BindPoseNormal |
||||||
|
|| bufferType == VertexBuffer.Type.BindPosePosition |
||||||
|
|| bufferType == VertexBuffer.Type.Binormal |
||||||
|
|| bufferType == VertexBuffer.Type.Normal |
||||||
|
|| bufferType == VertexBuffer.Type.Position : bufferType; |
||||||
|
if (storeResult == null) { |
||||||
|
storeResult = new Vector3f(); |
||||||
|
} |
||||||
|
|
||||||
|
VertexBuffer vertexBuffer = mesh.getBuffer(bufferType); |
||||||
|
FloatBuffer floatBuffer = (FloatBuffer) vertexBuffer.getDataReadOnly(); |
||||||
|
floatBuffer.position(3 * vertexIndex); |
||||||
|
storeResult.x = floatBuffer.get(); |
||||||
|
storeResult.y = floatBuffer.get(); |
||||||
|
storeResult.z = floatBuffer.get(); |
||||||
|
|
||||||
|
return storeResult; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Tabulate the total bone weight associated with each bone/torso link in a |
||||||
|
* ragdoll. |
||||||
|
* |
||||||
|
* @param biArray the array of bone indices (not null, unaffected) |
||||||
|
* @param bwArray the array of bone weights (not null, unaffected) |
||||||
|
* @param managerMap a map from bone indices to managing link names (not |
||||||
|
* null, unaffected) |
||||||
|
* @return a new map from link names to total weight |
||||||
|
*/ |
||||||
|
private static Map<String, Float> weightMap(int[] biArray, |
||||||
|
float[] bwArray, String[] managerMap) { |
||||||
|
assert biArray.length == 4; |
||||||
|
assert bwArray.length == 4; |
||||||
|
|
||||||
|
Map<String, Float> weightMap = new HashMap<>(4); |
||||||
|
for (int j = 0; j < 4; ++j) { |
||||||
|
int boneIndex = biArray[j]; |
||||||
|
if (boneIndex != -1) { |
||||||
|
String managerName = managerMap[boneIndex]; |
||||||
|
if (weightMap.containsKey(managerName)) { |
||||||
|
float oldWeight = weightMap.get(managerName); |
||||||
|
float newWeight = oldWeight + bwArray[j]; |
||||||
|
weightMap.put(managerName, newWeight); |
||||||
|
} else { |
||||||
|
weightMap.put(managerName, bwArray[j]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return weightMap; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,56 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2018 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.bullet.collision.PhysicsCollisionEvent; |
||||||
|
import com.jme3.bullet.collision.PhysicsCollisionObject; |
||||||
|
|
||||||
|
/** |
||||||
|
* Interface to receive notifications whenever a linked rigid body in a |
||||||
|
* DynamicAnimControl collides with another physics object. |
||||||
|
* <p> |
||||||
|
* This class is shared between JBullet and Native Bullet. |
||||||
|
* |
||||||
|
* @author Nehon |
||||||
|
*/ |
||||||
|
public interface RagdollCollisionListener { |
||||||
|
|
||||||
|
/** |
||||||
|
* Invoked when a collision involving a linked rigid body occurs. |
||||||
|
* |
||||||
|
* @param physicsLink the physics link that collided (not null) |
||||||
|
* @param object the collision object that collided with the bone (not null) |
||||||
|
* @param event other event details (not null) |
||||||
|
*/ |
||||||
|
void collide(PhysicsLink physicsLink, PhysicsCollisionObject object, |
||||||
|
PhysicsCollisionEvent event); |
||||||
|
} |
@ -0,0 +1,313 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2018-2019 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.bullet.PhysicsSpace; |
||||||
|
import com.jme3.bullet.joints.SixDofJoint; |
||||||
|
import com.jme3.bullet.joints.motors.RotationalLimitMotor; |
||||||
|
import com.jme3.bullet.joints.motors.TranslationalLimitMotor; |
||||||
|
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.Vector3f; |
||||||
|
import java.io.IOException; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
/** |
||||||
|
* Range of motion for a ragdoll joint. Immutable except for |
||||||
|
* {@link #read(com.jme3.export.JmeImporter)}. |
||||||
|
* <p> |
||||||
|
* This class is shared between JBullet and Native Bullet. |
||||||
|
* |
||||||
|
* @author Stephen Gold sgold@sonic.net |
||||||
|
* |
||||||
|
* Based on RagdollPreset by Rémy Bouquet (Nehon). |
||||||
|
*/ |
||||||
|
public class RangeOfMotion implements Savable { |
||||||
|
// *************************************************************************
|
||||||
|
// constants and loggers
|
||||||
|
|
||||||
|
/** |
||||||
|
* message logger for this class
|
||||||
|
*/ |
||||||
|
final public static Logger logger |
||||||
|
= Logger.getLogger(RangeOfMotion.class.getName()); |
||||||
|
/** |
||||||
|
* local copy of {@link com.jme3.math.Vector3f#ZERO} |
||||||
|
*/ |
||||||
|
final private static Vector3f translateIdentity = new Vector3f(0f, 0f, 0f); |
||||||
|
// *************************************************************************
|
||||||
|
// fields
|
||||||
|
|
||||||
|
/** |
||||||
|
* maximum rotation angle around the X axis (in radians) |
||||||
|
*/ |
||||||
|
private float maxX = 0f; |
||||||
|
/** |
||||||
|
* minimum rotation angle around the X axis (in radians) |
||||||
|
*/ |
||||||
|
private float minX = 0f; |
||||||
|
/** |
||||||
|
* maximum rotation angle around the Y axis (in radians) |
||||||
|
*/ |
||||||
|
private float maxY = 0f; |
||||||
|
/** |
||||||
|
* minimum rotation angle around the Y axis (in radians) |
||||||
|
*/ |
||||||
|
private float minY = 0f; |
||||||
|
/** |
||||||
|
* maximum rotation angle around the Z axis (in radians) |
||||||
|
*/ |
||||||
|
private float maxZ = 0f; |
||||||
|
/** |
||||||
|
* minimum rotation angle around the Z axis (in radians) |
||||||
|
*/ |
||||||
|
private float minZ = 0f; |
||||||
|
// *************************************************************************
|
||||||
|
// constructors
|
||||||
|
|
||||||
|
/** |
||||||
|
* Instantiate a preset with no motion allowed. |
||||||
|
*/ |
||||||
|
public RangeOfMotion() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Instantiate a preset with the specified range of motion. |
||||||
|
* |
||||||
|
* @param maxX the maximum rotation around the X axis (in radians) |
||||||
|
* @param minX the minimum rotation around the X axis (in radians) |
||||||
|
* @param maxY the maximum rotation around the Y axis (in radians) |
||||||
|
* @param minY the minimum rotation around the Y axis (in radians) |
||||||
|
* @param maxZ the maximum rotation around the Z axis (in radians) |
||||||
|
* @param minZ the minimum rotation around the Z axis (in radians) |
||||||
|
*/ |
||||||
|
public RangeOfMotion(float maxX, float minX, float maxY, float minY, |
||||||
|
float maxZ, float minZ) { |
||||||
|
this.maxX = maxX; |
||||||
|
this.minX = minX; |
||||||
|
this.maxY = maxY; |
||||||
|
this.minY = minY; |
||||||
|
this.maxZ = maxZ; |
||||||
|
this.minZ = minZ; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Instantiate a preset with the specified symmetric range of motion. |
||||||
|
* |
||||||
|
* @param maxX the maximum rotation around the X axis (in radians, ≥0) |
||||||
|
* @param maxY the maximum rotation around the Y axis (in radians, ≥0) |
||||||
|
* @param maxZ the maximum rotation around the Z axis (in radians, ≥0) |
||||||
|
*/ |
||||||
|
public RangeOfMotion(float maxX, float maxY, float maxZ) { |
||||||
|
this.maxX = maxX; |
||||||
|
this.minX = -maxX; |
||||||
|
this.maxY = maxY; |
||||||
|
this.minY = -maxY; |
||||||
|
this.maxZ = maxZ; |
||||||
|
this.minZ = -maxZ; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Instantiate a preset with the specified symmetric range of motion. |
||||||
|
* |
||||||
|
* @param maxAngle the maximum rotation around each axis (in radians, ≥0) |
||||||
|
*/ |
||||||
|
public RangeOfMotion(float maxAngle) { |
||||||
|
maxX = maxAngle; |
||||||
|
minX = -maxAngle; |
||||||
|
maxY = maxAngle; |
||||||
|
minY = -maxAngle; |
||||||
|
maxZ = maxAngle; |
||||||
|
minZ = -maxAngle; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Instantiate a preset for rotation on a single axis. |
||||||
|
* |
||||||
|
* @param axisIndex which axis: 0→X, 1→Y, 2→Z |
||||||
|
*/ |
||||||
|
public RangeOfMotion(int axisIndex) { |
||||||
|
switch (axisIndex) { |
||||||
|
case PhysicsSpace.AXIS_X: |
||||||
|
maxX = 1f; |
||||||
|
minX = -1f; |
||||||
|
break; |
||||||
|
case PhysicsSpace.AXIS_Y: |
||||||
|
maxY = 1f; |
||||||
|
minY = -1f; |
||||||
|
break; |
||||||
|
case PhysicsSpace.AXIS_Z: |
||||||
|
maxZ = 1f; |
||||||
|
minZ = -1f; |
||||||
|
break; |
||||||
|
default: |
||||||
|
String msg = String.format("axisIndex=%d", axisIndex); |
||||||
|
throw new IllegalArgumentException(msg); |
||||||
|
} |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// new methods exposed
|
||||||
|
|
||||||
|
/** |
||||||
|
* Read the maximum rotation around the indexed axis. |
||||||
|
* |
||||||
|
* @param axisIndex which axis: 0→X, 1→Y, 2→Z |
||||||
|
* |
||||||
|
* @return the rotation angle (in radians) |
||||||
|
*/ |
||||||
|
public float getMaxRotation(int axisIndex) { |
||||||
|
float result; |
||||||
|
switch (axisIndex) { |
||||||
|
case PhysicsSpace.AXIS_X: |
||||||
|
result = maxX; |
||||||
|
break; |
||||||
|
case PhysicsSpace.AXIS_Y: |
||||||
|
result = maxY; |
||||||
|
break; |
||||||
|
case PhysicsSpace.AXIS_Z: |
||||||
|
result = maxZ; |
||||||
|
break; |
||||||
|
default: |
||||||
|
String msg = String.format("axisIndex=%d", axisIndex); |
||||||
|
throw new IllegalArgumentException(msg); |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Read the minimum rotation around the indexed axis. |
||||||
|
* |
||||||
|
* @param axisIndex which axis: 0→X, 1→Y, 2→Z |
||||||
|
* |
||||||
|
* @return the rotation angle (in radians) |
||||||
|
*/ |
||||||
|
public float getMinRotation(int axisIndex) { |
||||||
|
float result; |
||||||
|
switch (axisIndex) { |
||||||
|
case PhysicsSpace.AXIS_X: |
||||||
|
result = minX; |
||||||
|
break; |
||||||
|
case PhysicsSpace.AXIS_Y: |
||||||
|
result = minY; |
||||||
|
break; |
||||||
|
case PhysicsSpace.AXIS_Z: |
||||||
|
result = minZ; |
||||||
|
break; |
||||||
|
default: |
||||||
|
String msg = String.format("axisIndex=%d", axisIndex); |
||||||
|
throw new IllegalArgumentException(msg); |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Apply this preset to the specified joint. |
||||||
|
* |
||||||
|
* @param joint where to apply this preset (not null, modified) |
||||||
|
*/ |
||||||
|
public void setupJoint(SixDofJoint joint) { |
||||||
|
Vector3f lower = new Vector3f(minX, minY, minZ); |
||||||
|
Vector3f upper = new Vector3f(maxX, maxY, maxZ); |
||||||
|
|
||||||
|
RotationalLimitMotor rotX |
||||||
|
= joint.getRotationalLimitMotor(PhysicsSpace.AXIS_X); |
||||||
|
rotX.setLoLimit(lower.x); |
||||||
|
rotX.setHiLimit(upper.x); |
||||||
|
|
||||||
|
RotationalLimitMotor rotY |
||||||
|
= joint.getRotationalLimitMotor(PhysicsSpace.AXIS_Y); |
||||||
|
rotY.setLoLimit(lower.y); |
||||||
|
rotY.setHiLimit(upper.y); |
||||||
|
|
||||||
|
RotationalLimitMotor rotZ |
||||||
|
= joint.getRotationalLimitMotor(PhysicsSpace.AXIS_Z); |
||||||
|
rotZ.setLoLimit(lower.z); |
||||||
|
rotZ.setHiLimit(upper.z); |
||||||
|
|
||||||
|
joint.setAngularLowerLimit(lower); |
||||||
|
joint.setAngularUpperLimit(upper); |
||||||
|
|
||||||
|
for (int i = 0; i < 3; ++i) { |
||||||
|
RotationalLimitMotor rot = joint.getRotationalLimitMotor(i); |
||||||
|
rot.setMaxMotorForce(1e8f); |
||||||
|
rot.setMaxLimitForce(1e9f); |
||||||
|
} |
||||||
|
|
||||||
|
joint.setLinearLowerLimit(translateIdentity); |
||||||
|
joint.setLinearUpperLimit(translateIdentity); |
||||||
|
|
||||||
|
TranslationalLimitMotor tra = joint.getTranslationalLimitMotor(); |
||||||
|
tra.setLowerLimit(translateIdentity); |
||||||
|
tra.setUpperLimit(translateIdentity); |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// Savable methods
|
||||||
|
|
||||||
|
/** |
||||||
|
* De-serialize this preset, for example when loading from a J3O file. |
||||||
|
* |
||||||
|
* @param im importer (not null) |
||||||
|
* @throws IOException from importer |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void read(JmeImporter im) throws IOException { |
||||||
|
InputCapsule capsule = im.getCapsule(this); |
||||||
|
maxX = capsule.readFloat("maxX", 0f); |
||||||
|
minX = capsule.readFloat("minX", 0f); |
||||||
|
maxY = capsule.readFloat("maxY", 0f); |
||||||
|
minY = capsule.readFloat("minY", 0f); |
||||||
|
maxZ = capsule.readFloat("maxZ", 0f); |
||||||
|
minZ = capsule.readFloat("minZ", 0f); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Serialize this preset, for example when saving to a J3O file. |
||||||
|
* |
||||||
|
* @param ex exporter (not null) |
||||||
|
* @throws IOException from exporter |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void write(JmeExporter ex) throws IOException { |
||||||
|
OutputCapsule capsule = ex.getCapsule(this); |
||||||
|
capsule.write(maxX, "maxX", 0f); |
||||||
|
capsule.write(minX, "minX", 0f); |
||||||
|
capsule.write(maxY, "maxY", 0f); |
||||||
|
capsule.write(minY, "minY", 0f); |
||||||
|
capsule.write(maxZ, "maxZ", 0f); |
||||||
|
capsule.write(minZ, "minZ", 0f); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,486 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2018-2019 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.bullet.collision.shapes.CollisionShape; |
||||||
|
import com.jme3.bullet.objects.PhysicsRigidBody; |
||||||
|
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.Quaternion; |
||||||
|
import com.jme3.math.Transform; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.scene.Node; |
||||||
|
import com.jme3.util.clone.Cloner; |
||||||
|
import java.io.IOException; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
/** |
||||||
|
* Link the torso of an animated model to a rigid body in a ragdoll. |
||||||
|
* <p> |
||||||
|
* This class is shared between JBullet and Native Bullet. |
||||||
|
* |
||||||
|
* @author Stephen Gold sgold@sonic.net |
||||||
|
* |
||||||
|
* Based on KinematicRagdollControl by Normen Hansen and Rémy Bouquet (Nehon). |
||||||
|
*/ |
||||||
|
public class TorsoLink extends PhysicsLink { |
||||||
|
// *************************************************************************
|
||||||
|
// constants and loggers
|
||||||
|
|
||||||
|
/** |
||||||
|
* message logger for this class
|
||||||
|
*/ |
||||||
|
final public static Logger logger2 |
||||||
|
= Logger.getLogger(TorsoLink.class.getName()); |
||||||
|
// *************************************************************************
|
||||||
|
// fields
|
||||||
|
|
||||||
|
/** |
||||||
|
* bones managed by this link, in a pre-order, depth-first traversal of the |
||||||
|
* skeleton |
||||||
|
*/ |
||||||
|
private Joint[] managedBones = null; |
||||||
|
/** |
||||||
|
* submode when kinematic |
||||||
|
*/ |
||||||
|
private KinematicSubmode submode = KinematicSubmode.Animated; |
||||||
|
/** |
||||||
|
* local transform for the controlled spatial at the end of this link's most |
||||||
|
* recent blend interval, or null for no spatial blending |
||||||
|
*/ |
||||||
|
private Transform endModelTransform = null; |
||||||
|
/** |
||||||
|
* transform from mesh coordinates to model coordinates |
||||||
|
*/ |
||||||
|
private Transform meshToModel = null; |
||||||
|
/** |
||||||
|
* local transform of the controlled spatial at the start of this link's |
||||||
|
* most recent blend interval |
||||||
|
*/ |
||||||
|
private Transform startModelTransform = new Transform(); |
||||||
|
/** |
||||||
|
* 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. Do not invoke |
||||||
|
* directly! |
||||||
|
*/ |
||||||
|
public TorsoLink() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Instantiate a purely kinematic link between the torso of the specified |
||||||
|
* control and the specified rigid body. |
||||||
|
* |
||||||
|
* @param control the control that will manage this link (not null, alias |
||||||
|
* created) |
||||||
|
* @param mainRootBone the root bone with the most animation weight (not |
||||||
|
* null, alias created) |
||||||
|
* @param collisionShape the desired shape (not null, alias created) |
||||||
|
* @param mass the mass (in physics-mass units) |
||||||
|
* @param meshToModel the transform from mesh coordinates to model |
||||||
|
* coordinates (not null, unaffected) |
||||||
|
* @param localOffset the location of the body's center (in the bone's local |
||||||
|
* coordinates, not null, unaffected) |
||||||
|
*/ |
||||||
|
TorsoLink(DacLinks control, Joint mainRootBone, |
||||||
|
CollisionShape collisionShape, float mass, |
||||||
|
Transform meshToModel, Vector3f localOffset) { |
||||||
|
super(control, mainRootBone, collisionShape, mass, localOffset); |
||||||
|
this.meshToModel = meshToModel.clone(); |
||||||
|
managedBones = control.listManagedBones(DynamicAnimControl.torsoName); |
||||||
|
|
||||||
|
int numManagedBones = managedBones.length; |
||||||
|
startBoneTransforms = new Transform[numManagedBones]; |
||||||
|
for (int i = 0; i < numManagedBones; ++i) { |
||||||
|
startBoneTransforms[i] = new Transform(); |
||||||
|
} |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// new methods exposed
|
||||||
|
|
||||||
|
/** |
||||||
|
* 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) |
||||||
|
* @param endModelTransform the desired local transform for the controlled |
||||||
|
* spatial when the blend completes or null for no change to local transform |
||||||
|
* (unaffected) |
||||||
|
*/ |
||||||
|
public void blendToKinematicMode(KinematicSubmode submode, |
||||||
|
float blendInterval, Transform endModelTransform) { |
||||||
|
super.blendToKinematicMode(blendInterval); |
||||||
|
|
||||||
|
this.submode = submode; |
||||||
|
this.endModelTransform = endModelTransform; |
||||||
|
/* |
||||||
|
* Save initial transforms for blending. |
||||||
|
*/ |
||||||
|
if (endModelTransform != null) { |
||||||
|
Transform current = getControl().getSpatial().getLocalTransform(); |
||||||
|
startModelTransform.set(current); |
||||||
|
} |
||||||
|
int numManagedBones = managedBones.length; |
||||||
|
for (int mbIndex = 0; mbIndex < numManagedBones; ++mbIndex) { |
||||||
|
Transform transform; |
||||||
|
if (prevBoneTransforms == null) { // this link not updated yet
|
||||||
|
Joint managedBone = managedBones[mbIndex]; |
||||||
|
transform = managedBone.getLocalTransform().clone(); |
||||||
|
} else { |
||||||
|
transform = prevBoneTransforms[mbIndex]; |
||||||
|
} |
||||||
|
startBoneTransforms[mbIndex].set(transform); |
||||||
|
} |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// 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 |
||||||
|
* (unused) |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void cloneFields(Cloner cloner, Object original) { |
||||||
|
super.cloneFields(cloner, original); |
||||||
|
|
||||||
|
managedBones = cloner.clone(managedBones); |
||||||
|
endModelTransform = cloner.clone(endModelTransform); |
||||||
|
meshToModel = cloner.clone(meshToModel); |
||||||
|
prevBoneTransforms = cloner.clone(prevBoneTransforms); |
||||||
|
startBoneTransforms = cloner.clone(startBoneTransforms); |
||||||
|
startModelTransform = cloner.clone(startModelTransform); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Update this link in Dynamic mode, setting the local transform of the |
||||||
|
* model's root spatial based on the transform of the linked rigid body. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
protected void dynamicUpdate() { |
||||||
|
/* |
||||||
|
* Calculate the inverse world transform of the model's parent node. |
||||||
|
*/ |
||||||
|
Transform worldToParent; |
||||||
|
Node parent = getControl().getSpatial().getParent(); |
||||||
|
if (parent == null) { |
||||||
|
worldToParent = new Transform(); |
||||||
|
} else { |
||||||
|
Transform parentToWorld = parent.getWorldTransform(); |
||||||
|
worldToParent = parentToWorld.invert(); |
||||||
|
} |
||||||
|
|
||||||
|
Transform transform = meshToModel.clone(); |
||||||
|
Transform shapeToWorld = new Transform(); |
||||||
|
PhysicsRigidBody body = getRigidBody(); |
||||||
|
body.getPhysicsLocation(shapeToWorld.getTranslation()); |
||||||
|
body.getPhysicsRotation(shapeToWorld.getRotation()); |
||||||
|
shapeToWorld.setScale(body.getCollisionShape().getScale()); |
||||||
|
|
||||||
|
transform.combineWithParent(shapeToWorld); |
||||||
|
transform.combineWithParent(worldToParent); |
||||||
|
getControl().getSpatial().setLocalTransform(transform); |
||||||
|
|
||||||
|
localBoneTransform(transform); |
||||||
|
Joint[] rootBones = getControl().getSkeleton().getRoots(); |
||||||
|
for (Joint rootBone : rootBones) { |
||||||
|
rootBone.setLocalTransform(transform); |
||||||
|
} |
||||||
|
|
||||||
|
for (Joint managedBone : managedBones) { |
||||||
|
managedBone.updateModelTransforms(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a shallow clone for the JME cloner. |
||||||
|
* |
||||||
|
* @return a new instance |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public TorsoLink jmeClone() { |
||||||
|
try { |
||||||
|
TorsoLink clone = (TorsoLink) super.clone(); |
||||||
|
return clone; |
||||||
|
} catch (CloneNotSupportedException exception) { |
||||||
|
throw new RuntimeException(exception); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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(); |
||||||
|
|
||||||
|
if (endModelTransform != null) { |
||||||
|
/* |
||||||
|
* For a smooth transition, blend the saved model transform |
||||||
|
* (from the start of the blend interval) into the goal transform. |
||||||
|
*/ |
||||||
|
transform.interpolateTransforms(startModelTransform.clone(), |
||||||
|
endModelTransform, kinematicWeight()); |
||||||
|
getControl().getSpatial().setLocalTransform(transform); |
||||||
|
} |
||||||
|
|
||||||
|
for (int mbIndex = 0; mbIndex < managedBones.length; ++mbIndex) { |
||||||
|
Joint managedBone = managedBones[mbIndex]; |
||||||
|
switch (submode) { |
||||||
|
case Animated: |
||||||
|
transform.set(managedBone.getLocalTransform()); |
||||||
|
break; |
||||||
|
case Frozen: |
||||||
|
transform.set(prevBoneTransforms[mbIndex]); |
||||||
|
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.interpolateTransforms( |
||||||
|
startBoneTransforms[mbIndex].clone(), transform, |
||||||
|
kinematicWeight()); |
||||||
|
} |
||||||
|
/* |
||||||
|
* Update the managed bone. |
||||||
|
*/ |
||||||
|
managedBone.setLocalTransform(transform); |
||||||
|
managedBone.updateModelTransforms(); |
||||||
|
} |
||||||
|
|
||||||
|
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() { |
||||||
|
return "Torso:"; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Copy animation data from the specified link, which must have the same |
||||||
|
* main bone. |
||||||
|
* |
||||||
|
* @param oldLink the link to copy from (not null, unaffected) |
||||||
|
*/ |
||||||
|
void postRebuild(TorsoLink oldLink) { |
||||||
|
int numManagedBones = managedBones.length; |
||||||
|
assert oldLink.managedBones.length == numManagedBones; |
||||||
|
|
||||||
|
super.postRebuild(oldLink); |
||||||
|
if (oldLink.isKinematic()) { |
||||||
|
submode = oldLink.submode; |
||||||
|
} else { |
||||||
|
submode = KinematicSubmode.Frozen; |
||||||
|
} |
||||||
|
|
||||||
|
Transform emt = oldLink.endModelTransform; |
||||||
|
endModelTransform = (emt == null) ? null : emt.clone(); |
||||||
|
startModelTransform.set(oldLink.startModelTransform); |
||||||
|
|
||||||
|
if (prevBoneTransforms == null) { |
||||||
|
prevBoneTransforms = new Transform[numManagedBones]; |
||||||
|
for (int i = 0; i < numManagedBones; ++i) { |
||||||
|
prevBoneTransforms[i] = new Transform(); |
||||||
|
} |
||||||
|
} |
||||||
|
for (int i = 0; i < numManagedBones; ++i) { |
||||||
|
prevBoneTransforms[i].set(oldLink.prevBoneTransforms[i]); |
||||||
|
startBoneTransforms[i].set(oldLink.startBoneTransforms[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* De-serialize this link, for example when loading from a J3O file. |
||||||
|
* |
||||||
|
* @param im importer (not null) |
||||||
|
* @throws IOException from importer |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void read(JmeImporter im) throws IOException { |
||||||
|
super.read(im); |
||||||
|
InputCapsule ic = im.getCapsule(this); |
||||||
|
|
||||||
|
Savable[] tmp = ic.readSavableArray("managedBones", null); |
||||||
|
if (tmp == null) { |
||||||
|
managedBones = null; |
||||||
|
} else { |
||||||
|
managedBones = new Joint[tmp.length]; |
||||||
|
for (int i = 0; i < tmp.length; ++i) { |
||||||
|
managedBones[i] = (Joint) tmp[i]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
submode = ic.readEnum("submode", KinematicSubmode.class, |
||||||
|
KinematicSubmode.Animated); |
||||||
|
endModelTransform = (Transform) ic.readSavable("endModelTransform", |
||||||
|
new Transform()); |
||||||
|
meshToModel |
||||||
|
= (Transform) ic.readSavable("meshToModel", new Transform()); |
||||||
|
startModelTransform = (Transform) ic.readSavable("startModelTransform", |
||||||
|
new Transform()); |
||||||
|
prevBoneTransforms = RagUtils.readTransformArray(ic, |
||||||
|
"prevBoneTransforms"); |
||||||
|
startBoneTransforms = RagUtils.readTransformArray(ic, |
||||||
|
"startBoneTransforms"); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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; |
||||||
|
|
||||||
|
if (prevBoneTransforms == null) { |
||||||
|
/* |
||||||
|
* On the first update, allocate and initialize |
||||||
|
* the array of previous bone transforms, if it wasn't |
||||||
|
* allocated in blendToKinematicMode(). |
||||||
|
*/ |
||||||
|
int numManagedBones = managedBones.length; |
||||||
|
prevBoneTransforms = new Transform[numManagedBones]; |
||||||
|
for (int mbIndex = 0; mbIndex < numManagedBones; ++mbIndex) { |
||||||
|
Joint managedBone = managedBones[mbIndex]; |
||||||
|
Transform boneTransform |
||||||
|
= managedBone.getLocalTransform().clone(); |
||||||
|
prevBoneTransforms[mbIndex] = boneTransform; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
super.update(tpf); |
||||||
|
/* |
||||||
|
* Save copies of the latest bone transforms. |
||||||
|
*/ |
||||||
|
for (int mbIndex = 0; mbIndex < managedBones.length; ++mbIndex) { |
||||||
|
Transform lastTransform = prevBoneTransforms[mbIndex]; |
||||||
|
Joint managedBone = managedBones[mbIndex]; |
||||||
|
lastTransform.set(managedBone.getLocalTransform()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Serialize this link, for example when saving to a J3O file. |
||||||
|
* |
||||||
|
* @param ex exporter (not null) |
||||||
|
* @throws IOException from exporter |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void write(JmeExporter ex) throws IOException { |
||||||
|
super.write(ex); |
||||||
|
OutputCapsule oc = ex.getCapsule(this); |
||||||
|
|
||||||
|
oc.write(managedBones, "managedBones", null); |
||||||
|
oc.write(submode, "submode", KinematicSubmode.Animated); |
||||||
|
oc.write(endModelTransform, "endModelTransforms", new Transform()); |
||||||
|
oc.write(meshToModel, "meshToModel", new Transform()); |
||||||
|
oc.write(startModelTransform, "startModelTransforms", new Transform()); |
||||||
|
oc.write(prevBoneTransforms, "prevBoneTransforms", new Transform[0]); |
||||||
|
oc.write(startBoneTransforms, "startBoneTransforms", new Transform[0]); |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// private methods
|
||||||
|
|
||||||
|
/** |
||||||
|
* 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(); |
||||||
|
Quaternion orientation = result.getRotation(); |
||||||
|
Vector3f scale = result.getScale(); |
||||||
|
/* |
||||||
|
* Start with the rigid body's transform in physics/world coordinates. |
||||||
|
*/ |
||||||
|
PhysicsRigidBody body = getRigidBody(); |
||||||
|
body.getPhysicsLocation(result.getTranslation()); |
||||||
|
body.getPhysicsRotation(result.getRotation()); |
||||||
|
result.setScale(body.getCollisionShape().getScale()); |
||||||
|
/* |
||||||
|
* Convert to mesh coordinates. |
||||||
|
*/ |
||||||
|
Transform worldToMesh = getControl().meshTransform(null).invert(); |
||||||
|
result.combineWithParent(worldToMesh); |
||||||
|
/* |
||||||
|
* Subtract the body's local offset, rotated and scaled. |
||||||
|
*/ |
||||||
|
Vector3f meshOffset = localOffset(null); |
||||||
|
meshOffset.multLocal(scale); |
||||||
|
orientation.mult(meshOffset, meshOffset); |
||||||
|
location.subtractLocal(meshOffset); |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,151 @@ |
|||||||
|
/* |
||||||
|
Copyright (c) 2019 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.math.Vector3f; |
||||||
|
import com.jme3.util.BufferUtils; |
||||||
|
import java.nio.FloatBuffer; |
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
/** |
||||||
|
* A simplified collection of Vector3f values without duplicates, implemented |
||||||
|
* using a Collection. |
||||||
|
* <p> |
||||||
|
* This class is shared between JBullet and Native Bullet. |
||||||
|
* |
||||||
|
* @author Stephen Gold sgold@sonic.net |
||||||
|
*/ |
||||||
|
public class VectorSet { |
||||||
|
// *************************************************************************
|
||||||
|
// constants and loggers
|
||||||
|
|
||||||
|
/** |
||||||
|
* message logger for this class
|
||||||
|
*/ |
||||||
|
final private static Logger logger |
||||||
|
= Logger.getLogger(VectorSet.class.getName()); |
||||||
|
// *************************************************************************
|
||||||
|
// fields
|
||||||
|
|
||||||
|
/** |
||||||
|
* collection of values |
||||||
|
*/ |
||||||
|
final private Set<Vector3f> set; |
||||||
|
// *************************************************************************
|
||||||
|
// constructors
|
||||||
|
|
||||||
|
/** |
||||||
|
* Instantiate an empty set with the specified initial capacity and default |
||||||
|
* load factor. |
||||||
|
* |
||||||
|
* @param numVectors the initial capacity of the hash table (>0) |
||||||
|
*/ |
||||||
|
public VectorSet(int numVectors) { |
||||||
|
set = new HashSet<>(numVectors); |
||||||
|
} |
||||||
|
// *************************************************************************
|
||||||
|
// VectorSet methods
|
||||||
|
|
||||||
|
/** |
||||||
|
* Add the value of the specified Vector3f to this set. |
||||||
|
* |
||||||
|
* @param vector the value to add (not null, unaffected) |
||||||
|
*/ |
||||||
|
public void add(Vector3f vector) { |
||||||
|
set.add(vector.clone()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Test whether this set contains the value of the specified Vector3f. |
||||||
|
* |
||||||
|
* @param vector the value to find (not null, unaffected) |
||||||
|
* @return true if found, otherwise false |
||||||
|
*/ |
||||||
|
public boolean contains(Vector3f vector) { |
||||||
|
boolean result = set.contains(vector); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Calculate the sample mean for each axis over the Vector3f values in this |
||||||
|
* set. |
||||||
|
* |
||||||
|
* @param storeResult (modified if not null) |
||||||
|
* @return the sample mean for each axis (either storeResult or a new |
||||||
|
* Vector3f) |
||||||
|
*/ |
||||||
|
public Vector3f mean(Vector3f storeResult) { |
||||||
|
int numVectors = numVectors(); |
||||||
|
assert numVectors > 0 : numVectors; |
||||||
|
Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; |
||||||
|
|
||||||
|
result.zero(); |
||||||
|
for (Vector3f tempVector : set) { |
||||||
|
result.addLocal(tempVector); |
||||||
|
} |
||||||
|
result.divideLocal(numVectors); |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Calculate the number of Vector3f values in this set. |
||||||
|
* |
||||||
|
* @return the count (≥0) |
||||||
|
*/ |
||||||
|
public int numVectors() { |
||||||
|
int numVectors = set.size(); |
||||||
|
assert numVectors >= 0 : numVectors; |
||||||
|
return numVectors; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Access the buffer containing all the Vector3f values in this set. No |
||||||
|
* further add() is allowed. |
||||||
|
* |
||||||
|
* @return a new buffer, flipped |
||||||
|
*/ |
||||||
|
public FloatBuffer toBuffer() { |
||||||
|
int numFloats = 3 * set.size(); |
||||||
|
FloatBuffer buffer = BufferUtils.createFloatBuffer(numFloats); |
||||||
|
for (Vector3f tempVector : set) { |
||||||
|
buffer.put(tempVector.x); |
||||||
|
buffer.put(tempVector.y); |
||||||
|
buffer.put(tempVector.z); |
||||||
|
} |
||||||
|
buffer.flip(); |
||||||
|
|
||||||
|
return buffer; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,35 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2018 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. |
||||||
|
*/ |
||||||
|
/** |
||||||
|
* A dynamic animation control and some associated classes. |
||||||
|
*/ |
||||||
|
package com.jme3.bullet.animation; |
@ -1,93 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright (c) 2009-2010 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 jme3test.bullet; |
|
||||||
|
|
||||||
import com.jme3.animation.AnimEventListener; |
|
||||||
import com.jme3.animation.Bone; |
|
||||||
import com.jme3.bullet.collision.RagdollCollisionListener; |
|
||||||
import com.jme3.bullet.control.KinematicRagdollControl; |
|
||||||
import com.jme3.input.KeyInput; |
|
||||||
import com.jme3.input.controls.ActionListener; |
|
||||||
import com.jme3.input.controls.KeyTrigger; |
|
||||||
import com.jme3.math.Vector3f; |
|
||||||
import com.jme3.scene.Node; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author reden |
|
||||||
*/ |
|
||||||
public class TestIK extends TestBoneRagdoll implements RagdollCollisionListener, AnimEventListener { |
|
||||||
|
|
||||||
Node targetNode = new Node(""); |
|
||||||
Vector3f targetPoint; |
|
||||||
Bone mouseBone; |
|
||||||
Vector3f oldMousePos; |
|
||||||
|
|
||||||
public static void main(String[] args) { |
|
||||||
TestIK app = new TestIK(); |
|
||||||
app.start(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void simpleInitApp() { |
|
||||||
super.simpleInitApp(); |
|
||||||
final KinematicRagdollControl ikControl = model.getControl(KinematicRagdollControl.class); |
|
||||||
inputManager.addListener(new ActionListener() { |
|
||||||
|
|
||||||
public void onAction(String name, boolean isPressed, float tpf) { |
|
||||||
|
|
||||||
if (name.equals("stop") && isPressed) { |
|
||||||
ikControl.setEnabled(!ikControl.isEnabled()); |
|
||||||
ikControl.setIKMode(); |
|
||||||
} |
|
||||||
|
|
||||||
if (name.equals("one") && isPressed) { |
|
||||||
//ragdoll.setKinematicMode();
|
|
||||||
targetPoint = model.getWorldTranslation().add(new Vector3f(0,2,4)); |
|
||||||
targetNode.setLocalTranslation(targetPoint); |
|
||||||
ikControl.setIKTarget(ikControl.getBone("Hand.L"), targetPoint, 2); |
|
||||||
ikControl.setIKMode(); |
|
||||||
} |
|
||||||
if (name.equals("two") && isPressed) { |
|
||||||
//ragdoll.setKinematicMode();
|
|
||||||
targetPoint = model.getWorldTranslation().add(new Vector3f(-3,3,0)); |
|
||||||
targetNode.setLocalTranslation(targetPoint); |
|
||||||
ikControl.setIKTarget(ikControl.getBone("Hand.R"), targetPoint, 3); |
|
||||||
ikControl.setIKMode(); |
|
||||||
} |
|
||||||
} |
|
||||||
}, "one", "two"); |
|
||||||
inputManager.addMapping("one", new KeyTrigger(KeyInput.KEY_1)); |
|
||||||
inputManager.addMapping("two", new KeyTrigger(KeyInput.KEY_2)); |
|
||||||
inputManager.addMapping("stop", new KeyTrigger(KeyInput.KEY_H)); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
Loading…
Reference in new issue