Bone animation :

- Split of the AnimControl in two parts : 
	- The AnimControl that handles skeleton transformation via tha animation data
	- The SkeletonControl that handles the skinning of the mesh using skeleton transformations
- Ensured backward compatibility with old j3o files, and changed the ogre mesh loader to create the controls properly
- Reverted change http://code.google.com/p/jmonkeyengine/source/detail?r=7142 transforms passed to the setUserTransform methods must be considered as increments to current transform
- Fixed some issues in the ragdollControl and test case (still WIP don't use it)

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7163 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
rem..om 14 years ago
parent e4c8769be0
commit dc030c897f
  1. 276
      engine/src/core/com/jme3/animation/AnimControl.java
  2. 3
      engine/src/core/com/jme3/animation/Bone.java
  3. 274
      engine/src/core/com/jme3/animation/SkeletonControl.java
  4. 90
      engine/src/core/com/jme3/scene/Spatial.java
  5. 10
      engine/src/jbullet/com/jme3/bullet/control/RagdollControl.java
  6. 106
      engine/src/ogre/com/jme3/scene/plugins/ogre/MeshLoader.java
  7. 10
      engine/src/test/jme3test/bullet/TestBoneRagdoll.java
  8. 8
      engine/src/test/jme3test/model/anim/TestOgreComplexAnim.java

@ -29,31 +29,21 @@
* 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.animation;
import com.jme3.bullet.control.RagdollControl;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.InputCapsule;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
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.VertexBuffer.Type;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import com.jme3.util.TempVars;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@ -83,26 +73,23 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
/**
* List of targets which this controller effects.
*/
Mesh[] targets;
// Mesh[] targets;
/**
* Skeleton object must contain corresponding data for the targets' weight buffers.
*/
Skeleton skeleton;
/** only used for backward compatibility */
@Deprecated
private SkeletonControl skeletonControl;
/**
* List of animations
*/
HashMap<String, BoneAnimation> animationMap;
/**
* Animation channels
*/
transient ArrayList<AnimChannel> channels
= new ArrayList<AnimChannel>();
transient ArrayList<AnimEventListener> listeners
= new ArrayList<AnimEventListener>();
transient ArrayList<AnimChannel> channels = new ArrayList<AnimChannel>();
transient ArrayList<AnimEventListener> listeners = new ArrayList<AnimEventListener>();
/**
* Create a new <code>AnimControl</code> that will animate the given skins
@ -114,12 +101,22 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
* properly set BoneIndex and BoneWeight buffers.
* @param skeleton The skeleton structure represents a bone hierarchy
* to be animated.
* @deprecated AnimControl doesnt' hande the skinning anymore, use AnimControl(Skeleton skeleton);
* Then create a SkeletonControl(Node model, Mesh[] meshes, Skeleton skeleton);
* and add it to the spatial.
*/
@Deprecated
public AnimControl(Node model, Mesh[] meshes, Skeleton skeleton) {
super(model);
this.skeleton = skeleton;
this.targets = meshes;
skeletonControl = new SkeletonControl(model, meshes, this.skeleton);
reset();
}
public AnimControl(Skeleton skeleton) {
this.skeleton = skeleton;
reset();
}
@ -133,30 +130,9 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
public Control cloneForSpatial(Spatial spatial) {
try {
Node clonedNode = (Node) spatial;
AnimControl clone = (AnimControl) super.clone();
clone.spatial = spatial;
clone.skeleton = new Skeleton(skeleton);
Mesh[] meshes = new Mesh[targets.length];
for (int i = 0; i < meshes.length; i++) {
meshes[i] = ((Geometry) clonedNode.getChild(i)).getMesh();
}
for (int i = meshes.length; i < clonedNode.getQuantity(); i++){
// go through attachment nodes, apply them to correct bone
Spatial child = clonedNode.getChild(i);
if (child instanceof Node){
Node clonedAttachNode = (Node) child;
Bone originalBone = (Bone) clonedAttachNode.getUserData("AttachedBone");
if (originalBone != null){
Bone clonedBone = clone.skeleton.getBone(originalBone.getName());
clonedAttachNode.setUserData("AttachedBone", clonedBone);
clonedBone.setAttachmentsNode(clonedAttachNode);
}
}
}
clone.targets = meshes;
clone.channels = new ArrayList<AnimChannel>();
return clone;
} catch (CloneNotSupportedException ex) {
@ -197,22 +173,33 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
* @param anim The animation to remove.
*/
public void removeAnim(BoneAnimation anim) {
if (!animationMap.containsKey(anim.getName()))
throw new IllegalArgumentException("Given animation does not exist " +
"in this AnimControl");
if (!animationMap.containsKey(anim.getName())) {
throw new IllegalArgumentException("Given animation does not exist "
+ "in this AnimControl");
}
animationMap.remove(anim.getName());
}
/**
*
* @param boneName the name of the bone
* @return the node attached to this bone
* @deprecated use SkeletonControl.getAttachementNode instead.
*/
@Deprecated
public Node getAttachmentsNode(String boneName) {
Bone b = skeleton.getBone(boneName);
if (b == null)
throw new IllegalArgumentException("Given bone name does not exist " +
"in the skeleton.");
if (b == null) {
throw new IllegalArgumentException("Given bone name does not exist "
+ "in the skeleton.");
}
Node n = b.getAttachmentsNode();
if (spatial != null) {
Node model = (Node) spatial;
model.attachChild(n);
}
return n;
}
@ -269,9 +256,12 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
/**
* @return The targets, or skins, being influenced by this
* <code>AnimControl</code>.
* @deprecated use SkeletonControl.getTargets() instead
* get the SkeletonControl doing spatial.getControl(SkeletonControl.class);
*/
@Deprecated
public Mesh[] getTargets() {
return targets;
return skeletonControl.getTargets();
}
/**
@ -279,9 +269,10 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
* @param listener The listener to add.
*/
public void addListener(AnimEventListener listener) {
if (listeners.contains(listener))
throw new IllegalArgumentException("The given listener is already " +
"registed at this AnimControl");
if (listeners.contains(listener)) {
throw new IllegalArgumentException("The given listener is already "
+ "registed at this AnimControl");
}
listeners.add(listener);
}
@ -292,9 +283,10 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
* @see AnimControl#addListener(com.jme3.animation.AnimEventListener)
*/
public void removeListener(AnimEventListener listener) {
if (!listeners.remove(listener))
throw new IllegalArgumentException("The given listener is not " +
"registed at this AnimControl");
if (!listeners.remove(listener)) {
throw new IllegalArgumentException("The given listener is not "
+ "registed at this AnimControl");
}
}
/**
@ -318,37 +310,20 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
}
}
final void reset(){
resetToBind();
if (skeleton != null){
skeleton.resetAndUpdate();
}
}
@Override
public void setSpatial(Spatial spatial) {
super.setSpatial(spatial);
void resetToBind(){
for (int i = 0; i < targets.length; i++){
Mesh mesh = targets[i];
if (targets[i].getBuffer(Type.BindPosePosition) != null){
VertexBuffer bi = mesh.getBuffer(Type.BoneIndex);
ByteBuffer bib = (ByteBuffer) bi.getData();
if (!bib.hasArray())
mesh.prepareForAnim(true); // prepare for software animation
//Backward compatibility.
if (skeletonControl != null) {
spatial.addControl(skeletonControl);
}
VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition);
VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal);
VertexBuffer pos = mesh.getBuffer(Type.Position);
VertexBuffer norm = mesh.getBuffer(Type.Normal);
FloatBuffer pb = (FloatBuffer) pos.getData();
FloatBuffer nb = (FloatBuffer) norm.getData();
FloatBuffer bpb = (FloatBuffer) bindPos.getData();
FloatBuffer bnb = (FloatBuffer) bindNorm.getData();
pb.clear();
nb.clear();
bpb.clear();
bnb.clear();
pb.put(bpb).clear();
nb.put(bnb).clear();
}
final void reset() {
if (skeleton != null) {
skeleton.resetAndUpdate();
}
}
@ -367,23 +342,16 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
*/
public float getAnimationLength(String name) {
BoneAnimation a = animationMap.get(name);
if (a == null)
throw new IllegalArgumentException("The animation " + name +
" does not exist in this AnimControl");
return a.getLength();
if (a == null) {
throw new IllegalArgumentException("The animation " + name
+ " does not exist in this AnimControl");
}
private RagdollControl ragdoll=null;
public void setRagdoll(RagdollControl ragdoll) {
this.ragdoll = ragdoll;
return a.getLength();
}
@Override
protected void controlUpdate(float tpf) {
resetToBind(); // reset morph meshes to bind pose
skeleton.reset(); // reset skeleton to bind pose
for (int i = 0; i < channels.size(); i++) {
@ -391,119 +359,16 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
}
skeleton.updateWorldVectors();
// here update the targets vertices if no hardware skinning supported
if(ragdoll!=null){
ragdoll.update(tpf);
}
Matrix4f[] offsetMatrices = skeleton.computeSkinningMatrices();
// if hardware skinning is supported, the matrices and weight buffer
// will be sent by the SkinningShaderLogic object assigned to the shader
for (int i = 0; i < targets.length; i++){
// only update targets with bone-vertex assignments
if (targets[i].getBuffer(Type.BoneIndex) != null)
softwareSkinUpdate(targets[i], offsetMatrices);
}
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices){
int maxWeightsPerVert = mesh.getMaxNumWeights();
int fourMinusMaxWeights = 4 - maxWeightsPerVert;
// NOTE: This code assumes the vertex buffer is in bind pose
// resetToBind() has been called this frame
VertexBuffer vb = mesh.getBuffer(Type.Position);
FloatBuffer fvb = (FloatBuffer) vb.getData();
fvb.rewind();
VertexBuffer nb = mesh.getBuffer(Type.Normal);
FloatBuffer fnb = (FloatBuffer) nb.getData();
fnb.rewind();
// get boneIndexes and weights for mesh
ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
ib.rewind();
wb.rewind();
float[] weights = wb.array();
byte[] indices = ib.array();
int idxWeights = 0;
TempVars vars = TempVars.get();
float[] posBuf = vars.skinPositions;
float[] normBuf = vars.skinNormals;
int iterations = (int) FastMath.ceil(fvb.capacity() / ((float)posBuf.length));
int bufLength = posBuf.length * 3;
for (int i = iterations-1; i >= 0; i--){
// read next set of positions and normals from native buffer
bufLength = Math.min(posBuf.length, fvb.remaining());
fvb.get(posBuf, 0, bufLength);
fnb.get(normBuf, 0, bufLength);
int verts = bufLength / 3;
int idxPositions = 0;
// iterate vertices and apply skinning transform for each effecting bone
for (int vert = verts - 1; vert >= 0; vert--){
float nmx = normBuf[idxPositions];
float vtx = posBuf[idxPositions++];
float nmy = normBuf[idxPositions];
float vty = posBuf[idxPositions++];
float nmz = normBuf[idxPositions];
float vtz = posBuf[idxPositions++];
float rx=0, ry=0, rz=0, rnx=0, rny=0, rnz=0;
for (int w = maxWeightsPerVert - 1; w >= 0; w--){
float weight = weights[idxWeights];
Matrix4f mat = offsetMatrices[indices[idxWeights++]];
rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight;
rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;
}
idxWeights += fourMinusMaxWeights;
idxPositions -= 3;
normBuf[idxPositions] = rnx;
posBuf[idxPositions++] = rx;
normBuf[idxPositions] = rny;
posBuf[idxPositions++] = ry;
normBuf[idxPositions] = rnz;
posBuf[idxPositions++] = rz;
}
fvb.position(fvb.position()-bufLength);
fvb.put(posBuf, 0, bufLength);
fnb.position(fnb.position()-bufLength);
fnb.put(normBuf, 0, bufLength);
}
vb.updateData(fvb);
nb.updateData(fnb);
// mesh.updateBound();
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.write(targets, "targets", null);
oc.write(skeleton, "skeleton", null);
oc.writeStringSavableMap(animationMap, "animations", null);
}
@ -512,13 +377,22 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule in = im.getCapsule(this);
Savable[] sav = in.readSavableArray("targets", null);
if (sav != null){
targets = new Mesh[sav.length];
System.arraycopy(sav, 0, targets, 0, sav.length);
}
skeleton = (Skeleton) in.readSavable("skeleton", null);
animationMap = (HashMap<String, BoneAnimation>) in.readStringSavableMap("animations", null);
//changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split
//if we find a target mesh array the AnimControl creates the SkeletonControl for old files and add it to the spatial.
//When backward compatibility won't be needed anymore this can deleted
Savable[] sav = in.readSavableArray("targets", null);
if (sav != null) {
Mesh[] tg = null;
tg = new Mesh[sav.length];
System.arraycopy(sav, 0, tg, 0, sav.length);
skeletonControl = new SkeletonControl((Node) spatial, tg, skeleton);
spatial.addControl(skeletonControl);
}
//------
}
}

@ -347,6 +347,7 @@ public final class Bone implements Savable {
/**
* Set user transform.
* The transforms are used as increments to current translations
* @see setUserControl
*/
public void setUserTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) {
@ -360,7 +361,7 @@ public final class Bone implements Savable {
localPos.addLocal(translation);
localRot = localRot.mult(rotation);
localScale.multLocal(scale);
localScale.addLocal(scale);
}
/**

@ -0,0 +1,274 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.jme3.animation;
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.FastMath;
import com.jme3.math.Matrix4f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
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.VertexBuffer.Type;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import com.jme3.util.TempVars;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
/**
*
* @author Nehon
*/
public class SkeletonControl extends AbstractControl implements Savable, Cloneable {
/**
* The skelrton of the model
*/
private Skeleton skeleton;
/**
* List of targets which this controller effects.
*/
Mesh[] targets;
public SkeletonControl() {
}
public SkeletonControl(Node model, Mesh[] targets, Skeleton skeleton) {
super(model);
this.skeleton = skeleton;
this.targets = targets;
}
@Override
protected void controlUpdate(float tpf) {
resetToBind(); // reset morph meshes to bind pose
Matrix4f[] offsetMatrices = skeleton.computeSkinningMatrices();
// if hardware skinning is supported, the matrices and weight buffer
// will be sent by the SkinningShaderLogic object assigned to the shader
for (int i = 0; i < targets.length; i++) {
// only update targets with bone-vertex assignments
if (targets[i].getBuffer(Type.BoneIndex) != null) {
softwareSkinUpdate(targets[i], offsetMatrices);
}
}
}
void resetToBind() {
for (int i = 0; i < targets.length; i++) {
Mesh mesh = targets[i];
if (targets[i].getBuffer(Type.BindPosePosition) != null) {
VertexBuffer bi = mesh.getBuffer(Type.BoneIndex);
ByteBuffer bib = (ByteBuffer) bi.getData();
if (!bib.hasArray()) {
mesh.prepareForAnim(true); // prepare for software animation
}
VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition);
VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal);
VertexBuffer pos = mesh.getBuffer(Type.Position);
VertexBuffer norm = mesh.getBuffer(Type.Normal);
FloatBuffer pb = (FloatBuffer) pos.getData();
FloatBuffer nb = (FloatBuffer) norm.getData();
FloatBuffer bpb = (FloatBuffer) bindPos.getData();
FloatBuffer bnb = (FloatBuffer) bindNorm.getData();
pb.clear();
nb.clear();
bpb.clear();
bnb.clear();
pb.put(bpb).clear();
nb.put(bnb).clear();
}
}
}
private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) {
int maxWeightsPerVert = mesh.getMaxNumWeights();
int fourMinusMaxWeights = 4 - maxWeightsPerVert;
// NOTE: This code assumes the vertex buffer is in bind pose
// resetToBind() has been called this frame
VertexBuffer vb = mesh.getBuffer(Type.Position);
FloatBuffer fvb = (FloatBuffer) vb.getData();
fvb.rewind();
VertexBuffer nb = mesh.getBuffer(Type.Normal);
FloatBuffer fnb = (FloatBuffer) nb.getData();
fnb.rewind();
// get boneIndexes and weights for mesh
ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
ib.rewind();
wb.rewind();
float[] weights = wb.array();
byte[] indices = ib.array();
int idxWeights = 0;
TempVars vars = TempVars.get();
float[] posBuf = vars.skinPositions;
float[] normBuf = vars.skinNormals;
int iterations = (int) FastMath.ceil(fvb.capacity() / ((float) posBuf.length));
int bufLength = posBuf.length * 3;
for (int i = iterations - 1; i >= 0; i--) {
// read next set of positions and normals from native buffer
bufLength = Math.min(posBuf.length, fvb.remaining());
fvb.get(posBuf, 0, bufLength);
fnb.get(normBuf, 0, bufLength);
int verts = bufLength / 3;
int idxPositions = 0;
// iterate vertices and apply skinning transform for each effecting bone
for (int vert = verts - 1; vert >= 0; vert--) {
float nmx = normBuf[idxPositions];
float vtx = posBuf[idxPositions++];
float nmy = normBuf[idxPositions];
float vty = posBuf[idxPositions++];
float nmz = normBuf[idxPositions];
float vtz = posBuf[idxPositions++];
float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0;
for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
float weight = weights[idxWeights];
Matrix4f mat = offsetMatrices[indices[idxWeights++]];
rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight;
rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;
}
idxWeights += fourMinusMaxWeights;
idxPositions -= 3;
normBuf[idxPositions] = rnx;
posBuf[idxPositions++] = rx;
normBuf[idxPositions] = rny;
posBuf[idxPositions++] = ry;
normBuf[idxPositions] = rnz;
posBuf[idxPositions++] = rz;
}
fvb.position(fvb.position() - bufLength);
fvb.put(posBuf, 0, bufLength);
fnb.position(fnb.position() - bufLength);
fnb.put(normBuf, 0, bufLength);
}
vb.updateData(fvb);
nb.updateData(fnb);
// mesh.updateBound();
}
final void reset() {
resetToBind();
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
public Control cloneForSpatial(Spatial spatial) {
Node clonedNode = (Node) spatial;
AnimControl ctrl = spatial.getControl(AnimControl.class);
SkeletonControl clone = new SkeletonControl();
clone.skeleton = ctrl.getSkeleton();
Mesh[] meshes = new Mesh[targets.length];
for (int i = 0; i < meshes.length; i++) {
meshes[i] = ((Geometry) clonedNode.getChild(i)).getMesh();
}
for (int i = meshes.length; i < clonedNode.getQuantity(); i++) {
// go through attachment nodes, apply them to correct bone
Spatial child = clonedNode.getChild(i);
if (child instanceof Node) {
Node clonedAttachNode = (Node) child;
Bone originalBone = (Bone) clonedAttachNode.getUserData("AttachedBone");
if (originalBone != null) {
Bone clonedBone = clone.skeleton.getBone(originalBone.getName());
clonedAttachNode.setUserData("AttachedBone", clonedBone);
clonedBone.setAttachmentsNode(clonedAttachNode);
}
}
}
clone.targets = meshes;
return clone;
}
/**
*
* @param boneName the name of the bone
* @return the node attached to this bone
*/
public Node getAttachmentsNode(String boneName) {
Bone b = skeleton.getBone(boneName);
if (b == null) {
throw new IllegalArgumentException("Given bone name does not exist "
+ "in the skeleton.");
}
Node n = b.getAttachmentsNode();
Node model = (Node) spatial;
model.attachChild(n);
return n;
}
public Skeleton getSkeleton() {
return skeleton;
}
public void setSkeleton(Skeleton skeleton) {
this.skeleton = skeleton;
}
public Mesh[] getTargets() {
return targets;
}
public void setTargets(Mesh[] targets) {
this.targets = targets;
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
oc.write(targets, "targets", null);
oc.write(skeleton, "skeleton", null);
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule in = im.getCapsule(this);
Savable[] sav = in.readSavableArray("targets", null);
if (sav != null) {
targets = new Mesh[sav.length];
System.arraycopy(sav, 0, targets, 0, sav.length);
}
skeleton = (Skeleton) in.readSavable("skeleton", null);
}
}

@ -29,7 +29,6 @@
* 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.scene;
import com.jme3.bounding.BoundingVolume;
@ -64,7 +63,6 @@ import java.util.LinkedList;
import java.util.Queue;
import java.util.logging.Logger;
/**
* <code>Spatial</code> defines the base class for scene graph nodes. It
* maintains a link to a parent, it's local transforms and the world's
@ -80,77 +78,59 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
private static final Logger logger = Logger.getLogger(Spatial.class.getName());
public enum CullHint {
/**
* Do whatever our parent does. If no parent, we'll default to dynamic.
*/
Inherit,
/**
* Do not draw if we are not at least partially within the view frustum
* of the renderer's camera.
*/
Dynamic,
/**
* Always cull this from view.
*/
Always,
/**
* Never cull this from view. Note that we will still get culled if our
* parent is culled.
*/
Never;
}
/**
* Refresh flag types
*/
protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms
RF_BOUND = 0x02,
RF_LIGHTLIST = 0x04; // changes in light lists
protected CullHint cullHint = CullHint.Inherit;
/**
* Spatial's bounding volume relative to the world.
*/
protected BoundingVolume worldBound;
/**
* LightList
*/
protected LightList localLights;
protected transient LightList worldLights;
/**
* This spatial's name.
*/
protected String name;
// scale values
protected transient Camera.FrustumIntersect frustrumIntersects = Camera.FrustumIntersect.Intersects;
protected RenderQueue.Bucket queueBucket = RenderQueue.Bucket.Inherit;
protected ShadowMode shadowMode = RenderQueue.ShadowMode.Inherit;
public transient float queueDistance = Float.NEGATIVE_INFINITY;
protected Transform localTransform;
protected Transform worldTransform;
protected ArrayList<Control> controls = new ArrayList<Control>(1);
protected HashMap<String, Savable> userData = null;
/**
* Spatial's parent, or null if it has none.
*/
protected transient Node parent;
/**
* Refresh flags. Indicate what data of the spatial need to be
* updated to reflect the correct state.
@ -206,8 +186,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
// XXX: Replace with a recursive call?
Spatial p = parent;
while (p != null) {
if ((p.refreshFlags & RF_BOUND) != 0)
if ((p.refreshFlags & RF_BOUND) != 0) {
return;
}
p.refreshFlags |= RF_BOUND;
p = p.parent;
@ -437,8 +418,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
}
void checkDoTransformUpdate() {
if ( (refreshFlags & RF_TRANSFORM) == 0 )
if ((refreshFlags & RF_TRANSFORM) == 0) {
return;
}
if (parent == null) {
worldTransform.set(localTransform);
@ -482,8 +464,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
}
void checkDoBoundUpdate() {
if ( (refreshFlags & RF_BOUND) == 0 )
if ((refreshFlags & RF_BOUND) == 0) {
return;
}
checkDoTransformUpdate();
@ -502,8 +485,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
}
private void runControlUpdate(float tpf) {
if (controls.size() == 0)
if (controls.size() == 0) {
return;
}
for (int i = 0; i < controls.size(); i++) {
controls.get(i).update(tpf);
@ -521,8 +505,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* @see Spatial#getControl(java.lang.Class)
*/
public void runControlRender(RenderManager rm, ViewPort vp) {
if (controls.size() == 0)
if (controls.size() == 0) {
return;
}
for (int i = 0; i < controls.size(); i++) {
controls.get(i).render(rm, vp);
@ -565,8 +550,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
*/
public boolean removeControl(Control control) {
boolean result = controls.remove(control);
if (result)
if (result) {
control.setSpatial(null);
}
return result;
}
@ -613,7 +599,6 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
return controls.size();
}
/**
* <code>updateLogicalState</code> calls the <code>update()</code> method
* for all controls attached to this Spatial.
@ -998,14 +983,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* the cullmode of it's parent.
*/
public CullHint getCullHint() {
if (cullHint != CullHint.Inherit)
if (cullHint != CullHint.Inherit) {
return cullHint;
else if (parent != null)
} else if (parent != null) {
return parent.getCullHint();
else
} else {
return CullHint.Dynamic;
}
}
/**
* Returns this spatial's renderqueue bucket. If the mode is set to inherit,
@ -1014,13 +999,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* @return The spatial's current renderqueue mode.
*/
public RenderQueue.Bucket getQueueBucket() {
if (queueBucket != RenderQueue.Bucket.Inherit)
if (queueBucket != RenderQueue.Bucket.Inherit) {
return queueBucket;
else if (parent != null)
} else if (parent != null) {
return parent.getQueueBucket();
else
} else {
return RenderQueue.Bucket.Opaque;
}
}
/**
* @return The shadow mode of this spatial, if the local shadow
@ -1030,13 +1016,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* @see ShadowMode
*/
public RenderQueue.ShadowMode getShadowMode() {
if (shadowMode != RenderQueue.ShadowMode.Inherit)
if (shadowMode != RenderQueue.ShadowMode.Inherit) {
return shadowMode;
else if (parent != null)
} else if (parent != null) {
return parent.getShadowMode();
else
} else {
return ShadowMode.Off;
}
}
/**
* Sets the level of detail to use when rendering this Spatial,
@ -1086,8 +1073,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
public Spatial clone(boolean cloneMaterial) {
try {
Spatial clone = (Spatial) super.clone();
if (worldBound != null)
if (worldBound != null) {
clone.worldBound = worldBound.clone();
}
clone.worldLights = worldLights.clone();
clone.localLights = localLights.clone();
@ -1156,8 +1144,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
public abstract Spatial deepClone();
public void setUserData(String key, Object data) {
if (userData == null)
if (userData == null) {
userData = new HashMap<String, Savable>();
}
if (data instanceof Savable) {
userData.put(key, (Savable) data);
@ -1167,8 +1156,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
}
public Object getUserData(String key) {
if (userData == null)
if (userData == null) {
return null;
}
Savable s = userData.get(key);
if (s instanceof UserData) {
@ -1179,8 +1169,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
}
public Collection<String> getUserDataKeys() {
if (userData != null)
if (userData != null) {
return userData.keySet();
}
return Collections.EMPTY_SET;
}
@ -1203,11 +1194,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
*/
public boolean matches(Class<? extends Spatial> spatialSubclass,
String nameRegex) {
if (spatialSubclass != null && !spatialSubclass.isInstance(this))
if (spatialSubclass != null && !spatialSubclass.isInstance(this)) {
return false;
}
if (nameRegex != null && (name == null || !name.matches(nameRegex)))
if (nameRegex != null && (name == null || !name.matches(nameRegex))) {
return false;
}
return true;
}
@ -1241,7 +1234,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
localLights = (LightList) ic.readSavable("lights", null);
localLights.setOwner(this);
controls = ic.readSavableArrayList("controlsList", null);
//changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split
//the AnimControl creates the SkeletonControl for old files and add it to the spatial.
//The SkeletonControl must be the last in the stack so we add the list of all other control before it.
//When backward compatibility won't be needed anymore this can be replaced by :
//controls = ic.readSavableArrayList("controlsList", null));
controls.addAll(0, ic.readSavableArrayList("controlsList", null));
userData = (HashMap<String, Savable>) ic.readStringSavableMap("user_data", null);
}
@ -1422,4 +1421,3 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
protected abstract void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue);
}

@ -3,6 +3,7 @@ package com.jme3.bullet.control;
import com.jme3.animation.AnimControl;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.animation.SkeletonControl;
import com.jme3.asset.AssetManager;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
@ -107,6 +108,15 @@ public class RagdollControl implements PhysicsControl {
public void setSpatial(Spatial model) {
targetModel = model;
//HACK ALERT change this
//I remove the skeletonControl and readd it to the spatial to make sure it's after the ragdollControl in the stack
//Find a proper way to order the controls.
SkeletonControl sc = model.getControl(SkeletonControl.class);
model.removeControl(sc);
model.addControl(sc);
//----
removeFromPhysicsSpace();
clearData();
// put into bind pose and compute bone transforms in model space

@ -29,11 +29,11 @@
* 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.scene.plugins.ogre;
import com.jme3.animation.AnimControl;
import com.jme3.animation.BoneAnimation;
import com.jme3.animation.SkeletonControl;
import com.jme3.asset.AssetInfo;
import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetLoader;
@ -81,15 +81,12 @@ import static com.jme3.util.xml.SAXUtil.*;
public class MeshLoader extends DefaultHandler implements AssetLoader {
private static final Logger logger = Logger.getLogger(MeshLoader.class.getName());
public static boolean AUTO_INTERLEAVE = true;
public static boolean HARDWARE_SKINNING = false;
private String meshName;
private String folderName;
private AssetManager assetManager;
private MaterialList materialList;
private ShortBuffer sb;
private IntBuffer ib;
private FloatBuffer fb;
@ -105,12 +102,10 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
private boolean bigindices = false;
private int vertCount;
private int triCount;
private List<Geometry> geoms = new ArrayList<Geometry>();
private List<Boolean> usesSharedGeom = new ArrayList<Boolean>();
private IntMap<List<VertexBuffer>> lodLevels = new IntMap<List<VertexBuffer>>();
private AnimData animData;
private ByteBuffer indicesData;
private FloatBuffer weightsFloatData;
@ -210,11 +205,13 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
}
}
if (mat == null)
if (mat == null) {
throw new RuntimeException("Cannot locate material named " + matName);
}
if (mat.isTransparent())
if (mat.isTransparent()) {
geom.setQueueBucket(Bucket.Transparent);
}
// else
// geom.setShadowMode(ShadowMode.CastAndReceive);
@ -248,10 +245,11 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
usesSharedGeom.add(false);
}
if (meshName == null)
if (meshName == null) {
geom = new Geometry("OgreSubmesh-" + (++geomIdx), mesh);
else
} else {
geom = new Geometry(meshName + "-geom-" + (++geomIdx), mesh);
}
applyMaterial(geom, matName);
geoms.add(geom);
@ -262,10 +260,11 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
vertCount = parseInt(vertexcount);
// sharedmesh.setVertexCount(vertCount);
if (meshName == null)
if (meshName == null) {
sharedgeom = new Geometry("Ogre-SharedGeom", sharedmesh);
else
} else {
sharedgeom = new Geometry(meshName + "-sharedgeom", sharedmesh);
}
sharedgeom.setCullHint(CullHint.Always);
geoms.add(sharedgeom);
@ -394,11 +393,13 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
int texCoords = parseInt(attribs.getValue("texture_coords"), 0);
for (int i = 0; i < texCoords; i++) {
int dims = parseInt(attribs.getValue("texture_coord_dimensions_" + i), 2);
if (dims < 1 || dims > 4)
if (dims < 1 || dims > 4) {
throw new SAXException("Texture coord dimensions must be 1 <= dims <= 4");
}
if (i >= 2)
if (i >= 2) {
throw new SAXException("More than 2 texture coordinates not supported");
}
if (i == 0) {
vb = new VertexBuffer(Type.TexCoord);
@ -418,9 +419,7 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
private void pushAttrib(Type type, Attributes attribs) throws SAXException {
try {
FloatBuffer buf = (FloatBuffer) mesh.getBuffer(type).getData();
buf.put(parseFloat(attribs.getValue("x")))
.put(parseFloat(attribs.getValue("y")))
.put(parseFloat(attribs.getValue("z")));
buf.put(parseFloat(attribs.getValue("x"))).put(parseFloat(attribs.getValue("y"))).put(parseFloat(attribs.getValue("z")));
} catch (Exception ex) {
throw new SAXException("Failed to push attrib", ex);
}
@ -430,9 +429,7 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
try {
VertexBuffer tangentBuf = mesh.getBuffer(Type.Tangent);
FloatBuffer buf = (FloatBuffer) tangentBuf.getData();
buf.put(parseFloat(attribs.getValue("x")))
.put(parseFloat(attribs.getValue("y")))
.put(parseFloat(attribs.getValue("z")));
buf.put(parseFloat(attribs.getValue("x"))).put(parseFloat(attribs.getValue("y"))).put(parseFloat(attribs.getValue("z")));
if (tangentBuf.getNumComponents() == 4) {
buf.put(parseFloat(attribs.getValue("w")));
}
@ -442,9 +439,9 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
}
private void pushTexCoord(Attributes attribs) throws SAXException {
if (texCoordIdx >= 2)
if (texCoordIdx >= 2) {
return; // TODO: More than 2 texcoords
}
Type type = texCoordIdx == 0 ? Type.TexCoord : Type.TexCoord2;
VertexBuffer tcvb = mesh.getBuffer(type);
@ -468,17 +465,19 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
FloatBuffer buf = (FloatBuffer) mesh.getBuffer(Type.Color).getData();
String value = parseString(attribs.getValue("value"));
String[] vals = value.split(" ");
if (vals.length != 3 && vals.length != 4)
if (vals.length != 3 && vals.length != 4) {
throw new SAXException("Color value must contain 3 or 4 components");
}
ColorRGBA color = new ColorRGBA();
color.r = parseFloat(vals[0]);
color.g = parseFloat(vals[1]);
color.b = parseFloat(vals[2]);
if (vals.length == 3)
if (vals.length == 3) {
color.a = 1f;
else
} else {
color.a = parseFloat(vals[3]);
}
buf.put(color.r).put(color.g).put(color.b).put(color.a);
}
@ -534,9 +533,10 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
// see which weights are unused for a given bone
for (i = vert * 4; i < vert * 4 + 4; i++) {
float v = weightsFloatData.get(i);
if (v == 0)
if (v == 0) {
break;
}
}
weightsFloatData.put(i, w);
indicesData.put(i, bone);
@ -545,9 +545,10 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
private void startSkeleton(String name) {
animData = (AnimData) assetManager.loadAsset(folderName + name + ".xml");
//TODO:workaround for meshxml / mesh.xml
if(animData==null)
if (animData == null) {
animData = (AnimData) assetManager.loadAsset(folderName + name + "xml");
}
}
private void startSubmeshName(String indexStr, String nameStr) {
int index = Integer.parseInt(indexStr);
@ -556,8 +557,9 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
@Override
public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException {
if (ignoreUntilEnd != null)
if (ignoreUntilEnd != null) {
return;
}
if (qName.equals("texcoord")) {
pushTexCoord(attribs);
@ -585,8 +587,9 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
startFaces(attribs.getValue("count"));
} else if (qName.equals("geometry")) {
String count = attribs.getValue("vertexcount");
if (count == null)
if (count == null) {
count = attribs.getValue("count");
}
startGeometry(count);
} else if (qName.equals("vertexbuffer")) {
@ -607,11 +610,13 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
attribs.getValue("operationtype"));
} else if (qName.equals("sharedgeometry")) {
String count = attribs.getValue("vertexcount");
if (count == null)
if (count == null) {
count = attribs.getValue("count");
}
if (count != null && !count.equals("0"))
if (count != null && !count.equals("0")) {
startSharedGeom(count);
}
} else if (qName.equals("submeshes")) {
// ok
} else if (qName.equals("skeletonlink")) {
@ -629,8 +634,9 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
@Override
public void endElement(String uri, String name, String qName) {
if (ignoreUntilEnd != null) {
if (ignoreUntilEnd.equals(qName))
if (ignoreUntilEnd.equals(qName)) {
ignoreUntilEnd = null;
}
return;
}
@ -644,10 +650,11 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
geom = sharedgeom;
mesh = sharedmesh;
} else if (qName.equals("faces")) {
if (ib != null)
if (ib != null) {
ib.flip();
else
} else {
sb.flip();
}
vb = null;
ib = null;
@ -661,9 +668,10 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
IntMap<VertexBuffer> bufs = mesh.getBuffers();
for (Entry<VertexBuffer> entry : bufs) {
Buffer data = entry.getValue().getData();
if (data.position() != 0)
if (data.position() != 0) {
data.flip();
}
}
mesh.updateBound();
mesh.setStatic();
@ -719,10 +727,11 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
private Node compileModel() {
String nodeName;
if (meshName == null)
if (meshName == null) {
nodeName = "OgreMesh" + (++nodeIdx);
else
} else {
nodeName = meshName + "-ogremesh";
}
Node model = new Node(nodeName);
if (animData != null) {
@ -745,22 +754,27 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
VertexBuffer boneIndex = sharedmesh.getBuffer(Type.BoneIndex);
VertexBuffer boneWeight = sharedmesh.getBuffer(Type.BoneWeight);
if (bindPos != null)
if (bindPos != null) {
m.setBuffer(bindPos);
}
if (bindNorm != null)
if (bindNorm != null) {
m.setBuffer(bindNorm);
}
if (boneIndex != null)
if (boneIndex != null) {
m.setBuffer(boneIndex);
}
if (boneWeight != null)
if (boneWeight != null) {
m.setBuffer(boneWeight);
}
}
}
Mesh[] meshes = new Mesh[newMeshes.size()];
for (int i = 0; i < meshes.length; i++)
for (int i = 0; i < meshes.length; i++) {
meshes[i] = newMeshes.get(i);
}
HashMap<String, BoneAnimation> anims = new HashMap<String, BoneAnimation>();
ArrayList<BoneAnimation> animList = animData.anims;
@ -769,11 +783,12 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
anims.put(anim.getName(), anim);
}
AnimControl ctrl = new AnimControl(model,
meshes,
animData.skeleton);
//AnimControl ctrl = new AnimControl(model, meshes, animData.skeleton);
AnimControl ctrl = new AnimControl(animData.skeleton);
ctrl.setAnimations(anims);
model.addControl(ctrl);
SkeletonControl skeletonControl = new SkeletonControl(model, meshes, animData.skeleton);
model.addControl(skeletonControl);
}
for (int i = 0; i < geoms.size(); i++) {
@ -828,5 +843,4 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
}
}
}

@ -105,12 +105,12 @@ public class TestBoneRagdoll extends SimpleApplication {
// ragdoll.setEnabled(true);
// ragdoll.attachDebugShape(assetManager);
ragdoll.setSpatial(model);
ragdoll.setPhysicsSpace(getPhysicsSpace());
control.setRagdoll(ragdoll);
// ragdoll.setSpatial(model);
// ragdoll.setPhysicsSpace(getPhysicsSpace());
// control.setRagdoll(ragdoll);
// model.addControl(ragdoll);
// getPhysicsSpace().add(ragdoll);
model.addControl(ragdoll);
getPhysicsSpace().add(ragdoll);
speed = 1f;
rootNode.attachChild(model);

@ -131,11 +131,11 @@ public class TestOgreComplexAnim extends SimpleApplication {
q.fromAngles(0, angle, 0);
b.setUserControl(true);
b.setUserTransforms(Vector3f.ZERO, q, new Vector3f(angle, angle, angle));
b.setUserTransforms(Vector3f.ZERO, q, Vector3f.ZERO);
b2.setUserControl(true);
b2.setUserTransforms(Vector3f.ZERO, Quaternion.IDENTITY, new Vector3f(angle, angle, angle));
// b2.setUserControl(true);
// b2.setUserTransforms(Vector3f.ZERO, Quaternion.IDENTITY, new Vector3f(angle, angle, angle));
//
}

Loading…
Cancel
Save