publicize Joint.applyBindPose(), add DynamicAnimControl & related stuff
This commit is contained in:
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);
|
||||
}
|
||||
}
|
||||
}
|
1109
jme3-bullet/src/common/java/com/jme3/bullet/animation/DacLinks.java
Normal file
1109
jme3-bullet/src/common/java/com/jme3/bullet/animation/DacLinks.java
Normal file
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;
|
@ -154,7 +154,7 @@ public class Joint implements Savable, JmeCloneable, HasLocalTransform {
|
||||
/**
|
||||
* Sets the local transform with the bind transforms
|
||||
*/
|
||||
protected void applyBindPose() {
|
||||
public void applyBindPose() {
|
||||
jointModelTransform.applyBindPose(localTransform, inverseModelBindMatrix, parent);
|
||||
updateModelTransforms();
|
||||
|
||||
|
@ -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…
x
Reference in New Issue
Block a user