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. 328
      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. 290
      engine/src/core/com/jme3/scene/Spatial.java
  5. 10
      engine/src/jbullet/com/jme3/bullet/control/RagdollControl.java
  6. 440
      engine/src/ogre/com/jme3/scene/plugins/ogre/MeshLoader.java
  7. 14
      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 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.jme3.animation; package com.jme3.animation;
import com.jme3.bullet.control.RagdollControl;
import com.jme3.export.JmeExporter; import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter; import com.jme3.export.JmeImporter;
import com.jme3.export.InputCapsule; import com.jme3.export.InputCapsule;
import com.jme3.export.OutputCapsule; import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable; import com.jme3.export.Savable;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f;
import com.jme3.renderer.RenderManager; import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort; import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh; import com.jme3.scene.Mesh;
import com.jme3.scene.Node; import com.jme3.scene.Node;
import com.jme3.scene.Spatial; 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.AbstractControl;
import com.jme3.scene.control.Control; import com.jme3.scene.control.Control;
import com.jme3.util.TempVars;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@ -83,26 +73,23 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
/** /**
* List of targets which this controller effects. * List of targets which this controller effects.
*/ */
Mesh[] targets; // Mesh[] targets;
/** /**
* Skeleton object must contain corresponding data for the targets' weight buffers. * Skeleton object must contain corresponding data for the targets' weight buffers.
*/ */
Skeleton skeleton; Skeleton skeleton;
/** only used for backward compatibility */
@Deprecated
private SkeletonControl skeletonControl;
/** /**
* List of animations * List of animations
*/ */
HashMap<String, BoneAnimation> animationMap; HashMap<String, BoneAnimation> animationMap;
/** /**
* Animation channels * Animation channels
*/ */
transient ArrayList<AnimChannel> channels transient ArrayList<AnimChannel> channels = new ArrayList<AnimChannel>();
= new ArrayList<AnimChannel>(); transient ArrayList<AnimEventListener> listeners = new ArrayList<AnimEventListener>();
transient ArrayList<AnimEventListener> listeners
= new ArrayList<AnimEventListener>();
/** /**
* Create a new <code>AnimControl</code> that will animate the given skins * 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. * properly set BoneIndex and BoneWeight buffers.
* @param skeleton The skeleton structure represents a bone hierarchy * @param skeleton The skeleton structure represents a bone hierarchy
* to be animated. * 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.
*/ */
public AnimControl(Node model, Mesh[] meshes, Skeleton skeleton){ @Deprecated
public AnimControl(Node model, Mesh[] meshes, Skeleton skeleton) {
super(model); super(model);
this.skeleton = skeleton; this.skeleton = skeleton;
this.targets = meshes;
skeletonControl = new SkeletonControl(model, meshes, this.skeleton);
reset();
}
public AnimControl(Skeleton skeleton) {
this.skeleton = skeleton;
reset(); reset();
} }
@ -131,32 +128,11 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
public AnimControl() { public AnimControl() {
} }
public Control cloneForSpatial(Spatial spatial){ public Control cloneForSpatial(Spatial spatial) {
try { try {
Node clonedNode = (Node) spatial;
AnimControl clone = (AnimControl) super.clone(); AnimControl clone = (AnimControl) super.clone();
clone.spatial = spatial; clone.spatial = spatial;
clone.skeleton = new Skeleton(skeleton); 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>(); clone.channels = new ArrayList<AnimChannel>();
return clone; return clone;
} catch (CloneNotSupportedException ex) { } catch (CloneNotSupportedException ex) {
@ -169,7 +145,7 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
* will be capable of playing. The animations should be compatible * will be capable of playing. The animations should be compatible
* with the skeleton given in the constructor. * with the skeleton given in the constructor.
*/ */
public void setAnimations(HashMap<String, BoneAnimation> animations){ public void setAnimations(HashMap<String, BoneAnimation> animations) {
animationMap = animations; animationMap = animations;
} }
@ -179,7 +155,7 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
* @return The animation corresponding to the given name, or null, if no * @return The animation corresponding to the given name, or null, if no
* such named animation exists. * such named animation exists.
*/ */
public BoneAnimation getAnim(String name){ public BoneAnimation getAnim(String name) {
return animationMap.get(name); return animationMap.get(name);
} }
@ -188,7 +164,7 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
* <code>AnimControl</code>. * <code>AnimControl</code>.
* @param anim The animation to add. * @param anim The animation to add.
*/ */
public void addAnim(BoneAnimation anim){ public void addAnim(BoneAnimation anim) {
animationMap.put(anim.getName(), anim); animationMap.put(anim.getName(), anim);
} }
@ -196,23 +172,34 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
* Remove an animation so that it is no longer available for playing. * Remove an animation so that it is no longer available for playing.
* @param anim The animation to remove. * @param anim The animation to remove.
*/ */
public void removeAnim(BoneAnimation anim){ public void removeAnim(BoneAnimation anim) {
if (!animationMap.containsKey(anim.getName())) if (!animationMap.containsKey(anim.getName())) {
throw new IllegalArgumentException("Given animation does not exist " + throw new IllegalArgumentException("Given animation does not exist "
"in this AnimControl"); + "in this AnimControl");
}
animationMap.remove(anim.getName()); 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) { public Node getAttachmentsNode(String boneName) {
Bone b = skeleton.getBone(boneName); Bone b = skeleton.getBone(boneName);
if (b == null) if (b == null) {
throw new IllegalArgumentException("Given bone name does not exist " + throw new IllegalArgumentException("Given bone name does not exist "
"in the skeleton."); + "in the skeleton.");
}
Node n = b.getAttachmentsNode(); Node n = b.getAttachmentsNode();
Node model = (Node) spatial; if (spatial != null) {
model.attachChild(n); Node model = (Node) spatial;
model.attachChild(n);
}
return n; return n;
} }
@ -222,7 +209,7 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
* *
* @return A new animation channel for this <code>AnimControl</code>. * @return A new animation channel for this <code>AnimControl</code>.
*/ */
public AnimChannel createChannel(){ public AnimChannel createChannel() {
AnimChannel channel = new AnimChannel(this); AnimChannel channel = new AnimChannel(this);
channels.add(channel); channels.add(channel);
return channel; return channel;
@ -236,7 +223,7 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
* *
* @throws IndexOutOfBoundsException If no channel exists at the given index. * @throws IndexOutOfBoundsException If no channel exists at the given index.
*/ */
public AnimChannel getChannel(int index){ public AnimChannel getChannel(int index) {
return channels.get(index); return channels.get(index);
} }
@ -246,7 +233,7 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
* *
* @see AnimControl#createChannel() * @see AnimControl#createChannel()
*/ */
public int getNumChannels(){ public int getNumChannels() {
return channels.size(); return channels.size();
} }
@ -255,7 +242,7 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
* *
* @see AnimControl#createChannel() * @see AnimControl#createChannel()
*/ */
public void clearChannels(){ public void clearChannels() {
channels.clear(); channels.clear();
} }
@ -269,19 +256,23 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
/** /**
* @return The targets, or skins, being influenced by this * @return The targets, or skins, being influenced by this
* <code>AnimControl</code>. * <code>AnimControl</code>.
* @deprecated use SkeletonControl.getTargets() instead
* get the SkeletonControl doing spatial.getControl(SkeletonControl.class);
*/ */
@Deprecated
public Mesh[] getTargets() { public Mesh[] getTargets() {
return targets; return skeletonControl.getTargets();
} }
/** /**
* Adds a new listener to receive animation related events. * Adds a new listener to receive animation related events.
* @param listener The listener to add. * @param listener The listener to add.
*/ */
public void addListener(AnimEventListener listener){ public void addListener(AnimEventListener listener) {
if (listeners.contains(listener)) if (listeners.contains(listener)) {
throw new IllegalArgumentException("The given listener is already " + throw new IllegalArgumentException("The given listener is already "
"registed at this AnimControl"); + "registed at this AnimControl");
}
listeners.add(listener); listeners.add(listener);
} }
@ -291,10 +282,11 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
* @param listener * @param listener
* @see AnimControl#addListener(com.jme3.animation.AnimEventListener) * @see AnimControl#addListener(com.jme3.animation.AnimEventListener)
*/ */
public void removeListener(AnimEventListener listener){ public void removeListener(AnimEventListener listener) {
if (!listeners.remove(listener)) if (!listeners.remove(listener)) {
throw new IllegalArgumentException("The given listener is not " + throw new IllegalArgumentException("The given listener is not "
"registed at this AnimControl"); + "registed at this AnimControl");
}
} }
/** /**
@ -302,53 +294,36 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
* *
* @see AnimControl#addListener(com.jme3.animation.AnimEventListener) * @see AnimControl#addListener(com.jme3.animation.AnimEventListener)
*/ */
public void clearListeners(){ public void clearListeners() {
listeners.clear(); listeners.clear();
} }
void notifyAnimChange(AnimChannel channel, String name){ void notifyAnimChange(AnimChannel channel, String name) {
for (int i = 0; i < listeners.size(); i++){ for (int i = 0; i < listeners.size(); i++) {
listeners.get(i).onAnimChange(this, channel, name); listeners.get(i).onAnimChange(this, channel, name);
} }
} }
void notifyAnimCycleDone(AnimChannel channel, String name){ void notifyAnimCycleDone(AnimChannel channel, String name) {
for (int i = 0; i < listeners.size(); i++){ for (int i = 0; i < listeners.size(); i++) {
listeners.get(i).onAnimCycleDone(this, channel, name); listeners.get(i).onAnimCycleDone(this, channel, name);
} }
} }
final void reset(){ @Override
resetToBind(); public void setSpatial(Spatial spatial) {
if (skeleton != null){ super.setSpatial(spatial);
skeleton.resetAndUpdate();
//Backward compatibility.
if (skeletonControl != null) {
spatial.addControl(skeletonControl);
} }
} }
void resetToBind(){ final void reset() {
for (int i = 0; i < targets.length; i++){ if (skeleton != null) {
Mesh mesh = targets[i]; skeleton.resetAndUpdate();
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();
}
} }
} }
@ -356,7 +331,7 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
* @return The names of all animations that this <code>AnimControl</code> * @return The names of all animations that this <code>AnimControl</code>
* can play. * can play.
*/ */
public Collection<String> getAnimationNames(){ public Collection<String> getAnimationNames() {
return animationMap.keySet(); return animationMap.keySet();
} }
@ -365,160 +340,59 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
* @param name The name of the animation * @param name The name of the animation
* @return The length of time, in seconds, of the named animation. * @return The length of time, in seconds, of the named animation.
*/ */
public float getAnimationLength(String name){ public float getAnimationLength(String name) {
BoneAnimation a = animationMap.get(name); BoneAnimation a = animationMap.get(name);
if (a == null) if (a == null) {
throw new IllegalArgumentException("The animation " + name + throw new IllegalArgumentException("The animation " + name
" does not exist in this AnimControl"); + " does not exist in this AnimControl");
}
return a.getLength(); return a.getLength();
} }
private RagdollControl ragdoll=null;
public void setRagdoll(RagdollControl ragdoll) {
this.ragdoll = ragdoll;
}
@Override @Override
protected void controlUpdate(float tpf) { protected void controlUpdate(float tpf) {
resetToBind(); // reset morph meshes to bind pose
skeleton.reset(); // reset skeleton to bind pose skeleton.reset(); // reset skeleton to bind pose
for (int i = 0; i < channels.size(); i++){ for (int i = 0; i < channels.size(); i++) {
channels.get(i).update(tpf); channels.get(i).update(tpf);
} }
skeleton.updateWorldVectors(); 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 @Override
protected void controlRender(RenderManager rm, ViewPort vp) { 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 @Override
public void write(JmeExporter ex) throws IOException{ public void write(JmeExporter ex) throws IOException {
super.write(ex); super.write(ex);
OutputCapsule oc = ex.getCapsule(this); OutputCapsule oc = ex.getCapsule(this);
oc.write(targets, "targets", null);
oc.write(skeleton, "skeleton", null); oc.write(skeleton, "skeleton", null);
oc.writeStringSavableMap(animationMap, "animations", null); oc.writeStringSavableMap(animationMap, "animations", null);
} }
@Override @Override
public void read(JmeImporter im) throws IOException{ public void read(JmeImporter im) throws IOException {
super.read(im); super.read(im);
InputCapsule in = im.getCapsule(this); 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); skeleton = (Skeleton) in.readSavable("skeleton", null);
animationMap = (HashMap<String, BoneAnimation>) in.readStringSavableMap("animations", 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. * Set user transform.
* The transforms are used as increments to current translations
* @see setUserControl * @see setUserControl
*/ */
public void setUserTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) { public void setUserTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) {
@ -360,7 +361,7 @@ public final class Bone implements Savable {
localPos.addLocal(translation); localPos.addLocal(translation);
localRot = localRot.mult(rotation); 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 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.jme3.scene; package com.jme3.scene;
import com.jme3.bounding.BoundingVolume; import com.jme3.bounding.BoundingVolume;
@ -64,7 +63,6 @@ import java.util.LinkedList;
import java.util.Queue; import java.util.Queue;
import java.util.logging.Logger; import java.util.logging.Logger;
/** /**
* <code>Spatial</code> defines the base class for scene graph nodes. It * <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 * 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()); private static final Logger logger = Logger.getLogger(Spatial.class.getName());
public enum CullHint { public enum CullHint {
/** /**
* Do whatever our parent does. If no parent, we'll default to dynamic. * Do whatever our parent does. If no parent, we'll default to dynamic.
*/ */
Inherit, Inherit,
/** /**
* Do not draw if we are not at least partially within the view frustum * Do not draw if we are not at least partially within the view frustum
* of the renderer's camera. * of the renderer's camera.
*/ */
Dynamic, Dynamic,
/** /**
* Always cull this from view. * Always cull this from view.
*/ */
Always, Always,
/** /**
* Never cull this from view. Note that we will still get culled if our * Never cull this from view. Note that we will still get culled if our
* parent is culled. * parent is culled.
*/ */
Never; Never;
} }
/** /**
* Refresh flag types * Refresh flag types
*/ */
protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms
RF_BOUND = 0x02, RF_BOUND = 0x02,
RF_LIGHTLIST = 0x04; // changes in light lists RF_LIGHTLIST = 0x04; // changes in light lists
protected CullHint cullHint = CullHint.Inherit; protected CullHint cullHint = CullHint.Inherit;
/** /**
* Spatial's bounding volume relative to the world. * Spatial's bounding volume relative to the world.
*/ */
protected BoundingVolume worldBound; protected BoundingVolume worldBound;
/** /**
* LightList * LightList
*/ */
protected LightList localLights; protected LightList localLights;
protected transient LightList worldLights; protected transient LightList worldLights;
/** /**
* This spatial's name. * This spatial's name.
*/ */
protected String name; protected String name;
// scale values // scale values
protected transient Camera.FrustumIntersect frustrumIntersects = Camera.FrustumIntersect.Intersects; protected transient Camera.FrustumIntersect frustrumIntersects = Camera.FrustumIntersect.Intersects;
protected RenderQueue.Bucket queueBucket = RenderQueue.Bucket.Inherit; protected RenderQueue.Bucket queueBucket = RenderQueue.Bucket.Inherit;
protected ShadowMode shadowMode = RenderQueue.ShadowMode.Inherit; protected ShadowMode shadowMode = RenderQueue.ShadowMode.Inherit;
public transient float queueDistance = Float.NEGATIVE_INFINITY; public transient float queueDistance = Float.NEGATIVE_INFINITY;
protected Transform localTransform; protected Transform localTransform;
protected Transform worldTransform; protected Transform worldTransform;
protected ArrayList<Control> controls = new ArrayList<Control>(1); protected ArrayList<Control> controls = new ArrayList<Control>(1);
protected HashMap<String, Savable> userData = null; protected HashMap<String, Savable> userData = null;
/** /**
* Spatial's parent, or null if it has none. * Spatial's parent, or null if it has none.
*/ */
protected transient Node parent; protected transient Node parent;
/** /**
* Refresh flags. Indicate what data of the spatial need to be * Refresh flags. Indicate what data of the spatial need to be
* updated to reflect the correct state. * updated to reflect the correct state.
@ -187,12 +167,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* Indicate that the transform of this spatial has changed and that * Indicate that the transform of this spatial has changed and that
* a refresh is required. * a refresh is required.
*/ */
protected void setTransformRefresh(){ protected void setTransformRefresh() {
refreshFlags |= RF_TRANSFORM; refreshFlags |= RF_TRANSFORM;
setBoundRefresh(); setBoundRefresh();
} }
protected void setLightListRefresh(){ protected void setLightListRefresh() {
refreshFlags |= RF_LIGHTLIST; refreshFlags |= RF_LIGHTLIST;
} }
@ -200,14 +180,15 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* Indicate that the bounding of this spatial has changed and that * Indicate that the bounding of this spatial has changed and that
* a refresh is required. * a refresh is required.
*/ */
protected void setBoundRefresh(){ protected void setBoundRefresh() {
refreshFlags |= RF_BOUND; refreshFlags |= RF_BOUND;
// XXX: Replace with a recursive call? // XXX: Replace with a recursive call?
Spatial p = parent; Spatial p = parent;
while (p != null){ while (p != null) {
if ((p.refreshFlags & RF_BOUND) != 0) if ((p.refreshFlags & RF_BOUND) != 0) {
return; return;
}
p.refreshFlags |= RF_BOUND; p.refreshFlags |= RF_BOUND;
p = p.parent; p = p.parent;
@ -225,20 +206,20 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* @return true if inside or intersecting camera frustum * @return true if inside or intersecting camera frustum
* (should be rendered), false if outside. * (should be rendered), false if outside.
*/ */
public boolean checkCulling(Camera cam){ public boolean checkCulling(Camera cam) {
if (refreshFlags != 0){ if (refreshFlags != 0) {
throw new IllegalStateException("Scene graph is not properly updated for rendering.\n" throw new IllegalStateException("Scene graph is not properly updated for rendering.\n"
+ "Make sure scene graph state was not changed after\n" + "Make sure scene graph state was not changed after\n"
+ " rootNode.updateGeometricState() call. \n" + " rootNode.updateGeometricState() call. \n"
+ "Problem spatial name: "+getName()); + "Problem spatial name: " + getName());
} }
CullHint cm = getCullHint(); CullHint cm = getCullHint();
assert cm != CullHint.Inherit; assert cm != CullHint.Inherit;
if (cm == Spatial.CullHint.Always){ if (cm == Spatial.CullHint.Always) {
setLastFrustumIntersection(Camera.FrustumIntersect.Outside); setLastFrustumIntersection(Camera.FrustumIntersect.Outside);
return false; return false;
} else if (cm == Spatial.CullHint.Never){ } else if (cm == Spatial.CullHint.Never) {
setLastFrustumIntersection(Camera.FrustumIntersect.Intersects); setLastFrustumIntersection(Camera.FrustumIntersect.Intersects);
return true; return true;
} }
@ -252,9 +233,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
return cam.containsGui(getWorldBound()); return cam.containsGui(getWorldBound());
} else { } else {
int state = cam.getPlaneState(); int state = cam.getPlaneState();
frustrumIntersects = cam.contains(getWorldBound()); frustrumIntersects = cam.contains(getWorldBound());
cam.setPlaneState(state); cam.setPlaneState(state);
} }
} }
@ -281,7 +262,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
return name; return name;
} }
public LightList getLocalLightList(){ public LightList getLocalLightList() {
return localLights; return localLights;
} }
@ -328,7 +309,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* *
* @return the world transform. * @return the world transform.
*/ */
public Transform getWorldTransform(){ public Transform getWorldTransform() {
checkDoTransformUpdate(); checkDoTransformUpdate();
return worldTransform; return worldTransform;
} }
@ -387,7 +368,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
assert vars.lock(); assert vars.lock();
Vector3f compVecA = vars.vect4; Vector3f compVecA = vars.vect4;
assert vars.unlock(); assert vars.unlock();
compVecA.set(position).subtractLocal(worldTranslation); compVecA.set(position).subtractLocal(worldTranslation);
getLocalRotation().lookAt(compVecA, upVector); getLocalRotation().lookAt(compVecA, upVector);
@ -397,7 +378,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
/** /**
* Should be overriden by Node and Geometry. * Should be overriden by Node and Geometry.
*/ */
protected void updateWorldBound(){ protected void updateWorldBound() {
// the world bound of a leaf is the same as it's model bound // the world bound of a leaf is the same as it's model bound
// for a node, the world bound is a combination of all it's children // for a node, the world bound is a combination of all it's children
// bounds // bounds
@ -405,15 +386,15 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
refreshFlags &= ~RF_BOUND; refreshFlags &= ~RF_BOUND;
} }
protected void updateWorldLightList(){ protected void updateWorldLightList() {
if (parent == null){ if (parent == null) {
worldLights.update(localLights, null); worldLights.update(localLights, null);
refreshFlags &= ~RF_LIGHTLIST; refreshFlags &= ~RF_LIGHTLIST;
}else{ } else {
if ((parent.refreshFlags & RF_LIGHTLIST) == 0){ if ((parent.refreshFlags & RF_LIGHTLIST) == 0) {
worldLights.update(localLights, parent.worldLights); worldLights.update(localLights, parent.worldLights);
refreshFlags &= ~RF_LIGHTLIST; refreshFlags &= ~RF_LIGHTLIST;
}else{ } else {
assert false; assert false;
} }
} }
@ -423,11 +404,11 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* Should only be called from updateGeometricState(). * Should only be called from updateGeometricState().
* In most cases should not be subclassed. * In most cases should not be subclassed.
*/ */
protected void updateWorldTransforms(){ protected void updateWorldTransforms() {
if (parent == null){ if (parent == null) {
worldTransform.set(localTransform); worldTransform.set(localTransform);
refreshFlags &= ~RF_TRANSFORM; refreshFlags &= ~RF_TRANSFORM;
}else{ } else {
// check if transform for parent is updated // check if transform for parent is updated
assert ((parent.refreshFlags & RF_TRANSFORM) == 0); assert ((parent.refreshFlags & RF_TRANSFORM) == 0);
worldTransform.set(localTransform); worldTransform.set(localTransform);
@ -436,23 +417,24 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
} }
} }
void checkDoTransformUpdate(){ void checkDoTransformUpdate() {
if ( (refreshFlags & RF_TRANSFORM) == 0 ) if ((refreshFlags & RF_TRANSFORM) == 0) {
return; return;
}
if (parent == null){ if (parent == null) {
worldTransform.set(localTransform); worldTransform.set(localTransform);
refreshFlags &= ~RF_TRANSFORM; refreshFlags &= ~RF_TRANSFORM;
}else{ } else {
TempVars vars = TempVars.get(); TempVars vars = TempVars.get();
assert vars.lock(); assert vars.lock();
Spatial[] stack = vars.spatialStack; Spatial[] stack = vars.spatialStack;
Spatial rootNode = this; Spatial rootNode = this;
int i = 0; int i = 0;
while (true){ while (true) {
Spatial hisParent = rootNode.parent; Spatial hisParent = rootNode.parent;
if (hisParent == null){ if (hisParent == null) {
rootNode.worldTransform.set(rootNode.localTransform); rootNode.worldTransform.set(rootNode.localTransform);
rootNode.refreshFlags &= ~RF_TRANSFORM; rootNode.refreshFlags &= ~RF_TRANSFORM;
i--; i--;
@ -461,7 +443,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
stack[i] = rootNode; stack[i] = rootNode;
if ( (hisParent.refreshFlags & RF_TRANSFORM) == 0 ){ if ((hisParent.refreshFlags & RF_TRANSFORM) == 0) {
break; break;
} }
@ -471,7 +453,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
assert vars.unlock(); assert vars.unlock();
for (int j = i; j >= 0; j--){ for (int j = i; j >= 0; j--) {
rootNode = stack[j]; rootNode = stack[j];
//rootNode.worldTransform.set(rootNode.localTransform); //rootNode.worldTransform.set(rootNode.localTransform);
//rootNode.worldTransform.combineWithParent(rootNode.parent.worldTransform); //rootNode.worldTransform.combineWithParent(rootNode.parent.worldTransform);
@ -481,17 +463,18 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
} }
} }
void checkDoBoundUpdate(){ void checkDoBoundUpdate() {
if ( (refreshFlags & RF_BOUND) == 0 ) if ((refreshFlags & RF_BOUND) == 0) {
return; return;
}
checkDoTransformUpdate(); checkDoTransformUpdate();
// Go to children recursively and update their bound // Go to children recursively and update their bound
if (this instanceof Node){ if (this instanceof Node) {
Node node = (Node) this; Node node = (Node) this;
int len = node.getQuantity(); int len = node.getQuantity();
for (int i = 0; i < len; i++){ for (int i = 0; i < len; i++) {
Spatial child = node.getChild(i); Spatial child = node.getChild(i);
child.checkDoBoundUpdate(); child.checkDoBoundUpdate();
} }
@ -501,11 +484,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
updateWorldBound(); updateWorldBound();
} }
private void runControlUpdate(float tpf){ private void runControlUpdate(float tpf) {
if (controls.size() == 0) if (controls.size() == 0) {
return; return;
}
for (int i = 0; i < controls.size(); i++){ for (int i = 0; i < controls.size(); i++) {
controls.get(i).update(tpf); controls.get(i).update(tpf);
} }
} }
@ -520,11 +504,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* @see Spatial#addControl(com.jme3.scene.control.Control) * @see Spatial#addControl(com.jme3.scene.control.Control)
* @see Spatial#getControl(java.lang.Class) * @see Spatial#getControl(java.lang.Class)
*/ */
public void runControlRender(RenderManager rm, ViewPort vp){ public void runControlRender(RenderManager rm, ViewPort vp) {
if (controls.size() == 0) if (controls.size() == 0) {
return; return;
}
for (int i = 0; i < controls.size(); i++){ for (int i = 0; i < controls.size(); i++) {
controls.get(i).render(rm, vp); controls.get(i).render(rm, vp);
} }
} }
@ -535,7 +520,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* *
* @see Spatial#removeControl(java.lang.Class) * @see Spatial#removeControl(java.lang.Class)
*/ */
public void addControl(Control control){ public void addControl(Control control) {
controls.add(control); controls.add(control);
control.setSpatial(this); control.setSpatial(this);
} }
@ -545,9 +530,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* *
* @see Spatial#addControl(com.jme3.scene.control.Control) * @see Spatial#addControl(com.jme3.scene.control.Control)
*/ */
public void removeControl(Class<? extends Control> controlType){ public void removeControl(Class<? extends Control> controlType) {
for (int i = 0; i < controls.size(); i++){ for (int i = 0; i < controls.size(); i++) {
if (controlType.isAssignableFrom(controls.get(i).getClass())){ if (controlType.isAssignableFrom(controls.get(i).getClass())) {
Control control = controls.remove(i); Control control = controls.remove(i);
control.setSpatial(null); control.setSpatial(null);
} }
@ -563,11 +548,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* *
* @see Spatial#addControl(com.jme3.scene.control.Control) * @see Spatial#addControl(com.jme3.scene.control.Control)
*/ */
public boolean removeControl(Control control){ public boolean removeControl(Control control) {
boolean result = controls.remove(control); boolean result = controls.remove(control);
if (result) if (result) {
control.setSpatial(null); control.setSpatial(null);
}
return result; return result;
} }
@ -580,9 +566,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* *
* @see Spatial#addControl(com.jme3.scene.control.Control) * @see Spatial#addControl(com.jme3.scene.control.Control)
*/ */
public <T extends Control> T getControl(Class<T> controlType){ public <T extends Control> T getControl(Class<T> controlType) {
for (int i = 0; i < controls.size(); i++){ for (int i = 0; i < controls.size(); i++) {
if (controlType.isAssignableFrom(controls.get(i).getClass())){ if (controlType.isAssignableFrom(controls.get(i).getClass())) {
return (T) controls.get(i); return (T) controls.get(i);
} }
} }
@ -600,7 +586,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* *
* @see Spatial#addControl(com.jme3.scene.control.Control) * @see Spatial#addControl(com.jme3.scene.control.Control)
*/ */
public Control getControl(int index){ public Control getControl(int index) {
return controls.get(index); return controls.get(index);
} }
@ -609,11 +595,10 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* @see Spatial#addControl(com.jme3.scene.control.Control) * @see Spatial#addControl(com.jme3.scene.control.Control)
* @see Spatial#removeControl(java.lang.Class) * @see Spatial#removeControl(java.lang.Class)
*/ */
public int getNumControls(){ public int getNumControls() {
return controls.size(); return controls.size();
} }
/** /**
* <code>updateLogicalState</code> calls the <code>update()</code> method * <code>updateLogicalState</code> calls the <code>update()</code> method
* for all controls attached to this Spatial. * for all controls attached to this Spatial.
@ -622,7 +607,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* *
* @see Spatial#addControl(com.jme3.scene.control.Control) * @see Spatial#addControl(com.jme3.scene.control.Control)
*/ */
public void updateLogicalState(float tpf){ public void updateLogicalState(float tpf) {
runControlUpdate(tpf); runControlUpdate(tpf);
} }
@ -638,19 +623,19 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* @see Spatial#getWorldTransform() * @see Spatial#getWorldTransform()
* @see Spatial#getWorldBound() * @see Spatial#getWorldBound()
*/ */
public void updateGeometricState(){ public void updateGeometricState() {
// assume that this Spatial is a leaf, a proper implementation // assume that this Spatial is a leaf, a proper implementation
// for this method should be provided by Node. // for this method should be provided by Node.
// NOTE: Update world transforms first because // NOTE: Update world transforms first because
// bound transform depends on them. // bound transform depends on them.
if ((refreshFlags & RF_LIGHTLIST) != 0){ if ((refreshFlags & RF_LIGHTLIST) != 0) {
updateWorldLightList(); updateWorldLightList();
} }
if ((refreshFlags & RF_TRANSFORM) != 0){ if ((refreshFlags & RF_TRANSFORM) != 0) {
updateWorldTransforms(); updateWorldTransforms();
} }
if ((refreshFlags & RF_BOUND) != 0){ if ((refreshFlags & RF_BOUND) != 0) {
updateWorldBound(); updateWorldBound();
} }
@ -846,7 +831,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* spatial. * spatial.
*/ */
public void setLocalTranslation(float x, float y, float z) { public void setLocalTranslation(float x, float y, float z) {
this.localTransform.setTranslation(x,y,z); this.localTransform.setTranslation(x, y, z);
this.worldTransform.setTranslation(this.localTransform.getTranslation()); this.worldTransform.setTranslation(this.localTransform.getTranslation());
setTransformRefresh(); setTransformRefresh();
} }
@ -866,7 +851,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* *
* @return the local transform of this spatial. * @return the local transform of this spatial.
*/ */
public Transform getLocalTransform(){ public Transform getLocalTransform() {
return localTransform; return localTransform;
} }
@ -876,7 +861,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* *
* @param material The material to set. * @param material The material to set.
*/ */
public void setMaterial(Material material){ public void setMaterial(Material material) {
} }
/** /**
@ -885,7 +870,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* *
* @param light The light to add. * @param light The light to add.
*/ */
public void addLight(Light light){ public void addLight(Light light) {
localLights.add(light); localLights.add(light);
setLightListRefresh(); setLightListRefresh();
} }
@ -896,7 +881,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* @param light The light to remove. * @param light The light to remove.
* @see Spatial#addLight(com.jme3.light.Light) * @see Spatial#addLight(com.jme3.light.Light)
*/ */
public void removeLight(Light light){ public void removeLight(Light light) {
localLights.remove(light); localLights.remove(light);
setLightListRefresh(); setLightListRefresh();
} }
@ -906,7 +891,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* *
* @return The spatial on which this method is called, e.g <code>this</code>. * @return The spatial on which this method is called, e.g <code>this</code>.
*/ */
public Spatial move(float x, float y, float z){ public Spatial move(float x, float y, float z) {
this.localTransform.getTranslation().addLocal(x, y, z); this.localTransform.getTranslation().addLocal(x, y, z);
this.worldTransform.setTranslation(this.localTransform.getTranslation()); this.worldTransform.setTranslation(this.localTransform.getTranslation());
setTransformRefresh(); setTransformRefresh();
@ -919,7 +904,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* *
* @return The spatial on which this method is called, e.g <code>this</code>. * @return The spatial on which this method is called, e.g <code>this</code>.
*/ */
public Spatial move(Vector3f offset){ public Spatial move(Vector3f offset) {
this.localTransform.getTranslation().addLocal(offset); this.localTransform.getTranslation().addLocal(offset);
this.worldTransform.setTranslation(this.localTransform.getTranslation()); this.worldTransform.setTranslation(this.localTransform.getTranslation());
setTransformRefresh(); setTransformRefresh();
@ -932,8 +917,8 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* *
* @return The spatial on which this method is called, e.g <code>this</code>. * @return The spatial on which this method is called, e.g <code>this</code>.
*/ */
public Spatial scale(float s){ public Spatial scale(float s) {
return scale(s,s,s); return scale(s, s, s);
} }
/** /**
@ -941,8 +926,8 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* *
* @return The spatial on which this method is called, e.g <code>this</code>. * @return The spatial on which this method is called, e.g <code>this</code>.
*/ */
public Spatial scale(float x, float y, float z){ public Spatial scale(float x, float y, float z) {
this.localTransform.getScale().multLocal(x,y,z); this.localTransform.getScale().multLocal(x, y, z);
this.worldTransform.setScale(this.localTransform.getScale()); this.worldTransform.setScale(this.localTransform.getScale());
setTransformRefresh(); setTransformRefresh();
@ -954,7 +939,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* *
* @return The spatial on which this method is called, e.g <code>this</code>. * @return The spatial on which this method is called, e.g <code>this</code>.
*/ */
public Spatial rotate(Quaternion rot){ public Spatial rotate(Quaternion rot) {
this.localTransform.getRotation().multLocal(rot); this.localTransform.getRotation().multLocal(rot);
this.worldTransform.setRotation(this.localTransform.getRotation()); this.worldTransform.setRotation(this.localTransform.getRotation());
setTransformRefresh(); setTransformRefresh();
@ -968,7 +953,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* *
* @return The spatial on which this method is called, e.g <code>this</code>. * @return The spatial on which this method is called, e.g <code>this</code>.
*/ */
public Spatial rotate(float yaw, float roll, float pitch){ public Spatial rotate(float yaw, float roll, float pitch) {
assert TempVars.get().lock(); assert TempVars.get().lock();
Quaternion q = TempVars.get().quat1; Quaternion q = TempVars.get().quat1;
q.fromAngles(yaw, roll, pitch); q.fromAngles(yaw, roll, pitch);
@ -982,7 +967,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* Centers the spatial in the origin of the world bound. * Centers the spatial in the origin of the world bound.
* @return The spatial on which this method is called, e.g <code>this</code>. * @return The spatial on which this method is called, e.g <code>this</code>.
*/ */
public Spatial center(){ public Spatial center() {
Vector3f worldTrans = getWorldTranslation(); Vector3f worldTrans = getWorldTranslation();
Vector3f worldCenter = getWorldBound().getCenter(); Vector3f worldCenter = getWorldBound().getCenter();
@ -998,15 +983,15 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* the cullmode of it's parent. * the cullmode of it's parent.
*/ */
public CullHint getCullHint() { public CullHint getCullHint() {
if (cullHint != CullHint.Inherit) if (cullHint != CullHint.Inherit) {
return cullHint; return cullHint;
else if (parent != null) } else if (parent != null) {
return parent.getCullHint(); return parent.getCullHint();
else } else {
return CullHint.Dynamic; return CullHint.Dynamic;
}
} }
/** /**
* Returns this spatial's renderqueue bucket. If the mode is set to inherit, * Returns this spatial's renderqueue bucket. If the mode is set to inherit,
* then the spatial gets its renderqueue bucket from its parent. * then the spatial gets its renderqueue bucket from its parent.
@ -1014,12 +999,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* @return The spatial's current renderqueue mode. * @return The spatial's current renderqueue mode.
*/ */
public RenderQueue.Bucket getQueueBucket() { public RenderQueue.Bucket getQueueBucket() {
if (queueBucket != RenderQueue.Bucket.Inherit) if (queueBucket != RenderQueue.Bucket.Inherit) {
return queueBucket; return queueBucket;
else if (parent != null) } else if (parent != null) {
return parent.getQueueBucket(); return parent.getQueueBucket();
else } else {
return RenderQueue.Bucket.Opaque; return RenderQueue.Bucket.Opaque;
}
} }
/** /**
@ -1030,12 +1016,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* @see ShadowMode * @see ShadowMode
*/ */
public RenderQueue.ShadowMode getShadowMode() { public RenderQueue.ShadowMode getShadowMode() {
if (shadowMode != RenderQueue.ShadowMode.Inherit) if (shadowMode != RenderQueue.ShadowMode.Inherit) {
return shadowMode; return shadowMode;
else if (parent != null) } else if (parent != null) {
return parent.getShadowMode(); return parent.getShadowMode();
else } else {
return ShadowMode.Off; return ShadowMode.Off;
}
} }
/** /**
@ -1044,7 +1031,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* *
* @param lod The lod level to set. * @param lod The lod level to set.
*/ */
public void setLodLevel(int lod){ public void setLodLevel(int lod) {
} }
/** /**
@ -1083,11 +1070,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* *
* @see Mesh#cloneForAnim() * @see Mesh#cloneForAnim()
*/ */
public Spatial clone(boolean cloneMaterial){ public Spatial clone(boolean cloneMaterial) {
try{ try {
Spatial clone = (Spatial) super.clone(); Spatial clone = (Spatial) super.clone();
if (worldBound != null) if (worldBound != null) {
clone.worldBound = worldBound.clone(); clone.worldBound = worldBound.clone();
}
clone.worldLights = worldLights.clone(); clone.worldLights = worldLights.clone();
clone.localLights = localLights.clone(); clone.localLights = localLights.clone();
@ -1098,11 +1086,11 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
clone.worldTransform = worldTransform.clone(); clone.worldTransform = worldTransform.clone();
clone.localTransform = localTransform.clone(); clone.localTransform = localTransform.clone();
if (clone instanceof Node){ if (clone instanceof Node) {
Node node = (Node) this; Node node = (Node) this;
Node nodeClone = (Node) clone; Node nodeClone = (Node) clone;
nodeClone.children = new ArrayList<Spatial>(); nodeClone.children = new ArrayList<Spatial>();
for (Spatial child : node.children){ for (Spatial child : node.children) {
Spatial childClone = child.clone(cloneMaterial); Spatial childClone = child.clone(cloneMaterial);
childClone.parent = nodeClone; childClone.parent = nodeClone;
nodeClone.children.add(childClone); nodeClone.children.add(childClone);
@ -1115,16 +1103,16 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
clone.setLightListRefresh(); clone.setLightListRefresh();
clone.controls = new ArrayList<Control>(); clone.controls = new ArrayList<Control>();
for (int i = 0; i < controls.size(); i++){ for (int i = 0; i < controls.size(); i++) {
clone.controls.add( controls.get(i).cloneForSpatial(clone) ); clone.controls.add(controls.get(i).cloneForSpatial(clone));
} }
if (userData != null){ if (userData != null) {
clone.userData = (HashMap<String, Savable>) userData.clone(); clone.userData = (HashMap<String, Savable>) userData.clone();
} }
return clone; return clone;
}catch (CloneNotSupportedException ex){ } catch (CloneNotSupportedException ex) {
throw new AssertionError(); throw new AssertionError();
} }
} }
@ -1142,7 +1130,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* @see Mesh#cloneForAnim() * @see Mesh#cloneForAnim()
*/ */
@Override @Override
public Spatial clone(){ public Spatial clone() {
return clone(true); return clone(true);
} }
@ -1155,33 +1143,36 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
*/ */
public abstract Spatial deepClone(); public abstract Spatial deepClone();
public void setUserData(String key, Object data){ public void setUserData(String key, Object data) {
if (userData == null) if (userData == null) {
userData = new HashMap<String, Savable>(); userData = new HashMap<String, Savable>();
}
if (data instanceof Savable){ if (data instanceof Savable) {
userData.put(key, (Savable) data); userData.put(key, (Savable) data);
}else{ } else {
userData.put(key, new UserData(UserData.getObjectType(data), data)); userData.put(key, new UserData(UserData.getObjectType(data), data));
} }
} }
public Object getUserData(String key){ public Object getUserData(String key) {
if (userData == null) if (userData == null) {
return null; return null;
}
Savable s = userData.get(key); Savable s = userData.get(key);
if (s instanceof UserData){ if (s instanceof UserData) {
return ((UserData)s).getValue(); return ((UserData) s).getValue();
}else{ } else {
return s; return s;
} }
} }
public Collection<String> getUserDataKeys(){ public Collection<String> getUserDataKeys() {
if (userData != null) if (userData != null) {
return userData.keySet(); return userData.keySet();
}
return Collections.EMPTY_SET; return Collections.EMPTY_SET;
} }
@ -1202,12 +1193,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* @see java.util.regex.Pattern * @see java.util.regex.Pattern
*/ */
public boolean matches(Class<? extends Spatial> spatialSubclass, public boolean matches(Class<? extends Spatial> spatialSubclass,
String nameRegex) { String nameRegex) {
if (spatialSubclass != null && !spatialSubclass.isInstance(this)) if (spatialSubclass != null && !spatialSubclass.isInstance(this)) {
return false; return false;
}
if (nameRegex != null && (name == null || !name.matches(nameRegex)))
if (nameRegex != null && (name == null || !name.matches(nameRegex))) {
return false; return false;
}
return true; return true;
} }
@ -1232,16 +1225,22 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
worldBound = (BoundingVolume) ic.readSavable("world_bound", null); worldBound = (BoundingVolume) ic.readSavable("world_bound", null);
cullHint = ic.readEnum("cull_mode", CullHint.class, CullHint.Inherit); cullHint = ic.readEnum("cull_mode", CullHint.class, CullHint.Inherit);
queueBucket = ic.readEnum("queue", RenderQueue.Bucket.class, queueBucket = ic.readEnum("queue", RenderQueue.Bucket.class,
RenderQueue.Bucket.Inherit); RenderQueue.Bucket.Inherit);
shadowMode = ic.readEnum("shadow_mode", ShadowMode.class, shadowMode = ic.readEnum("shadow_mode", ShadowMode.class,
ShadowMode.Inherit); ShadowMode.Inherit);
localTransform = (Transform) ic.readSavable("transform", Transform.Identity); localTransform = (Transform) ic.readSavable("transform", Transform.Identity);
localLights = (LightList) ic.readSavable("lights", null); localLights = (LightList) ic.readSavable("lights", null);
localLights.setOwner(this); 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); userData = (HashMap<String, Savable>) ic.readStringSavableMap("user_data", null);
} }
@ -1315,7 +1314,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
* *
* @param shadowMode The local shadow mode to set. * @param shadowMode The local shadow mode to set.
*/ */
public void setShadowMode(RenderQueue.ShadowMode shadowMode){ public void setShadowMode(RenderQueue.ShadowMode shadowMode) {
this.shadowMode = shadowMode; this.shadowMode = shadowMode;
} }
@ -1398,13 +1397,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
store.setTranslation(getWorldTranslation()); store.setTranslation(getWorldTranslation());
return store; return store;
} }
/** /**
* Visit each scene graph element ordered by DFS * Visit each scene graph element ordered by DFS
* @param visitor * @param visitor
*/ */
public abstract void depthFirstTraversal(SceneGraphVisitor visitor); public abstract void depthFirstTraversal(SceneGraphVisitor visitor);
/** /**
* Visit each scene graph element ordered by BFS * Visit each scene graph element ordered by BFS
* @param visitor * @param visitor
@ -1412,14 +1411,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
public void breadthFirstTraversal(SceneGraphVisitor visitor) { public void breadthFirstTraversal(SceneGraphVisitor visitor) {
Queue<Spatial> queue = new LinkedList<Spatial>(); Queue<Spatial> queue = new LinkedList<Spatial>();
queue.add(this); queue.add(this);
while (!queue.isEmpty()) { while (!queue.isEmpty()) {
Spatial s = queue.poll(); Spatial s = queue.poll();
visitor.visit(s); visitor.visit(s);
s.breadthFirstTraversal(visitor, queue); s.breadthFirstTraversal(visitor, queue);
} }
} }
protected abstract void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue); 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.AnimControl;
import com.jme3.animation.Bone; import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton; import com.jme3.animation.Skeleton;
import com.jme3.animation.SkeletonControl;
import com.jme3.asset.AssetManager; import com.jme3.asset.AssetManager;
import com.jme3.bullet.PhysicsSpace; import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.shapes.BoxCollisionShape; import com.jme3.bullet.collision.shapes.BoxCollisionShape;
@ -107,6 +108,15 @@ public class RagdollControl implements PhysicsControl {
public void setSpatial(Spatial model) { public void setSpatial(Spatial model) {
targetModel = 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(); removeFromPhysicsSpace();
clearData(); clearData();
// put into bind pose and compute bone transforms in model space // 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 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.jme3.scene.plugins.ogre; package com.jme3.scene.plugins.ogre;
import com.jme3.animation.AnimControl; import com.jme3.animation.AnimControl;
import com.jme3.animation.BoneAnimation; import com.jme3.animation.BoneAnimation;
import com.jme3.animation.SkeletonControl;
import com.jme3.asset.AssetInfo; import com.jme3.asset.AssetInfo;
import com.jme3.asset.AssetKey; import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetLoader; import com.jme3.asset.AssetLoader;
@ -81,15 +81,12 @@ import static com.jme3.util.xml.SAXUtil.*;
public class MeshLoader extends DefaultHandler implements AssetLoader { public class MeshLoader extends DefaultHandler implements AssetLoader {
private static final Logger logger = Logger.getLogger(MeshLoader.class.getName()); private static final Logger logger = Logger.getLogger(MeshLoader.class.getName());
public static boolean AUTO_INTERLEAVE = true; public static boolean AUTO_INTERLEAVE = true;
public static boolean HARDWARE_SKINNING = false; public static boolean HARDWARE_SKINNING = false;
private String meshName; private String meshName;
private String folderName; private String folderName;
private AssetManager assetManager; private AssetManager assetManager;
private MaterialList materialList; private MaterialList materialList;
private ShortBuffer sb; private ShortBuffer sb;
private IntBuffer ib; private IntBuffer ib;
private FloatBuffer fb; private FloatBuffer fb;
@ -105,16 +102,14 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
private boolean bigindices = false; private boolean bigindices = false;
private int vertCount; private int vertCount;
private int triCount; private int triCount;
private List<Geometry> geoms = new ArrayList<Geometry>(); private List<Geometry> geoms = new ArrayList<Geometry>();
private List<Boolean> usesSharedGeom = new ArrayList<Boolean>(); private List<Boolean> usesSharedGeom = new ArrayList<Boolean>();
private IntMap<List<VertexBuffer>> lodLevels = new IntMap<List<VertexBuffer>>(); private IntMap<List<VertexBuffer>> lodLevels = new IntMap<List<VertexBuffer>>();
private AnimData animData; private AnimData animData;
private ByteBuffer indicesData; private ByteBuffer indicesData;
private FloatBuffer weightsFloatData; private FloatBuffer weightsFloatData;
public MeshLoader(){ public MeshLoader() {
super(); super();
} }
@ -150,44 +145,44 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
public void endDocument() { public void endDocument() {
} }
private void pushFace(String v1, String v2, String v3) throws SAXException{ private void pushFace(String v1, String v2, String v3) throws SAXException {
int i1 = parseInt(v1); int i1 = parseInt(v1);
// TODO: fan/strip support // TODO: fan/strip support
int i2 = parseInt(v2); int i2 = parseInt(v2);
int i3 = parseInt(v3); int i3 = parseInt(v3);
if (ib != null){ if (ib != null) {
ib.put(i1).put(i2).put(i3); ib.put(i1).put(i2).put(i3);
}else{ } else {
sb.put((short)i1).put((short)i2).put((short)i3); sb.put((short) i1).put((short) i2).put((short) i3);
} }
} }
private void startFaces(String count) throws SAXException{ private void startFaces(String count) throws SAXException {
int numFaces = parseInt(count); int numFaces = parseInt(count);
int numIndices; int numIndices;
if (mesh.getMode() == Mesh.Mode.Triangles){ if (mesh.getMode() == Mesh.Mode.Triangles) {
//mesh.setTriangleCount(numFaces); //mesh.setTriangleCount(numFaces);
numIndices = numFaces * 3; numIndices = numFaces * 3;
}else{ } else {
throw new SAXException("Triangle strip or fan not supported!"); throw new SAXException("Triangle strip or fan not supported!");
} }
int numVerts; int numVerts;
if (usesSharedGeom.size() > 0 && usesSharedGeom.get(geoms.size()-1)){ if (usesSharedGeom.size() > 0 && usesSharedGeom.get(geoms.size() - 1)) {
// sharedgeom.getMesh().updateCounts(); // sharedgeom.getMesh().updateCounts();
numVerts = sharedmesh.getVertexCount(); numVerts = sharedmesh.getVertexCount();
}else{ } else {
// mesh.updateCounts(); // mesh.updateCounts();
numVerts = mesh.getVertexCount(); numVerts = mesh.getVertexCount();
} }
vb = new VertexBuffer(VertexBuffer.Type.Index); vb = new VertexBuffer(VertexBuffer.Type.Index);
if (!bigindices){ if (!bigindices) {
sb = BufferUtils.createShortBuffer(numIndices); sb = BufferUtils.createShortBuffer(numIndices);
ib = null; ib = null;
vb.setupData(Usage.Static, 3, Format.UnsignedShort, sb); vb.setupData(Usage.Static, 3, Format.UnsignedShort, sb);
}else{ } else {
ib = BufferUtils.createIntBuffer(numIndices); ib = BufferUtils.createIntBuffer(numIndices);
sb = null; sb = null;
vb.setupData(Usage.Static, 3, Format.UnsignedInt, ib); vb.setupData(Usage.Static, 3, Format.UnsignedInt, ib);
@ -195,77 +190,81 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
mesh.setBuffer(vb); mesh.setBuffer(vb);
} }
private void applyMaterial(Geometry geom, String matName){ private void applyMaterial(Geometry geom, String matName) {
Material mat = null; Material mat = null;
if (matName.endsWith(".j3m")){ if (matName.endsWith(".j3m")) {
// load as native jme3 material instance // load as native jme3 material instance
mat = assetManager.loadMaterial(matName); mat = assetManager.loadMaterial(matName);
}else{ } else {
if (materialList != null){ if (materialList != null) {
mat = materialList.get(matName); mat = materialList.get(matName);
} }
if (mat == null){ if (mat == null) {
logger.log(Level.WARNING, "Material {0} not found. Applying default material", matName); logger.log(Level.WARNING, "Material {0} not found. Applying default material", matName);
mat = (Material) assetManager.loadAsset(new AssetKey("Common/Materials/RedColor.j3m")); mat = (Material) assetManager.loadAsset(new AssetKey("Common/Materials/RedColor.j3m"));
} }
} }
if (mat == null) if (mat == null) {
throw new RuntimeException("Cannot locate material named " + matName); throw new RuntimeException("Cannot locate material named " + matName);
}
if (mat.isTransparent()) if (mat.isTransparent()) {
geom.setQueueBucket(Bucket.Transparent); geom.setQueueBucket(Bucket.Transparent);
}
// else // else
// geom.setShadowMode(ShadowMode.CastAndReceive); // geom.setShadowMode(ShadowMode.CastAndReceive);
// if (mat.isReceivesShadows()) // if (mat.isReceivesShadows())
geom.setMaterial(mat); geom.setMaterial(mat);
} }
private void startMesh(String matName, String usesharedvertices, String use32bitIndices, String opType) throws SAXException{ private void startMesh(String matName, String usesharedvertices, String use32bitIndices, String opType) throws SAXException {
mesh = new Mesh(); mesh = new Mesh();
if (opType == null || opType.equals("triangle_list")){ if (opType == null || opType.equals("triangle_list")) {
mesh.setMode(Mesh.Mode.Triangles); mesh.setMode(Mesh.Mode.Triangles);
}else if (opType.equals("triangle_strip")){ } else if (opType.equals("triangle_strip")) {
mesh.setMode(Mesh.Mode.TriangleStrip); mesh.setMode(Mesh.Mode.TriangleStrip);
}else if (opType.equals("triangle_fan")){ } else if (opType.equals("triangle_fan")) {
mesh.setMode(Mesh.Mode.TriangleFan); mesh.setMode(Mesh.Mode.TriangleFan);
} }
bigindices = parseBool(use32bitIndices, false); bigindices = parseBool(use32bitIndices, false);
boolean sharedverts = parseBool(usesharedvertices, false); boolean sharedverts = parseBool(usesharedvertices, false);
if (sharedverts){ if (sharedverts) {
usesSharedGeom.add(true); usesSharedGeom.add(true);
// import vertexbuffers from shared geom // import vertexbuffers from shared geom
IntMap<VertexBuffer> sharedBufs = sharedmesh.getBuffers(); IntMap<VertexBuffer> sharedBufs = sharedmesh.getBuffers();
for (Entry<VertexBuffer> entry : sharedBufs){ for (Entry<VertexBuffer> entry : sharedBufs) {
mesh.setBuffer(entry.getValue()); mesh.setBuffer(entry.getValue());
} }
// this mesh is shared! // this mesh is shared!
}else{ } else {
usesSharedGeom.add(false); usesSharedGeom.add(false);
} }
if (meshName == null) if (meshName == null) {
geom = new Geometry("OgreSubmesh-"+(++geomIdx), mesh); geom = new Geometry("OgreSubmesh-" + (++geomIdx), mesh);
else } else {
geom = new Geometry(meshName+"-geom-"+(++geomIdx), mesh); geom = new Geometry(meshName + "-geom-" + (++geomIdx), mesh);
}
applyMaterial(geom, matName); applyMaterial(geom, matName);
geoms.add(geom); geoms.add(geom);
} }
private void startSharedGeom(String vertexcount) throws SAXException{ private void startSharedGeom(String vertexcount) throws SAXException {
sharedmesh = new Mesh(); sharedmesh = new Mesh();
vertCount = parseInt(vertexcount); vertCount = parseInt(vertexcount);
// sharedmesh.setVertexCount(vertCount); // sharedmesh.setVertexCount(vertCount);
if (meshName == null) if (meshName == null) {
sharedgeom = new Geometry("Ogre-SharedGeom", sharedmesh); sharedgeom = new Geometry("Ogre-SharedGeom", sharedmesh);
else } else {
sharedgeom = new Geometry(meshName+"-sharedgeom", sharedmesh); sharedgeom = new Geometry(meshName + "-sharedgeom", sharedmesh);
}
sharedgeom.setCullHint(CullHint.Always); sharedgeom.setCullHint(CullHint.Always);
geoms.add(sharedgeom); geoms.add(sharedgeom);
@ -275,7 +274,7 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
mesh = sharedmesh; mesh = sharedmesh;
} }
private void startGeometry(String vertexcount) throws SAXException{ private void startGeometry(String vertexcount) throws SAXException {
vertCount = parseInt(vertexcount); vertCount = parseInt(vertexcount);
// mesh.setVertexCount(vertCount); // mesh.setVertexCount(vertCount);
} }
@ -284,51 +283,51 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
* Normalizes weights if needed and finds largest amount of weights used * Normalizes weights if needed and finds largest amount of weights used
* for all vertices in the buffer. * for all vertices in the buffer.
*/ */
private void endBoneAssigns(){ private void endBoneAssigns() {
if (mesh != sharedmesh && usesSharedGeom.get(geoms.size() - 1)){ if (mesh != sharedmesh && usesSharedGeom.get(geoms.size() - 1)) {
return; return;
} }
//int vertCount = mesh.getVertexCount(); //int vertCount = mesh.getVertexCount();
int maxWeightsPerVert = 0; int maxWeightsPerVert = 0;
weightsFloatData.rewind(); weightsFloatData.rewind();
for (int v = 0; v < vertCount; v++){ for (int v = 0; v < vertCount; v++) {
float w0 = weightsFloatData.get(), float w0 = weightsFloatData.get(),
w1 = weightsFloatData.get(), w1 = weightsFloatData.get(),
w2 = weightsFloatData.get(), w2 = weightsFloatData.get(),
w3 = weightsFloatData.get(); w3 = weightsFloatData.get();
if (w3 != 0){ if (w3 != 0) {
maxWeightsPerVert = Math.max(maxWeightsPerVert, 4); maxWeightsPerVert = Math.max(maxWeightsPerVert, 4);
}else if (w2 != 0){ } else if (w2 != 0) {
maxWeightsPerVert = Math.max(maxWeightsPerVert, 3); maxWeightsPerVert = Math.max(maxWeightsPerVert, 3);
}else if (w1 != 0){ } else if (w1 != 0) {
maxWeightsPerVert = Math.max(maxWeightsPerVert, 2); maxWeightsPerVert = Math.max(maxWeightsPerVert, 2);
}else if (w0 != 0){ } else if (w0 != 0) {
maxWeightsPerVert = Math.max(maxWeightsPerVert, 1); maxWeightsPerVert = Math.max(maxWeightsPerVert, 1);
} }
float sum = w0 + w1 + w2 + w3; float sum = w0 + w1 + w2 + w3;
if (sum != 1f){ if (sum != 1f) {
weightsFloatData.position(weightsFloatData.position()-4); weightsFloatData.position(weightsFloatData.position() - 4);
// compute new vals based on sum // compute new vals based on sum
float sumToB = 1f / sum; float sumToB = 1f / sum;
weightsFloatData.put( w0 * sumToB ); weightsFloatData.put(w0 * sumToB);
weightsFloatData.put( w1 * sumToB ); weightsFloatData.put(w1 * sumToB);
weightsFloatData.put( w2 * sumToB ); weightsFloatData.put(w2 * sumToB);
weightsFloatData.put( w3 * sumToB ); weightsFloatData.put(w3 * sumToB);
} }
} }
weightsFloatData.rewind(); weightsFloatData.rewind();
weightsFloatData = null; weightsFloatData = null;
indicesData = null; indicesData = null;
mesh.setMaxNumWeights(maxWeightsPerVert); mesh.setMaxNumWeights(maxWeightsPerVert);
} }
private void startBoneAssigns(){ private void startBoneAssigns() {
if (mesh != sharedmesh && usesSharedGeom.get(geoms.size() - 1)){ if (mesh != sharedmesh && usesSharedGeom.get(geoms.size() - 1)) {
// will use bone assignments from shared mesh (?) // will use bone assignments from shared mesh (?)
return; return;
} }
@ -338,53 +337,53 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
// each vertex has // each vertex has
// - 4 bone weights // - 4 bone weights
// - 4 bone indices // - 4 bone indices
if (HARDWARE_SKINNING){ if (HARDWARE_SKINNING) {
weightsFloatData = BufferUtils.createFloatBuffer(vertCount * 4); weightsFloatData = BufferUtils.createFloatBuffer(vertCount * 4);
indicesData = BufferUtils.createByteBuffer(vertCount * 4); indicesData = BufferUtils.createByteBuffer(vertCount * 4);
}else{ } else {
// create array-backed buffers if software skinning for access speed // create array-backed buffers if software skinning for access speed
weightsFloatData = FloatBuffer.allocate(vertCount * 4); weightsFloatData = FloatBuffer.allocate(vertCount * 4);
indicesData = ByteBuffer.allocate(vertCount * 4); indicesData = ByteBuffer.allocate(vertCount * 4);
} }
VertexBuffer weights = new VertexBuffer(Type.BoneWeight); VertexBuffer weights = new VertexBuffer(Type.BoneWeight);
VertexBuffer indices = new VertexBuffer(Type.BoneIndex); VertexBuffer indices = new VertexBuffer(Type.BoneIndex);
Usage usage = HARDWARE_SKINNING ? Usage.Static : Usage.CpuOnly; Usage usage = HARDWARE_SKINNING ? Usage.Static : Usage.CpuOnly;
weights.setupData(usage, 4, Format.Float, weightsFloatData); weights.setupData(usage, 4, Format.Float, weightsFloatData);
indices.setupData(usage, 4, Format.UnsignedByte, indicesData); indices.setupData(usage, 4, Format.UnsignedByte, indicesData);
mesh.setBuffer(weights); mesh.setBuffer(weights);
mesh.setBuffer(indices); mesh.setBuffer(indices);
} }
private void startVertexBuffer(Attributes attribs) throws SAXException{ private void startVertexBuffer(Attributes attribs) throws SAXException {
if (parseBool(attribs.getValue("positions"), false)){ if (parseBool(attribs.getValue("positions"), false)) {
vb = new VertexBuffer(Type.Position); vb = new VertexBuffer(Type.Position);
fb = BufferUtils.createFloatBuffer(vertCount * 3); fb = BufferUtils.createFloatBuffer(vertCount * 3);
vb.setupData(Usage.Static, 3, Format.Float, fb); vb.setupData(Usage.Static, 3, Format.Float, fb);
mesh.setBuffer(vb); mesh.setBuffer(vb);
} }
if (parseBool(attribs.getValue("normals"), false)){ if (parseBool(attribs.getValue("normals"), false)) {
vb = new VertexBuffer(Type.Normal); vb = new VertexBuffer(Type.Normal);
fb = BufferUtils.createFloatBuffer(vertCount * 3); fb = BufferUtils.createFloatBuffer(vertCount * 3);
vb.setupData(Usage.Static, 3, Format.Float, fb); vb.setupData(Usage.Static, 3, Format.Float, fb);
mesh.setBuffer(vb); mesh.setBuffer(vb);
} }
if (parseBool(attribs.getValue("colours_diffuse"), false)){ if (parseBool(attribs.getValue("colours_diffuse"), false)) {
vb = new VertexBuffer(Type.Color); vb = new VertexBuffer(Type.Color);
fb = BufferUtils.createFloatBuffer(vertCount * 4); fb = BufferUtils.createFloatBuffer(vertCount * 4);
vb.setupData(Usage.Static, 4, Format.Float, fb); vb.setupData(Usage.Static, 4, Format.Float, fb);
mesh.setBuffer(vb); mesh.setBuffer(vb);
} }
if (parseBool(attribs.getValue("tangents"), false)){ if (parseBool(attribs.getValue("tangents"), false)) {
int dimensions = parseInt(attribs.getValue("tangent_dimensions"), 3); int dimensions = parseInt(attribs.getValue("tangent_dimensions"), 3);
vb = new VertexBuffer(Type.Tangent); vb = new VertexBuffer(Type.Tangent);
fb = BufferUtils.createFloatBuffer(vertCount * dimensions); fb = BufferUtils.createFloatBuffer(vertCount * dimensions);
vb.setupData(Usage.Static, dimensions, Format.Float, fb); vb.setupData(Usage.Static, dimensions, Format.Float, fb);
mesh.setBuffer(vb); mesh.setBuffer(vb);
} }
if (parseBool(attribs.getValue("binormals"), false)){ if (parseBool(attribs.getValue("binormals"), false)) {
vb = new VertexBuffer(Type.Binormal); vb = new VertexBuffer(Type.Binormal);
fb = BufferUtils.createFloatBuffer(vertCount * 3); fb = BufferUtils.createFloatBuffer(vertCount * 3);
vb.setupData(Usage.Static, 3, Format.Float, fb); vb.setupData(Usage.Static, 3, Format.Float, fb);
@ -392,17 +391,19 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
} }
int texCoords = parseInt(attribs.getValue("texture_coords"), 0); int texCoords = parseInt(attribs.getValue("texture_coords"), 0);
for (int i = 0; i < texCoords; i++){ for (int i = 0; i < texCoords; i++) {
int dims = parseInt(attribs.getValue("texture_coord_dimensions_" + i), 2); 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"); 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"); throw new SAXException("More than 2 texture coordinates not supported");
}
if (i == 0){ if (i == 0) {
vb = new VertexBuffer(Type.TexCoord); vb = new VertexBuffer(Type.TexCoord);
}else{ } else {
vb = new VertexBuffer(Type.TexCoord2); vb = new VertexBuffer(Type.TexCoord2);
} }
fb = BufferUtils.createFloatBuffer(vertCount * dims); fb = BufferUtils.createFloatBuffer(vertCount * dims);
@ -411,51 +412,47 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
} }
} }
private void startVertex(){ private void startVertex() {
texCoordIdx = 0; texCoordIdx = 0;
} }
private void pushAttrib(Type type, Attributes attribs) throws SAXException{ private void pushAttrib(Type type, Attributes attribs) throws SAXException {
try { try {
FloatBuffer buf = (FloatBuffer) mesh.getBuffer(type).getData(); FloatBuffer buf = (FloatBuffer) mesh.getBuffer(type).getData();
buf.put(parseFloat(attribs.getValue("x"))) buf.put(parseFloat(attribs.getValue("x"))).put(parseFloat(attribs.getValue("y"))).put(parseFloat(attribs.getValue("z")));
.put(parseFloat(attribs.getValue("y"))) } catch (Exception ex) {
.put(parseFloat(attribs.getValue("z"))); throw new SAXException("Failed to push attrib", ex);
} catch (Exception ex){
throw new SAXException("Failed to push attrib", ex);
} }
} }
private void pushTangent(Attributes attribs) throws SAXException{ private void pushTangent(Attributes attribs) throws SAXException {
try { try {
VertexBuffer tangentBuf = mesh.getBuffer(Type.Tangent); VertexBuffer tangentBuf = mesh.getBuffer(Type.Tangent);
FloatBuffer buf = (FloatBuffer) tangentBuf.getData(); FloatBuffer buf = (FloatBuffer) tangentBuf.getData();
buf.put(parseFloat(attribs.getValue("x"))) buf.put(parseFloat(attribs.getValue("x"))).put(parseFloat(attribs.getValue("y"))).put(parseFloat(attribs.getValue("z")));
.put(parseFloat(attribs.getValue("y"))) if (tangentBuf.getNumComponents() == 4) {
.put(parseFloat(attribs.getValue("z")));
if (tangentBuf.getNumComponents() == 4){
buf.put(parseFloat(attribs.getValue("w"))); buf.put(parseFloat(attribs.getValue("w")));
} }
} catch (Exception ex){ } catch (Exception ex) {
throw new SAXException("Failed to push attrib", ex); throw new SAXException("Failed to push attrib", ex);
} }
} }
private void pushTexCoord(Attributes attribs) throws SAXException{ private void pushTexCoord(Attributes attribs) throws SAXException {
if (texCoordIdx >= 2) if (texCoordIdx >= 2) {
return; // TODO: More than 2 texcoords return; // TODO: More than 2 texcoords
}
Type type = texCoordIdx == 0 ? Type.TexCoord : Type.TexCoord2; Type type = texCoordIdx == 0 ? Type.TexCoord : Type.TexCoord2;
VertexBuffer tcvb = mesh.getBuffer(type); VertexBuffer tcvb = mesh.getBuffer(type);
FloatBuffer buf = (FloatBuffer) tcvb.getData(); FloatBuffer buf = (FloatBuffer) tcvb.getData();
buf.put(parseFloat(attribs.getValue("u"))); buf.put(parseFloat(attribs.getValue("u")));
if (tcvb.getNumComponents() >= 2){ if (tcvb.getNumComponents() >= 2) {
buf.put(parseFloat(attribs.getValue("v"))); buf.put(parseFloat(attribs.getValue("v")));
if (tcvb.getNumComponents() >= 3){ if (tcvb.getNumComponents() >= 3) {
buf.put(parseFloat(attribs.getValue("w"))); buf.put(parseFloat(attribs.getValue("w")));
if (tcvb.getNumComponents() == 4){ if (tcvb.getNumComponents() == 4) {
buf.put(parseFloat(attribs.getValue("x"))); buf.put(parseFloat(attribs.getValue("x")));
} }
} }
@ -464,36 +461,38 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
texCoordIdx++; texCoordIdx++;
} }
private void pushColor(Attributes attribs) throws SAXException{ private void pushColor(Attributes attribs) throws SAXException {
FloatBuffer buf = (FloatBuffer) mesh.getBuffer(Type.Color).getData(); FloatBuffer buf = (FloatBuffer) mesh.getBuffer(Type.Color).getData();
String value = parseString(attribs.getValue("value")); String value = parseString(attribs.getValue("value"));
String[] vals = value.split(" "); 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"); throw new SAXException("Color value must contain 3 or 4 components");
}
ColorRGBA color = new ColorRGBA(); ColorRGBA color = new ColorRGBA();
color.r = parseFloat(vals[0]); color.r = parseFloat(vals[0]);
color.g = parseFloat(vals[1]); color.g = parseFloat(vals[1]);
color.b = parseFloat(vals[2]); color.b = parseFloat(vals[2]);
if (vals.length == 3) if (vals.length == 3) {
color.a = 1f; color.a = 1f;
else } else {
color.a = parseFloat(vals[3]); color.a = parseFloat(vals[3]);
}
buf.put(color.r).put(color.g).put(color.b).put(color.a); buf.put(color.r).put(color.g).put(color.b).put(color.a);
} }
private void startLodFaceList(String submeshindex, String numfaces){ private void startLodFaceList(String submeshindex, String numfaces) {
int index = Integer.parseInt(submeshindex); int index = Integer.parseInt(submeshindex);
int faceCount = Integer.parseInt(numfaces); int faceCount = Integer.parseInt(numfaces);
vb = new VertexBuffer(VertexBuffer.Type.Index); vb = new VertexBuffer(VertexBuffer.Type.Index);
sb = BufferUtils.createShortBuffer(faceCount * 3); sb = BufferUtils.createShortBuffer(faceCount * 3);
ib = null; ib = null;
vb.setupData(Usage.Static, 3, Format.UnsignedShort, sb); vb.setupData(Usage.Static, 3, Format.UnsignedShort, sb);
List<VertexBuffer> levels = lodLevels.get(index); List<VertexBuffer> levels = lodLevels.get(index);
if (levels == null){ if (levels == null) {
levels = new ArrayList<VertexBuffer>(); levels = new ArrayList<VertexBuffer>();
Mesh submesh = geoms.get(index).getMesh(); Mesh submesh = geoms.get(index).getMesh();
levels.add(submesh.getBuffer(Type.Index)); levels.add(submesh.getBuffer(Type.Index));
@ -503,13 +502,13 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
levels.add(vb); levels.add(vb);
} }
private void startLevelOfDetail(String numlevels){ private void startLevelOfDetail(String numlevels) {
// numLevels = Integer.parseInt(numlevels); // numLevels = Integer.parseInt(numlevels);
} }
private void endLevelOfDetail(){ private void endLevelOfDetail() {
// set the lod data for each mesh // set the lod data for each mesh
for (Entry<List<VertexBuffer>> entry : lodLevels){ for (Entry<List<VertexBuffer>> entry : lodLevels) {
Mesh m = geoms.get(entry.getKey()).getMesh(); Mesh m = geoms.get(entry.getKey()).getMesh();
List<VertexBuffer> levels = entry.getValue(); List<VertexBuffer> levels = entry.getValue();
VertexBuffer[] levelArray = new VertexBuffer[levels.size()]; VertexBuffer[] levelArray = new VertexBuffer[levels.size()];
@ -518,11 +517,11 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
} }
} }
private void startLodGenerated(String depthsqr){ private void startLodGenerated(String depthsqr) {
// dist = Float.parseFloat(depthsqr); // dist = Float.parseFloat(depthsqr);
} }
private void pushBoneAssign(String vertIndex, String boneIndex, String weight) throws SAXException{ private void pushBoneAssign(String vertIndex, String boneIndex, String weight) throws SAXException {
int vert = parseInt(vertIndex); int vert = parseInt(vertIndex);
float w = parseFloat(weight); float w = parseFloat(weight);
byte bone = (byte) parseInt(boneIndex); byte bone = (byte) parseInt(boneIndex);
@ -532,95 +531,101 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
int i; int i;
// see which weights are unused for a given bone // see which weights are unused for a given bone
for (i = vert * 4; i < vert * 4 + 4; i++){ for (i = vert * 4; i < vert * 4 + 4; i++) {
float v = weightsFloatData.get(i); float v = weightsFloatData.get(i);
if (v == 0) if (v == 0) {
break; break;
}
} }
weightsFloatData.put(i, w); weightsFloatData.put(i, w);
indicesData.put(i, bone); indicesData.put(i, bone);
} }
private void startSkeleton(String name){ private void startSkeleton(String name) {
animData = (AnimData) assetManager.loadAsset(folderName + name + ".xml"); animData = (AnimData) assetManager.loadAsset(folderName + name + ".xml");
//TODO:workaround for meshxml / mesh.xml //TODO:workaround for meshxml / mesh.xml
if(animData==null) if (animData == null) {
animData = (AnimData) assetManager.loadAsset(folderName + name + "xml"); animData = (AnimData) assetManager.loadAsset(folderName + name + "xml");
}
} }
private void startSubmeshName(String indexStr, String nameStr){ private void startSubmeshName(String indexStr, String nameStr) {
int index = Integer.parseInt(indexStr); int index = Integer.parseInt(indexStr);
geoms.get(index).setName(nameStr); geoms.get(index).setName(nameStr);
} }
@Override @Override
public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException{ public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException {
if (ignoreUntilEnd != null) if (ignoreUntilEnd != null) {
return; return;
}
if (qName.equals("texcoord")){ if (qName.equals("texcoord")) {
pushTexCoord(attribs); pushTexCoord(attribs);
}else if (qName.equals("vertexboneassignment")){ } else if (qName.equals("vertexboneassignment")) {
pushBoneAssign(attribs.getValue("vertexindex"), pushBoneAssign(attribs.getValue("vertexindex"),
attribs.getValue("boneindex"), attribs.getValue("boneindex"),
attribs.getValue("weight")); attribs.getValue("weight"));
}else if (qName.equals("face")){ } else if (qName.equals("face")) {
pushFace(attribs.getValue("v1"), pushFace(attribs.getValue("v1"),
attribs.getValue("v2"), attribs.getValue("v2"),
attribs.getValue("v3")); attribs.getValue("v3"));
}else if (qName.equals("position")){ } else if (qName.equals("position")) {
pushAttrib(Type.Position, attribs); pushAttrib(Type.Position, attribs);
}else if (qName.equals("normal")){ } else if (qName.equals("normal")) {
pushAttrib(Type.Normal, attribs); pushAttrib(Type.Normal, attribs);
}else if (qName.equals("tangent")){ } else if (qName.equals("tangent")) {
pushTangent(attribs); pushTangent(attribs);
}else if (qName.equals("binormal")){ } else if (qName.equals("binormal")) {
pushAttrib(Type.Binormal, attribs); pushAttrib(Type.Binormal, attribs);
}else if (qName.equals("colour_diffuse")){ } else if (qName.equals("colour_diffuse")) {
pushColor(attribs); pushColor(attribs);
}else if (qName.equals("vertex")){ } else if (qName.equals("vertex")) {
startVertex(); startVertex();
}else if (qName.equals("faces")){ } else if (qName.equals("faces")) {
startFaces(attribs.getValue("count")); startFaces(attribs.getValue("count"));
}else if (qName.equals("geometry")){ } else if (qName.equals("geometry")) {
String count = attribs.getValue("vertexcount"); String count = attribs.getValue("vertexcount");
if (count == null) if (count == null) {
count = attribs.getValue("count"); count = attribs.getValue("count");
}
startGeometry(count); startGeometry(count);
}else if (qName.equals("vertexbuffer")){ } else if (qName.equals("vertexbuffer")) {
startVertexBuffer(attribs); startVertexBuffer(attribs);
}else if (qName.equals("lodfacelist")){ } else if (qName.equals("lodfacelist")) {
startLodFaceList(attribs.getValue("submeshindex"), startLodFaceList(attribs.getValue("submeshindex"),
attribs.getValue("numfaces")); attribs.getValue("numfaces"));
}else if (qName.equals("lodgenerated")){ } else if (qName.equals("lodgenerated")) {
startLodGenerated(attribs.getValue("fromdepthsquared")); startLodGenerated(attribs.getValue("fromdepthsquared"));
}else if (qName.equals("levelofdetail")){ } else if (qName.equals("levelofdetail")) {
startLevelOfDetail(attribs.getValue("numlevels")); startLevelOfDetail(attribs.getValue("numlevels"));
}else if (qName.equals("boneassignments")){ } else if (qName.equals("boneassignments")) {
startBoneAssigns(); startBoneAssigns();
}else if (qName.equals("submesh")){ } else if (qName.equals("submesh")) {
startMesh(attribs.getValue("material"), startMesh(attribs.getValue("material"),
attribs.getValue("usesharedvertices"), attribs.getValue("usesharedvertices"),
attribs.getValue("use32bitindexes"), attribs.getValue("use32bitindexes"),
attribs.getValue("operationtype")); attribs.getValue("operationtype"));
}else if (qName.equals("sharedgeometry")){ } else if (qName.equals("sharedgeometry")) {
String count = attribs.getValue("vertexcount"); String count = attribs.getValue("vertexcount");
if (count == null) if (count == null) {
count = attribs.getValue("count"); count = attribs.getValue("count");
}
if (count != null && !count.equals("0")) if (count != null && !count.equals("0")) {
startSharedGeom(count); startSharedGeom(count);
}else if (qName.equals("submeshes")){ }
} else if (qName.equals("submeshes")) {
// ok // ok
}else if (qName.equals("skeletonlink")){ } else if (qName.equals("skeletonlink")) {
startSkeleton(attribs.getValue("name")); startSkeleton(attribs.getValue("name"));
}else if (qName.equals("submeshname")){ } else if (qName.equals("submeshname")) {
startSubmeshName(attribs.getValue("index"), attribs.getValue("name")); startSubmeshName(attribs.getValue("index"), attribs.getValue("name"));
}else if (qName.equals("mesh")){ } else if (qName.equals("mesh")) {
// ok // ok
}else{ } else {
logger.log(Level.WARNING, "Unknown tag: {0}. Ignoring.", qName); logger.log(Level.WARNING, "Unknown tag: {0}. Ignoring.", qName);
ignoreUntilEnd = qName; ignoreUntilEnd = qName;
} }
@ -628,56 +633,59 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
@Override @Override
public void endElement(String uri, String name, String qName) { public void endElement(String uri, String name, String qName) {
if (ignoreUntilEnd != null){ if (ignoreUntilEnd != null) {
if (ignoreUntilEnd.equals(qName)) if (ignoreUntilEnd.equals(qName)) {
ignoreUntilEnd = null; ignoreUntilEnd = null;
}
return; return;
} }
if (qName.equals("submesh")){ if (qName.equals("submesh")) {
bigindices = false; bigindices = false;
geom = null; geom = null;
mesh = null; mesh = null;
}else if (qName.equals("submeshes")){ } else if (qName.equals("submeshes")) {
// IMPORTANT: restore sharedgeoemtry, for use with shared boneweights // IMPORTANT: restore sharedgeoemtry, for use with shared boneweights
geom = sharedgeom; geom = sharedgeom;
mesh = sharedmesh; mesh = sharedmesh;
}else if (qName.equals("faces")){ } else if (qName.equals("faces")) {
if (ib != null) if (ib != null) {
ib.flip(); ib.flip();
else } else {
sb.flip(); sb.flip();
}
vb = null; vb = null;
ib = null; ib = null;
sb = null; sb = null;
}else if (qName.equals("vertexbuffer")){ } else if (qName.equals("vertexbuffer")) {
fb = null; fb = null;
vb = null; vb = null;
}else if (qName.equals("geometry") } else if (qName.equals("geometry")
|| qName.equals("sharedgeometry")){ || qName.equals("sharedgeometry")) {
// finish writing to buffers // finish writing to buffers
IntMap<VertexBuffer> bufs = mesh.getBuffers(); IntMap<VertexBuffer> bufs = mesh.getBuffers();
for (Entry<VertexBuffer> entry : bufs){ for (Entry<VertexBuffer> entry : bufs) {
Buffer data = entry.getValue().getData(); Buffer data = entry.getValue().getData();
if (data.position() != 0) if (data.position() != 0) {
data.flip(); data.flip();
}
} }
mesh.updateBound(); mesh.updateBound();
mesh.setStatic(); mesh.setStatic();
if (qName.equals("sharedgeometry")){ if (qName.equals("sharedgeometry")) {
geom = null; geom = null;
mesh = null; mesh = null;
} }
}else if (qName.equals("lodfacelist")){ } else if (qName.equals("lodfacelist")) {
sb.flip(); sb.flip();
vb = null; vb = null;
sb = null; sb = null;
}else if (qName.equals("levelofdetail")){ } else if (qName.equals("levelofdetail")) {
endLevelOfDetail(); endLevelOfDetail();
}else if (qName.equals("boneassignments")){ } else if (qName.equals("boneassignments")) {
endBoneAssigns(); endBoneAssigns();
} }
} }
@ -686,9 +694,9 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
public void characters(char ch[], int start, int length) { public void characters(char ch[], int start, int length) {
} }
private void createBindPose(Mesh mesh){ private void createBindPose(Mesh mesh) {
VertexBuffer pos = mesh.getBuffer(Type.Position); VertexBuffer pos = mesh.getBuffer(Type.Position);
if (pos == null || mesh.getBuffer(Type.BoneIndex) == null){ if (pos == null || mesh.getBuffer(Type.BoneIndex) == null) {
// ignore, this mesh doesn't have positional data // ignore, this mesh doesn't have positional data
// or it doesn't have bone-vertex assignments, so its not animated // or it doesn't have bone-vertex assignments, so its not animated
return; return;
@ -696,9 +704,9 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
VertexBuffer bindPos = new VertexBuffer(Type.BindPosePosition); VertexBuffer bindPos = new VertexBuffer(Type.BindPosePosition);
bindPos.setupData(Usage.CpuOnly, bindPos.setupData(Usage.CpuOnly,
3, 3,
Format.Float, Format.Float,
BufferUtils.clone(pos.getData())); BufferUtils.clone(pos.getData()));
mesh.setBuffer(bindPos); mesh.setBuffer(bindPos);
// XXX: note that this method also sets stream mode // XXX: note that this method also sets stream mode
@ -706,111 +714,118 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
pos.setUsage(Usage.Stream); pos.setUsage(Usage.Stream);
VertexBuffer norm = mesh.getBuffer(Type.Normal); VertexBuffer norm = mesh.getBuffer(Type.Normal);
if (norm != null){ if (norm != null) {
VertexBuffer bindNorm = new VertexBuffer(Type.BindPoseNormal); VertexBuffer bindNorm = new VertexBuffer(Type.BindPoseNormal);
bindNorm.setupData(Usage.CpuOnly, bindNorm.setupData(Usage.CpuOnly,
3, 3,
Format.Float, Format.Float,
BufferUtils.clone(norm.getData())); BufferUtils.clone(norm.getData()));
mesh.setBuffer(bindNorm); mesh.setBuffer(bindNorm);
norm.setUsage(Usage.Stream); norm.setUsage(Usage.Stream);
} }
} }
private Node compileModel(){ private Node compileModel() {
String nodeName; String nodeName;
if (meshName == null) if (meshName == null) {
nodeName = "OgreMesh"+(++nodeIdx); nodeName = "OgreMesh" + (++nodeIdx);
else } else {
nodeName = meshName+"-ogremesh"; nodeName = meshName + "-ogremesh";
}
Node model = new Node(nodeName); Node model = new Node(nodeName);
if (animData != null){ if (animData != null) {
ArrayList<Mesh> newMeshes = new ArrayList<Mesh>(geoms.size()); ArrayList<Mesh> newMeshes = new ArrayList<Mesh>(geoms.size());
// generate bind pose for mesh and add to skin-list // generate bind pose for mesh and add to skin-list
// ONLY if not using shared geometry // ONLY if not using shared geometry
// This includes the shared geoemtry itself actually // This includes the shared geoemtry itself actually
for (int i = 0; i < geoms.size(); i++){ for (int i = 0; i < geoms.size(); i++) {
Geometry g = geoms.get(i); Geometry g = geoms.get(i);
Mesh m = geoms.get(i).getMesh(); Mesh m = geoms.get(i).getMesh();
boolean useShared = usesSharedGeom.get(i); boolean useShared = usesSharedGeom.get(i);
// create bind pose // create bind pose
if (!useShared){ if (!useShared) {
createBindPose(m); createBindPose(m);
newMeshes.add(m); newMeshes.add(m);
}else{ } else {
VertexBuffer bindPos = sharedmesh.getBuffer(Type.BindPosePosition); VertexBuffer bindPos = sharedmesh.getBuffer(Type.BindPosePosition);
VertexBuffer bindNorm = sharedmesh.getBuffer(Type.BindPoseNormal); VertexBuffer bindNorm = sharedmesh.getBuffer(Type.BindPoseNormal);
VertexBuffer boneIndex = sharedmesh.getBuffer(Type.BoneIndex); VertexBuffer boneIndex = sharedmesh.getBuffer(Type.BoneIndex);
VertexBuffer boneWeight = sharedmesh.getBuffer(Type.BoneWeight); VertexBuffer boneWeight = sharedmesh.getBuffer(Type.BoneWeight);
if (bindPos != null) if (bindPos != null) {
m.setBuffer(bindPos); m.setBuffer(bindPos);
}
if (bindNorm != null) if (bindNorm != null) {
m.setBuffer(bindNorm); m.setBuffer(bindNorm);
}
if (boneIndex != null) if (boneIndex != null) {
m.setBuffer(boneIndex); m.setBuffer(boneIndex);
}
if (boneWeight != null) if (boneWeight != null) {
m.setBuffer(boneWeight); m.setBuffer(boneWeight);
}
} }
} }
Mesh[] meshes = new Mesh[newMeshes.size()]; 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); meshes[i] = newMeshes.get(i);
}
HashMap<String, BoneAnimation> anims = new HashMap<String, BoneAnimation>(); HashMap<String, BoneAnimation> anims = new HashMap<String, BoneAnimation>();
ArrayList<BoneAnimation> animList = animData.anims; ArrayList<BoneAnimation> animList = animData.anims;
for (int i = 0; i < animList.size(); i++){ for (int i = 0; i < animList.size(); i++) {
BoneAnimation anim = animList.get(i); BoneAnimation anim = animList.get(i);
anims.put(anim.getName(), anim); anims.put(anim.getName(), anim);
} }
AnimControl ctrl = new AnimControl(model, //AnimControl ctrl = new AnimControl(model, meshes, animData.skeleton);
meshes, AnimControl ctrl = new AnimControl(animData.skeleton);
animData.skeleton);
ctrl.setAnimations(anims); ctrl.setAnimations(anims);
model.addControl(ctrl); model.addControl(ctrl);
SkeletonControl skeletonControl = new SkeletonControl(model, meshes, animData.skeleton);
model.addControl(skeletonControl);
} }
for (int i = 0; i < geoms.size(); i++){ for (int i = 0; i < geoms.size(); i++) {
Geometry g = geoms.get(i); Geometry g = geoms.get(i);
Mesh m = g.getMesh(); Mesh m = g.getMesh();
if (sharedmesh != null && usesSharedGeom.get(i)){ if (sharedmesh != null && usesSharedGeom.get(i)) {
m.setBound(sharedmesh.getBound().clone()); m.setBound(sharedmesh.getBound().clone());
} }
model.attachChild(geoms.get(i)); model.attachChild(geoms.get(i));
} }
return model; return model;
} }
public Object load(AssetInfo info) throws IOException { public Object load(AssetInfo info) throws IOException {
try{ try {
AssetKey key = info.getKey(); AssetKey key = info.getKey();
meshName = key.getName(); meshName = key.getName();
folderName = key.getFolder(); folderName = key.getFolder();
String ext = key.getExtension(); String ext = key.getExtension();
meshName = meshName.substring(0, meshName.length() - ext.length() - 1); meshName = meshName.substring(0, meshName.length() - ext.length() - 1);
if (folderName != null && folderName.length() > 0){ if (folderName != null && folderName.length() > 0) {
meshName = meshName.substring(folderName.length()); meshName = meshName.substring(folderName.length());
} }
assetManager = info.getManager(); assetManager = info.getManager();
OgreMeshKey meshKey = null; OgreMeshKey meshKey = null;
if (key instanceof OgreMeshKey){ if (key instanceof OgreMeshKey) {
meshKey = (OgreMeshKey) key; meshKey = (OgreMeshKey) key;
materialList = meshKey.getMaterialList(); materialList = meshKey.getMaterialList();
}else{ } else {
try { try {
materialList = (MaterialList) assetManager.loadAsset(folderName + meshName + ".material"); materialList = (MaterialList) assetManager.loadAsset(folderName + meshName + ".material");
} catch (AssetNotFoundException e) { } catch (AssetNotFoundException e) {
logger.log(Level.WARNING, "Cannot locate {0}{1}.material for model {2}{3}.{4}", new Object[]{folderName, meshName, folderName, meshName, ext}); logger.log(Level.WARNING, "Cannot locate {0}{1}.material for model {2}{3}.{4}", new Object[]{folderName, meshName, folderName, meshName, ext});
} }
} }
XMLReader xr = XMLReaderFactory.createXMLReader(); XMLReader xr = XMLReaderFactory.createXMLReader();
@ -821,12 +836,11 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
r.close(); r.close();
return compileModel(); return compileModel();
}catch (SAXException ex){ } catch (SAXException ex) {
IOException ioEx = new IOException("Error while parsing Ogre3D mesh.xml"); IOException ioEx = new IOException("Error while parsing Ogre3D mesh.xml");
ioEx.initCause(ex); ioEx.initCause(ex);
throw ioEx; throw ioEx;
} }
} }
} }

@ -87,8 +87,8 @@ public class TestBoneRagdoll extends SimpleApplication {
setupLight(); setupLight();
model = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); model = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml");
// model.setLocalTranslation(5,5,5); // model.setLocalTranslation(5,5,5);
// model.setLocalRotation(new Quaternion().fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X)); // model.setLocalRotation(new Quaternion().fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X));
//debug view //debug view
AnimControl control= model.getControl(AnimControl.class); AnimControl control= model.getControl(AnimControl.class);
@ -105,12 +105,12 @@ public class TestBoneRagdoll extends SimpleApplication {
// ragdoll.setEnabled(true); // ragdoll.setEnabled(true);
// ragdoll.attachDebugShape(assetManager); // ragdoll.attachDebugShape(assetManager);
ragdoll.setSpatial(model); // ragdoll.setSpatial(model);
ragdoll.setPhysicsSpace(getPhysicsSpace()); // ragdoll.setPhysicsSpace(getPhysicsSpace());
control.setRagdoll(ragdoll); // control.setRagdoll(ragdoll);
// model.addControl(ragdoll); model.addControl(ragdoll);
// getPhysicsSpace().add(ragdoll); getPhysicsSpace().add(ragdoll);
speed = 1f; speed = 1f;
rootNode.attachChild(model); rootNode.attachChild(model);

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

Loading…
Cancel
Save