hi @sgold, could you help me with a doubt that I can’t solve?
I’m experimenting with the Minie library. I created a model and added the DynamicAnimControl to it.
Here is the script (if I made some mistakes in the configuration please report them to me):
Question: Is it possible to remove a BoneLink and all its children during the game loop?
I tried with this instruction:
ragdoll.unlinkBone(bone.boneName());
but I get the following error message:
java.lang.IllegalStateException: Cannot unlink a bone while the Control is added to a Spatial.
at com.jme3.bullet.animation.DacConfiguration.verifyNotAddedToSpatial(DacConfiguration.java:562)
at com.jme3.bullet.animation.DacConfiguration.unlinkBone(DacConfiguration.java:348)
at jme3test.bullet.TestBoneRagdoll.onAction(TestBoneRagdoll.java:300)
at com.jme3.input.InputManager.invokeActions(InputManager.java:171)
at com.jme3.input.InputManager.onKeyEventQueued(InputManager.java:471)
at com.jme3.input.InputManager.processQueue(InputManager.java:865)
at com.jme3.input.InputManager.update(InputManager.java:917)
at com.jme3.app.LegacyApplication.update(LegacyApplication.java:724)
at com.jme3.app.SimpleApplication.update(SimpleApplication.java:246)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:153)
at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:193)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:234)
at java.base/java.lang.Thread.run(Thread.java:834)
You could try removing the control, unlinking the bone(s), and then adding the control back. That should avoid the exception.
Or we could discuss your objective. What are you actually trying to do: simulate the sudden amputation of a limb? For that, I’d suggest a different approach.
DynamicAnimControl has a method that causes a limb to shrivel up: DynamicAnimControl.amputateSubtree(boneLink, blendSeconds)
You can see it in action if you run the TestDac demo application and press the DEL key.
It works by scaling the Joint transform, so the point of amputation has to be where a joint connects to another: shoulder, elbow, wrist, knee, ankle, etc. The next step would be “dropping” a copy of the amputated limb onto the ground.
thank you very much @sgold, I do some experiments and share the results. I’m taking this video as an example. I would like to expand the idea by adding limb amputation as well.
Hi @sgold, the new Minie library is really cool, has a lot of useful features and comes with great options for debugging.
Following your guide Project overview :: The Minie project I tried the DacWizard tool to generate the configuration code of my 3d model. The tool is great!
I did some experiments in ragdoll and kinematic modes. Here are the results.
Test Kinematic mode:
private RaycastHit hitInfo = new RaycastHit();
private float maxDistance = 200f;
private void fire() {
if (Physics.doRaycast(cam.getLocation(), cam.getDirection(), hitInfo, maxDistance)) {
if (hitInfo.userObject instanceof BoneLink) {
BoneLink bone = (BoneLink) hitInfo.userObject;
System.out.println("BoneName=" + bone.boneName() + ", BoneLink=" + bone.name());
if (bone.boneName().endsWith("Arm")
|| bone.boneName().endsWith("Leg")
|| bone.boneName().endsWith("Head")) {
Vector3f position = bone.getRigidBody().getPhysicsLocation();
Quaternion rotation = bone.getRigidBody().getPhysicsRotation();
Vector3f impulse = hitInfo.normal.negate().multLocal(2);
for (int i = 0; i < 15; i++) {
Spatial limb = dropLimb(position, rotation, rootNode);
limb.getControl(RigidBodyControl.class).applyImpulse(impulse, hitInfo.point);
}
dac.amputateSubtree(bone, 0.1f);
if (bone.boneName().endsWith("Head") || bone.boneName().endsWith("Leg")) {
composer.setCurrentAction(AnimDefs.Dying);
}
}
}
}
}
private Spatial dropLimb(Vector3f position, Quaternion rotation, Node parent) {
Node bodyPart = new Node("BodyPart");
Box mesh = new Box(0.1f, 0.1f, 0.1f);
Geometry geo = new Geometry("Limb", mesh);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", ColorRGBA.Red.clone());
geo.setMaterial(mat);
bodyPart.attachChild(geo);
bodyPart.setLocalTranslation(position);
bodyPart.setLocalRotation(rotation);
float mass = 5f;
CollisionShape shape = CollisionShapeFactory.createBoxShape(bodyPart);
RigidBodyControl rbc = new RigidBodyControl(shape, mass);
rbc.setKinematic(false);
rbc.setFriction(10);
bodyPart.addControl(rbc);
parent.attachChild(bodyPart);
physicsSpace.add(bodyPart);
rbc.setGravity(defaultGravity);
return bodyPart;
}
Test Ragdoll Mode part 1:
private RaycastHit hitInfo = new RaycastHit();
private float maxDistance = 200f;
private void fire() {
if (Physics.doRaycast(cam.getLocation(), cam.getDirection(), hitInfo, maxDistance)) {
if (hitInfo.userObject instanceof BoneLink) {
BoneLink bone = (BoneLink) hitInfo.userObject;
System.out.println("BoneName=" + bone.boneName() + ", BoneLink=" + bone.name());
if (bone.boneName().endsWith("Arm")
|| bone.boneName().endsWith("Leg")
|| bone.boneName().endsWith("Head")) {
Vector3f position = bone.getRigidBody().getPhysicsLocation();
Quaternion rotation = bone.getRigidBody().getPhysicsRotation();
Vector3f impulse = hitInfo.normal.negate().multLocal(2);
for (int i = 0; i < 15; i++) {
Spatial limb = dropLimb(position, rotation, rootNode);
limb.getControl(RigidBodyControl.class).applyImpulse(impulse, hitInfo.point);
}
if (bone.boneName().endsWith("Head") || bone.boneName().endsWith("Leg")) {
dac.setRagdollMode();
}
dac.amputateSubtree(bone, 0.1f);
}
}
}
}
Test Ragdoll Mode part 2: In the test where I remove the legs and then switch to Ragdoll mode, the 3d model keeps moving in circle. Is there any way to avoid it?
if you have any advice to give me let me know.
I’d be curious to know if anyone else has tried using DynamicAnimControl in a fun way to share ideas about it. Greetings to all users of the community
Sweet! It’s nice to see someone having fun with Minie.
Thanks for reporting the issues with unspecified locales in String.format(). Based on your report, I’ve committed fixes to MInie’s “master” branch, which will appear in the 4.0.0 release. Any day now…
The circular motion of the ragdoll might be caused by insufficient friction with the floor. You can increase the friction of a RigidBody using the setFriction() method. You can increase the friction of a DynamicAnimControl by looping over its rigid bodies using the listRigidBodies() method.
Edit: another possibility would be to increase damping. The key difference is that damping applies to all moving bodies, while friction only applies to bodies in contact with other bodies.
hi @sgold, I am continuing with the experiments but I cannot solve a seemingly simple problem.
The scenario is the following: I have an AbstractControl that adds an NPC to the game arena every 6 seconds.
Since adding the code for configuring the DynamicAnimControl, the program takes longer to process, and the game freezes for a few seconds.
Could you give me some ideas to speed up this operation?
public class Spawner extends AbstractControl {
int maxObjects = 6;
float spawnTime = 6f;
float timer = 0;
...
@Override
protected void controlUpdate(float tpf) {
timer += tpf;
if (timer > spawnTime) {
timer = 0;
int nChild = ((Node) spatial).getQuantity();
if (nChild < maxObjects) {
Vector3f spawnPoint = getRandomSpawnPoint();
instantiate(spawnPoint, Quaternion.IDENTITY, (Node) spatial);
}
}
}
private void instantiate(Vector3f position, Quaternion rotation, Node parent) {
Node enemy = assetManager.loadModel("Models/Dismember/zombie.j3o");
enemy.setLocalTranslation(position);
// setup Dac
DynamicAnimControl dac = new YBotControl();
enemy.addControl(dac);
dac.setGravity(gravityVector);
dac.setDamping(0.8f);
for (PhysicsRigidBody body : dac.listRigidBodies()) {
body.setFriction(10);
body.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_03);
}
PhysicsSpace.getPhysicsSpace().add(dac);
// setup Physics Character
float radius = 0.8f;
float height = 4f;
float mass = 5;
BetterCharacterControl bcc = new BetterCharacterControl(radius, height, mass);
bcc.getRigidBody().setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_02);
for (PhysicsRigidBody rb : dac.listRigidBodies()) {
bcc.getRigidBody().addToIgnoreList(rb);
rb.addToIgnoreList(bcc.getRigidBody());
}
enemy.addControl(bcc);
PhysicsSpace.getPhysicsSpace().add(bcc);
parent.attachChild(enemy);
}
}
If I recall correctly, the expensive part of initializing DAC is generating the collision shapes for meshes. If you’re spawning NPC models whose meshes are stored in J3O assets, I suggest storing the shapes in J3O assets as well. In other words, add DAC to the model before the game starts.
You can write your own asset processor to do this. Or you can use the “Save J3O” button in the Test screen of DacWizard.
I saved the model in j3o format with the DacWizard tool, but when I try to reload it via assetManager.loadModel(“zombie3-002648.j3o”) the program crashes without errors and I have to kill the task.
I tried to convert the model to j3o format by writing the following code, but the program crashes.
I’m not sure what went wrong. There’s no stack trace and no JVM crash log? Even with assertions enabled and a debug Minie library?
One slight problem I see with the code snippet you posted is that you add the DAC to a PhysicsSpace before writing to J3O. It would be better to write it without adding it to any space.
If you run your application from the command line then you should be to hit Ctrl-Break to get it to dump the stack trace of all threads. (At least on Windows… note: Break not C)
I wrote a test class. The conversion of the 3d model in j3o format with the addition of the DynamicAnimControl works correctly, but the loading fails. Here is the stack:
public class TestSaveLoadDac extends SimpleApplication {
/**
*
* @param args
*/
public static void main(String[] args) {
TestSaveLoadDac app = new TestSaveLoadDac();
app.setShowSettings(false);
app.setPauseOnLostFocus(false);
app.start();
}
@Override
public void simpleInitApp() {
saveModel();
System.out.println("Loading model...");
Spatial model = assetManager.loadModel("Models/Dismember/Sinbad.j3o");
rootNode.attachChild(model);
System.out.println("Done");
}
private void saveModel() {
Spatial model = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml");
Spatial animRoot = model.getControl(AnimComposer.class).getSpatial();
DynamicAnimControl dac = new DynamicAnimControl();
setupSinbad(dac);
animRoot.addControl(dac);
convertModelToJ3o(model, "Sinbad");
}
private void setupSinbad(DynamicAnimControl dac) {
dac.link("Waist", 1f, new RangeOfMotion(1f, -0.4f, 0.8f, -0.8f, 0.4f, -0.4f));
dac.link("Chest", 1f, new RangeOfMotion(0.4f, 0f, 0.4f));
dac.link("Neck", 1f, new RangeOfMotion(0.5f, 1f, 0.7f));
dac.link("Clavicle.R", 1f, new RangeOfMotion(0.3f, -0.6f, 0f, 0f, 0.4f, -0.4f));
dac.link("Humerus.R", 1f, new RangeOfMotion(1.6f, -0.8f, 1f, -1f, 1.6f, -1f));
dac.link("Ulna.R", 1f, new RangeOfMotion(0f, 0f, 1f, -1f, 0f, -2f));
dac.link("Hand.R", 1f, new RangeOfMotion(0.8f, 0f, 0.2f));
dac.link("Clavicle.L", 1f, new RangeOfMotion(0.6f, -0.3f, 0f, 0f, 0.4f, -0.4f));
dac.link("Humerus.L", 1f, new RangeOfMotion(0.8f, -1.6f, 1f, -1f, 1f, -1.6f));
dac.link("Ulna.L", 1f, new RangeOfMotion(0f, 0f, 1f, -1f, 2f, 0f));
dac.link("Hand.L", 1f, new RangeOfMotion(0.8f, 0f, 0.2f));
dac.link("Thigh.R", 1f, new RangeOfMotion(0.4f, -1f, 0.4f, -0.4f, 1f, -0.5f));
dac.link("Calf.R", 1f, new RangeOfMotion(2f, 0f, 0f, 0f, 0f, 0f));
dac.link("Foot.R", 1f, new RangeOfMotion(0.3f, 0.5f, 0f));
dac.link("Thigh.L", 1f, new RangeOfMotion(0.4f, -1f, 0.4f, -0.4f, 0.5f, -1f));
dac.link("Calf.L", 1f, new RangeOfMotion(2f, 0f, 0f, 0f, 0f, 0f));
dac.link("Foot.L", 1f, new RangeOfMotion(0.3f, 0.5f, 0f));
}
private void convertModelToJ3o(Spatial model, String assetName) {
String dirName = System.getProperty("user.dir") + "/assets/Models/Dismember";
String fileName = assetName + ".j3o";
File file = new File(dirName, fileName);
System.out.println("Converting file: " + file.getAbsolutePath());
try {
BinaryExporter.getInstance().save(model, file);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("Done");
}
}
Converting file: <my_local_path>
Done
Loading model...
SEVERE: Uncaught exception thrown in Thread[jME3 Main,5,main]
java.lang.StackOverflowError
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at com.jme3.util.clone.Cloner.javaClone(Cloner.java:375)
at com.jme3.util.clone.ListCloneFunction.cloneObject(ListCloneFunction.java:47)
at com.jme3.util.clone.ListCloneFunction.cloneObject(ListCloneFunction.java:43)
at com.jme3.util.clone.Cloner.clone(Cloner.java:221)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.anim.Joint.cloneFields(Joint.java:307)
at com.jme3.util.clone.Cloner.clone(Cloner.java:255)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.util.clone.ListCloneFunction.cloneFields(ListCloneFunction.java:66)
at com.jme3.util.clone.ListCloneFunction.cloneFields(ListCloneFunction.java:43)
at com.jme3.util.clone.Cloner.clone(Cloner.java:228)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.anim.Joint.cloneFields(Joint.java:307)
at com.jme3.util.clone.Cloner.clone(Cloner.java:255)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.util.clone.ListCloneFunction.cloneFields(ListCloneFunction.java:66)
at com.jme3.util.clone.ListCloneFunction.cloneFields(ListCloneFunction.java:43)
at com.jme3.util.clone.Cloner.clone(Cloner.java:228)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.anim.Joint.cloneFields(Joint.java:307)
at com.jme3.util.clone.Cloner.clone(Cloner.java:255)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.util.clone.ListCloneFunction.cloneFields(ListCloneFunction.java:66)
at com.jme3.util.clone.ListCloneFunction.cloneFields(ListCloneFunction.java:43)
at com.jme3.util.clone.Cloner.clone(Cloner.java:228)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.anim.Joint.cloneFields(Joint.java:307)
at com.jme3.util.clone.Cloner.clone(Cloner.java:255)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.util.clone.ListCloneFunction.cloneFields(ListCloneFunction.java:66)
at com.jme3.util.clone.ListCloneFunction.cloneFields(ListCloneFunction.java:43)
at com.jme3.util.clone.Cloner.clone(Cloner.java:228)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.anim.Joint.cloneFields(Joint.java:307)
at com.jme3.util.clone.Cloner.clone(Cloner.java:255)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.util.clone.ListCloneFunction.cloneFields(ListCloneFunction.java:66)
at com.jme3.util.clone.ListCloneFunction.cloneFields(ListCloneFunction.java:43)
at com.jme3.util.clone.Cloner.clone(Cloner.java:228)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.anim.Joint.cloneFields(Joint.java:307)
at com.jme3.util.clone.Cloner.clone(Cloner.java:255)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.util.clone.ListCloneFunction.cloneFields(ListCloneFunction.java:66)
at com.jme3.util.clone.ListCloneFunction.cloneFields(ListCloneFunction.java:43)
at com.jme3.util.clone.Cloner.clone(Cloner.java:228)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.anim.Joint.cloneFields(Joint.java:307)
at com.jme3.util.clone.Cloner.clone(Cloner.java:255)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.util.clone.ListCloneFunction.cloneFields(ListCloneFunction.java:66)
at com.jme3.util.clone.ListCloneFunction.cloneFields(ListCloneFunction.java:43)
at com.jme3.util.clone.Cloner.clone(Cloner.java:228)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.anim.Joint.cloneFields(Joint.java:307)
at com.jme3.util.clone.Cloner.clone(Cloner.java:255)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.bullet.animation.PhysicsLink.cloneFields(PhysicsLink.java:664)
at com.jme3.bullet.animation.BoneLink.cloneFields(BoneLink.java:472)
at com.jme3.util.clone.Cloner.clone(Cloner.java:255)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.bullet.collision.PhysicsCollisionObject.cloneFields(PhysicsCollisionObject.java:1375)
at com.jme3.bullet.objects.PhysicsRigidBody.cloneFields(PhysicsRigidBody.java:1029)
at com.jme3.util.clone.Cloner.clone(Cloner.java:255)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.bullet.collision.PhysicsCollisionObject.cloneIgnoreList(PhysicsCollisionObject.java:1216)
at com.jme3.bullet.objects.PhysicsRigidBody.cloneFields(PhysicsRigidBody.java:1033)
at com.jme3.util.clone.Cloner.clone(Cloner.java:255)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.bullet.collision.PhysicsCollisionObject.cloneIgnoreList(PhysicsCollisionObject.java:1216)
at com.jme3.bullet.objects.PhysicsRigidBody.cloneFields(PhysicsRigidBody.java:1033)
at com.jme3.util.clone.Cloner.clone(Cloner.java:255)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.bullet.collision.PhysicsCollisionObject.cloneIgnoreList(PhysicsCollisionObject.java:1216)
at com.jme3.bullet.objects.PhysicsRigidBody.cloneFields(PhysicsRigidBody.java:1033)
at com.jme3.util.clone.Cloner.clone(Cloner.java:255)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
at com.jme3.bullet.collision.PhysicsCollisionObject.cloneIgnoreList(PhysicsCollisionObject.java:1216)
at com.jme3.bullet.objects.PhysicsRigidBody.cloneFields(PhysicsRigidBody.java:1033)
at com.jme3.util.clone.Cloner.clone(Cloner.java:255)
at com.jme3.util.clone.Cloner.clone(Cloner.java:160)
...
hi @sgold, i did some tests and i don’t understand why the collision shapes are not positioned correctly after loading a model previously saved in j3o format with the DynamicAnimControl. Am I doing something wrong or maybe there is still some problem?
Using Sinbad as a model the situation is even stranger. The model is crumpled on itself and also in this case the collision shapes seem centered in the origin of the model.
I modified the code as you suggested. Sinbad is no longer crumpled as before, but the problem of the collisions shapes not correctly positioned on the bones of the 3d model remains.