fix SkeletonControl.getAttachmentNode() for odd models (such as Jaime)

fix-456
Stephen Gold 8 years ago
parent 5f9cf52c13
commit b2aa1ff9f1
  1. 56
      jme3-core/src/main/java/com/jme3/animation/Bone.java
  2. 64
      jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java
  3. 13
      jme3-core/src/main/java/com/jme3/math/Transform.java
  4. 127
      jme3-examples/src/main/java/jme3test/model/anim/TestAttachmentsNode.java

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2009-2012 jMonkeyEngine * Copyright (c) 2009-2017 jMonkeyEngine
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -33,7 +33,9 @@ package com.jme3.animation;
import com.jme3.export.*; import com.jme3.export.*;
import com.jme3.math.*; import com.jme3.math.*;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node; import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.util.TempVars; import com.jme3.util.TempVars;
import com.jme3.util.clone.JmeCloneable; import com.jme3.util.clone.JmeCloneable;
import com.jme3.util.clone.Cloner; import com.jme3.util.clone.Cloner;
@ -80,7 +82,10 @@ public final class Bone implements Savable, JmeCloneable {
* The attachment node. * The attachment node.
*/ */
private Node attachNode; private Node attachNode;
/**
* A geometry animated by this node, used when updating the attachments node.
*/
private Geometry targetGeometry = null;
/** /**
* Bind transform is the local bind transform of this bone. (local space) * Bind transform is the local bind transform of this bone. (local space)
*/ */
@ -187,6 +192,7 @@ public final class Bone implements Savable, JmeCloneable {
this.children = cloner.clone(children); this.children = cloner.clone(children);
this.attachNode = cloner.clone(attachNode); this.attachNode = cloner.clone(attachNode);
this.targetGeometry = cloner.clone(targetGeometry);
this.bindPos = cloner.clone(bindPos); this.bindPos = cloner.clone(bindPos);
this.bindRot = cloner.clone(bindRot); this.bindRot = cloner.clone(bindRot);
@ -505,9 +511,39 @@ public final class Bone implements Savable, JmeCloneable {
} }
if (attachNode != null) { if (attachNode != null) {
updateAttachNode();
}
}
/**
* Update the local transform of the attachments node.
*/
private void updateAttachNode() {
Node attachParent = attachNode.getParent();
if (attachParent == null || targetGeometry == null
|| targetGeometry.getParent() == attachParent
&& targetGeometry.getLocalTransform().isIdentity()) {
/*
* The animated meshes are in the same coordinate system as the
* attachments node: no further transforms are needed.
*/
attachNode.setLocalTranslation(modelPos); attachNode.setLocalTranslation(modelPos);
attachNode.setLocalRotation(modelRot); attachNode.setLocalRotation(modelRot);
attachNode.setLocalScale(modelScale); attachNode.setLocalScale(modelScale);
} else {
Spatial loopSpatial = targetGeometry;
Transform combined = new Transform(modelPos, modelRot, modelScale);
/*
* Climb the scene graph applying local transforms until the
* attachments node's parent is reached.
*/
while (loopSpatial != attachParent && loopSpatial != null) {
Transform localTransform = loopSpatial.getLocalTransform();
combined.combineWithParent(localTransform);
loopSpatial = loopSpatial.getParent();
}
attachNode.setLocalTransform(combined);
} }
} }
@ -661,15 +697,21 @@ public final class Bone implements Savable, JmeCloneable {
} }
/** /**
* Returns the attachment node. * Access the attachments node of this bone. If this bone doesn't already
* Attach models and effects to this node to make * have an attachments node, create one. Models and effects attached to the
* them follow this bone's motions. * attachments node will follow this bone's motions.
*
* @param target a geometry animated by this bone, or null to indicate that
* all geometries affected by this bone have the same global transform as
* the attachment node's parent
*/ */
Node getAttachmentsNode() { Node getAttachmentsNode(Geometry target) {
if (attachNode == null) { if (attachNode == null) {
attachNode = new Node(name + "_attachnode"); attachNode = new Node(name + "_attachnode");
attachNode.setUserData("AttachedBone", this); attachNode.setUserData("AttachedBone", this);
} }
targetGeometry = target;
return attachNode; return attachNode;
} }
@ -823,6 +865,7 @@ public final class Bone implements Savable, JmeCloneable {
} }
attachNode = (Node) input.readSavable("attachNode", null); attachNode = (Node) input.readSavable("attachNode", null);
targetGeometry = (Geometry) input.readSavable("targetGeometry", null);
localPos.set(bindPos); localPos.set(bindPos);
localRot.set(bindRot); localRot.set(bindRot);
@ -845,6 +888,7 @@ public final class Bone implements Savable, JmeCloneable {
output.write(name, "name", null); output.write(name, "name", null);
output.write(attachNode, "attachNode", null); output.write(attachNode, "attachNode", null);
output.write(targetGeometry, "targetGeometry", null);
output.write(bindPos, "bindPos", null); output.write(bindPos, "bindPos", null);
output.write(bindRot, "bindRot", null); output.write(bindRot, "bindRot", null);
output.write(bindScale, "bindScale", new Vector3f(1.0f, 1.0f, 1.0f)); output.write(bindScale, "bindScale", new Vector3f(1.0f, 1.0f, 1.0f));

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2009-2012 jMonkeyEngine * Copyright (c) 2009-2017 jMonkeyEngine
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -52,7 +52,6 @@ import java.io.IOException;
import java.nio.Buffer; import java.nio.Buffer;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
@ -75,6 +74,11 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
* List of targets which this controller effects. * List of targets which this controller effects.
*/ */
private SafeArrayList<Mesh> targets = new SafeArrayList<Mesh>(Mesh.class); private SafeArrayList<Mesh> targets = new SafeArrayList<Mesh>(Mesh.class);
/**
* Geometry with an animated mesh, for calculating attachments node
* transforms. A null means no geometry is subject to this control.
*/
private Geometry targetGeometry = null;
/** /**
* Used to track when a mesh was updated. Meshes are only updated if they * Used to track when a mesh was updated. Meshes are only updated if they
* are visible in at least one camera. * are visible in at least one camera.
@ -210,15 +214,23 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
this.skeleton = skeleton; this.skeleton = skeleton;
} }
private void findTargets(Node node) { /**
for (Spatial child : node.getChildren()) { * If specified the geometry has an animated mesh, add its mesh and material
if (child instanceof Geometry) { * to the lists of animation targets.
Geometry geom = (Geometry) child; */
Mesh mesh = geom.getMesh(); private void findTargets(Geometry geometry) {
Mesh mesh = geometry.getMesh();
if (mesh.isAnimated()) { if (mesh.isAnimated()) {
targets.add(mesh); targets.add(mesh);
materials.add(geom.getMaterial()); targetGeometry = geometry;
materials.add(geometry.getMaterial());
}
} }
private void findTargets(Node node) {
for (Spatial child : node.getChildren()) {
if (child instanceof Geometry) {
findTargets((Geometry) child);
} else if (child instanceof Node) { } else if (child instanceof Node) {
findTargets((Node) child); findTargets((Node) child);
} }
@ -432,9 +444,13 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
} }
/** /**
* Access the attachments node of the named bone. If the bone doesn't
* already have an attachments node, create one and attach it to the scene
* graph. Models and effects attached to the attachments node will follow
* the bone's motions.
* *
* @param boneName the name of the bone * @param boneName the name of the bone
* @return the node attached to this bone * @return the attachments node of the bone
*/ */
public Node getAttachmentsNode(String boneName) { public Node getAttachmentsNode(String boneName) {
Bone b = skeleton.getBone(boneName); Bone b = skeleton.getBone(boneName);
@ -443,9 +459,19 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
+ "in the skeleton."); + "in the skeleton.");
} }
Node n = b.getAttachmentsNode(); updateTargetsAndMaterials(spatial);
Node model = (Node) spatial; Node n = b.getAttachmentsNode(targetGeometry);
model.attachChild(n); /*
* Select a node to parent the attachments node.
*/
Node parent;
if (spatial instanceof Node) {
parent = (Node) spatial; // the usual case
} else {
parent = spatial.getParent();
}
parent.attachChild(n);
return n; return n;
} }
@ -758,12 +784,20 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
skeleton = (Skeleton) in.readSavable("skeleton", null); skeleton = (Skeleton) in.readSavable("skeleton", null);
} }
/**
* Update the lists of animation targets.
*
* @param spatial the controlled spatial
*/
private void updateTargetsAndMaterials(Spatial spatial) { private void updateTargetsAndMaterials(Spatial spatial) {
targets.clear(); targets.clear();
targetGeometry = null;
materials.clear(); materials.clear();
if (spatial != null && spatial instanceof Node) {
Node node = (Node) spatial; if (spatial instanceof Node) {
findTargets(node); findTargets((Node) spatial);
} else if (spatial instanceof Geometry) {
findTargets((Geometry) spatial);
} }
} }
} }

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2009-2012 jMonkeyEngine * Copyright (c) 2009-2017 jMonkeyEngine
* All rights reserved. * All rights reserved.
* *
* Redistribution and use in source and binary forms, with or without * Redistribution and use in source and binary forms, with or without
@ -288,6 +288,17 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
rot.set(0, 0, 0, 1); rot.set(0, 0, 0, 1);
} }
/**
* Test for exact identity.
*
* @return true if exactly equal to {@link #IDENTITY}, otherwise false
*/
public boolean isIdentity() {
return translation.x == 0f && translation.y == 0f && translation.z == 0f
&& scale.x == 1f && scale.y == 1f && scale.z == 1f
&& rot.w == 1f && rot.x == 0f && rot.y == 0f && rot.z == 0f;
}
@Override @Override
public int hashCode() { public int hashCode() {
int hash = 7; int hash = 7;

@ -0,0 +1,127 @@
/*
* Copyright (c) 2009-2017 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package jme3test.model.anim;
import com.jme3.animation.AnimChannel;
import com.jme3.animation.AnimControl;
import com.jme3.animation.AnimEventListener;
import com.jme3.animation.LoopMode;
import com.jme3.animation.SkeletonControl;
import com.jme3.app.SimpleApplication;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
/**
* Simple application to an test attachments node on the Jaime model.
*
* Derived from {@link jme3test.model.anim.TestOgreAnim}.
*/
public class TestAttachmentsNode extends SimpleApplication
implements AnimEventListener, ActionListener {
public static void main(String[] args) {
TestAttachmentsNode app = new TestAttachmentsNode();
app.start();
}
private AnimChannel channel;
private AnimControl control;
@Override
public void simpleInitApp() {
flyCam.setMoveSpeed(10f);
cam.setLocation(new Vector3f(6.4f, 7.5f, 12.8f));
cam.setRotation(new Quaternion(-0.060740203f, 0.93925786f, -0.2398315f, -0.2378785f));
DirectionalLight dl = new DirectionalLight();
dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal());
dl.setColor(ColorRGBA.White);
rootNode.addLight(dl);
Spatial model = assetManager.loadModel("Models/Jaime/Jaime.j3o");
control = model.getControl(AnimControl.class);
SkeletonControl skeletonControl = model.getControl(SkeletonControl.class);
model.center();
model.setLocalScale(5f);
control.addListener(this);
channel = control.createChannel();
channel.setAnim("Idle");
Box box = new Box(0.3f, 0.02f, 0.02f);
Geometry saber = new Geometry("saber", box);
saber.move(0.4f, 0.05f, 0.01f);
Material red = assetManager.loadMaterial("Common/Materials/RedColor.j3m");
saber.setMaterial(red);
Node n = skeletonControl.getAttachmentsNode("hand.R");
n.attachChild(saber);
rootNode.attachChild(model);
inputManager.addListener(this, "Attack");
inputManager.addMapping("Attack", new KeyTrigger(KeyInput.KEY_SPACE));
}
@Override
public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
if (animName.equals("Punches")) {
channel.setAnim("Idle", 0.5f);
channel.setLoopMode(LoopMode.DontLoop);
channel.setSpeed(1f);
}
}
@Override
public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
}
@Override
public void onAction(String binding, boolean value, float tpf) {
if (binding.equals("Attack") && value) {
if (!channel.getAnimationName().equals("Punches")) {
channel.setAnim("Punches", 0.5f);
channel.setLoopMode(LoopMode.Cycle);
channel.setSpeed(0.5f);
}
}
}
}
Loading…
Cancel
Save