
com.jme3.bullet.animation.DacConfiguration Maven / Gradle / Ivy
/*
* 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.
*
* This class is shared between JBullet and Native Bullet.
*
* @author Stephen Gold [email protected]
*
* 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 blConfigMap = new HashMap<>(50);
/**
* map linked bone names to ranges of motion for createSpatialData()
*/
private Map 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 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.
*
* 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 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.
*
* 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 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 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);
}
}
}