getAdjacentEdges(Integer index) {
/**
* Tells if the given edge is a boundary edge. The boundary edge means that it belongs to a single
* face or to none.
- * @param the
- * edge of the mesh
+ * @param edge the edge of the mesh
* @return true if the edge is a boundary one and false otherwise
*/
public boolean isBoundary(Edge edge) {
@@ -289,8 +286,9 @@ public TemporalMesh clone() {
/**
* The method rebuilds the mappings between faces and edges. Should be called after
* every major change of the temporal mesh done outside it.
- * @note I will remove this method soon and make the mappings to be done automatically
- * when the mesh is modified.
+ *
+ * Note: I will remove this method soon and cause the mappings to be done
+ * automatically when the mesh is modified.
*/
public void rebuildIndexesMappings() {
indexToEdgeMapping.clear();
diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UserUVCollection.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UserUVCollection.java
index 083e5f50de..ea276af56c 100644
--- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UserUVCollection.java
+++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UserUVCollection.java
@@ -62,7 +62,7 @@ public void addUV(int materialIndex, String uvSetName, Vector2f uv, int jmeVerte
* the name of the UV set
* @param vertexIndex
* the vertex index corresponds to the index in jme mesh and not the original one in blender
- * @return
+ * @return a pre-existing coordinate vector
*/
public Vector2f getUVForVertex(String uvSetName, int vertexIndex) {
return uvsMap.get(uvSetName).get(vertexIndex);
diff --git a/jme3-bullet-native-android/build.gradle b/jme3-bullet-native-android/build.gradle
index 499330a9e4..1058ef3225 100644
--- a/jme3-bullet-native-android/build.gradle
+++ b/jme3-bullet-native-android/build.gradle
@@ -104,7 +104,8 @@ task copyJmeAndroid(type: Copy) {
into outputDir
}
-task buildBulletNativeLib(type: Exec, dependsOn: [copyJmeAndroid, copyJmeCpp, copyBullet]) {
+//dependsOn ':jme3-bullet:generateNativeHeaders'
+task buildBulletNativeLib(type: Exec, dependsOn: [copyJmeAndroid, ':jme3-bullet:generateNativeHeaders', copyJmeCpp, copyBullet]) {
// args 'TARGET_PLATFORM=android-9'
// println "buildBulletNativeLib ndkWorkingPath: " + ndkWorkingPath
// println "buildBulletNativeLib rootProject.ndkCommandPath: " + rootProject.ndkCommandPath
diff --git a/jme3-bullet-native-android/libs/arm64-v8a/libbulletjme.so b/jme3-bullet-native-android/libs/arm64-v8a/libbulletjme.so
index 0aff124f85..e64b52ba1b 100755
Binary files a/jme3-bullet-native-android/libs/arm64-v8a/libbulletjme.so and b/jme3-bullet-native-android/libs/arm64-v8a/libbulletjme.so differ
diff --git a/jme3-bullet-native-android/libs/armeabi-v7a/libbulletjme.so b/jme3-bullet-native-android/libs/armeabi-v7a/libbulletjme.so
index a57b41067e..7e2aad17b7 100755
Binary files a/jme3-bullet-native-android/libs/armeabi-v7a/libbulletjme.so and b/jme3-bullet-native-android/libs/armeabi-v7a/libbulletjme.so differ
diff --git a/jme3-bullet-native-android/libs/armeabi/libbulletjme.so b/jme3-bullet-native-android/libs/armeabi/libbulletjme.so
index 0750264f69..f54710e4a7 100755
Binary files a/jme3-bullet-native-android/libs/armeabi/libbulletjme.so and b/jme3-bullet-native-android/libs/armeabi/libbulletjme.so differ
diff --git a/jme3-bullet-native-android/libs/mips/libbulletjme.so b/jme3-bullet-native-android/libs/mips/libbulletjme.so
index 90dd9f57ca..adc7fa1aa7 100755
Binary files a/jme3-bullet-native-android/libs/mips/libbulletjme.so and b/jme3-bullet-native-android/libs/mips/libbulletjme.so differ
diff --git a/jme3-bullet-native-android/libs/mips64/libbulletjme.so b/jme3-bullet-native-android/libs/mips64/libbulletjme.so
index d79b0d9b7d..094ae8478d 100755
Binary files a/jme3-bullet-native-android/libs/mips64/libbulletjme.so and b/jme3-bullet-native-android/libs/mips64/libbulletjme.so differ
diff --git a/jme3-bullet-native-android/libs/x86/libbulletjme.so b/jme3-bullet-native-android/libs/x86/libbulletjme.so
index 66b1b67aeb..1089c838e7 100755
Binary files a/jme3-bullet-native-android/libs/x86/libbulletjme.so and b/jme3-bullet-native-android/libs/x86/libbulletjme.so differ
diff --git a/jme3-bullet-native-android/libs/x86_64/libbulletjme.so b/jme3-bullet-native-android/libs/x86_64/libbulletjme.so
index 59c6a2ef90..2f26225794 100755
Binary files a/jme3-bullet-native-android/libs/x86_64/libbulletjme.so and b/jme3-bullet-native-android/libs/x86_64/libbulletjme.so differ
diff --git a/jme3-bullet-native-android/src/native/android/Android.mk b/jme3-bullet-native-android/src/native/android/Android.mk
index b46f8ec29c..1a1e64b89c 100644
--- a/jme3-bullet-native-android/src/native/android/Android.mk
+++ b/jme3-bullet-native-android/src/native/android/Android.mk
@@ -54,12 +54,23 @@ LOCAL_C_INCLUDES := $(BULLET_PATH)/\
$(BULLET_PATH)/vectormath/sse\
$(BULLET_PATH)/vectormath/neon
-LOCAL_CFLAGS := $(LOCAL_C_INCLUDES:%=-I%)
+#ARM mode more performant than thumb for old armeabi
+ifeq ($(TARGET_ARCH_ABI),$(filter $(TARGET_ARCH_ABI), armeabi))
+LOCAL_ARM_MODE := arm
+endif
+
+#Enable neon for armv7
+ifeq ($(TARGET_ARCH_ABI),$(filter $(TARGET_ARCH_ABI), armeabi-v7a))
+LOCAL_ARM_NEON := true
+endif
+
+LOCAL_CFLAGS := $(LOCAL_C_INCLUDES:%=-I%)
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -ldl -lm -llog
FILE_LIST := $(wildcard $(LOCAL_PATH)/*.cpp)
FILE_LIST += $(wildcard $(LOCAL_PATH)/**/*.cpp)
FILE_LIST += $(wildcard $(LOCAL_PATH)/**/**/*.cpp)
+FILE_LIST := $(filter-out $(wildcard $(LOCAL_PATH)/Bullet3OpenCL/**/*.cpp), $(FILE_LIST))
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%)
-include $(BUILD_SHARED_LIBRARY)
\ No newline at end of file
+include $(BUILD_SHARED_LIBRARY)
diff --git a/jme3-bullet-native-android/src/native/android/Application.mk b/jme3-bullet-native-android/src/native/android/Application.mk
index 34f65f6e89..44c5c842e5 100644
--- a/jme3-bullet-native-android/src/native/android/Application.mk
+++ b/jme3-bullet-native-android/src/native/android/Application.mk
@@ -1,4 +1,7 @@
APP_OPTIM := release
APP_ABI := all
-#APP_ABI := armeabi-v7a
+APP_STL := stlport_static
+# gnustl_static or stlport_static
APP_MODULES := bulletjme
+APP_CFLAGS += -funroll-loops -Ofast
+
diff --git a/jme3-bullet-native/build.gradle b/jme3-bullet-native/build.gradle
index 55273f09e9..558940173f 100644
--- a/jme3-bullet-native/build.gradle
+++ b/jme3-bullet-native/build.gradle
@@ -21,6 +21,10 @@ task cleanHeaders(type: Delete) {
task cleanUnzipped(type: Delete) {
delete bulletFolder
}
+// clean up the downloaded archive
+task cleanZipFile(type: Delete) {
+ delete bulletZipFile
+}
model {
components {
@@ -37,13 +41,21 @@ model {
source {
srcDir 'src/native/cpp'
srcDir bulletSrcPath
+ exclude 'Bullet3Collision/**'
+ exclude 'Bullet3Dynamics/**'
+ exclude 'Bullet3Geometry/**'
exclude 'Bullet3OpenCL/**'
+ exclude 'Bullet3Serialize/**'
include '**/*.cpp'
}
exportedHeaders {
srcDir 'src/native/cpp'
srcDir bulletSrcPath
+ exclude 'Bullet3Collision/**'
+ exclude 'Bullet3Dynamics/**'
+ exclude 'Bullet3Geometry/**'
exclude 'Bullet3OpenCL/**'
+ exclude 'Bullet3Serialize/**'
include '**/*.h'
}
}
@@ -208,7 +220,7 @@ task unzipBulletIfNeeded {
}
unzipBulletIfNeeded.dependsOn {
- if (buildNativeProjects == "true" && !file(bulletFolder).isDirectory()) {
+ if (buildNativeProjects == "true") {
unzipBullet
}
}
diff --git a/jme3-bullet-native/libs/native/linux/x86/libbulletjme.so b/jme3-bullet-native/libs/native/linux/x86/libbulletjme.so
index 9aa3d7a26c..d0ea174f71 100755
Binary files a/jme3-bullet-native/libs/native/linux/x86/libbulletjme.so and b/jme3-bullet-native/libs/native/linux/x86/libbulletjme.so differ
diff --git a/jme3-bullet-native/libs/native/linux/x86_64/libbulletjme.so b/jme3-bullet-native/libs/native/linux/x86_64/libbulletjme.so
index ae2e70eb0b..8cad657b2a 100755
Binary files a/jme3-bullet-native/libs/native/linux/x86_64/libbulletjme.so and b/jme3-bullet-native/libs/native/linux/x86_64/libbulletjme.so differ
diff --git a/jme3-bullet-native/libs/native/osx/x86/libbulletjme.dylib b/jme3-bullet-native/libs/native/osx/x86/libbulletjme.dylib
index 953b2eade2..d85332e863 100755
Binary files a/jme3-bullet-native/libs/native/osx/x86/libbulletjme.dylib and b/jme3-bullet-native/libs/native/osx/x86/libbulletjme.dylib differ
diff --git a/jme3-bullet-native/libs/native/osx/x86_64/libbulletjme.dylib b/jme3-bullet-native/libs/native/osx/x86_64/libbulletjme.dylib
index 1a9432c8dc..62733e513d 100755
Binary files a/jme3-bullet-native/libs/native/osx/x86_64/libbulletjme.dylib and b/jme3-bullet-native/libs/native/osx/x86_64/libbulletjme.dylib differ
diff --git a/jme3-bullet-native/libs/native/windows/x86/bulletjme.dll b/jme3-bullet-native/libs/native/windows/x86/bulletjme.dll
index f4556c5ad5..f826da28f7 100755
Binary files a/jme3-bullet-native/libs/native/windows/x86/bulletjme.dll and b/jme3-bullet-native/libs/native/windows/x86/bulletjme.dll differ
diff --git a/jme3-bullet-native/libs/native/windows/x86_64/bulletjme.dll b/jme3-bullet-native/libs/native/windows/x86_64/bulletjme.dll
index 8a4ac5c807..9468e84681 100755
Binary files a/jme3-bullet-native/libs/native/windows/x86_64/bulletjme.dll and b/jme3-bullet-native/libs/native/windows/x86_64/bulletjme.dll differ
diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp
index 3ecf7fd6b3..4e143cb4a6 100644
--- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp
+++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp
@@ -46,16 +46,21 @@ extern "C" {
* Signature: (FFFFFFI)J
*/
JNIEXPORT jlong JNICALL Java_com_jme3_bullet_PhysicsSpace_createPhysicsSpace
- (JNIEnv * env, jobject object, jfloat minX, jfloat minY, jfloat minZ, jfloat maxX, jfloat maxY, jfloat maxZ, jint broadphase, jboolean threading) {
+ (JNIEnv * env, jobject object, jfloat minX, jfloat minY, jfloat minZ,
+ jfloat maxX, jfloat maxY, jfloat maxZ, jint broadphase,
+ jboolean threading) {
jmeClasses::initJavaClasses(env);
+
jmePhysicsSpace* space = new jmePhysicsSpace(env, object);
if (space == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The physics space has not been created.");
return 0;
}
- space->createPhysicsSpace(minX, minY, minZ, maxX, maxY, maxZ, broadphase, threading);
- return reinterpret_cast(space);
+
+ space->createPhysicsSpace(minX, minY, minZ, maxX, maxY, maxZ,
+ broadphase, threading);
+ return reinterpret_cast (space);
}
/*
@@ -64,8 +69,9 @@ extern "C" {
* Signature: (JFIF)V
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_stepSimulation
- (JNIEnv * env, jobject object, jlong spaceId, jfloat tpf, jint maxSteps, jfloat accuracy) {
- jmePhysicsSpace* space = reinterpret_cast(spaceId);
+ (JNIEnv * env, jobject object, jlong spaceId, jfloat tpf, jint maxSteps,
+ jfloat accuracy) {
+ jmePhysicsSpace* space = reinterpret_cast (spaceId);
if (space == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The physics space does not exist.");
@@ -81,8 +87,8 @@ extern "C" {
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addCollisionObject
(JNIEnv * env, jobject object, jlong spaceId, jlong objectId) {
- jmePhysicsSpace* space = reinterpret_cast(spaceId);
- btCollisionObject* collisionObject = reinterpret_cast(objectId);
+ jmePhysicsSpace* space = reinterpret_cast (spaceId);
+ btCollisionObject* collisionObject = reinterpret_cast (objectId);
if (space == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The physics space does not exist.");
@@ -93,7 +99,7 @@ extern "C" {
env->ThrowNew(newExc, "The collision object does not exist.");
return;
}
- jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer();
+ jmeUserPointer *userPointer = (jmeUserPointer*) collisionObject->getUserPointer();
userPointer -> space = space;
space->getDynamicsWorld()->addCollisionObject(collisionObject);
@@ -106,8 +112,8 @@ extern "C" {
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeCollisionObject
(JNIEnv * env, jobject object, jlong spaceId, jlong objectId) {
- jmePhysicsSpace* space = reinterpret_cast(spaceId);
- btCollisionObject* collisionObject = reinterpret_cast(objectId);
+ jmePhysicsSpace* space = reinterpret_cast (spaceId);
+ btCollisionObject* collisionObject = reinterpret_cast (objectId);
if (space == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The physics space does not exist.");
@@ -119,7 +125,7 @@ extern "C" {
return;
}
space->getDynamicsWorld()->removeCollisionObject(collisionObject);
- jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer();
+ jmeUserPointer *userPointer = (jmeUserPointer*) collisionObject->getUserPointer();
userPointer -> space = NULL;
}
@@ -130,8 +136,8 @@ extern "C" {
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addRigidBody
(JNIEnv * env, jobject object, jlong spaceId, jlong rigidBodyId) {
- jmePhysicsSpace* space = reinterpret_cast(spaceId);
- btRigidBody* collisionObject = reinterpret_cast(rigidBodyId);
+ jmePhysicsSpace* space = reinterpret_cast (spaceId);
+ btRigidBody* collisionObject = reinterpret_cast (rigidBodyId);
if (space == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The physics space does not exist.");
@@ -142,7 +148,7 @@ extern "C" {
env->ThrowNew(newExc, "The collision object does not exist.");
return;
}
- jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer();
+ jmeUserPointer *userPointer = (jmeUserPointer*) collisionObject->getUserPointer();
userPointer -> space = space;
space->getDynamicsWorld()->addRigidBody(collisionObject);
}
@@ -154,8 +160,8 @@ extern "C" {
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeRigidBody
(JNIEnv * env, jobject object, jlong spaceId, jlong rigidBodyId) {
- jmePhysicsSpace* space = reinterpret_cast(spaceId);
- btRigidBody* collisionObject = reinterpret_cast(rigidBodyId);
+ jmePhysicsSpace* space = reinterpret_cast (spaceId);
+ btRigidBody* collisionObject = reinterpret_cast (rigidBodyId);
if (space == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The physics space does not exist.");
@@ -166,7 +172,7 @@ extern "C" {
env->ThrowNew(newExc, "The collision object does not exist.");
return;
}
- jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer();
+ jmeUserPointer *userPointer = (jmeUserPointer*) collisionObject->getUserPointer();
userPointer -> space = NULL;
space->getDynamicsWorld()->removeRigidBody(collisionObject);
}
@@ -178,8 +184,8 @@ extern "C" {
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addCharacterObject
(JNIEnv * env, jobject object, jlong spaceId, jlong objectId) {
- jmePhysicsSpace* space = reinterpret_cast(spaceId);
- btCollisionObject* collisionObject = reinterpret_cast(objectId);
+ jmePhysicsSpace* space = reinterpret_cast (spaceId);
+ btCollisionObject* collisionObject = reinterpret_cast (objectId);
if (space == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The physics space does not exist.");
@@ -190,12 +196,12 @@ extern "C" {
env->ThrowNew(newExc, "The collision object does not exist.");
return;
}
- jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer();
+ jmeUserPointer *userPointer = (jmeUserPointer*) collisionObject->getUserPointer();
userPointer -> space = space;
space->getDynamicsWorld()->addCollisionObject(collisionObject,
btBroadphaseProxy::CharacterFilter,
btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter
- );
+ );
}
/*
@@ -205,8 +211,8 @@ extern "C" {
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeCharacterObject
(JNIEnv * env, jobject object, jlong spaceId, jlong objectId) {
- jmePhysicsSpace* space = reinterpret_cast(spaceId);
- btCollisionObject* collisionObject = reinterpret_cast(objectId);
+ jmePhysicsSpace* space = reinterpret_cast (spaceId);
+ btCollisionObject* collisionObject = reinterpret_cast (objectId);
if (space == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The physics space does not exist.");
@@ -217,7 +223,7 @@ extern "C" {
env->ThrowNew(newExc, "The collision object does not exist.");
return;
}
- jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer();
+ jmeUserPointer *userPointer = (jmeUserPointer*) collisionObject->getUserPointer();
userPointer -> space = NULL;
space->getDynamicsWorld()->removeCollisionObject(collisionObject);
}
@@ -229,8 +235,8 @@ extern "C" {
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addAction
(JNIEnv * env, jobject object, jlong spaceId, jlong objectId) {
- jmePhysicsSpace* space = reinterpret_cast(spaceId);
- btActionInterface* actionObject = reinterpret_cast(objectId);
+ jmePhysicsSpace* space = reinterpret_cast (spaceId);
+ btActionInterface* actionObject = reinterpret_cast (objectId);
if (space == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The physics space does not exist.");
@@ -251,8 +257,8 @@ extern "C" {
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeAction
(JNIEnv * env, jobject object, jlong spaceId, jlong objectId) {
- jmePhysicsSpace* space = reinterpret_cast(spaceId);
- btActionInterface* actionObject = reinterpret_cast(objectId);
+ jmePhysicsSpace* space = reinterpret_cast (spaceId);
+ btActionInterface* actionObject = reinterpret_cast (objectId);
if (space == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The physics space does not exist.");
@@ -273,8 +279,8 @@ extern "C" {
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addVehicle
(JNIEnv * env, jobject object, jlong spaceId, jlong objectId) {
- jmePhysicsSpace* space = reinterpret_cast(spaceId);
- btActionInterface* actionObject = reinterpret_cast(objectId);
+ jmePhysicsSpace* space = reinterpret_cast (spaceId);
+ btActionInterface* actionObject = reinterpret_cast (objectId);
if (space == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The physics space does not exist.");
@@ -295,8 +301,8 @@ extern "C" {
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeVehicle
(JNIEnv * env, jobject object, jlong spaceId, jlong objectId) {
- jmePhysicsSpace* space = reinterpret_cast(spaceId);
- btActionInterface* actionObject = reinterpret_cast(objectId);
+ jmePhysicsSpace* space = reinterpret_cast (spaceId);
+ btActionInterface* actionObject = reinterpret_cast (objectId);
if (space == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The physics space does not exist.");
@@ -317,8 +323,8 @@ extern "C" {
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addConstraint
(JNIEnv * env, jobject object, jlong spaceId, jlong objectId) {
- jmePhysicsSpace* space = reinterpret_cast(spaceId);
- btTypedConstraint* constraint = reinterpret_cast(objectId);
+ jmePhysicsSpace* space = reinterpret_cast (spaceId);
+ btTypedConstraint* constraint = reinterpret_cast (objectId);
if (space == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The physics space does not exist.");
@@ -339,8 +345,8 @@ extern "C" {
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addConstraintC
(JNIEnv * env, jobject object, jlong spaceId, jlong objectId, jboolean collision) {
- jmePhysicsSpace* space = reinterpret_cast(spaceId);
- btTypedConstraint* constraint = reinterpret_cast(objectId);
+ jmePhysicsSpace* space = reinterpret_cast (spaceId);
+ btTypedConstraint* constraint = reinterpret_cast (objectId);
if (space == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The physics space does not exist.");
@@ -361,8 +367,8 @@ extern "C" {
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeConstraint
(JNIEnv * env, jobject object, jlong spaceId, jlong objectId) {
- jmePhysicsSpace* space = reinterpret_cast(spaceId);
- btTypedConstraint* constraint = reinterpret_cast(objectId);
+ jmePhysicsSpace* space = reinterpret_cast (spaceId);
+ btTypedConstraint* constraint = reinterpret_cast (objectId);
if (space == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The physics space does not exist.");
@@ -383,7 +389,7 @@ extern "C" {
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_setGravity
(JNIEnv * env, jobject object, jlong spaceId, jobject vector) {
- jmePhysicsSpace* space = reinterpret_cast(spaceId);
+ jmePhysicsSpace* space = reinterpret_cast (spaceId);
if (space == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The physics space does not exist.");
@@ -391,6 +397,7 @@ extern "C" {
}
btVector3 gravity = btVector3();
jmeBulletUtil::convert(env, vector, &gravity);
+
space->getDynamicsWorld()->setGravity(gravity);
}
@@ -411,13 +418,13 @@ extern "C" {
*/
JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_finalizeNative
(JNIEnv * env, jobject object, jlong spaceId) {
- jmePhysicsSpace* space = reinterpret_cast(spaceId);
+ jmePhysicsSpace* space = reinterpret_cast (spaceId);
if (space == NULL) {
return;
}
delete(space);
}
-
+
JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_rayTest_1native
(JNIEnv * env, jobject object, jobject from, jobject to, jlong spaceId, jobject resultlist, jint flags) {
@@ -465,13 +472,12 @@ extern "C" {
resultCallback.resultlist = resultlist;
resultCallback.m_flags = flags;
space->getDynamicsWorld()->rayTest(native_from, native_to, resultCallback);
+
return;
}
-
-
JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_sweepTest_1native
- (JNIEnv * env, jobject object, jlong shapeId, jobject from, jobject to, jlong spaceId, jobject resultlist, jfloat allowedCcdPenetration) {
+ (JNIEnv * env, jobject object, jlong shapeId, jobject from, jobject to, jlong spaceId, jobject resultlist, jfloat allowedCcdPenetration) {
jmePhysicsSpace* space = reinterpret_cast (spaceId);
if (space == NULL) {
@@ -489,7 +495,7 @@ extern "C" {
struct AllConvexResultCallback : public btCollisionWorld::ConvexResultCallback {
- AllConvexResultCallback(const btTransform& convexFromWorld, const btTransform & convexToWorld) : m_convexFromWorld(convexFromWorld), m_convexToWorld(convexToWorld) {
+ AllConvexResultCallback(const btTransform& convexFromWorld, const btTransform & convexToWorld) : m_convexFromWorld(convexFromWorld), m_convexToWorld(convexToWorld) {
}
jobject resultlist;
JNIEnv* env;
@@ -500,17 +506,16 @@ extern "C" {
btVector3 m_hitPointWorld;
virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) {
- if (normalInWorldSpace) {
- m_hitNormalWorld = convexResult.m_hitNormalLocal;
- }
- else {
- m_hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis() * convexResult.m_hitNormalLocal;
- }
- m_hitPointWorld.setInterpolate3(m_convexFromWorld.getBasis() * m_convexFromWorld.getOrigin(), m_convexToWorld.getBasis() * m_convexToWorld.getOrigin(), convexResult.m_hitFraction);
+ if (normalInWorldSpace) {
+ m_hitNormalWorld = convexResult.m_hitNormalLocal;
+ } else {
+ m_hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis() * convexResult.m_hitNormalLocal;
+ }
+ m_hitPointWorld.setInterpolate3(m_convexFromWorld.getBasis() * m_convexFromWorld.getOrigin(), m_convexToWorld.getBasis() * m_convexToWorld.getOrigin(), convexResult.m_hitFraction);
- jmeBulletUtil::addSweepResult(env, resultlist, &m_hitNormalWorld, &m_hitPointWorld, convexResult.m_hitFraction, convexResult.m_hitCollisionObject);
+ jmeBulletUtil::addSweepResult(env, resultlist, &m_hitNormalWorld, &m_hitPointWorld, convexResult.m_hitFraction, convexResult.m_hitCollisionObject);
- return 1.f;
+ return 1.f;
}
};
@@ -528,16 +533,16 @@ extern "C" {
space->getDynamicsWorld()->convexSweepTest((btConvexShape *) shape, native_from, native_to, resultCallback, native_allowed_ccd_penetration);
return;
}
-
+
JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_setSolverNumIterations
(JNIEnv *env, jobject object, jlong spaceId, jint value) {
- jmePhysicsSpace* space = reinterpret_cast(spaceId);
+ jmePhysicsSpace* space = reinterpret_cast (spaceId);
if (space == NULL) {
jclass newExc = env->FindClass("java/lang/NullPointerException");
env->ThrowNew(newExc, "The physics space does not exist.");
return;
}
-
+
space->getDynamicsWorld()->getSolverInfo().m_numIterations = value;
}
diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionObject.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionObject.cpp
index e3852c76a4..0c7f0161ae 100644
--- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionObject.cpp
+++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionObject.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2018 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -143,6 +143,42 @@ extern "C" {
}
}
+ /*
+ * Class: com_jme3_bullet_collision_PhysicsCollisionObject
+ * Method: getCollisionFlags
+ * Signature: (J)I
+ */
+ JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_getCollisionFlags
+ (JNIEnv *env, jobject object, jlong objectId) {
+ btCollisionObject* collisionObject = reinterpret_cast (objectId);
+ if (collisionObject == NULL) {
+ jclass newExc = env->FindClass("java/lang/NullPointerException");
+ env->ThrowNew(newExc, "The native object does not exist.");
+ return 0;
+ }
+
+ jint result = collisionObject->getCollisionFlags();
+ return result;
+ }
+
+ /*
+ * Class: com_jme3_bullet_collision_PhysicsCollisionObject
+ * Method: setCollisionFlags
+ * Signature: (JI)V
+ */
+ JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_setCollisionFlags
+ (JNIEnv *env, jobject object, jlong objectId, jint desiredFlags) {
+ btCollisionObject* collisionObject = reinterpret_cast (objectId);
+ if (collisionObject == NULL) {
+ jclass newExc = env->FindClass("java/lang/NullPointerException");
+ env->ThrowNew(newExc, "The native object does not exist.");
+ return;
+ }
+
+ collisionObject->setCollisionFlags(desiredFlags);
+ }
+
+
#ifdef __cplusplus
}
#endif
diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HullCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HullCollisionShape.cpp
index e3878d01ed..604f5a0e9b 100644
--- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HullCollisionShape.cpp
+++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HullCollisionShape.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2019 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -60,6 +60,8 @@ extern "C" {
shape->addPoint(vect);
}
+
+ shape->optimizeConvexHull();
return reinterpret_cast(shape);
}
diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_HingeJoint.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_HingeJoint.cpp
index 48ba90f374..351be3ffa3 100644
--- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_HingeJoint.cpp
+++ b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_HingeJoint.cpp
@@ -85,7 +85,7 @@ extern "C" {
env->ThrowNew(newExc, "The native object does not exist.");
return 0;
}
- return joint->getMotorTargetVelosity();
+ return joint->getMotorTargetVelocity();
}
/*
diff --git a/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.cpp b/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.cpp
index 485dffffe4..953511a713 100644
--- a/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.cpp
+++ b/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2019 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -74,17 +74,16 @@ void jmePhysicsSpace::createPhysicsSpace(jfloat minX, jfloat minY, jfloat minZ,
btBroadphaseInterface* broadphase;
switch (broadphaseId) {
- case 0:
+ case 0: // SIMPLE
broadphase = new btSimpleBroadphase();
break;
- case 1:
+ case 1: // AXIS_SWEEP_3
broadphase = new btAxisSweep3(min, max);
break;
- case 2:
- //TODO: 32bit!
- broadphase = new btAxisSweep3(min, max);
+ case 2: // AXIS_SWEEP_3_32
+ broadphase = new bt32BitAxisSweep3(min, max);
break;
- case 3:
+ case 3: // DBVT
broadphase = new btDbvtBroadphase();
break;
}
@@ -150,8 +149,8 @@ void jmePhysicsSpace::createPhysicsSpace(jfloat minX, jfloat minY, jfloat minZ,
dynamicsWorld->getPairCache()->setOverlapFilterCallback(new jmeFilterCallback());
dynamicsWorld->setInternalTickCallback(&jmePhysicsSpace::preTickCallback, static_cast (this), true);
dynamicsWorld->setInternalTickCallback(&jmePhysicsSpace::postTickCallback, static_cast (this));
- if (gContactProcessedCallback == NULL) {
- gContactProcessedCallback = &jmePhysicsSpace::contactProcessedCallback;
+ if (gContactStartedCallback == NULL) {
+ gContactStartedCallback = &jmePhysicsSpace::contactStartedCallback;
}
}
@@ -183,11 +182,10 @@ void jmePhysicsSpace::postTickCallback(btDynamicsWorld *world, btScalar timeStep
}
}
-bool jmePhysicsSpace::contactProcessedCallback(btManifoldPoint &cp, void *body0, void *body1) {
- // printf("contactProcessedCallback %d %dn", body0, body1);
- btCollisionObject* co0 = (btCollisionObject*) body0;
+void jmePhysicsSpace::contactStartedCallback(btPersistentManifold* const &pm) {
+ const btCollisionObject* co0 = pm->getBody0();
+ const btCollisionObject* co1 = pm->getBody1();
jmeUserPointer *up0 = (jmeUserPointer*) co0 -> getUserPointer();
- btCollisionObject* co1 = (btCollisionObject*) body1;
jmeUserPointer *up1 = (jmeUserPointer*) co1 -> getUserPointer();
if (up0 != NULL) {
jmePhysicsSpace *dynamicsWorld = (jmePhysicsSpace *)up0->space;
@@ -197,18 +195,18 @@ bool jmePhysicsSpace::contactProcessedCallback(btManifoldPoint &cp, void *body0,
if (javaPhysicsSpace != NULL) {
jobject javaCollisionObject0 = env->NewLocalRef(up0->javaCollisionObject);
jobject javaCollisionObject1 = env->NewLocalRef(up1->javaCollisionObject);
- env->CallVoidMethod(javaPhysicsSpace, jmeClasses::PhysicsSpace_addCollisionEvent, javaCollisionObject0, javaCollisionObject1, (jlong) & cp);
+ for(int i=0;igetNumContacts();i++){
+ env->CallVoidMethod(javaPhysicsSpace, jmeClasses::PhysicsSpace_addCollisionEvent, javaCollisionObject0, javaCollisionObject1, (jlong) & pm->getContactPoint(i));
+ if (env->ExceptionCheck()) {
+ env->Throw(env->ExceptionOccurred());
+ }
+ }
env->DeleteLocalRef(javaPhysicsSpace);
env->DeleteLocalRef(javaCollisionObject0);
env->DeleteLocalRef(javaCollisionObject1);
- if (env->ExceptionCheck()) {
- env->Throw(env->ExceptionOccurred());
- return true;
- }
}
}
}
- return true;
}
btDynamicsWorld* jmePhysicsSpace::getDynamicsWorld() {
diff --git a/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.h b/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.h
index 7a61e4e1ff..72c49b23cf 100644
--- a/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.h
+++ b/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.h
@@ -62,5 +62,5 @@ class jmePhysicsSpace {
JNIEnv* getEnv();
static void preTickCallback(btDynamicsWorld*, btScalar);
static void postTickCallback(btDynamicsWorld*, btScalar);
- static bool contactProcessedCallback(btManifoldPoint &, void *, void *);
+ static void contactStartedCallback(btPersistentManifold* const &);
};
\ No newline at end of file
diff --git a/jme3-bullet/build.gradle b/jme3-bullet/build.gradle
index 075d30163c..404e6cb002 100644
--- a/jme3-bullet/build.gradle
+++ b/jme3-bullet/build.gradle
@@ -2,6 +2,8 @@ if (!hasProperty('mainClass')) {
ext.mainClass = ''
}
+String classBuildDir = "${buildDir}" + File.separator + 'classes'
+
sourceSets {
main {
java {
@@ -17,52 +19,21 @@ dependencies {
}
task generateNativeHeaders(type: Exec, dependsOn: classes) {
- def classes = " \
- com.jme3.bullet.PhysicsSpace, \
- \
- com.jme3.bullet.collision.PhysicsCollisionEvent, \
- com.jme3.bullet.collision.PhysicsCollisionObject,\
- com.jme3.bullet.objects.PhysicsCharacter, \
- com.jme3.bullet.objects.PhysicsGhostObject, \
- com.jme3.bullet.objects.PhysicsRigidBody, \
- com.jme3.bullet.objects.PhysicsVehicle, \
- com.jme3.bullet.objects.VehicleWheel, \
- com.jme3.bullet.objects.infos.RigidBodyMotionState, \
- \
- com.jme3.bullet.collision.shapes.CollisionShape, \
- com.jme3.bullet.collision.shapes.BoxCollisionShape, \
- com.jme3.bullet.collision.shapes.CapsuleCollisionShape, \
- com.jme3.bullet.collision.shapes.CompoundCollisionShape, \
- com.jme3.bullet.collision.shapes.ConeCollisionShape, \
- com.jme3.bullet.collision.shapes.CylinderCollisionShape, \
- com.jme3.bullet.collision.shapes.GImpactCollisionShape, \
- com.jme3.bullet.collision.shapes.HeightfieldCollisionShape, \
- com.jme3.bullet.collision.shapes.HullCollisionShape, \
- com.jme3.bullet.collision.shapes.MeshCollisionShape, \
- com.jme3.bullet.collision.shapes.PlaneCollisionShape, \
- com.jme3.bullet.collision.shapes.SimplexCollisionShape, \
- com.jme3.bullet.collision.shapes.SphereCollisionShape, \
- \
- com.jme3.bullet.joints.PhysicsJoint, \
- com.jme3.bullet.joints.ConeJoint, \
- com.jme3.bullet.joints.HingeJoint, \
- com.jme3.bullet.joints.Point2PointJoint, \
- com.jme3.bullet.joints.SixDofJoint, \
- com.jme3.bullet.joints.SixDofSpringJoint, \
- com.jme3.bullet.joints.SliderJoint, \
- com.jme3.bullet.joints.motors.RotationalLimitMotor, \
- com.jme3.bullet.joints.motors.TranslationalLimitMotor, \
- \
- com.jme3.bullet.util.NativeMeshUtil, \
- com.jme3.bullet.util.DebugShapeFactory"
-
+ def files0 = fileTree("src/main/java/").filter { it.isFile() && it.getName().endsWith(".java") }.files
+ def files1 = fileTree("src/common/java/").filter { it.isFile() && it.getName().endsWith(".java") }.files
+ def files2 = fileTree("../jme3-core/src/main/java/").filter { it.isFile() && it.getName().endsWith(".java") }.files
+ def files3 = fileTree("../jme3-core/src/plugins/java/").filter { it.isFile() && it.getName().endsWith(".java") }.files
+ def files4 = fileTree("../jme3-core/src/tools/java/").filter { it.isFile() && it.getName().endsWith(".java") }.files
+ def files5 = fileTree("../jme3-terrain/src/main/java/").filter { it.isFile() && it.getName().endsWith(".java") }.files
def classpath = sourceSets.main.runtimeClasspath.asPath
def nativeIncludes = new File(project(":jme3-bullet-native").projectDir, "src/native/cpp")
-
- executable org.gradle.internal.jvm.Jvm.current().getExecutable('javah')
- args "-d", nativeIncludes
- args "-classpath", classpath
- args classes.split(",").collect { it.trim() }
+ def filesList = "\"" + files0.join("\"\n\"") + "\"\n\"" + files1.join("\"\n\"") + "\"\n\"" + files2.join("\"\n\"") + "\"\n\"" + files3.join("\"\n\"") + "\"\n\"" + files4.join("\"\n\"") + "\"\n\"" + files5.join("\"\n\"") + "\""
+ new File("$projectDir/java_classes.jtxt").text = filesList.replaceAll(java.util.regex.Pattern.quote("\\"), java.util.regex.Matcher.quoteReplacement("/"))
+ executable org.gradle.internal.jvm.Jvm.current().getExecutable('javac')
+ args "-h", nativeIncludes
+ args "@$projectDir/java_classes.jtxt"
+ args '-d', classBuildDir
+ args "-encoding", "UTF-8"
}
assemble.dependsOn(generateNativeHeaders)
\ No newline at end of file
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/BoneLink.java b/jme3-bullet/src/common/java/com/jme3/bullet/animation/BoneLink.java
new file mode 100644
index 0000000000..730b1b1708
--- /dev/null
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/animation/BoneLink.java
@@ -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.
+ *
+ * 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;
+ }
+}
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/DacConfiguration.java b/jme3-bullet/src/common/java/com/jme3/bullet/animation/DacConfiguration.java
new file mode 100644
index 0000000000..ea03107b6b
--- /dev/null
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/animation/DacConfiguration.java
@@ -0,0 +1,565 @@
+/*
+ * 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 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 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);
+ }
+ }
+}
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/DacLinks.java b/jme3-bullet/src/common/java/com/jme3/bullet/animation/DacLinks.java
new file mode 100644
index 0000000000..d80adc4a21
--- /dev/null
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/animation/DacLinks.java
@@ -0,0 +1,1108 @@
+/*
+ * 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.anim.SkinningControl;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.PhysicsTickListener;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.collision.shapes.HullCollisionShape;
+import com.jme3.bullet.joints.PhysicsJoint;
+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.Quaternion;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.util.clone.Cloner;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Access a DynamicAnimControl at the PhysicsLink level once it's been added to
+ * a Spatial.
+ *
+ * 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 DacLinks
+ extends DacConfiguration
+ implements PhysicsTickListener {
+ // *************************************************************************
+ // constants and loggers
+
+ /**
+ * message logger for this class
+ */
+ final public static Logger logger3
+ = Logger.getLogger(DacLinks.class.getName());
+ /**
+ * local copy of {@link com.jme3.math.Quaternion#IDENTITY}
+ */
+ final private static Quaternion rotateIdentity = new Quaternion();
+ /**
+ * local copy of {@link com.jme3.math.Transform#IDENTITY}
+ */
+ final private static Transform transformIdentity = new Transform();
+ /**
+ * local copy of {@link com.jme3.math.Vector3f#ZERO}
+ */
+ final private static Vector3f translateIdentity = new Vector3f(0f, 0f, 0f);
+ // *************************************************************************
+ // fields
+
+ /**
+ * false until the 1st physics tick, true thereafter, indicating that all
+ * links are ready for dynamic mode
+ */
+ private boolean isReady = false;
+ /**
+ * bone links in a pre-order, depth-first traversal of the link hierarchy
+ */
+ private List boneLinkList = null;
+ /**
+ * map bone names to bone links
+ */
+ private Map boneLinks = new HashMap<>(32);
+ /**
+ * skeleton being controlled
+ */
+ private Armature skeleton = null;
+ /**
+ * spatial that provides the mesh-coordinate transform
+ */
+ private Spatial transformer = null;
+ /**
+ * torso link for this control
+ */
+ private TorsoLink torsoLink = null;
+ // *************************************************************************
+ // constructors
+
+ /**
+ * Instantiate an enabled control without any linked bones (torso only).
+ */
+ DacLinks() {
+ }
+ // *************************************************************************
+ // new methods exposed
+
+ /**
+ * Access the named bone.
+ *
+ * Allowed only when the control IS added to a spatial.
+ *
+ * @param boneName the name of the skeleton bone to access
+ * @return the pre-existing instance, or null if not found
+ */
+ public Joint findBone(String boneName) {
+ verifyAddedToSpatial("access a bone");
+ Joint result = skeleton.getJoint(boneName);
+ return result;
+ }
+
+ /**
+ * Access the BoneLink for the named bone. Returns null if bone is not
+ * linked, or if the control is not added to a spatial.
+ *
+ * @param boneName the name of the bone (not null, not empty)
+ * @return the pre-existing BoneLink, or null if not found
+ */
+ public BoneLink findBoneLink(String boneName) {
+ BoneLink boneLink = boneLinks.get(boneName);
+ return boneLink;
+ }
+
+ /**
+ * Access the named link. Returns null if the name is invalid, or if the
+ * control is not added to a spatial.
+ *
+ * @param linkName the name of the link (not null, not empty)
+ * @return the pre-existing link, or null if not found
+ */
+ public PhysicsLink findLink(String linkName) {
+ PhysicsLink link;
+ if (linkName.startsWith("Bone:")) {
+ String boneName = linkName.substring(5);
+ link = findBoneLink(boneName);
+ } else {
+ assert linkName.equals("Torso:");
+ link = torsoLink;
+ }
+
+ return link;
+ }
+
+ /**
+ * Access the skeleton. Returns null if the control is not added to a
+ * spatial.
+ *
+ * @return the pre-existing skeleton, or null
+ */
+ public Armature getSkeleton() {
+ return skeleton;
+ }
+
+ /**
+ * Access the TorsoLink. Returns null if the control is not added to a
+ * spatial.
+ *
+ * @return the pre-existing TorsoLink, or null
+ */
+ public TorsoLink getTorsoLink() {
+ return torsoLink;
+ }
+
+ /**
+ * Access the spatial with the mesh-coordinate transform. Returns null if
+ * the control is not added to a spatial.
+ *
+ * @return the pre-existing spatial, or null
+ */
+ Spatial getTransformer() {
+ return transformer;
+ }
+
+ /**
+ * Test whether this control is ready for dynamic mode.
+ *
+ * @return true if ready, otherwise false
+ */
+ public boolean isReady() {
+ return isReady;
+ }
+
+ /**
+ * Enumerate all physics links of the specified type managed by this
+ * control.
+ *
+ * @param subclass of PhysicsLink
+ * @param linkType the subclass of PhysicsLink to search for (not null)
+ * @return a new array of links (not null, not empty)
+ */
+ @SuppressWarnings("unchecked")
+ public List listLinks(Class linkType) {
+ int numLinks = countLinks();
+ List result = new ArrayList<>(numLinks);
+
+ if (torsoLink != null
+ && linkType.isAssignableFrom(torsoLink.getClass())) {
+ result.add((T) torsoLink);
+ }
+ for (BoneLink link : boneLinkList) {
+ if (linkType.isAssignableFrom(link.getClass())) {
+ result.add((T) link);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Enumerate all managed bones of the named link, in a pre-order,
+ * depth-first traversal of the skeleton, such that child bones never
+ * precede their ancestors.
+ *
+ * @param managerName the name of the managing link (not null)
+ * @return a new array of managed bones, including the manager if it is not
+ * the torso
+ */
+ Joint[] listManagedBones(String managerName) {
+ List list = new ArrayList<>(8);
+
+ if (torsoName.equals(managerName)) {
+ Joint[] roots = skeleton.getRoots();
+ for (Joint rootBone : roots) {
+ list.add(rootBone);
+ addUnlinkedDescendants(rootBone, list);
+ }
+
+ } else {
+ BoneLink manager = findBoneLink(managerName);
+ if (manager == null) {
+ String msg = "No link named " + managerName;
+ throw new IllegalArgumentException(msg);
+ }
+ Joint managerBone = manager.getBone();
+ list.add(managerBone);
+ addUnlinkedDescendants(managerBone, list);
+ }
+ /*
+ * Convert the list to an array.
+ */
+ int numManagedBones = list.size();
+ Joint[] array = new Joint[numManagedBones];
+ list.toArray(array);
+
+ return array;
+ }
+
+ /**
+ * Enumerate all rigid bodies managed by this control.
+ *
+ * Allowed only when the control IS added to a spatial.
+ *
+ * @return a new array of pre-existing rigid bodies (not null, not empty)
+ */
+ public PhysicsRigidBody[] listRigidBodies() {
+ verifyAddedToSpatial("enumerate rigid bodies");
+
+ int numLinks = countLinks();
+ PhysicsRigidBody[] result = new PhysicsRigidBody[numLinks];
+
+ int linkIndex = 0;
+ if (torsoLink != null) {
+ result[0] = torsoLink.getRigidBody();
+ ++linkIndex;
+ }
+ for (BoneLink boneLink : boneLinkList) {
+ result[linkIndex] = boneLink.getRigidBody();
+ ++linkIndex;
+ }
+ assert linkIndex == numLinks;
+
+ return result;
+ }
+
+ /**
+ * Copy the model's mesh-to-world transform.
+ *
+ * @param storeResult storage for the result (modified if not null)
+ * @return the model's mesh transform (in world coordinates, either
+ * storeResult or a new transform, not null)
+ */
+ Transform meshTransform(Transform storeResult) {
+ Transform result = transformer.getWorldTransform().clone();
+ return result;
+ }
+
+ /**
+ * Calculate the physics transform to match the specified skeleton bone.
+ *
+ * @param bone the skeleton bone to match (not null, unaffected)
+ * @param localOffset the location of the body's center (in the bone's local
+ * coordinates, not null, unaffected)
+ * @param storeResult storage for the result (modified if not null)
+ * @return the calculated physics transform (either storeResult or a new
+ * transform, not null)
+ */
+ Transform physicsTransform(Joint bone, Vector3f localOffset,
+ Transform storeResult) {
+ Transform result
+ = (storeResult == null) ? new Transform() : storeResult;
+ /*
+ * Start with the body's transform in the bone's local coordinates.
+ */
+ result.setTranslation(localOffset);
+ result.setRotation(rotateIdentity);
+ result.setScale(1f);
+ /*
+ * Convert to mesh coordinates.
+ */
+ Transform localToMesh = bone.getModelTransform();
+ result.combineWithParent(localToMesh);
+ /*
+ * Convert to world (physics-space) coordinates.
+ */
+ Transform meshToWorld = meshTransform(null);
+ result.combineWithParent(meshToWorld);
+
+ return result;
+ }
+
+ /**
+ * Rebuild the ragdoll. This is useful if you applied scale to the model
+ * after it was initialized.
+ *
+ * Allowed only when the control IS added to a spatial.
+ */
+ public void rebuild() {
+ verifyAddedToSpatial("rebuild the ragdoll");
+
+ Map saveBones = new HashMap<>(boneLinks);
+ TorsoLink saveTorso = torsoLink;
+
+ Spatial controlledSpatial = getSpatial();
+ removeSpatialData(controlledSpatial);
+ createSpatialData(controlledSpatial);
+
+ for (Map.Entry entry : boneLinks.entrySet()) {
+ String name = entry.getKey();
+ BoneLink newLink = entry.getValue();
+ BoneLink oldLink = saveBones.get(name);
+ newLink.postRebuild(oldLink);
+ }
+ if (torsoLink != null) {
+ torsoLink.postRebuild(saveTorso);
+ }
+ }
+
+ /**
+ * Alter the mass of the specified link.
+ *
+ * @param link the link to modify (not null)
+ * @param mass the desired mass (>0)
+ */
+ public void setMass(PhysicsLink link, float mass) {
+ if (link instanceof BoneLink) {
+ String boneName = link.boneName();
+ setMass(boneName, mass);
+ } else {
+ assert link instanceof TorsoLink;
+ setMass(torsoName, mass);
+ }
+ }
+
+ /**
+ * Verify that this control is ready for dynamic mode, which implies that it
+ * is added to a Spatial.
+ *
+ * @param desiredAction (not null, not empty)
+ */
+ public void verifyReadyForDynamicMode(String desiredAction) {
+ assert desiredAction != null;
+
+ verifyAddedToSpatial(desiredAction);
+
+ if (!isReady) {
+ String message = "Cannot " + desiredAction
+ + " until the physics has been stepped.";
+ throw new IllegalStateException(message);
+ }
+ }
+ // *************************************************************************
+ // new protected methods
+
+ /**
+ * Access the list of bone links in a pre-order, depth-first traversal of
+ * the link hierarchy.
+ *
+ * @return the pre-existing list (not null)
+ */
+ protected List getBoneLinks() {
+ assert boneLinkList != null;
+ return boneLinkList;
+ }
+
+ /**
+ * Verify that this control is added to a Spatial.
+ *
+ * @param desiredAction (not null, not empty)
+ */
+ protected void verifyAddedToSpatial(String desiredAction) {
+ assert desiredAction != null;
+
+ Spatial controlledSpatial = getSpatial();
+ if (controlledSpatial == null) {
+ String message = "Cannot " + desiredAction
+ + " unless the Control is added to a Spatial.";
+ throw new IllegalStateException(message);
+ }
+ }
+ // *************************************************************************
+ // DacConfiguration methods
+
+ /**
+ * Add all managed physics objects to the PhysicsSpace.
+ */
+ @Override
+ protected void addPhysics(PhysicsSpace space) {
+ Vector3f gravity = gravity(null);
+
+ PhysicsRigidBody rigidBody;
+ if (torsoLink != null) {
+ rigidBody = torsoLink.getRigidBody();
+ space.add(rigidBody);
+ rigidBody.setGravity(gravity);
+ }
+
+ for (BoneLink boneLink : boneLinkList) {
+ rigidBody = boneLink.getRigidBody();
+ space.add(rigidBody);
+ rigidBody.setGravity(gravity);
+
+ PhysicsJoint joint = boneLink.getJoint();
+ space.add(joint);
+ }
+ }
+
+ /**
+ * 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);
+ DacLinks originalDac = (DacLinks) original;
+
+ boneLinkList = cloner.clone(boneLinkList);
+
+ boneLinks = new HashMap<>(32);
+ for (Map.Entry entry
+ : originalDac.boneLinks.entrySet()) {
+ String boneName = entry.getKey();
+ BoneLink link = entry.getValue();
+ BoneLink copyLink = cloner.clone(link);
+ boneLinks.put(boneName, copyLink);
+ }
+
+ skeleton = cloner.clone(skeleton);
+ transformer = cloner.clone(transformer);
+ torsoLink = cloner.clone(torsoLink);
+ }
+
+ /**
+ * Create spatial-dependent data. Invoked each time the control is added to
+ * a spatial. Also invoked by {@link #rebuild()}.
+ *
+ * @param spatial the controlled spatial (not null)
+ */
+ @Override
+ protected void createSpatialData(Spatial spatial) {
+ RagUtils.validate(spatial);
+
+ SkinningControl skeletonControl
+ = spatial.getControl(SkinningControl.class);
+ if (skeletonControl == null) {
+ throw new IllegalArgumentException(
+ "The controlled spatial must have a SkinningControl. "
+ + "Make sure the control is there and not on a subnode.");
+ }
+ sortControls(skeletonControl);
+ skeletonControl.setHardwareSkinningPreferred(false);
+ /*
+ * Analyze the model's skeleton.
+ */
+ skeleton = skeletonControl.getArmature();
+ validateSkeleton();
+ String[] tempManagerMap = managerMap(skeleton);
+ int numBones = skeleton.getJointCount();
+ /*
+ * Temporarily set all bones' local translations and rotations to bind.
+ */
+ Transform[] savedTransforms = new Transform[numBones];
+ for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) {
+ Joint bone = skeleton.getJoint(boneIndex);
+ savedTransforms[boneIndex] = bone.getLocalTransform().clone();
+ bone.applyBindPose(); // TODO adjust the scale?
+ }
+ skeleton.update();
+ /*
+ * Find the target meshes and choose the transform spatial.
+ */
+ List targetList = RagUtils.listAnimatedMeshes(spatial, null);
+ Mesh[] targets = new Mesh[targetList.size()];
+ targetList.toArray(targets);
+ transformer = RagUtils.findAnimatedGeometry(spatial);
+ if (transformer == null) {
+ transformer = spatial;
+ }
+ /*
+ * Enumerate mesh-vertex coordinates and assign them to managers.
+ */
+ Map coordsMap
+ = RagUtils.coordsMap(targets, tempManagerMap);
+ /*
+ * Create the torso link.
+ */
+ VectorSet vertexLocations = coordsMap.get(torsoName);
+ createTorsoLink(vertexLocations, targets);
+ /*
+ * Create bone links without joints.
+ */
+ String[] linkedBoneNames = listLinkedBoneNames();
+ for (String boneName : linkedBoneNames) {
+ vertexLocations = coordsMap.get(boneName);
+ createBoneLink(boneName, vertexLocations);
+ }
+ int numLinkedBones = countLinkedBones();
+ assert boneLinks.size() == numLinkedBones;
+ /*
+ * Add joints to connect each BoneLink rigid body with its parent in the
+ * link hierarchy. Also initialize the boneLinkList.
+ */
+ boneLinkList = new ArrayList<>(numLinkedBones);
+ addJoints(torsoLink);
+ assert boneLinkList.size() == numLinkedBones : boneLinkList.size();
+ /*
+ * Restore the skeleton's pose.
+ */
+ for (int boneIndex = 0; boneIndex < numBones; ++boneIndex) {
+ Joint bone = skeleton.getJoint(boneIndex);
+ bone.setLocalTransform(savedTransforms[boneIndex]);
+ }
+ skeleton.update();
+
+ if (added) {
+ addPhysics(space);
+ }
+
+ logger3.log(Level.FINE, "Created ragdoll for skeleton.");
+ }
+
+ /**
+ * Create a shallow clone for the JME cloner.
+ *
+ * @return a new instance
+ */
+ @Override
+ public DacLinks jmeClone() {
+ try {
+ DacLinks clone = (DacLinks) super.clone();
+ return clone;
+ } catch (CloneNotSupportedException exception) {
+ throw new RuntimeException(exception);
+ }
+ }
+
+ /**
+ * Read the mass of the named bone/torso.
+ *
+ * @param boneName the name of the bone/torso (not null)
+ * @return the mass (>0) or NaN if undetermined
+ */
+ @Override
+ public float mass(String boneName) {
+ float mass;
+ if (getSpatial() == null) {
+ mass = super.mass(boneName);
+ } else if (torsoName.equals(boneName)) {
+ PhysicsRigidBody rigidBody = torsoLink.getRigidBody();
+ mass = rigidBody.getMass();
+ } else if (boneLinks.containsKey(boneName)) {
+ BoneLink link = boneLinks.get(boneName);
+ PhysicsRigidBody rigidBody = link.getRigidBody();
+ mass = rigidBody.getMass();
+ } else {
+ String msg = "No bone/torso named " + boneName;
+ throw new IllegalArgumentException(msg);
+ }
+
+ return mass;
+ }
+
+ /**
+ * De-serialize this control, 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 {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+
+ boneLinkList
+ = ic.readSavableArrayList("boneLinkList", null);
+ for (BoneLink link : boneLinkList) {
+ String name = link.boneName();
+ boneLinks.put(name, link);
+ }
+
+ skeleton = (Armature) ic.readSavable("skeleton", null);
+ transformer = (Spatial) ic.readSavable("transformer", null);
+ torsoLink = (TorsoLink) ic.readSavable("torsoLink", null);
+ }
+
+ /**
+ * Remove all managed physics objects from the PhysicsSpace.
+ */
+ @Override
+ protected void removePhysics(PhysicsSpace space) {
+ assert added;
+
+ PhysicsRigidBody rigidBody;
+ if (torsoLink != null) {
+ rigidBody = torsoLink.getRigidBody();
+ space.remove(rigidBody);
+ }
+
+ for (BoneLink boneLink : boneLinks.values()) {
+ rigidBody = boneLink.getRigidBody();
+ space.remove(rigidBody);
+
+ PhysicsJoint joint = boneLink.getJoint();
+ space.remove(joint);
+ }
+ }
+
+ /**
+ * Remove spatial-dependent data. Invoked each time this control is rebuilt
+ * or removed from a spatial.
+ *
+ * @param spat the previously controlled spatial (unused)
+ */
+ @Override
+ protected void removeSpatialData(Spatial spat) {
+ if (added) {
+ removePhysics(space);
+ }
+
+ skeleton = null;
+
+ boneLinks.clear();
+ boneLinkList = null;
+ torsoLink = null;
+ transformer = null;
+ }
+
+ /**
+ * Alter the viscous damping ratio for all rigid bodies, including new ones.
+ *
+ * @param dampingRatio the desired damping ratio (non-negative, 0→no
+ * damping, 1→critically damped, default=0.6)
+ */
+ @Override
+ public void setDamping(float dampingRatio) {
+ super.setDamping(dampingRatio);
+
+ if (getSpatial() != null) {
+ PhysicsRigidBody[] bodies = listRigidBodies();
+ for (PhysicsRigidBody rigidBody : bodies) {
+ rigidBody.setDamping(dampingRatio, dampingRatio);
+ }
+ }
+ }
+
+ /**
+ * 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)
+ */
+ @Override
+ public void setGravity(Vector3f gravity) {
+ super.setGravity(gravity);
+
+ if (getSpatial() != null) { // TODO make sure it's in ragdoll mode
+ PhysicsRigidBody[] bodies = listRigidBodies();
+ for (PhysicsRigidBody rigidBody : bodies) {
+ rigidBody.setGravity(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)
+ */
+ @Override
+ public void setJointLimits(String boneName, RangeOfMotion rom) {
+ if (!hasBoneLink(boneName)) {
+ String msg = "No linked bone named " + boneName;
+ throw new IllegalArgumentException(msg);
+ }
+
+ super.setJointLimits(boneName, rom);
+
+ if (getSpatial() != null) {
+ BoneLink boneLink = findBoneLink(boneName);
+ SixDofJoint joint = (SixDofJoint) boneLink.getJoint();
+ rom.setupJoint(joint);
+ }
+ }
+
+ /**
+ * 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)
+ */
+ @Override
+ public void setMass(String boneName, float mass) {
+ super.setMass(boneName, mass);
+
+ if (getSpatial() != null) {
+ PhysicsRigidBody rigidBody;
+ if (torsoName.equals(boneName)) {
+ rigidBody = torsoLink.getRigidBody();
+ } else {
+ BoneLink link = findBoneLink(boneName);
+ rigidBody = link.getRigidBody();
+ }
+ rigidBody.setMass(mass);
+ }
+ }
+
+ /**
+ * Translate the torso to the specified location.
+ *
+ * @param vec desired location (not null, unaffected)
+ */
+ @Override
+ protected void setPhysicsLocation(Vector3f vec) {
+ torsoLink.getRigidBody().setPhysicsLocation(vec);
+ }
+
+ /**
+ * Rotate the torso to the specified orientation.
+ *
+ * @param quat desired orientation (not null, unaffected)
+ */
+ @Override
+ protected void setPhysicsRotation(Quaternion quat) {
+ torsoLink.getRigidBody().setPhysicsRotation(quat);
+ }
+
+ /**
+ * Update this control. Invoked once per frame during the logical-state
+ * update, provided the control is added to a scene. Do not invoke directly
+ * from user code.
+ *
+ * @param tpf the time interval between frames (in seconds, ≥0)
+ */
+ @Override
+ public void update(float tpf) {
+ verifyAddedToSpatial("update the control");
+ if (!isEnabled()) {
+ return;
+ }
+
+ if (torsoLink != null) {
+ torsoLink.update(tpf);
+ }
+ for (BoneLink boneLink : boneLinkList) {
+ boneLink.update(tpf);
+ }
+ }
+
+ /**
+ * 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);
+
+ int count = countLinkedBones();
+ Savable[] savableArray = new Savable[count];
+ boneLinkList.toArray(savableArray);
+ oc.write(savableArray, "boneLinkList", null);
+
+ oc.write(skeleton, "skeleton", null);
+ oc.write(transformer, "transformer", null);
+ oc.write(torsoLink, "torsoLink", null);
+ }
+ // *************************************************************************
+ // PhysicsTickListener methods
+
+ /**
+ * Callback from Bullet, invoked just after the physics has been stepped.
+ * Used to re-activate any deactivated rigid bodies.
+ *
+ * @param space the space that was just stepped (not null)
+ * @param timeStep the time per physics step (in seconds, ≥0)
+ */
+ @Override
+ public void physicsTick(PhysicsSpace space, float timeStep) {
+ assert space == getPhysicsSpace();
+
+ torsoLink.postTick();
+ for (BoneLink boneLink : boneLinkList) {
+ boneLink.postTick();
+ }
+
+ isReady = true;
+ }
+
+ /**
+ * Callback from Bullet, invoked just before the physics is stepped. A good
+ * time to clear/apply forces.
+ *
+ * @param space the space that is about to be stepped (not null)
+ * @param timeStep the time per physics step (in seconds, ≥0)
+ */
+ @Override
+ public void prePhysicsTick(PhysicsSpace space, float timeStep) {
+ assert space == getPhysicsSpace();
+
+ torsoLink.preTick(timeStep);
+ for (BoneLink boneLink : boneLinkList) {
+ boneLink.preTick(timeStep);
+ }
+ }
+ // *************************************************************************
+ // private methods
+
+ /**
+ * Add joints to connect the named bone/torso link with each of its
+ * children. Also fill in the boneLinkList. Note: recursive!
+ *
+ * @param parentName the parent bone/torso link (not null)
+ */
+ private void addJoints(PhysicsLink parentLink) {
+ List childNames = childNames(parentLink);
+ for (String childName : childNames) {
+ /*
+ * Add the joint and configure its range of motion.
+ * Also initialize the BoneLink's parent and its array
+ * of managed bones.
+ */
+ BoneLink childLink = findBoneLink(childName);
+ childLink.addJoint(parentLink);
+ /*
+ * Add the BoneLink to the pre-order list.
+ */
+ boneLinkList.add(childLink);
+
+ addJoints(childLink);
+ }
+ }
+
+ /**
+ * Enumerate all immediate child BoneLinks of the specified bone/torso link.
+ *
+ * @param link the bone/torso link (not null)
+ * @return a new list of bone names
+ */
+ private List childNames(PhysicsLink link) {
+ assert link != null;
+
+ String linkName;
+ if (link == torsoLink) {
+ linkName = torsoName;
+ } else {
+ linkName = link.boneName();
+ }
+
+ List result = new ArrayList<>(8);
+ for (String childName : listLinkedBoneNames()) {
+ Joint bone = findBone(childName);
+ Joint parent = bone.getParent();
+ if (parent != null && findManager(parent).equals(linkName)) {
+ result.add(childName);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Create a jointless BoneLink for the named bone, and add it to the
+ * boneLinks map.
+ *
+ * @param boneName the name of the bone to be linked (not null)
+ * @param vertexLocations the set of vertex locations (not null, not empty)
+ */
+ private void createBoneLink(String boneName, VectorSet vertexLocations) {
+ Joint bone = findBone(boneName);
+ Transform boneToMesh = bone.getModelTransform();
+ Transform meshToBone = boneToMesh.invert();
+ //logger3.log(Level.INFO, "meshToBone = {0}", meshToBone);
+ /*
+ * Create the CollisionShape and locate the center of mass.
+ */
+ CollisionShape shape;
+ Vector3f center;
+ if (vertexLocations == null || vertexLocations.numVectors() == 0) {
+ throw new IllegalStateException("no vertex for " + boneName);
+ } else {
+ center = vertexLocations.mean(null);
+ center.subtractLocal(bone.getModelTransform().getTranslation());
+ shape = createShape(meshToBone, center, vertexLocations);
+ }
+
+ meshToBone.getTranslation().zero();
+ float mass = super.mass(boneName);
+ Vector3f offset = meshToBone.transformVector(center, null);
+ BoneLink link = new BoneLink(this, bone, shape, mass, offset);
+ boneLinks.put(boneName, link);
+ }
+
+ /**
+ * Create a CollisionShape for the specified transform, center, and vertex
+ * locations.
+ *
+ * @param vertexToShape the transform from vertex coordinates to de-scaled
+ * shape coordinates (not null, unaffected)
+ * @param center the location of the shape's center, in vertex coordinates
+ * (not null, unaffected)
+ * @param vertexLocations the set of vertex locations (not null, not empty,
+ * TRASHED)
+ * @return a new CollisionShape
+ */
+ private CollisionShape createShape(Transform vertexToShape, Vector3f center,
+ VectorSet vertexLocations) {
+ int numVectors = vertexLocations.numVectors();
+ assert numVectors > 0 : numVectors;
+
+ Vector3f tempLocation = new Vector3f();
+ int numPoints = vertexLocations.numVectors();
+ float points[] = new float[3 * numPoints];
+ FloatBuffer buffer = vertexLocations.toBuffer();
+ buffer.rewind();
+ int floatIndex = 0;
+ while (buffer.hasRemaining()) {
+ tempLocation.x = buffer.get();
+ tempLocation.y = buffer.get();
+ tempLocation.z = buffer.get();
+ /*
+ * Translate so that vertex coordinates are relative to
+ * the shape's center.
+ */
+ tempLocation.subtractLocal(center);
+ /*
+ * Transform vertex coordinates to de-scaled shape coordinates.
+ */
+ vertexToShape.transformVector(tempLocation, tempLocation);
+ points[floatIndex] = tempLocation.x;
+ points[floatIndex + 1] = tempLocation.y;
+ points[floatIndex + 2] = tempLocation.z;
+ floatIndex += 3;
+ }
+
+ CollisionShape result = new HullCollisionShape(points);
+
+ return result;
+ }
+
+ /**
+ * Create the TorsoLink.
+ *
+ * @param vertexLocations the set of vertex locations (not null, not empty)
+ * @param meshes array of animated meshes to use (not null, unaffected)
+ */
+ private void createTorsoLink(VectorSet vertexLocations, Mesh[] meshes) {
+ if (vertexLocations == null || vertexLocations.numVectors() == 0) {
+ throw new IllegalArgumentException(
+ "No mesh vertices for the torso."
+ + " Make sure the root bone is not linked.");
+ }
+ /*
+ * Create the CollisionShape.
+ */
+ Joint bone = RagUtils.findMainBone(skeleton, meshes);
+ assert bone.getParent() == null;
+ Transform boneToMesh = bone.getModelTransform();
+ Transform meshToBone = boneToMesh.invert();
+ Vector3f center = vertexLocations.mean(null);
+ center.subtractLocal(boneToMesh.getTranslation());
+ CollisionShape shape = createShape(meshToBone, center, vertexLocations);
+
+ meshToBone.getTranslation().zero();
+ Vector3f offset = meshToBone.transformVector(center, null);
+
+ Transform meshToModel;
+ Spatial cgm = getSpatial();
+ if (cgm instanceof Node) {
+ Transform modelToMesh
+ = RagUtils.relativeTransform(transformer, (Node) cgm, null);
+ meshToModel = modelToMesh.invert();
+ } else {
+ meshToModel = transformIdentity;
+ }
+
+ float mass = super.mass(torsoName);
+ torsoLink = new TorsoLink(this, bone, shape, mass, meshToModel, offset);
+ }
+
+ /**
+ * Sort the controls of the controlled spatial, such that this control will
+ * come BEFORE the specified SkinningControl.
+ *
+ * @param skinningControl (not null)
+ */
+ private void sortControls(SkinningControl skinningControl) {
+ assert skinningControl != null;
+
+ int dacIndex = RagUtils.findIndex(spatial, this);
+ assert dacIndex != -1;
+ int scIndex = RagUtils.findIndex(spatial, skinningControl);
+ assert scIndex != -1;
+ assert dacIndex != scIndex;
+
+ if (dacIndex > scIndex) {
+ /*
+ * Remove the SkinningControl and re-add it to make sure it will get
+ * updated *after* this control.
+ */
+ spatial.removeControl(skinningControl);
+ spatial.addControl(skinningControl);
+
+ dacIndex = RagUtils.findIndex(spatial, this);
+ assert dacIndex != -1;
+ scIndex = RagUtils.findIndex(spatial, skinningControl);
+ assert scIndex != -1;
+ assert dacIndex < scIndex;
+ }
+ }
+
+ /**
+ * Validate the model's skeleton.
+ */
+ private void validateSkeleton() {
+ RagUtils.validate(skeleton);
+
+ for (String boneName : listLinkedBoneNames()) {
+ Joint bone = findBone(boneName);
+ if (bone == null) {
+ String msg = String.format(
+ "Linked bone %s not found in skeleton.", boneName);
+ throw new IllegalArgumentException(msg);
+ }
+ if (bone.getParent() == null) {
+ logger3.log(Level.WARNING, "Linked bone {0} is a root bone.",
+ boneName);
+ }
+ }
+ }
+}
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/DynamicAnimControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/animation/DynamicAnimControl.java
new file mode 100644
index 0000000000..7ae598f0be
--- /dev/null
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/animation/DynamicAnimControl.java
@@ -0,0 +1,530 @@
+/*
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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
+ */
+ private float ragdollMass = 0f;
+ /**
+ * list of registered collision listeners
+ */
+ private List 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 (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
+ *
+ * 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.
+ *
+ * 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. Note: recursive!
+ *
+ * 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");
+
+ 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!
+ *
+ * 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 into dynamic
+ * mode. Note: recursive!
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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. 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 links = listLinks(PhysicsLink.class);
+ for (PhysicsLink link : links) {
+ 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;
+ }
+}
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/KinematicSubmode.java b/jme3-bullet/src/common/java/com/jme3/bullet/animation/KinematicSubmode.java
new file mode 100644
index 0000000000..95ffa186bf
--- /dev/null
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/animation/KinematicSubmode.java
@@ -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.
+ *
+ * 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;
+}
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/PhysicsLink.java b/jme3-bullet/src/common/java/com/jme3/bullet/animation/PhysicsLink.java
new file mode 100644
index 0000000000..22ed5a4da5
--- /dev/null
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/animation/PhysicsLink.java
@@ -0,0 +1,634 @@
+/*
+ * 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 BoneLink and TorsoLink. The links in each DynamicAnimControl form a
+ * hierarchy with the TorsoLink at its root.
+ *
+ * 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 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;
+ }
+ }
+
+ /**
+ * 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);
+ }
+}
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/RagUtils.java b/jme3-bullet/src/common/java/com/jme3/bullet/animation/RagUtils.java
new file mode 100644
index 0000000000..89b112283f
--- /dev/null
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/animation/RagUtils.java
@@ -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.
+ *
+ * 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 coordsMap(Mesh[] meshes,
+ String[] managerMap) {
+ float[] wArray = new float[4];
+ int[] iArray = new int[4];
+ Vector3f bindPosition = new Vector3f();
+ Map 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 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 listAnimatedMeshes(Spatial subtree,
+ List 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 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 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 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 addResult) {
+ assert bone != null;
+ addResult.add(bone);
+ List 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 weightMap = weightMap(iArray, wArray, managerMap);
+
+ float bestTotalWeight = Float.NEGATIVE_INFINITY;
+ String bestName = null;
+ for (Map.Entry 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 listGeometries(Spatial subtree,
+ List addResult) {
+ List result = (addResult == null) ? new ArrayList(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 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 preOrderJoints(Armature skeleton) {
+ int numBones = skeleton.getJointCount();
+ List 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 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.
+ *
+ * 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 weightMap(int[] biArray,
+ float[] bwArray, String[] managerMap) {
+ assert biArray.length == 4;
+ assert bwArray.length == 4;
+
+ Map 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;
+ }
+}
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/RagdollCollisionListener.java b/jme3-bullet/src/common/java/com/jme3/bullet/animation/RagdollCollisionListener.java
new file mode 100644
index 0000000000..3e0eccdb48
--- /dev/null
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/animation/RagdollCollisionListener.java
@@ -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.
+ *
+ * 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);
+}
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/RangeOfMotion.java b/jme3-bullet/src/common/java/com/jme3/bullet/animation/RangeOfMotion.java
new file mode 100644
index 0000000000..b79d8519c7
--- /dev/null
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/animation/RangeOfMotion.java
@@ -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)}.
+ *
+ * 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);
+ }
+}
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/TorsoLink.java b/jme3-bullet/src/common/java/com/jme3/bullet/animation/TorsoLink.java
new file mode 100644
index 0000000000..c44868116a
--- /dev/null
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/animation/TorsoLink.java
@@ -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.
+ *
+ * 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;
+ }
+}
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/VectorSet.java b/jme3-bullet/src/common/java/com/jme3/bullet/animation/VectorSet.java
new file mode 100644
index 0000000000..482f957396
--- /dev/null
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/animation/VectorSet.java
@@ -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.
+ *
+ * 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 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;
+ }
+}
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/package-info.java b/jme3-bullet/src/common/java/com/jme3/bullet/animation/package-info.java
new file mode 100644
index 0000000000..ac9b2f72ae
--- /dev/null
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/animation/package-info.java
@@ -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;
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java
index f858d0e00e..739701d9fc 100644
--- a/jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java
@@ -246,6 +246,13 @@ public void setSpatial(Spatial spatial) {
setPhysicsRotation(getSpatialRotation());
}
+ /**
+ * @return returns the spatial the control is added to, or null if the control is not attached to a spatial yet.
+ */
+ public Spatial getSpatial(){
+ return this.spatial;
+ }
+
/**
* Enable or disable this control.
*
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java
index 85dcf1a955..2ee7fbe622 100644
--- a/jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java
@@ -102,6 +102,7 @@ public Object jmeClone() {
control.setCcdSweptSphereRadius(getCcdSweptSphereRadius());
control.setCollideWithGroups(getCollideWithGroups());
control.setCollisionGroup(getCollisionGroup());
+ control.setContactResponse(isContactResponse());
control.setFallSpeed(getFallSpeed());
control.setGravity(getGravity());
control.setJumpSpeed(getJumpSpeed());
@@ -128,6 +129,13 @@ public void setSpatial(Spatial spatial) {
setPhysicsLocation(getSpatialTranslation());
}
+ /**
+ * @return returns the spatial the control is added to, or null if the control is not attached to a spatial yet.
+ */
+ public Spatial getSpatial(){
+ return this.spatial;
+ }
+
public void setEnabled(boolean enabled) {
this.enabled = enabled;
if (space != null) {
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java
index 7ff51670d2..ca3fc72543 100644
--- a/jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java
@@ -206,6 +206,13 @@ public void setSpatial(Spatial spatial) {
setPhysicsRotation(getSpatialRotation());
}
+ /**
+ * @return returns the spatial the control is added to, or null if the control is not attached to a spatial yet.
+ */
+ public Spatial getSpatial(){
+ return this.spatial;
+ }
+
/**
* Enable or disable this control.
*
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java
index 6b8714da8d..cee3401e1a 100644
--- a/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java
@@ -150,6 +150,7 @@ public Object jmeClone() {
control.setCcdSweptSphereRadius(getCcdSweptSphereRadius());
control.setCollideWithGroups(getCollideWithGroups());
control.setCollisionGroup(getCollisionGroup());
+ control.setContactResponse(isContactResponse());
control.setDamping(getLinearDamping(), getAngularDamping());
control.setFriction(getFriction());
control.setGravity(getGravity());
@@ -206,6 +207,13 @@ public void setSpatial(Spatial spatial) {
setPhysicsRotation(getSpatialRotation());
}
+ /**
+ * @return returns the spatial the control is added to, or null if the control is not attached to a spatial yet.
+ */
+ public Spatial getSpatial(){
+ return this.spatial;
+ }
+
/**
* Set the collision shape based on the controlled spatial and its
* descendents.
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java
index ccc90091a9..51210c6a7a 100644
--- a/jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java
@@ -168,6 +168,7 @@ public Object jmeClone() {
control.setCcdSweptSphereRadius(getCcdSweptSphereRadius());
control.setCollideWithGroups(getCollideWithGroups());
control.setCollisionGroup(getCollisionGroup());
+ control.setContactResponse(isContactResponse());
control.setDamping(getLinearDamping(), getAngularDamping());
control.setFriction(getFriction());
control.setGravity(getGravity());
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/RagdollUtils.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/RagdollUtils.java
index 419c337bca..9e354a1fa0 100644
--- a/jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/RagdollUtils.java
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/RagdollUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2018 jMonkeyEngine
+ * Copyright (c) 2009-2019 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -41,9 +41,12 @@
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.Spatial;
+import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Type;
+import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
import java.util.*;
/**
@@ -254,7 +257,8 @@ public static HullCollisionShape makeShapeFromVerticeWeights(Spatial model,
private static List getPoints(Mesh mesh, int boneIndex, Vector3f initialScale, Vector3f offset, float weightThreshold) {
FloatBuffer vertices = mesh.getFloatBuffer(Type.Position);
- ByteBuffer boneIndices = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
+ VertexBuffer biBuf = mesh.getBuffer(VertexBuffer.Type.BoneIndex);
+ Buffer boneIndices = biBuf.getDataReadOnly();
FloatBuffer boneWeight = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
vertices.rewind();
@@ -270,7 +274,8 @@ private static List getPoints(Mesh mesh, int boneIndex, Vector3f initialS
boolean add = false;
int start = i / 3 * 4;
for (k = start; k < start + 4; k++) {
- if (boneIndices.get(k) == boneIndex && boneWeight.get(k) >= weightThreshold) {
+ if (readIndex(boneIndices, k) == boneIndex
+ && boneWeight.get(k) >= weightThreshold) {
add = true;
break;
}
@@ -349,8 +354,8 @@ public static void setUserControl(Bone bone, boolean bool) {
public static boolean hasVertices(int boneIndex, Mesh[] targets,
float weightThreshold) {
for (Mesh mesh : targets) {
- ByteBuffer boneIndices
- = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
+ VertexBuffer biBuf = mesh.getBuffer(VertexBuffer.Type.BoneIndex);
+ Buffer boneIndices = biBuf.getDataReadOnly();
FloatBuffer boneWeight
= (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
@@ -361,7 +366,7 @@ public static boolean hasVertices(int boneIndex, Mesh[] targets,
for (int i = 0; i < vertexComponents; i += 3) {
int start = i / 3 * 4;
for (int k = start; k < start + 4; k++) {
- if (boneIndices.get(k) == boneIndex
+ if (readIndex(boneIndices, k) == boneIndex
&& boneWeight.get(k) >= weightThreshold) {
return true;
}
@@ -371,4 +376,29 @@ public static boolean hasVertices(int boneIndex, Mesh[] targets,
return false;
}
+
+ /**
+ * Read an index from a buffer.
+ *
+ * @param buffer a buffer of bytes or shorts (not null)
+ * @param k the position from which the index will be read
+ * @return the index value (≥0)
+ */
+ public static int readIndex(Buffer buffer, int k) {
+ int result;
+ if (buffer instanceof ByteBuffer) {
+ ByteBuffer byteBuffer = (ByteBuffer) buffer;
+ byte b = byteBuffer.get(k);
+ result = 0xff & b;
+ } else if (buffer instanceof ShortBuffer) {
+ ShortBuffer shortBuffer = (ShortBuffer) buffer;
+ short s = shortBuffer.get(k);
+ result = 0xffff & s;
+ } else {
+ throw new IllegalArgumentException(buffer.getClass().getName());
+ }
+
+ assert result >= 0 : result;
+ return result;
+ }
}
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletCharacterDebugControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletCharacterDebugControl.java
index 687d6ba27c..514e79b165 100644
--- a/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletCharacterDebugControl.java
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletCharacterDebugControl.java
@@ -67,6 +67,11 @@ public class BulletCharacterDebugControl extends AbstractPhysicsDebugControl {
* geometry to visualize myShape (not null)
*/
protected Spatial geom;
+ /**
+ * physics scale for which geom was generated
+ */
+ final private Vector3f oldScale = new Vector3f();
+
/**
* Instantiate an enabled control to visualize the specified character.
*
@@ -78,7 +83,10 @@ public BulletCharacterDebugControl(BulletDebugAppState debugAppState, PhysicsCha
super(debugAppState);
this.body = body;
myShape = body.getCollisionShape();
- this.geom = DebugShapeFactory.getDebugShape(body.getCollisionShape());
+ oldScale.set(myShape.getScale());
+
+ this.geom = DebugShapeFactory.getDebugShape(myShape);
+ this.geom.setName(body.toString());
geom.setMaterial(debugAppState.DEBUG_PINK);
}
@@ -110,15 +118,24 @@ public void setSpatial(Spatial spatial) {
*/
@Override
protected void controlUpdate(float tpf) {
- if(myShape != body.getCollisionShape()){
- Node node = (Node) this.spatial;
+ CollisionShape newShape = body.getCollisionShape();
+ Vector3f newScale = newShape.getScale();
+ if (myShape != newShape || !oldScale.equals(newScale)) {
+ myShape = newShape;
+ oldScale.set(newScale);
+
+ Node node = (Node) spatial;
node.detachChild(geom);
- geom = DebugShapeFactory.getDebugShape(body.getCollisionShape());
- geom.setMaterial(debugAppState.DEBUG_PINK);
+
+ geom = DebugShapeFactory.getDebugShape(myShape);
+ geom.setName(body.toString());
+
node.attachChild(geom);
}
- applyPhysicsTransform(body.getPhysicsLocation(location), Quaternion.IDENTITY);
- geom.setLocalScale(body.getCollisionShape().getScale());
+ geom.setMaterial(debugAppState.DEBUG_PINK);
+
+ body.getPhysicsLocation(location);
+ applyPhysicsTransform(location, Quaternion.IDENTITY);
}
/**
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletGhostObjectDebugControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletGhostObjectDebugControl.java
index 09f24c7fc7..bdb6932d7a 100644
--- a/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletGhostObjectDebugControl.java
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletGhostObjectDebugControl.java
@@ -70,7 +70,10 @@ public class BulletGhostObjectDebugControl extends AbstractPhysicsDebugControl {
* geometry to visualize myShape (not null)
*/
protected Spatial geom;
-
+ /**
+ * physics scale for which geom was generated
+ */
+ final private Vector3f oldScale = new Vector3f();
/**
* Instantiate an enabled control to visualize the specified ghost object.
*
@@ -81,8 +84,9 @@ public BulletGhostObjectDebugControl(BulletDebugAppState debugAppState, PhysicsG
super(debugAppState);
this.body = body;
myShape = body.getCollisionShape();
- this.geom = DebugShapeFactory.getDebugShape(body.getCollisionShape());
- this.geom.setName(body.toString());
+ oldScale.set(myShape.getScale());
+
+ this.geom = DebugShapeFactory.getDebugShape(myShape);
this.geom.setName(body.toString());
geom.setMaterial(debugAppState.DEBUG_YELLOW);
}
@@ -115,15 +119,25 @@ public void setSpatial(Spatial spatial) {
*/
@Override
protected void controlUpdate(float tpf) {
- if (myShape != body.getCollisionShape()) {
- Node node = (Node) this.spatial;
+ CollisionShape newShape = body.getCollisionShape();
+ Vector3f newScale = newShape.getScale();
+ if (myShape != newShape || !oldScale.equals(newScale)) {
+ myShape = newShape;
+ oldScale.set(newScale);
+
+ Node node = (Node) spatial;
node.detachChild(geom);
- geom = DebugShapeFactory.getDebugShape(body.getCollisionShape());
- geom.setMaterial(debugAppState.DEBUG_YELLOW);
+
+ geom = DebugShapeFactory.getDebugShape(myShape);
+ geom.setName(body.toString());
+
node.attachChild(geom);
}
- applyPhysicsTransform(body.getPhysicsLocation(location), body.getPhysicsRotation(rotation));
- geom.setLocalScale(body.getCollisionShape().getScale());
+ geom.setMaterial(debugAppState.DEBUG_YELLOW);
+
+ body.getPhysicsLocation(location);
+ body.getPhysicsRotation(rotation);
+ applyPhysicsTransform(location, rotation);
}
/**
diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletRigidBodyDebugControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletRigidBodyDebugControl.java
index 83e5b786fa..727deebc33 100644
--- a/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletRigidBodyDebugControl.java
+++ b/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletRigidBodyDebugControl.java
@@ -70,6 +70,10 @@ public class BulletRigidBodyDebugControl extends AbstractPhysicsDebugControl {
* geometry to visualize myShape (not null)
*/
protected Spatial geom;
+ /**
+ * physics scale for which geom was generated
+ */
+ final private Vector3f oldScale = new Vector3f();
/**
* Instantiate an enabled control to visualize the specified body.
@@ -81,7 +85,9 @@ public BulletRigidBodyDebugControl(BulletDebugAppState debugAppState, PhysicsRig
super(debugAppState);
this.body = body;
myShape = body.getCollisionShape();
- this.geom = DebugShapeFactory.getDebugShape(body.getCollisionShape());
+ oldScale.set(myShape.getScale());
+
+ this.geom = DebugShapeFactory.getDebugShape(myShape);
this.geom.setName(body.toString());
geom.setMaterial(debugAppState.DEBUG_BLUE);
}
@@ -114,10 +120,18 @@ public void setSpatial(Spatial spatial) {
*/
@Override
protected void controlUpdate(float tpf) {
- if(myShape != body.getCollisionShape()){
- Node node = (Node) this.spatial;
+ CollisionShape newShape = body.getCollisionShape();
+ Vector3f newScale = newShape.getScale();
+ if (myShape != newShape || !oldScale.equals(newScale)) {
+ myShape = newShape;
+ oldScale.set(newScale);
+
+ Node node = (Node) spatial;
node.detachChild(geom);
- geom = DebugShapeFactory.getDebugShape(body.getCollisionShape());
+
+ geom = DebugShapeFactory.getDebugShape(myShape);
+ geom.setName(body.toString());
+
node.attachChild(geom);
}
if(body.isActive()){
@@ -125,8 +139,10 @@ protected void controlUpdate(float tpf) {
}else{
geom.setMaterial(debugAppState.DEBUG_BLUE);
}
- applyPhysicsTransform(body.getPhysicsLocation(location), body.getPhysicsRotation(rotation));
- geom.setLocalScale(body.getCollisionShape().getScale());
+
+ body.getPhysicsLocation(location);
+ body.getPhysicsRotation(rotation);
+ applyPhysicsTransform(location, rotation);
}
/**
diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java b/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java
index ea5f74518d..f2d709131b 100644
--- a/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java
+++ b/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2018 jMonkeyEngine
+ * Copyright (c) 2009-2019 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -55,7 +55,6 @@
import java.util.List;
import java.util.Map;
import java.util.Comparator;
-import java.util.Deque;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -1002,7 +1001,7 @@ public void removeCollisionGroupListener(int collisionGroup) {
* unaffected)
* @return a new list of results (not null)
*/
- public List rayTest(Vector3f from, Vector3f to) {
+ public List rayTest(Vector3f from, Vector3f to) {
List results = new ArrayList();
rayTest(from, to, results);
@@ -1019,7 +1018,7 @@ public List rayTest(Vector3f from, Vector3f to) {
* unaffected)
* @return a new list of results (not null)
*/
- public List rayTestRaw(Vector3f from, Vector3f to) {
+ public List rayTestRaw(Vector3f from, Vector3f to) {
List results = new ArrayList();
rayTestRaw(from, to, results);
diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/CollisionFlag.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/CollisionFlag.java
new file mode 100644
index 0000000000..6445af53f3
--- /dev/null
+++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/CollisionFlag.java
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+package com.jme3.bullet.collision;
+
+/**
+ * Named collision flags for a {@link PhysicsCollisionObject}. Values must agree
+ * with those in BulletCollision/CollisionDispatch/btCollisionObject.h
+ *
+ * @author Stephen Gold sgold@sonic.net
+ * @see PhysicsCollisionObject#getCollisionFlags(long)
+ */
+public class CollisionFlag {
+ /**
+ * flag for a static object
+ */
+ final public static int STATIC_OBJECT = 0x1;
+ /**
+ * flag for a kinematic object
+ */
+ final public static int KINEMATIC_OBJECT = 0x2;
+ /**
+ * flag for an object with no contact response, such as a PhysicsGhostObject
+ */
+ final public static int NO_CONTACT_RESPONSE = 0x4;
+ /**
+ * flag to enable a custom material callback for per-triangle
+ * friction/restitution (not used by JME)
+ */
+ final public static int CUSTOM_MATERIAL_CALLBACK = 0x8;
+ /**
+ * flag for a character object, such as a PhysicsCharacter
+ */
+ final public static int CHARACTER_OBJECT = 0x10;
+ /**
+ * flag to disable debug visualization (not used by JME)
+ */
+ final public static int DISABLE_VISUALIZE_OBJECT = 0x20;
+ /**
+ * flag to disable parallel/SPU processing (not used by JME)
+ */
+ final public static int DISABLE_SPU_COLLISION_PROCESSING = 0x40;
+ /**
+ * flag not used by JME
+ */
+ final public static int HAS_CONTACT_STIFFNESS_DAMPING = 0x80;
+ /**
+ * flag not used by JME
+ */
+ final public static int HAS_CUSTOM_DEBUG_RENDERING_COLOR = 0x100;
+ /**
+ * flag not used by JME
+ */
+ final public static int HAS_FRICTION_ANCHOR = 0x200;
+ /**
+ * flag not used by JME
+ */
+ final public static int HAS_COLLISION_SOUND_TRIGGER = 0x400;
+}
diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionObject.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionObject.java
index 443433623d..ea8ced87c8 100644
--- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionObject.java
+++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionObject.java
@@ -254,6 +254,17 @@ public Object getUserObject() {
return userObject;
}
+ /**
+ * Test whether this object responds to contact with other objects.
+ *
+ * @return true if responsive, otherwise false
+ */
+ public boolean isContactResponse() {
+ int flags = getCollisionFlags(objectId);
+ boolean result = (flags & CollisionFlag.NO_CONTACT_RESPONSE) == 0x0;
+ return result;
+ }
+
/**
* Associate a user object (such as a Spatial) with this collision object.
*
@@ -314,6 +325,24 @@ public void read(JmeImporter e) throws IOException {
collisionShape = shape;
}
+ /**
+ * Read the collision flags of this object. Subclasses are responsible for
+ * cloning/reading/writing these flags.
+ *
+ * @param objectId the ID of the btCollisionObject (not zero)
+ * @return the collision flags (bit mask)
+ */
+ native protected int getCollisionFlags(long objectId);
+
+ /**
+ * Alter the collision flags of this object. Subclasses are responsible for
+ * cloning/reading/writing these flags.
+ *
+ * @param objectId the ID of the btCollisionObject (not zero)
+ * @param desiredFlags the desired collision flags (bit mask)
+ */
+ native protected void setCollisionFlags(long objectId, int desiredFlags);
+
/**
* Finalize this collision object just before it is destroyed. Should be
* invoked only by a subclass or by the garbage collector.
diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CollisionShape.java
index ea6f11be9c..acdd6ed04d 100644
--- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CollisionShape.java
+++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CollisionShape.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2018 jMonkeyEngine
+ * Copyright (c) 2009-2019 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -49,6 +49,11 @@
*/
public abstract class CollisionShape implements Savable {
+ /**
+ * default margin for new non-sphere/non-capsule shapes (in physics-space
+ * units, >0, default=0.04)
+ */
+ private static float defaultMargin = 0.04f;
/**
* unique identifier of the btCollisionShape
*
@@ -63,7 +68,7 @@ public abstract class CollisionShape implements Savable {
/**
* copy of collision margin (in physics-space units, >0, default=0)
*/
- protected float margin = 0.0f;
+ protected float margin = defaultMargin;
public CollisionShape() {
}
@@ -135,6 +140,27 @@ public float getMargin() {
private native float getMargin(long objectId);
+ /**
+ * Alter the default margin for new shapes that are neither capsules nor
+ * spheres.
+ *
+ * @param margin the desired margin distance (in physics-space units, >0,
+ * default=0.04)
+ */
+ public static void setDefaultMargin(float margin) {
+ defaultMargin = margin;
+ }
+
+ /**
+ * Read the default margin for new shapes.
+ *
+ * @return margin the default margin distance (in physics-space units,
+ * >0)
+ */
+ public static float getDefaultMargin() {
+ return defaultMargin;
+ }
+
/**
* Alter the collision margin of this shape. CAUTION: Margin is applied
* differently, depending on the type of shape. Generally the collision
@@ -145,7 +171,7 @@ public float getMargin() {
* compound shapes) changes can have unintended consequences.
*
* @param margin the desired margin distance (in physics-space units, >0,
- * default=0)
+ * default=0.04)
*/
public void setMargin(float margin) {
setMargin(objectId, margin);
diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java
index dead3294ca..60089fddeb 100644
--- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java
+++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java
@@ -33,7 +33,6 @@
import java.io.IOException;
import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsCharacter.java b/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsCharacter.java
index 148199b75b..3824462e92 100644
--- a/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsCharacter.java
+++ b/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsCharacter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2018 jMonkeyEngine
+ * Copyright (c) 2009-2019 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -31,6 +31,7 @@
*/
package com.jme3.bullet.objects;
+import com.jme3.bullet.collision.CollisionFlag;
import com.jme3.bullet.collision.PhysicsCollisionObject;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.export.InputCapsule;
@@ -50,7 +51,6 @@
* @author normenhansen
*/
public class PhysicsCharacter extends PhysicsCollisionObject {
-
/**
* Unique identifier of btKinematicCharacterController (as opposed to its
* collision object, which is a ghost). Constructors are responsible for
@@ -88,6 +88,12 @@ public PhysicsCharacter(CollisionShape shape, float stepHeight) {
// }
this.stepHeight = stepHeight;
buildObject();
+ /*
+ * The default gravity for a Bullet btKinematicCharacterController
+ * is (0,0,-29.4), which makes no sense for JME.
+ * So override the default.
+ */
+ setGravity(new Vector3f(0f, -29.4f, 0f));
}
/**
@@ -289,14 +295,14 @@ public float getJumpSpeed() {
/**
* @deprecated Deprecated in bullet 2.86.1. Use setGravity(Vector3f)
* instead.
- * @param value the desired upward component of the acceleration (typically
- * negative)
+ * @param value the desired downward (-Y) component of the acceleration
+ * (typically positive)
*/
@Deprecated
public void setGravity(float value) {
- setGravity(new Vector3f(0,value,0));
+ setGravity(new Vector3f(0, -value, 0));
}
-
+
/**
* Alter this character's gravitational acceleration.
*
@@ -311,11 +317,12 @@ public void setGravity(Vector3f value) {
/**
* @deprecated Deprecated in bullet 2.86.1. Use getGravity(Vector3f)
* instead.
- * @return the upward component of the acceleration (typically negative)
+ * @return the downward (-Y) component of the acceleration (typically
+ * positive)
*/
@Deprecated
public float getGravity() {
- return getGravity(null).y;
+ return -getGravity(null).y;
}
/**
@@ -456,6 +463,22 @@ public float getMaxSlope() {
private native float getMaxSlope(long characterId);
+ /**
+ * Enable/disable this character's contact response.
+ *
+ * @param responsive true to respond to contacts, false to ignore them
+ * (default=true)
+ */
+ public void setContactResponse(boolean responsive) {
+ int flags = getCollisionFlags(objectId);
+ if (responsive) {
+ flags &= ~CollisionFlag.NO_CONTACT_RESPONSE;
+ } else {
+ flags |= CollisionFlag.NO_CONTACT_RESPONSE;
+ }
+ setCollisionFlags(objectId, flags);
+ }
+
/**
* Test whether this character is on the ground.
*
@@ -472,10 +495,15 @@ public boolean onGround() {
*/
@Deprecated
public void jump() {
- jump(Vector3f.UNIT_Y);
+ jump(Vector3f.ZERO);
+ /*
+ * The zero vector is treated as a special case
+ * by Bullet's btKinematicCharacterController::jump(),
+ * causing the character to jump in its "up" direction
+ * with the pre-set speed.
+ */
}
-
-
+
/**
* Jump in the specified direction.
*
diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java b/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java
index 303060fe3f..2cbbf3c42d 100644
--- a/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java
+++ b/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java
@@ -32,6 +32,7 @@
package com.jme3.bullet.objects;
import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.CollisionFlag;
import com.jme3.bullet.collision.PhysicsCollisionObject;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.collision.shapes.MeshCollisionShape;
@@ -56,7 +57,6 @@
* @author normenhansen
*/
public class PhysicsRigidBody extends PhysicsCollisionObject {
-
/**
* motion state
*/
@@ -370,6 +370,22 @@ public boolean isKinematic() {
return kinematic;
}
+ /**
+ * Enable/disable this body's contact response.
+ *
+ * @param responsive true to respond to contacts, false to ignore them
+ * (default=true)
+ */
+ public void setContactResponse(boolean responsive) {
+ int flags = getCollisionFlags(objectId);
+ if (responsive) {
+ flags &= ~CollisionFlag.NO_CONTACT_RESPONSE;
+ } else {
+ flags |= CollisionFlag.NO_CONTACT_RESPONSE;
+ }
+ setCollisionFlags(objectId, flags);
+ }
+
/**
* Alter the radius of the swept sphere used for continuous collision
* detection (CCD).
@@ -993,6 +1009,7 @@ public void write(JmeExporter e) throws IOException {
capsule.write(getMass(), "mass", 1.0f);
capsule.write(getGravity(), "gravity", Vector3f.ZERO);
+ capsule.write(isContactResponse(), "contactResponse", true);
capsule.write(getFriction(), "friction", 0.5f);
capsule.write(getRestitution(), "restitution", 0);
Vector3f angularFactor = getAngularFactor(null);
@@ -1014,6 +1031,8 @@ public void write(JmeExporter e) throws IOException {
capsule.write(getPhysicsLocation(new Vector3f()), "physicsLocation", new Vector3f());
capsule.write(getPhysicsRotationMatrix(new Matrix3f()), "physicsRotation", new Matrix3f());
+ capsule.write(getLinearVelocity(), "linearVelocity", null);
+ capsule.write(getAngularVelocity(), "angularVelocity", null);
capsule.writeSavableArrayList(joints, "joints", null);
}
@@ -1033,6 +1052,7 @@ public void read(JmeImporter e) throws IOException {
this.mass = mass;
rebuildRigidBody();
setGravity((Vector3f) capsule.readSavable("gravity", Vector3f.ZERO.clone()));
+ setContactResponse(capsule.readBoolean("contactResponse", true));
setFriction(capsule.readFloat("friction", 0.5f));
setKinematic(capsule.readBoolean("kinematic", false));
@@ -1051,6 +1071,8 @@ public void read(JmeImporter e) throws IOException {
setPhysicsLocation((Vector3f) capsule.readSavable("physicsLocation", new Vector3f()));
setPhysicsRotation((Matrix3f) capsule.readSavable("physicsRotation", new Matrix3f()));
+ setLinearVelocity((Vector3f) capsule.readSavable("linearVelocity", new Vector3f()));
+ setAngularVelocity((Vector3f) capsule.readSavable("angularVelocity", new Vector3f()));
joints = capsule.readSavableArrayList("joints", null);
}
diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/util/DebugShapeFactory.java b/jme3-bullet/src/main/java/com/jme3/bullet/util/DebugShapeFactory.java
index 9351306852..d56440911a 100644
--- a/jme3-bullet/src/main/java/com/jme3/bullet/util/DebugShapeFactory.java
+++ b/jme3-bullet/src/main/java/com/jme3/bullet/util/DebugShapeFactory.java
@@ -35,7 +35,6 @@
import com.jme3.bullet.collision.shapes.CompoundCollisionShape;
import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape;
import com.jme3.math.Matrix3f;
-import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
@@ -46,6 +45,7 @@
import java.util.List;
/**
+ * A utility class to generate debug spatials from Bullet collision shapes.
*
* @author CJ Hare, normenhansen
*/
@@ -128,14 +128,8 @@ private static Geometry createDebugShape(CollisionShape shape) {
public static Mesh getDebugMesh(CollisionShape shape) {
Mesh mesh = new Mesh();
DebugMeshCallback callback = new DebugMeshCallback();
- /*
- * Populate the mesh based on an unscaled shape;
- * the shape's scale will be applied later, to the geometry.
- */
- Vector3f savedScale = shape.getScale().clone();
- shape.setScale(Vector3f.UNIT_XYZ);
- getVertices(shape.getObjectId(), callback);
- shape.setScale(savedScale);
+ long id = shape.getObjectId();
+ getVertices(id, callback);
mesh.setBuffer(Type.Position, 3, callback.getVertices());
mesh.getFloatBuffer(Type.Position).clear();
diff --git a/jme3-core/src/main/java/checkers/quals/SubtypeOf.java b/jme3-core/src/main/java/checkers/quals/SubtypeOf.java
index ffc8be0453..189969c050 100644
--- a/jme3-core/src/main/java/checkers/quals/SubtypeOf.java
+++ b/jme3-core/src/main/java/checkers/quals/SubtypeOf.java
@@ -6,8 +6,8 @@
* A meta-annotation to specify all the qualifiers that the given qualifier
* is a subtype of. This provides a declarative way to specify the type
* qualifier hierarchy. (Alternatively, the hierarchy can be defined
- * procedurally by subclassing {@link checkers.types.QualifierHierarchy} or
- * {@link checkers.types.TypeHierarchy}.)
+ * procedurally by subclassing checkers.types.QualifierHierarchy or
+ * checkers.types.TypeHierarchy.)
*
*
* Example:
diff --git a/jme3-core/src/main/java/checkers/quals/package-info.java b/jme3-core/src/main/java/checkers/quals/package-info.java
index 5ae80a7d68..192a8312f5 100644
--- a/jme3-core/src/main/java/checkers/quals/package-info.java
+++ b/jme3-core/src/main/java/checkers/quals/package-info.java
@@ -5,6 +5,6 @@
* They may serve as documentation for the type qualifiers, and aid the
* Checker Framework to infer the relations between the type qualifiers.
*
- * @checker.framework.manual #writing-a-checker Writing a checker
+ * checker.framework.manual #writing-a-checker Writing a checker
*/
package checkers.quals;
diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimClip.java b/jme3-core/src/main/java/com/jme3/anim/AnimClip.java
index 9b86858a33..be29e2105e 100644
--- a/jme3-core/src/main/java/com/jme3/anim/AnimClip.java
+++ b/jme3-core/src/main/java/com/jme3/anim/AnimClip.java
@@ -1,8 +1,6 @@
package com.jme3.anim;
-import com.jme3.anim.tween.Tween;
import com.jme3.export.*;
-import com.jme3.util.SafeArrayList;
import com.jme3.util.clone.Cloner;
import com.jme3.util.clone.JmeCloneable;
diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java b/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java
index 6462893678..d997ab39d1 100644
--- a/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java
+++ b/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java
@@ -3,7 +3,10 @@
import com.jme3.anim.tween.Tween;
import com.jme3.anim.tween.Tweens;
import com.jme3.anim.tween.action.*;
-import com.jme3.export.*;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.control.AbstractControl;
@@ -26,7 +29,7 @@ public class AnimComposer extends AbstractControl {
private Map layers = new LinkedHashMap<>();
public AnimComposer() {
- layers.put(DEFAULT_LAYER, new Layer());
+ layers.put(DEFAULT_LAYER, new Layer(this));
}
/**
@@ -67,18 +70,47 @@ public void removeAnimClip(AnimClip anim) {
public Action setCurrentAction(String name) {
return setCurrentAction(name, DEFAULT_LAYER);
}
-
+
+ /**
+ * Run an action on specified layer.
+ *
+ * @param actionName The name of the action to run.
+ * @param layerName The layer on which action should run.
+ * @return The action corresponding to the given name.
+ */
public Action setCurrentAction(String actionName, String layerName) {
Layer l = layers.get(layerName);
if (l == null) {
throw new IllegalArgumentException("Unknown layer " + layerName);
}
+
Action currentAction = action(actionName);
l.time = 0;
l.currentAction = currentAction;
return currentAction;
}
+
+ /**
+ * Remove current action on specified layer.
+ *
+ * @param layerName The name of the layer we want to remove its action.
+ */
+ public void removeCurrentAction(String layerName) {
+ Layer l = layers.get(layerName);
+ if (l == null) {
+ throw new IllegalArgumentException("Unknown layer " + layerName);
+ }
+
+ l.time = 0;
+ l.currentAction = null;
+ }
+ /**
+ *
+ * @param name The name of the action to return.
+ * @return The action registered with specified name. It will make a new action if there isn't any.
+ * @see #makeAction(java.lang.String)
+ */
public Action action(String name) {
Action action = actions.get(name);
if (action == null) {
@@ -87,7 +119,33 @@ public Action action(String name) {
}
return action;
}
+
+ /**
+ *
+ * @param name The name of the action to return.
+ * @return The action registered with specified name or null if nothing is registered.
+ */
+ public Action getAction(String name){
+ return actions.get(name);
+ }
+
+ /**
+ * Register given action with specified name.
+ *
+ * @param name The name of the action.
+ * @param action The action to add.
+ */
+ public void addAction(String name, Action action){
+ actions.put(name, action);
+ }
+ /**
+ * Create a new ClipAction with specified clip name.
+ *
+ * @param name The name of the clip.
+ * @return a new action
+ * @throws IllegalArgumentException if clip with specified name not found.
+ */
public Action makeAction(String name) {
Action action;
AnimClip clip = animClipMap.get(name);
@@ -97,13 +155,35 @@ public Action makeAction(String name) {
action = new ClipAction(clip);
return action;
}
+
+ public boolean hasAction(String name) {
+ return actions.containsKey(name);
+ }
+
+ /**
+ * Remove specified action.
+ *
+ * @param name The name of the action to remove.
+ * @return The removed action.
+ */
+ public Action removeAction(String name) {
+ return actions.remove(name);
+ }
- public void makeLayer(String name, AnimationMask mask){
- Layer l = new Layer();
+ public void makeLayer(String name, AnimationMask mask) {
+ Layer l = new Layer(this);
l.mask = mask;
layers.put(name, l);
}
+ /**
+ * Remove specified layer. This will stop the current action on this layer.
+ *
+ * @param name The name of the layer to remove.
+ */
+ public void removeLayer(String name) {
+ layers.remove(name);
+ }
public BaseAction actionSequence(String name, Tween... tweens) {
BaseAction action = new BaseAction(Tweens.sequence(tweens));
@@ -129,12 +209,25 @@ public void reset() {
}
}
+ /**
+ * Returns an unmodifiable collection of all available animations. When an attempt
+ * is made to modify the collection, an UnsupportedOperationException is thrown.
+ *
+ * @return the unmodifiable collection of animations
+ */
public Collection getAnimClips() {
return Collections.unmodifiableCollection(animClipMap.values());
}
- public Collection getAnimClipsNames() {
- return Collections.unmodifiableCollection(animClipMap.keySet());
+ /**
+ * Returns an unmodifiable set of all available animation names. When an
+ * attempt is made to modify the set, an UnsupportedOperationException is
+ * thrown.
+ *
+ * @return the unmodifiable set of animation names.
+ */
+ public Set getAnimClipsNames() {
+ return Collections.unmodifiableSet(animClipMap.keySet());
}
@Override
@@ -207,6 +300,7 @@ public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule ic = im.getCapsule(this);
animClipMap = (Map) ic.readStringSavableMap("animClipMap", new HashMap());
+ globalSpeed = ic.readFloat("globalSpeed", 1f);
}
@Override
@@ -214,16 +308,22 @@ public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.writeStringSavableMap(animClipMap, "animClipMap", new HashMap());
+ oc.write(globalSpeed, "globalSpeed", 1f);
}
- private class Layer implements JmeCloneable {
+ private static class Layer implements JmeCloneable {
+ private AnimComposer ac;
private Action currentAction;
private AnimationMask mask;
private float weight;
private double time;
+ public Layer(AnimComposer ac) {
+ this.ac = ac;
+ }
+
public void advance(float tpf) {
- time += tpf * currentAction.getSpeed() * globalSpeed;
+ time += tpf * currentAction.getSpeed() * ac.globalSpeed;
// make sure negative time is in [0, length] range
if (time < 0) {
double length = currentAction.getLength();
@@ -244,6 +344,7 @@ public Object jmeClone() {
@Override
public void cloneFields(Cloner cloner, Object original) {
+ ac = cloner.clone(ac);
currentAction = null;
}
}
diff --git a/jme3-core/src/main/java/com/jme3/anim/Armature.java b/jme3-core/src/main/java/com/jme3/anim/Armature.java
index ad7f5b0ac5..361603b9d1 100644
--- a/jme3-core/src/main/java/com/jme3/anim/Armature.java
+++ b/jme3-core/src/main/java/com/jme3/anim/Armature.java
@@ -63,7 +63,7 @@ public Armature(Joint[] jointList) {
}
/**
- * Update all joints sin this Amature.
+ * Update all joints in this Armature.
*/
public void update() {
for (Joint rootJoint : rootJoints) {
@@ -83,7 +83,9 @@ private void createSkinningMatrices() {
* Default is {@link MatrixJointModelTransform}
*
* @param modelTransformClass
- * @see {@link JointModelTransform},{@link MatrixJointModelTransform},{@link SeparateJointModelTransform},
+ * @see JointModelTransform
+ * @see MatrixJointModelTransform
+ * @see SeparateJointModelTransform
*/
public void setModelTransformClass(Class extends JointModelTransform> modelTransformClass) {
this.modelTransformClass = modelTransformClass;
@@ -106,7 +108,7 @@ private void instanciateJointModelTransform(Joint joint) {
/**
* returns the array of all root joints of this Armature
*
- * @return
+ * @return the pre-existing array
*/
public Joint[] getRoots() {
return rootJoints;
@@ -120,7 +122,7 @@ public List getJointList() {
* return a joint for the given index
*
* @param index
- * @return
+ * @return the pre-existing instance
*/
public Joint getJoint(int index) {
return jointList[index];
@@ -130,7 +132,7 @@ public Joint getJoint(int index) {
* returns the joint with the given name
*
* @param name
- * @return
+ * @return the pre-existing instance or null if not found
*/
public Joint getJoint(String name) {
for (int i = 0; i < jointList.length; i++) {
@@ -145,7 +147,7 @@ public Joint getJoint(String name) {
* returns the bone index of the given bone
*
* @param joint
- * @return
+ * @return the index (≥0) or -1 if not found
*/
public int getJointIndex(Joint joint) {
for (int i = 0; i < jointList.length; i++) {
@@ -161,7 +163,7 @@ public int getJointIndex(Joint joint) {
* returns the joint index of the joint that has the given name
*
* @param name
- * @return
+ * @return the index (≥0) or -1 if not found
*/
public int getJointIndex(String name) {
for (int i = 0; i < jointList.length; i++) {
@@ -219,7 +221,7 @@ public void applyInitialPose() {
/**
* Compute the skinning matrices for each bone of the armature that would be used to transform vertices of associated meshes
*
- * @return
+ * @return the pre-existing array
*/
public Matrix4f[] computeSkinningMatrices() {
for (int i = 0; i < jointList.length; i++) {
@@ -231,7 +233,7 @@ public Matrix4f[] computeSkinningMatrices() {
/**
* returns the number of joints of this armature
*
- * @return
+ * @return the count (≥0)
*/
public int getJointCount() {
return jointList.length;
@@ -274,7 +276,7 @@ public void read(JmeImporter im) throws IOException {
try {
modelTransformClass = (Class extends JointModelTransform>) Class.forName(className);
} catch (ClassNotFoundException e) {
- throw new AssetLoadException("Cannnot find class for name " + className);
+ throw new AssetLoadException("Cannot find class for name " + className);
}
int i = 0;
diff --git a/jme3-core/src/main/java/com/jme3/anim/Joint.java b/jme3-core/src/main/java/com/jme3/anim/Joint.java
index bf672fa29a..0c7153d296 100644
--- a/jme3-core/src/main/java/com/jme3/anim/Joint.java
+++ b/jme3-core/src/main/java/com/jme3/anim/Joint.java
@@ -154,7 +154,7 @@ protected void saveInitialPose() {
/**
* Sets the local transform with the bind transforms
*/
- protected void applyBindPose() {
+ public void applyBindPose() {
jointModelTransform.applyBindPose(localTransform, inverseModelBindMatrix, parent);
updateModelTransforms();
diff --git a/jme3-core/src/main/java/com/jme3/anim/MorphTrack.java b/jme3-core/src/main/java/com/jme3/anim/MorphTrack.java
index cd662c11e5..a72456c72e 100644
--- a/jme3-core/src/main/java/com/jme3/anim/MorphTrack.java
+++ b/jme3-core/src/main/java/com/jme3/anim/MorphTrack.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2019 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -32,11 +32,9 @@
package com.jme3.anim;
import com.jme3.anim.interpolator.FrameInterpolator;
-import com.jme3.animation.*;
import com.jme3.export.*;
import com.jme3.scene.Geometry;
import com.jme3.util.clone.Cloner;
-import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
@@ -79,7 +77,7 @@ public MorphTrack(Geometry target, float[] times, float[] weights, int nbMorphTa
/**
* return the array of weights of this track
*
- * @return
+ * @return the pre-existing array
*/
public float[] getWeights() {
return weights;
@@ -88,7 +86,7 @@ public float[] getWeights() {
/**
* returns the arrays of time for this track
*
- * @return
+ * @return the pre-existing array
*/
public float[] getTimes() {
return times;
diff --git a/jme3-core/src/main/java/com/jme3/anim/SkinningControl.java b/jme3-core/src/main/java/com/jme3/anim/SkinningControl.java
index 097840e815..02f4808256 100644
--- a/jme3-core/src/main/java/com/jme3/anim/SkinningControl.java
+++ b/jme3-core/src/main/java/com/jme3/anim/SkinningControl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2017 jMonkeyEngine
+ * Copyright (c) 2009-2019 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -409,7 +409,7 @@ public Node getAttachmentsNode(String jointName) {
/**
* returns the armature of this control
*
- * @return
+ * @return the pre-existing instance
*/
public Armature getArmature() {
return armature;
diff --git a/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java b/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java
index dee2aefb93..29354fead0 100644
--- a/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java
+++ b/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2019 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -32,14 +32,12 @@
package com.jme3.anim;
import com.jme3.anim.interpolator.FrameInterpolator;
-import com.jme3.anim.tween.Tween;
import com.jme3.anim.util.HasLocalTransform;
import com.jme3.animation.CompactQuaternionArray;
import com.jme3.animation.CompactVector3Array;
import com.jme3.export.*;
import com.jme3.math.*;
import com.jme3.util.clone.Cloner;
-import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
@@ -84,7 +82,7 @@ public TransformTrack(HasLocalTransform target, float[] times, Vector3f[] transl
/**
* return the array of rotations of this track
*
- * @return
+ * @return an array
*/
public Quaternion[] getRotations() {
return rotations.toObjectArray();
@@ -93,7 +91,7 @@ public Quaternion[] getRotations() {
/**
* returns the array of scales for this track
*
- * @return
+ * @return an array or null
*/
public Vector3f[] getScales() {
return scales == null ? null : scales.toObjectArray();
@@ -102,7 +100,7 @@ public Vector3f[] getScales() {
/**
* returns the arrays of time for this track
*
- * @return
+ * @return the pre-existing array
*/
public float[] getTimes() {
return times;
@@ -111,7 +109,7 @@ public float[] getTimes() {
/**
* returns the array of translations of this track
*
- * @return
+ * @return an array
*/
public Vector3f[] getTranslations() {
return translations.toObjectArray();
diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/AbstractTween.java b/jme3-core/src/main/java/com/jme3/anim/tween/AbstractTween.java
index 272ec8dae9..34d4fb83e3 100644
--- a/jme3-core/src/main/java/com/jme3/anim/tween/AbstractTween.java
+++ b/jme3-core/src/main/java/com/jme3/anim/tween/AbstractTween.java
@@ -36,10 +36,8 @@
package com.jme3.anim.tween;
-
-import com.jme3.export.*;
-
-import java.io.IOException;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
/**
* Base implementation of the Tween interface that provides
@@ -50,7 +48,7 @@
*
* @author Paul Speed
*/
-public abstract class AbstractTween implements Tween {
+public abstract class AbstractTween implements JmeCloneable, Tween {
private double length;
@@ -94,4 +92,32 @@ public boolean interpolate(double t) {
}
protected abstract void doInterpolate(double t);
+
+ /**
+ * Create a shallow clone for the JME cloner.
+ *
+ * @return a new tween (not null)
+ */
+ @Override
+ public AbstractTween jmeClone() {
+ try {
+ AbstractTween clone = (AbstractTween) super.clone();
+ return clone;
+ } catch (CloneNotSupportedException exception) {
+ throw new RuntimeException(exception);
+ }
+ }
+
+ /**
+ * Callback from {@link com.jme3.util.clone.Cloner} to convert this
+ * shallow-cloned tween into a deep-cloned one, using the specified cloner
+ * and original to resolve copied fields.
+ *
+ * @param cloner the cloner that's cloning this tween (not null)
+ * @param original the tween from which this tween was shallow-cloned
+ * (unused)
+ */
+ @Override
+ public void cloneFields(Cloner cloner, Object original) {
+ }
}
diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/Tween.java b/jme3-core/src/main/java/com/jme3/anim/tween/Tween.java
index 9ed4752c87..e79b4d29e6 100644
--- a/jme3-core/src/main/java/com/jme3/anim/tween/Tween.java
+++ b/jme3-core/src/main/java/com/jme3/anim/tween/Tween.java
@@ -36,9 +36,6 @@
package com.jme3.anim.tween;
-
-import com.jme3.export.Savable;
-
/**
* Represents some action that interpolates across input between 0
* and some length value. (For example, movement, rotation, fading.)
diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/Tweens.java b/jme3-core/src/main/java/com/jme3/anim/tween/Tweens.java
index 54ff3986b1..0fedf9b75a 100644
--- a/jme3-core/src/main/java/com/jme3/anim/tween/Tweens.java
+++ b/jme3-core/src/main/java/com/jme3/anim/tween/Tweens.java
@@ -378,7 +378,7 @@ public Stretch(Tween delegate, double length) {
this.length = length;
// Caller desires delegate to be 'length' instead of
- // it's actual length so we will calculate a time scale
+ // its actual length so we will calculate a time scale
// If the desired length is longer than delegate's then
// we need to feed time in slower, ie: scale < 1
if (length != 0) {
diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java
index e4038caff0..6846f91460 100644
--- a/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java
+++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java
@@ -2,8 +2,10 @@
import com.jme3.anim.AnimationMask;
import com.jme3.anim.tween.Tween;
+import com.jme3.util.clone.Cloner;
+import com.jme3.util.clone.JmeCloneable;
-public abstract class Action implements Tween {
+public abstract class Action implements JmeCloneable, Tween {
protected Action[] actions;
private double length;
@@ -67,4 +69,34 @@ protected void setForward(boolean forward) {
}
}
+
+ /**
+ * Create a shallow clone for the JME cloner.
+ *
+ * @return a new action (not null)
+ */
+ @Override
+ public Action jmeClone() {
+ try {
+ Action clone = (Action) super.clone();
+ return clone;
+ } catch (CloneNotSupportedException exception) {
+ throw new RuntimeException(exception);
+ }
+ }
+
+ /**
+ * Callback from {@link com.jme3.util.clone.Cloner} to convert this
+ * shallow-cloned action into a deep-cloned one, using the specified cloner
+ * and original to resolve copied fields.
+ *
+ * @param cloner the cloner that's cloning this action (not null)
+ * @param original the action from which this action was shallow-cloned
+ * (unused)
+ */
+ @Override
+ public void cloneFields(Cloner cloner, Object original) {
+ actions = cloner.clone(actions);
+ mask = cloner.clone(mask);
+ }
}
diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java
index d2e8913430..7de04bc742 100644
--- a/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java
+++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java
@@ -4,7 +4,6 @@
import com.jme3.anim.tween.Tween;
import com.jme3.util.SafeArrayList;
-import java.util.Collections;
import java.util.List;
public class BaseAction extends Action {
diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java
index 0ba79d8416..4b3b6f9660 100644
--- a/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java
+++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendableAction.java
@@ -3,9 +3,8 @@
import com.jme3.anim.tween.AbstractTween;
import com.jme3.anim.tween.Tween;
import com.jme3.anim.util.HasLocalTransform;
-import com.jme3.math.FastMath;
import com.jme3.math.Transform;
-
+import com.jme3.util.clone.Cloner;
import java.util.Collection;
public abstract class BlendableAction extends Action {
@@ -81,6 +80,37 @@ protected float getTransitionWeight() {
return transitionWeight;
}
+ /**
+ * Create a shallow clone for the JME cloner.
+ *
+ * @return a new action (not null)
+ */
+ @Override
+ public BlendableAction jmeClone() {
+ try {
+ BlendableAction clone = (BlendableAction) super.clone();
+ return clone;
+ } catch (CloneNotSupportedException exception) {
+ throw new RuntimeException(exception);
+ }
+ }
+
+ /**
+ * Callback from {@link com.jme3.util.clone.Cloner} to convert this
+ * shallow-cloned action into a deep-cloned one, using the specified cloner
+ * and original to resolve copied fields.
+ *
+ * @param cloner the cloner that's cloning this action (not null)
+ * @param original the action from which this action was shallow-cloned
+ * (unused)
+ */
+ @Override
+ public void cloneFields(Cloner cloner, Object original) {
+ super.cloneFields(cloner, original);
+ collectTransformDelegate = cloner.clone(collectTransformDelegate);
+ transition = cloner.clone(transition);
+ }
+
private class TransitionTween extends AbstractTween {
diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java
index 9f0d49f200..fdf211962a 100644
--- a/jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java
+++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java
@@ -1,11 +1,10 @@
package com.jme3.anim.tween.action;
import com.jme3.anim.*;
-import com.jme3.anim.tween.AbstractTween;
import com.jme3.anim.util.HasLocalTransform;
import com.jme3.math.Transform;
import com.jme3.scene.Geometry;
-
+import com.jme3.util.clone.Cloner;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -90,5 +89,34 @@ public void collectTransform(HasLocalTransform target, Transform t, float weight
}
}
+ /**
+ * Create a shallow clone for the JME cloner.
+ *
+ * @return a new action (not null)
+ */
+ @Override
+ public ClipAction jmeClone() {
+ try {
+ ClipAction clone = (ClipAction) super.clone();
+ return clone;
+ } catch (CloneNotSupportedException exception) {
+ throw new RuntimeException(exception);
+ }
+ }
+ /**
+ * Callback from {@link com.jme3.util.clone.Cloner} to convert this
+ * shallow-cloned action into a deep-cloned one, using the specified cloner
+ * and original to resolve copied fields.
+ *
+ * @param cloner the cloner that's cloning this action (not null)
+ * @param original the action from which this action was shallow-cloned
+ * (unused)
+ */
+ @Override
+ public void cloneFields(Cloner cloner, Object original) {
+ super.cloneFields(cloner, original);
+ clip = cloner.clone(clip);
+ transform = cloner.clone(transform);
+ }
}
diff --git a/jme3-core/src/main/java/com/jme3/anim/util/Weighted.java b/jme3-core/src/main/java/com/jme3/anim/util/Weighted.java
index 8fb6d32552..f771d44edd 100644
--- a/jme3-core/src/main/java/com/jme3/anim/util/Weighted.java
+++ b/jme3-core/src/main/java/com/jme3/anim/util/Weighted.java
@@ -1,7 +1,6 @@
package com.jme3.anim.util;
import com.jme3.anim.tween.action.Action;
-import com.jme3.math.Transform;
public interface Weighted {
diff --git a/jme3-core/src/main/java/com/jme3/animation/Animation.java b/jme3-core/src/main/java/com/jme3/animation/Animation.java
index 8345952382..fe0ac8ce43 100644
--- a/jme3-core/src/main/java/com/jme3/animation/Animation.java
+++ b/jme3-core/src/main/java/com/jme3/animation/Animation.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2019 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -194,7 +194,7 @@ public Animation clone() {
/**
*
* @param spat
- * @return
+ * @return a new instance
*/
public Animation cloneForSpatial(Spatial spat) {
try {
@@ -264,7 +264,7 @@ public void read(JmeImporter im) throws IOException {
// NOTE: Backward compat only .. Some animations have no
// tracks set at all even though it makes no sense.
// Since there's a null check in setTime(),
- // its only appropriate that the check is made here as well.
+ // it's only appropriate that the check is made here as well.
tracks = new SafeArrayList