Merge meshes in animated models by finding ones that maintain the same

relative position throughout all animations.  Also, don't include 
transforms in the animations for nodes that never move.


git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@218 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
Andrzej Kapolka
2007-05-01 23:36:23 +00:00
parent 86298ca832
commit 072926f814
8 changed files with 620 additions and 390 deletions
+142 -128
View File
@@ -69,11 +69,11 @@ import com.threerings.jme.util.SpatialVisitor;
*/ */
public class Model extends ModelNode public class Model extends ModelNode
{ {
/** The supported types of animation in decreasing order of complexity. */ /** The supported types of animation in decreasing order of complexity. */
public enum AnimationMode { public enum AnimationMode {
SKIN, MORPH, FLIPBOOK SKIN, MORPH, FLIPBOOK
}; };
/** Lets listeners know when animations are completed (which only happens /** Lets listeners know when animations are completed (which only happens
* for non-repeating animations) or cancelled. */ * for non-repeating animations) or cancelled. */
public interface AnimationObserver public interface AnimationObserver
@@ -84,14 +84,14 @@ public class Model extends ModelNode
* @return true to remain on the observer list, false to remove self * @return true to remain on the observer list, false to remove self
*/ */
public boolean animationStarted (Model model, String anim); public boolean animationStarted (Model model, String anim);
/** /**
* Called when a non-repeating animation has finished. * Called when a non-repeating animation has finished.
* *
* @return true to remain on the observer list, false to remove self * @return true to remain on the observer list, false to remove self
*/ */
public boolean animationCompleted (Model model, String anim); public boolean animationCompleted (Model model, String anim);
/** /**
* Called when an animation has been cancelled. * Called when an animation has been cancelled.
* *
@@ -99,30 +99,33 @@ public class Model extends ModelNode
*/ */
public boolean animationCancelled (Model model, String anim); public boolean animationCancelled (Model model, String anim);
} }
/** An animation for the model. */ /** An animation for the model. */
public static class Animation public static class Animation
implements Savable implements Savable
{ {
/** The rate of the animation in frames per second. */ /** The rate of the animation in frames per second. */
public int frameRate; public int frameRate;
/** The animation repeat type ({@link Controller#RT_CLAMP}, /** The animation repeat type ({@link Controller#RT_CLAMP},
* {@link Controller#RT_CYCLE}, or {@link Controller#RT_WRAP}). */ * {@link Controller#RT_CYCLE}, or {@link Controller#RT_WRAP}). */
public int repeatType; public int repeatType;
/** Any nodes visible that never move within the model. */
public Spatial[] staticTargets;
/** The transformation targets of the animation. */ /** The transformation targets of the animation. */
public Spatial[] transformTargets; public Spatial[] transformTargets;
/** The animation transforms (one transform per target per frame). */ /** The animation transforms (one transform per target per frame). */
public transient Transform[][] transforms; public transient Transform[][] transforms;
/** Uniquely identifies this animation within the model. */ /** Uniquely identifies this animation within the model. */
public transient int animId; public transient int animId;
/** For each frame, whether the frame has been stored in meshes. */ /** For each frame, whether the frame has been stored in meshes. */
public transient boolean[] stored; public transient boolean[] stored;
/** /**
* Returns this animation's duration in seconds. * Returns this animation's duration in seconds.
*/ */
@@ -130,7 +133,7 @@ public class Model extends ModelNode
{ {
return (float)transforms.length / frameRate; return (float)transforms.length / frameRate;
} }
/** /**
* Rebinds this animation for a prototype instance. * Rebinds this animation for a prototype instance.
* *
@@ -141,17 +144,14 @@ public class Model extends ModelNode
Animation anim = new Animation(); Animation anim = new Animation();
anim.frameRate = frameRate; anim.frameRate = frameRate;
anim.repeatType = repeatType; anim.repeatType = repeatType;
anim.staticTargets = rebind(staticTargets, pnodes);
anim.transformTargets = rebind(transformTargets, pnodes);
anim.transforms = transforms; anim.transforms = transforms;
anim.transformTargets = new Spatial[transformTargets.length];
for (int ii = 0; ii < transformTargets.length; ii++) {
anim.transformTargets[ii] =
(Spatial)pnodes.get(transformTargets[ii]);
}
anim.animId = animId; anim.animId = animId;
anim.stored = stored; anim.stored = stored;
return anim; return anim;
} }
/** /**
* Applies the transforms for a frame of this animation. * Applies the transforms for a frame of this animation.
*/ */
@@ -162,7 +162,7 @@ public class Model extends ModelNode
xforms[ii].apply(transformTargets[ii]); xforms[ii].apply(transformTargets[ii]);
} }
} }
/** /**
* Blends the transforms between two frames of this animation. * Blends the transforms between two frames of this animation.
*/ */
@@ -173,13 +173,13 @@ public class Model extends ModelNode
xforms[ii].blend(nxforms[ii], alpha, transformTargets[ii]); xforms[ii].blend(nxforms[ii], alpha, transformTargets[ii]);
} }
} }
// documentation inherited // documentation inherited
public Class getClassTag () public Class getClassTag ()
{ {
return getClass(); return getClass();
} }
// documentation inherited // documentation inherited
public void read (JMEImporter im) public void read (JMEImporter im)
throws IOException throws IOException
@@ -187,6 +187,8 @@ public class Model extends ModelNode
InputCapsule capsule = im.getCapsule(this); InputCapsule capsule = im.getCapsule(this);
frameRate = capsule.readInt("frameRate", 0); frameRate = capsule.readInt("frameRate", 0);
repeatType = capsule.readInt("repeatType", Controller.RT_CLAMP); repeatType = capsule.readInt("repeatType", Controller.RT_CLAMP);
staticTargets = ArrayUtil.copy(capsule.readSavableArray(
"staticTargets", null), new Spatial[0]);
transformTargets = ArrayUtil.copy(capsule.readSavableArray( transformTargets = ArrayUtil.copy(capsule.readSavableArray(
"transformTargets", null), new Spatial[0]); "transformTargets", null), new Spatial[0]);
FloatBuffer pxforms = capsule.readFloatBuffer("transforms", null); FloatBuffer pxforms = capsule.readFloatBuffer("transforms", null);
@@ -208,9 +210,10 @@ public class Model extends ModelNode
OutputCapsule capsule = ex.getCapsule(this); OutputCapsule capsule = ex.getCapsule(this);
capsule.write(frameRate, "frameRate", 0); capsule.write(frameRate, "frameRate", 0);
capsule.write(repeatType, "repeatType", Controller.RT_CLAMP); capsule.write(repeatType, "repeatType", Controller.RT_CLAMP);
capsule.write(staticTargets, "staticTargets", null);
capsule.write(transformTargets, "transformTargets", null); capsule.write(transformTargets, "transformTargets", null);
FloatBuffer pxforms = FloatBuffer.allocate(transforms.length * FloatBuffer pxforms = FloatBuffer.allocate(
transformTargets.length * Transform.PACKED_SIZE); transforms.length * transformTargets.length * Transform.PACKED_SIZE);
for (Transform[] frame : transforms) { for (Transform[] frame : transforms) {
for (Transform xform : frame) { for (Transform xform : frame) {
xform.writeToBuffer(pxforms); xform.writeToBuffer(pxforms);
@@ -219,16 +222,25 @@ public class Model extends ModelNode
pxforms.rewind(); pxforms.rewind();
capsule.write(pxforms, "transforms", null); capsule.write(pxforms, "transforms", null);
} }
protected Spatial[] rebind (Spatial[] targets, HashMap pnodes)
{
Spatial[] ntargets = new Spatial[targets.length];
for (int ii = 0; ii < targets.length; ii++) {
ntargets[ii] = (Spatial)pnodes.get(targets[ii]);
}
return ntargets;
}
private static final long serialVersionUID = 1; private static final long serialVersionUID = 1;
} }
/** A frame element that manipulates the target's transform. */ /** A frame element that manipulates the target's transform. */
public static final class Transform public static final class Transform
{ {
/** The number of floats required to store a packed transform. */ /** The number of floats required to store a packed transform. */
public static final int PACKED_SIZE = 3 + 4 + 3; public static final int PACKED_SIZE = 3 + 4 + 3;
public Transform ( public Transform (
Vector3f translation, Quaternion rotation, Vector3f scale) Vector3f translation, Quaternion rotation, Vector3f scale)
{ {
@@ -236,7 +248,7 @@ public class Model extends ModelNode
_rotation = rotation; _rotation = rotation;
_scale = scale; _scale = scale;
} }
public Transform (FloatBuffer buf) public Transform (FloatBuffer buf)
{ {
_translation = new Vector3f(buf.get(), buf.get(), buf.get()); _translation = new Vector3f(buf.get(), buf.get(), buf.get());
@@ -244,14 +256,14 @@ public class Model extends ModelNode
buf.get()); buf.get());
_scale = new Vector3f(buf.get(), buf.get(), buf.get()); _scale = new Vector3f(buf.get(), buf.get(), buf.get());
} }
public void apply (Spatial target) public void apply (Spatial target)
{ {
target.getLocalTranslation().set(_translation); target.getLocalTranslation().set(_translation);
target.getLocalRotation().set(_rotation); target.getLocalRotation().set(_rotation);
target.getLocalScale().set(_scale); target.getLocalScale().set(_scale);
} }
/** /**
* Blends between this transform and the next, applying the result to * Blends between this transform and the next, applying the result to
* the given target. * the given target.
@@ -266,7 +278,7 @@ public class Model extends ModelNode
target.getLocalRotation().slerp(_rotation, next._rotation, alpha); target.getLocalRotation().slerp(_rotation, next._rotation, alpha);
target.getLocalScale().interpolate(_scale, next._scale, alpha); target.getLocalScale().interpolate(_scale, next._scale, alpha);
} }
/** /**
* Writes this transform to the current position in the supplied * Writes this transform to the current position in the supplied
* buffer. * buffer.
@@ -276,31 +288,31 @@ public class Model extends ModelNode
buf.put(_translation.x); buf.put(_translation.x);
buf.put(_translation.y); buf.put(_translation.y);
buf.put(_translation.z); buf.put(_translation.z);
buf.put(_rotation.x); buf.put(_rotation.x);
buf.put(_rotation.y); buf.put(_rotation.y);
buf.put(_rotation.z); buf.put(_rotation.z);
buf.put(_rotation.w); buf.put(_rotation.w);
buf.put(_scale.x); buf.put(_scale.x);
buf.put(_scale.y); buf.put(_scale.y);
buf.put(_scale.z); buf.put(_scale.z);
} }
/** The transform at this frame. */ /** The transform at this frame. */
protected Vector3f _translation, _scale; protected Vector3f _translation, _scale;
protected Quaternion _rotation; protected Quaternion _rotation;
} }
/** Customized clone creator for models. */ /** Customized clone creator for models. */
public static class CloneCreator public static class CloneCreator
{ {
/** A shared seed used to select textures consistently. */ /** A shared seed used to select textures consistently. */
public int random; public int random;
/** Maps original objects to their copies. */ /** Maps original objects to their copies. */
public HashMap originalToCopy = new HashMap(); public HashMap originalToCopy = new HashMap();
public CloneCreator (Model toCopy) public CloneCreator (Model toCopy)
{ {
_toCopy = toCopy; _toCopy = toCopy;
@@ -314,7 +326,7 @@ public class Model extends ModelNode
addProperty("displaylistid"); addProperty("displaylistid");
addProperty("bound"); addProperty("bound");
} }
/** /**
* Sets the named property. * Sets the named property.
*/ */
@@ -322,7 +334,7 @@ public class Model extends ModelNode
{ {
_properties.add(name); _properties.add(name);
} }
/** /**
* Clears the named property. * Clears the named property.
*/ */
@@ -330,7 +342,7 @@ public class Model extends ModelNode
{ {
_properties.remove(name); _properties.remove(name);
} }
/** /**
* Checks whether the named property has been set. * Checks whether the named property has been set.
*/ */
@@ -338,7 +350,7 @@ public class Model extends ModelNode
{ {
return _properties.contains(name); return _properties.contains(name);
} }
/** /**
* Creates a new copy of the target model. * Creates a new copy of the target model.
*/ */
@@ -349,14 +361,14 @@ public class Model extends ModelNode
originalToCopy.clear(); // make sure no references remain originalToCopy.clear(); // make sure no references remain
return copy; return copy;
} }
/** The model to copy. */ /** The model to copy. */
protected Model _toCopy; protected Model _toCopy;
/** The set of added properties. */ /** The set of added properties. */
protected HashSet<String> _properties = new HashSet<String>(); protected HashSet<String> _properties = new HashSet<String>();
} }
/** /**
* Attempts to read a model from the specified file. * Attempts to read a model from the specified file.
*/ */
@@ -367,20 +379,20 @@ public class Model extends ModelNode
FileInputStream fis = new FileInputStream(file); FileInputStream fis = new FileInputStream(file);
Model model = (Model)BinaryImporter.getInstance().load(fis); Model model = (Model)BinaryImporter.getInstance().load(fis);
fis.close(); fis.close();
// initialize the model as a prototype // initialize the model as a prototype
model.initPrototype(); model.initPrototype();
return model; return model;
} }
/** /**
* No-arg constructor for deserialization. * No-arg constructor for deserialization.
*/ */
public Model () public Model ()
{ {
} }
/** /**
* Standard constructor. * Standard constructor.
*/ */
@@ -389,7 +401,7 @@ public class Model extends ModelNode
super(name); super(name);
_props = props; _props = props;
} }
/** /**
* Returns a reference to the properties of the model. * Returns a reference to the properties of the model.
*/ */
@@ -397,7 +409,7 @@ public class Model extends ModelNode
{ {
return _props; return _props;
} }
/** /**
* Initializes this model as prototype. Only necessary when the prototype * Initializes this model as prototype. Only necessary when the prototype
* was not loaded through {@link #readFromFile}. * was not loaded through {@link #readFromFile}.
@@ -416,7 +428,7 @@ public class Model extends ModelNode
cullInvisibleNodes(); cullInvisibleNodes();
initInstance(); initInstance();
} }
/** /**
* Returns the names of the model's variant configurations. * Returns the names of the model's variant configurations.
*/ */
@@ -424,7 +436,7 @@ public class Model extends ModelNode
{ {
return StringUtil.parseStringArray(_props.getProperty("variants", "")); return StringUtil.parseStringArray(_props.getProperty("variants", ""));
} }
/** /**
* Adds an animation to the model's library. This should only be called by * Adds an animation to the model's library. This should only be called by
* the model compiler. * the model compiler.
@@ -435,7 +447,7 @@ public class Model extends ModelNode
_anims = new HashMap<String, Animation>(); _anims = new HashMap<String, Animation>();
} }
_anims.put(name, anim); _anims.put(name, anim);
// store the original transforms // store the original transforms
Transform[] oxforms = new Transform[anim.transformTargets.length]; Transform[] oxforms = new Transform[anim.transformTargets.length];
for (int ii = 0; ii < anim.transformTargets.length; ii++) { for (int ii = 0; ii < anim.transformTargets.length; ii++) {
@@ -445,7 +457,7 @@ public class Model extends ModelNode
new Quaternion(target.getLocalRotation()), new Quaternion(target.getLocalRotation()),
new Vector3f(target.getLocalScale())); new Vector3f(target.getLocalScale()));
} }
// run through every frame of the animation, expanding the bounding // run through every frame of the animation, expanding the bounding
// volumes of any deformable meshes // volumes of any deformable meshes
for (int ii = 0; ii < anim.transforms.length; ii++) { for (int ii = 0; ii < anim.transforms.length; ii++) {
@@ -455,14 +467,14 @@ public class Model extends ModelNode
updateWorldData(0f); updateWorldData(0f);
expandModelBounds(); expandModelBounds();
} }
// restore the original transforms // restore the original transforms
for (int ii = 0; ii < anim.transformTargets.length; ii++) { for (int ii = 0; ii < anim.transformTargets.length; ii++) {
oxforms[ii].apply(anim.transformTargets[ii]); oxforms[ii].apply(anim.transformTargets[ii]);
} }
updateWorldData(0f); updateWorldData(0f);
} }
/** /**
* Sets the animation mode to use for this model. This should be set * Sets the animation mode to use for this model. This should be set
* on the prototype before any animations are started or any instances * on the prototype before any animations are started or any instances
@@ -472,7 +484,7 @@ public class Model extends ModelNode
{ {
_animMode = mode; _animMode = mode;
} }
/** /**
* Returns the animation mode configured for this model. * Returns the animation mode configured for this model.
*/ */
@@ -480,7 +492,7 @@ public class Model extends ModelNode
{ {
return _animMode; return _animMode;
} }
/** /**
* Returns the names of the model's animations. * Returns the names of the model's animations.
*/ */
@@ -492,7 +504,7 @@ public class Model extends ModelNode
return (_anims == null) ? new String[0] : return (_anims == null) ? new String[0] :
_anims.keySet().toArray(new String[_anims.size()]); _anims.keySet().toArray(new String[_anims.size()]);
} }
/** /**
* Checks whether the unit has an animation with the given name. * Checks whether the unit has an animation with the given name.
*/ */
@@ -503,7 +515,7 @@ public class Model extends ModelNode
} }
return (_anims == null) ? false : _anims.containsKey(name); return (_anims == null) ? false : _anims.containsKey(name);
} }
/** /**
* Starts the named animation. * Starts the named animation.
* *
@@ -533,14 +545,16 @@ public class Model extends ModelNode
if (_anim != null) { if (_anim != null) {
_animObservers.apply(new AnimCancelledOp(_animName)); _animObservers.apply(new AnimCancelledOp(_animName));
} }
// first cull all model nodes, then re-activate the ones in the // first cull all model nodes, then re-activate the ones in the target lists
// target list
cullModelNodes(); cullModelNodes();
for (Spatial target : anim.staticTargets) {
((ModelNode)target).updateCullMode();
}
for (Spatial target : anim.transformTargets) { for (Spatial target : anim.transformTargets) {
((ModelNode)target).updateCullMode(); ((ModelNode)target).updateCullMode();
} }
_paused = false; _paused = false;
_anim = anim; _anim = anim;
_animName = name; _animName = name;
@@ -552,7 +566,7 @@ public class Model extends ModelNode
_animObservers.apply(new AnimStartedOp(_animName)); _animObservers.apply(new AnimStartedOp(_animName));
return anim.getDuration() / _animSpeed; return anim.getDuration() / _animSpeed;
} }
/** /**
* Fast-forwards the current animation by the given number of seconds. * Fast-forwards the current animation by the given number of seconds.
*/ */
@@ -560,7 +574,7 @@ public class Model extends ModelNode
{ {
updateAnimation(time); updateAnimation(time);
} }
/** /**
* Gets a reference to the animation with the given name. * Gets a reference to the animation with the given name.
*/ */
@@ -583,7 +597,7 @@ public class Model extends ModelNode
Log.warning("Requested unknown animation [name=" + name + "]."); Log.warning("Requested unknown animation [name=" + name + "].");
return null; return null;
} }
/** /**
* Returns a reference to the currently running animation, or * Returns a reference to the currently running animation, or
* <code>null</code> if no animation is running. * <code>null</code> if no animation is running.
@@ -592,7 +606,7 @@ public class Model extends ModelNode
{ {
return _anim; return _anim;
} }
/** /**
* Stops the currently running animation. * Stops the currently running animation.
*/ */
@@ -633,7 +647,7 @@ public class Model extends ModelNode
{ {
_fdir *= -1; _fdir *= -1;
} }
/** /**
* Sets the animation speed, which acts as a multiplier for the frame rate * Sets the animation speed, which acts as a multiplier for the frame rate
* of each animation. * of each animation.
@@ -642,7 +656,7 @@ public class Model extends ModelNode
{ {
_animSpeed = speed; _animSpeed = speed;
} }
/** /**
* Returns the currently configured animation speed. * Returns the currently configured animation speed.
*/ */
@@ -650,7 +664,7 @@ public class Model extends ModelNode
{ {
return _animSpeed; return _animSpeed;
} }
/** /**
* Adds an animation observer. * Adds an animation observer.
*/ */
@@ -658,7 +672,7 @@ public class Model extends ModelNode
{ {
_animObservers.add(obs); _animObservers.add(obs);
} }
/** /**
* Removes an animation observer. * Removes an animation observer.
*/ */
@@ -666,7 +680,7 @@ public class Model extends ModelNode
{ {
_animObservers.remove(obs); _animObservers.remove(obs);
} }
/** /**
* Returns a reference to the node that contains this model's emissions * Returns a reference to the node that contains this model's emissions
* (in world space, so the emissions do not move with the model). This * (in world space, so the emissions do not move with the model). This
@@ -685,7 +699,7 @@ public class Model extends ModelNode
} }
return _emissionNode; return _emissionNode;
} }
/** /**
* Writes this model out to a file. * Writes this model out to a file.
*/ */
@@ -697,7 +711,7 @@ public class Model extends ModelNode
BinaryExporter.getInstance().save(this, fos); BinaryExporter.getInstance().save(this, fos);
fos.close(); fos.close();
} }
@Override // documentation inherited @Override // documentation inherited
public void read (JMEImporter im) public void read (JMEImporter im)
throws IOException throws IOException
@@ -729,7 +743,7 @@ public class Model extends ModelNode
} }
} }
} }
@Override // documentation inherited @Override // documentation inherited
public void write (JMEExporter ex) public void write (JMEExporter ex)
throws IOException throws IOException
@@ -756,7 +770,7 @@ public class Model extends ModelNode
} }
capsule.writeSavableArrayList(getControllers(), "controllers", null); capsule.writeSavableArrayList(getControllers(), "controllers", null);
} }
@Override // documentation inherited @Override // documentation inherited
public void resolveTextures (TextureProvider tprov) public void resolveTextures (TextureProvider tprov)
{ {
@@ -767,7 +781,7 @@ public class Model extends ModelNode
} }
} }
} }
/** /**
* Creates a new prototype using the given variant configuration. * Creates a new prototype using the given variant configuration.
* Use {@link #createInstance} on the returned prototype to create * Use {@link #createInstance} on the returned prototype to create
@@ -788,7 +802,7 @@ public class Model extends ModelNode
} }
prototype._prototype = null; prototype._prototype = null;
prototype._pnodes = null; prototype._pnodes = null;
// reconfigure meshes with new variant type // reconfigure meshes with new variant type
if (variant != null) { if (variant != null) {
prototype._props = PropertiesUtil.getFilteredProperties( prototype._props = PropertiesUtil.getFilteredProperties(
@@ -802,10 +816,10 @@ public class Model extends ModelNode
} }
return prototype; return prototype;
} }
/** /**
* Creates and returns a new instance of this model. * Creates and returns a new instance of this model.
*/ */
public Model createInstance () public Model createInstance ()
{ {
if (_prototype != null) { if (_prototype != null) {
@@ -838,7 +852,7 @@ public class Model extends ModelNode
} }
lockInstance(targets); lockInstance(targets);
} }
@Override // documentation inherited @Override // documentation inherited
public Spatial putClone (Spatial store, CloneCreator properties) public Spatial putClone (Spatial store, CloneCreator properties)
{ {
@@ -866,7 +880,7 @@ public class Model extends ModelNode
mstore._animMode = _animMode; mstore._animMode = _animMode;
return mstore; return mstore;
} }
@Override // documentation inherited @Override // documentation inherited
public void updateGeometricState (float time, boolean initiator) public void updateGeometricState (float time, boolean initiator)
{ {
@@ -875,13 +889,13 @@ public class Model extends ModelNode
// into view // into view
boolean wasOutside = _outside; boolean wasOutside = _outside;
_outside = isOutsideFrustum() && worldBound != null; _outside = isOutsideFrustum() && worldBound != null;
// slow evvvverything down by the animation speed // slow evvvverything down by the animation speed
time *= _animSpeed; time *= _animSpeed;
if (_anim != null) { if (_anim != null) {
updateAnimation(time); updateAnimation(time);
} }
// update controllers and children with accumulated time // update controllers and children with accumulated time
_accum += time; _accum += time;
if (_outside) { if (_outside) {
@@ -894,14 +908,14 @@ public class Model extends ModelNode
updateWorldVectors(); updateWorldVectors();
worldBound = _storedBound.transform(getWorldRotation(), worldBound = _storedBound.transform(getWorldRotation(),
getWorldTranslation(), getWorldScale(), worldBound); getWorldTranslation(), getWorldScale(), worldBound);
} else { } else {
super.updateGeometricState(_shouldAccumulate ? _accum : time, super.updateGeometricState(_shouldAccumulate ? _accum : time,
initiator); initiator);
_accum = 0f; _accum = 0f;
} }
} }
@Override // documentation inherited @Override // documentation inherited
public void onDraw (Renderer r) public void onDraw (Renderer r)
{ {
@@ -912,7 +926,7 @@ public class Model extends ModelNode
updateWorldData(0f); updateWorldData(0f);
} }
} }
/** /**
* Transforms the current world bound into model space and stores it for * Transforms the current world bound into model space and stores it for
* when the model is offscreen. * when the model is offscreen.
@@ -928,10 +942,10 @@ public class Model extends ModelNode
getChild(ii).updateGeometricState(0f, false); getChild(ii).updateGeometricState(0f, false);
} }
updateWorldBound(); updateWorldBound();
_storedBound = worldBound.clone(_storedBound); _storedBound = worldBound.clone(_storedBound);
} }
/** /**
* Determines whether this node was determined to be entirely outside the * Determines whether this node was determined to be entirely outside the
* view frustum. * view frustum.
@@ -945,7 +959,7 @@ public class Model extends ModelNode
} }
return false; return false;
} }
/** /**
* Initializes the per-instance state of this model. * Initializes the per-instance state of this model.
*/ */
@@ -960,7 +974,7 @@ public class Model extends ModelNode
} }
} }
} }
/** /**
* Updates the model's state according to the current animation. * Updates the model's state according to the current animation.
*/ */
@@ -972,7 +986,7 @@ public class Model extends ModelNode
_elapsed += (time * _anim.frameRate); _elapsed += (time * _anim.frameRate);
return; return;
} }
// advance the frame counter if necessary // advance the frame counter if necessary
while (_elapsed > 1f) { while (_elapsed > 1f) {
if (!_paused) { if (!_paused) {
@@ -982,13 +996,13 @@ public class Model extends ModelNode
_elapsed = 1f; _elapsed = 1f;
} }
} }
// update the target transforms and animation frame if not outside the // update the target transforms and animation frame if not outside the
// view frustum // view frustum
if (!_outside) { if (!_outside) {
updateMeshes(); updateMeshes();
} }
// if the next index is the same as this one, we are finished // if the next index is the same as this one, we are finished
if (_fidx == _nidx && !_paused) { if (_fidx == _nidx && !_paused) {
_anim = null; _anim = null;
@@ -997,7 +1011,7 @@ public class Model extends ModelNode
} }
_elapsed += (time * _anim.frameRate); _elapsed += (time * _anim.frameRate);
} }
/** /**
* Advances the frame counter by one frame. * Advances the frame counter by one frame.
*/ */
@@ -1007,14 +1021,14 @@ public class Model extends ModelNode
int nframes = _anim.transforms.length; int nframes = _anim.transforms.length;
if (_anim.repeatType == Controller.RT_CLAMP) { if (_anim.repeatType == Controller.RT_CLAMP) {
_nidx = Math.max(0, Math.min(_nidx + _fdir, nframes - 1)); _nidx = Math.max(0, Math.min(_nidx + _fdir, nframes - 1));
} else if (_anim.repeatType == Controller.RT_WRAP) { } else if (_anim.repeatType == Controller.RT_WRAP) {
// % is not a modulo operator, so is not guaranteed to be positive // % is not a modulo operator, so is not guaranteed to be positive
_nidx = (_nidx + _fdir) % nframes; _nidx = (_nidx + _fdir) % nframes;
if (_nidx < 0) { if (_nidx < 0) {
_nidx += nframes; _nidx += nframes;
} }
} else { // _anim.repeatType == Controller.RT_CYCLE } else { // _anim.repeatType == Controller.RT_CYCLE
if ((_nidx + _fdir) < 0 || (_nidx + _fdir) >= nframes) { if ((_nidx + _fdir) < 0 || (_nidx + _fdir) >= nframes) {
_fdir *= -1; // reverse direction _fdir *= -1; // reverse direction
@@ -1022,7 +1036,7 @@ public class Model extends ModelNode
_nidx += _fdir; _nidx += _fdir;
} }
} }
/** /**
* Updates the states of the model's meshes according to the animation state. * Updates the states of the model's meshes according to the animation state.
*/ */
@@ -1037,7 +1051,7 @@ public class Model extends ModelNode
_anim.stored[_fidx] = true; _anim.stored[_fidx] = true;
} }
setMeshFrame(frameId); setMeshFrame(frameId);
} else if (_animMode == AnimationMode.MORPH) { } else if (_animMode == AnimationMode.MORPH) {
int frameId1 = (_anim.animId << 16) | _fidx, int frameId1 = (_anim.animId << 16) | _fidx,
frameId2 = (_anim.animId << 16) | _nidx; frameId2 = (_anim.animId << 16) | _nidx;
@@ -1055,74 +1069,74 @@ public class Model extends ModelNode
} }
_anim.blendFrames(_fidx, _nidx, _elapsed); _anim.blendFrames(_fidx, _nidx, _elapsed);
blendMeshFrames(frameId1, frameId2, _elapsed); blendMeshFrames(frameId1, frameId2, _elapsed);
} else { // _animMode == AnimationMode.SKIN } else { // _animMode == AnimationMode.SKIN
_anim.blendFrames(_fidx, _nidx, _elapsed); _anim.blendFrames(_fidx, _nidx, _elapsed);
} }
} }
/** A reference to the prototype, or <code>null</code> if this is a /** A reference to the prototype, or <code>null</code> if this is a
* prototype. */ * prototype. */
protected Model _prototype; protected Model _prototype;
/** For prototype models, a customized clone creator used to generate /** For prototype models, a customized clone creator used to generate
* instances. */ * instances. */
protected CloneCreator _ccreator; protected CloneCreator _ccreator;
/** The animation mode to use for this model. */ /** The animation mode to use for this model. */
protected AnimationMode _animMode; protected AnimationMode _animMode;
/** For instances, maps prototype nodes to their corresponding instance /** For instances, maps prototype nodes to their corresponding instance
* nodes. */ * nodes. */
protected HashMap _pnodes; protected HashMap _pnodes;
/** The model properties. */ /** The model properties. */
protected Properties _props; protected Properties _props;
/** The model animations. */ /** The model animations. */
protected HashMap<String, Animation> _anims; protected HashMap<String, Animation> _anims;
/** The currently running animation, or <code>null</code> for none. */ /** The currently running animation, or <code>null</code> for none. */
protected Animation _anim; protected Animation _anim;
/** The name of the currently running animation, if any. */ /** The name of the currently running animation, if any. */
protected String _animName; protected String _animName;
/** The current animation speed multiplier. */ /** The current animation speed multiplier. */
protected float _animSpeed = 1f; protected float _animSpeed = 1f;
/** The index of the current and next frames. */ /** The index of the current and next frames. */
protected int _fidx, _nidx; protected int _fidx, _nidx;
/** The direction for wrapping animations (+1 forward, -1 backward). */ /** The direction for wrapping animations (+1 forward, -1 backward). */
protected int _fdir; protected int _fdir;
/** The frame portion elapsed since the start of the current frame. */ /** The frame portion elapsed since the start of the current frame. */
protected float _elapsed; protected float _elapsed;
/** The pause status of this model. */ /** The pause status of this model. */
protected boolean _paused; protected boolean _paused;
/** The amount of update time accumulated while outside of view frustum. */ /** The amount of update time accumulated while outside of view frustum. */
protected float _accum; protected float _accum;
/** Whether or not we should accumulate update time while out of view. */ /** Whether or not we should accumulate update time while out of view. */
protected boolean _shouldAccumulate; protected boolean _shouldAccumulate;
/** The child node that contains the model's emissions in world space. */ /** The child node that contains the model's emissions in world space. */
protected Node _emissionNode; protected Node _emissionNode;
/** The model space bounding volume stored for use when the model is /** The model space bounding volume stored for use when the model is
* offscreen. */ * offscreen. */
protected BoundingVolume _storedBound; protected BoundingVolume _storedBound;
/** Whether or not we were outside the frustum at the last update. */ /** Whether or not we were outside the frustum at the last update. */
protected boolean _outside; protected boolean _outside;
/** Animation completion listeners. */ /** Animation completion listeners. */
protected ObserverList<AnimationObserver> _animObservers = protected ObserverList<AnimationObserver> _animObservers =
new ObserverList<AnimationObserver>(ObserverList.FAST_UNSAFE_NOTIFY); new ObserverList<AnimationObserver>(ObserverList.FAST_UNSAFE_NOTIFY);
/** Used to notify observers of animation initiation. */ /** Used to notify observers of animation initiation. */
protected class AnimStartedOp protected class AnimStartedOp
implements ObserverList.ObserverOp<AnimationObserver> implements ObserverList.ObserverOp<AnimationObserver>
@@ -1131,17 +1145,17 @@ public class Model extends ModelNode
{ {
_name = name; _name = name;
} }
// documentation inherited from interface ObserverOp // documentation inherited from interface ObserverOp
public boolean apply (AnimationObserver obs) public boolean apply (AnimationObserver obs)
{ {
return obs.animationStarted(Model.this, _name); return obs.animationStarted(Model.this, _name);
} }
/** The name of the animation started. */ /** The name of the animation started. */
protected String _name; protected String _name;
} }
/** Used to notify observers of animation completion. */ /** Used to notify observers of animation completion. */
protected class AnimCompletedOp protected class AnimCompletedOp
implements ObserverList.ObserverOp<AnimationObserver> implements ObserverList.ObserverOp<AnimationObserver>
@@ -1150,17 +1164,17 @@ public class Model extends ModelNode
{ {
_name = name; _name = name;
} }
// documentation inherited from interface ObserverOp // documentation inherited from interface ObserverOp
public boolean apply (AnimationObserver obs) public boolean apply (AnimationObserver obs)
{ {
return obs.animationCompleted(Model.this, _name); return obs.animationCompleted(Model.this, _name);
} }
/** The name of the animation completed. */ /** The name of the animation completed. */
protected String _name; protected String _name;
} }
/** Used to notify observers of animation cancellation. */ /** Used to notify observers of animation cancellation. */
protected class AnimCancelledOp protected class AnimCancelledOp
implements ObserverList.ObserverOp<AnimationObserver> implements ObserverList.ObserverOp<AnimationObserver>
@@ -1169,13 +1183,13 @@ public class Model extends ModelNode
{ {
_name = name; _name = name;
} }
// documentation inherited from interface ObserverOp // documentation inherited from interface ObserverOp
public boolean apply (AnimationObserver obs) public boolean apply (AnimationObserver obs)
{ {
return obs.animationCancelled(Model.this, _name); return obs.animationCancelled(Model.this, _name);
} }
/** The name of the animation cancelled. */ /** The name of the animation cancelled. */
protected String _name; protected String _name;
} }
@@ -144,51 +144,6 @@ public class ModelMesh extends TriMesh
Boolean.parseBoolean(tprops.getProperty("translucent")); Boolean.parseBoolean(tprops.getProperty("translucent"));
} }
/**
* Returns an opaque object representing this mesh's attributes. The
* object will implement {@link Object#hashCode} and {@link Object#equals}
* so that meshes with the same attributes will have identical keys.
*/
public Object getAttributeKey ()
{
return new AttributeKey(_textureKey, _textures, _sphereMapped,
_filterMode, _mipMapMode, _compress, _emissiveMap, _emissive, _additive,
_solid, _transparent, _alphaThreshold, _translucent);
}
/**
* Merges another mesh into this one. The mesh is assumed to be in the same coordinate system
* and have the same attributes.
*/
public void merge (ModelMesh mesh)
{
TriangleBatch batch = (TriangleBatch)getBatch(0);
TriangleBatch mbatch = (TriangleBatch)mesh.getBatch(0);
int vcount = batch.getVertexCount();
batch.setVertexBuffer(concatenate(batch.getVertexBuffer(), mbatch.getVertexBuffer()));
batch.setNormalBuffer(concatenate(batch.getNormalBuffer(), mbatch.getNormalBuffer()));
if (batch.getColorBuffer() != null && mbatch.getColorBuffer() != null) {
batch.setColorBuffer(concatenate(batch.getColorBuffer(), mbatch.getColorBuffer()));
}
batch.setTextureBuffer(concatenate(
batch.getTextureBuffer(0), mbatch.getTextureBuffer(0)), 0);
for (int ii = 1, nn = getTextureCount(); ii < nn; ii++) {
batch.setTextureBuffer(batch.getTextureBuffer(0), ii);
}
IntBuffer ibuf = batch.getIndexBuffer(), mibuf = mbatch.getIndexBuffer();
IntBuffer nibuf = BufferUtils.createIntBuffer(ibuf.capacity() + mibuf.capacity());
ibuf.clear();
nibuf.put(ibuf);
mibuf.clear();
while (mibuf.hasRemaining()) {
nibuf.put(mibuf.get() + vcount);
}
nibuf.clear();
batch.setIndexBuffer(nibuf);
}
/** /**
* Adjusts the vertices and the transform of the mesh so that the mesh's * Adjusts the vertices and the transform of the mesh so that the mesh's
* position lies at the center of its bounding volume. * position lies at the center of its bounding volume.
@@ -819,30 +774,6 @@ public class ModelMesh extends TriMesh
new RenderState[RenderState.RS_MAX_STATE]; new RenderState[RenderState.RS_MAX_STATE];
} }
/** A wrapper around an array of attributes that uses {@link Arrays#deepHashCode} and
* {@link Arrays#deepEquals} for hashing/comparison. */
protected static class AttributeKey
{
public AttributeKey (Object... attrs)
{
_attrs = attrs;
}
@Override // documentation inherited
public int hashCode ()
{
return Arrays.deepHashCode(_attrs);
}
@Override // documentation inherited
public boolean equals (Object other)
{
return Arrays.deepEquals(_attrs, ((AttributeKey)other)._attrs);
}
protected Object[] _attrs;
}
/** The name of the texture specified in the model file, which acts as a /** The name of the texture specified in the model file, which acts as a
* property key and a default value. */ * property key and a default value. */
protected String _textureKey; protected String _textureKey;
@@ -41,6 +41,7 @@ import com.jme.util.export.InputCapsule;
import com.jme.util.export.OutputCapsule; import com.jme.util.export.OutputCapsule;
import com.threerings.jme.Log; import com.threerings.jme.Log;
import com.threerings.jme.util.JmeUtil;
/** /**
* A {@link Node} with a serialization mechanism tailored to stored models. * A {@link Node} with a serialization mechanism tailored to stored models.
@@ -55,7 +56,7 @@ public class ModelNode extends Node
{ {
super("node"); super("node");
} }
/** /**
* Standard constructor. * Standard constructor.
*/ */
@@ -63,14 +64,14 @@ public class ModelNode extends Node
{ {
super(name); super(name);
} }
@Override // documentation inherited @Override // documentation inherited
public int hashCode () public int hashCode ()
{ {
// hash on the name rather than the identity for consistent ordering // hash on the name rather than the identity for consistent ordering
return getName().hashCode(); return getName().hashCode();
} }
/** /**
* Recursively searches the scene graph rooted at this node for a * Recursively searches the scene graph rooted at this node for a
* node with the provided name. * node with the provided name.
@@ -90,7 +91,7 @@ public class ModelNode extends Node
} }
return null; return null;
} }
/** /**
* Returns a reference to the model space transform of the node. * Returns a reference to the model space transform of the node.
*/ */
@@ -98,7 +99,7 @@ public class ModelNode extends Node
{ {
return _modelTransform; return _modelTransform;
} }
@Override // documentation inherited @Override // documentation inherited
public void updateWorldData (float time) public void updateWorldData (float time)
{ {
@@ -108,7 +109,7 @@ public class ModelNode extends Node
super.updateWorldData(time); super.updateWorldData(time);
} }
} }
@Override // documentation inherited @Override // documentation inherited
public void updateWorldBound () public void updateWorldBound ()
{ {
@@ -117,22 +118,22 @@ public class ModelNode extends Node
super.updateWorldBound(); super.updateWorldBound();
} }
} }
@Override // documentation inherited @Override // documentation inherited
public void updateWorldVectors () public void updateWorldVectors ()
{ {
super.updateWorldVectors(); super.updateWorldVectors();
if (parent instanceof ModelNode) { if (parent instanceof ModelNode) {
setTransform(getLocalTranslation(), getLocalRotation(), JmeUtil.setTransform(getLocalTranslation(), getLocalRotation(),
getLocalScale(), _localTransform); getLocalScale(), _localTransform);
((ModelNode)parent).getModelTransform().mult(_localTransform, ((ModelNode)parent).getModelTransform().mult(_localTransform,
_modelTransform); _modelTransform);
} else { } else {
_modelTransform.loadIdentity(); _modelTransform.loadIdentity();
} }
} }
// documentation inherited from interface ModelSpatial // documentation inherited from interface ModelSpatial
public Spatial putClone (Spatial store, Model.CloneCreator properties) public Spatial putClone (Spatial store, Model.CloneCreator properties)
{ {
@@ -178,7 +179,7 @@ public class ModelNode extends Node
mstore._hasVisibleDescendants = _hasVisibleDescendants; mstore._hasVisibleDescendants = _hasVisibleDescendants;
return mstore; return mstore;
} }
@Override // documentation inherited @Override // documentation inherited
public void read (JMEImporter im) public void read (JMEImporter im)
throws IOException throws IOException
@@ -198,7 +199,7 @@ public class ModelNode extends Node
} }
} }
} }
@Override // documentation inherited @Override // documentation inherited
public void write (JMEExporter ex) public void write (JMEExporter ex)
throws IOException throws IOException
@@ -210,7 +211,7 @@ public class ModelNode extends Node
capsule.write(getLocalScale(), "localScale", null); capsule.write(getLocalScale(), "localScale", null);
capsule.writeSavableArrayList(getChildren(), "children", null); capsule.writeSavableArrayList(getChildren(), "children", null);
} }
// documentation inherited from interface ModelSpatial // documentation inherited from interface ModelSpatial
public void expandModelBounds () public void expandModelBounds ()
{ {
@@ -221,7 +222,7 @@ public class ModelNode extends Node
} }
} }
} }
// documentation inherited from interface ModelSpatial // documentation inherited from interface ModelSpatial
public void setReferenceTransforms () public void setReferenceTransforms ()
{ {
@@ -233,7 +234,7 @@ public class ModelNode extends Node
} }
} }
} }
// documentation inherited from interface ModelSpatial // documentation inherited from interface ModelSpatial
public void lockStaticMeshes ( public void lockStaticMeshes (
Renderer renderer, boolean useVBOs, boolean useDisplayLists) Renderer renderer, boolean useVBOs, boolean useDisplayLists)
@@ -246,7 +247,7 @@ public class ModelNode extends Node
} }
} }
} }
// documentation inherited from interface ModelSpatial // documentation inherited from interface ModelSpatial
public void resolveTextures (TextureProvider tprov) public void resolveTextures (TextureProvider tprov)
{ {
@@ -257,7 +258,7 @@ public class ModelNode extends Node
} }
} }
} }
// documentation inherited from interface ModelSpatial // documentation inherited from interface ModelSpatial
public void storeMeshFrame (int frameId, boolean blend) public void storeMeshFrame (int frameId, boolean blend)
{ {
@@ -268,7 +269,7 @@ public class ModelNode extends Node
} }
} }
} }
// documentation inherited from interface ModelSpatial // documentation inherited from interface ModelSpatial
public void setMeshFrame (int frameId) public void setMeshFrame (int frameId)
{ {
@@ -279,7 +280,7 @@ public class ModelNode extends Node
} }
} }
} }
// documentation inherited from interface ModelSpatial // documentation inherited from interface ModelSpatial
public void blendMeshFrames (int frameId1, int frameId2, float alpha) public void blendMeshFrames (int frameId1, int frameId2, float alpha)
{ {
@@ -291,7 +292,7 @@ public class ModelNode extends Node
} }
} }
} }
/** /**
* Sets whether this node should be culled even if it has mesh * Sets whether this node should be culled even if it has mesh
* descendants. * descendants.
@@ -300,7 +301,7 @@ public class ModelNode extends Node
{ {
_forceCull = force; _forceCull = force;
} }
/** /**
* Checks whether this node should be culled even if it has mesh * Checks whether this node should be culled even if it has mesh
* descendants. * descendants.
@@ -309,7 +310,7 @@ public class ModelNode extends Node
{ {
return _forceCull; return _forceCull;
} }
/** /**
* Sets the cull state of any nodes that do not contain geometric * Sets the cull state of any nodes that do not contain geometric
* descendants to {@link CULL_ALWAYS} so that they don't waste * descendants to {@link CULL_ALWAYS} so that they don't waste
@@ -330,7 +331,7 @@ public class ModelNode extends Node
updateCullMode(); updateCullMode();
return _hasVisibleDescendants; return _hasVisibleDescendants;
} }
/** /**
* Locks the transforms and bounds of this instance with the assumption * Locks the transforms and bounds of this instance with the assumption
* that the position will never change. * that the position will never change.
@@ -344,14 +345,14 @@ public class ModelNode extends Node
{ {
updateWorldVectors(); updateWorldVectors();
lockedMode |= LOCKED_TRANSFORMS; lockedMode |= LOCKED_TRANSFORMS;
boolean containsTargets = false; boolean containsTargets = false;
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) { for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
Spatial child = getChild(ii); Spatial child = getChild(ii);
if (targets.contains(child) || (child instanceof ModelNode && if (targets.contains(child) || (child instanceof ModelNode &&
((ModelNode)child).lockInstance(targets))) { ((ModelNode)child).lockInstance(targets))) {
containsTargets = true; containsTargets = true;
} else if (child instanceof ModelMesh) { } else if (child instanceof ModelMesh) {
((ModelMesh)child).lockInstance(); ((ModelMesh)child).lockInstance();
} }
@@ -364,7 +365,7 @@ public class ModelNode extends Node
return false; return false;
} }
} }
/** /**
* Recursively culls all model nodes under this one in preparation for * Recursively culls all model nodes under this one in preparation for
* activating the ones listed in an animation. * activating the ones listed in an animation.
@@ -379,7 +380,7 @@ public class ModelNode extends Node
} }
} }
} }
/** /**
* Makes this node visible if and only if it has visible descendants and * Makes this node visible if and only if it has visible descendants and
* has not been specifically culled. * has not been specifically culled.
@@ -389,42 +390,16 @@ public class ModelNode extends Node
setCullMode((_hasVisibleDescendants && !_forceCull) ? setCullMode((_hasVisibleDescendants && !_forceCull) ?
CULL_INHERIT : CULL_ALWAYS); CULL_INHERIT : CULL_ALWAYS);
} }
/**
* Sets a matrix to the transform defined by the given translation,
* rotation, and scale values.
*/
protected static Matrix4f setTransform (
Vector3f translation, Quaternion rotation, Vector3f scale,
Matrix4f result)
{
result.setRotationQuaternion(rotation);
result.setTranslation(translation);
result.m00 *= scale.x;
result.m01 *= scale.y;
result.m02 *= scale.z;
result.m10 *= scale.x;
result.m11 *= scale.y;
result.m12 *= scale.z;
result.m20 *= scale.x;
result.m21 *= scale.y;
result.m22 *= scale.z;
return result;
}
/** The node's transform in local and model space. */ /** The node's transform in local and model space. */
protected Matrix4f _localTransform = new Matrix4f(), protected Matrix4f _localTransform = new Matrix4f(),
_modelTransform = new Matrix4f(); _modelTransform = new Matrix4f();
/** Whether or not this node has mesh descendants. */ /** Whether or not this node has mesh descendants. */
protected boolean _hasVisibleDescendants; protected boolean _hasVisibleDescendants;
/** If true, cull the node even if it has mesh descendants. */ /** If true, cull the node even if it has mesh descendants. */
protected boolean _forceCull; protected boolean _forceCull;
private static final long serialVersionUID = 1; private static final long serialVersionUID = 1;
} }
+51 -50
View File
@@ -54,6 +54,7 @@ import com.samskivert.util.ArrayUtil;
import com.samskivert.util.HashIntMap; import com.samskivert.util.HashIntMap;
import com.threerings.jme.Log; import com.threerings.jme.Log;
import com.threerings.jme.util.JmeUtil;
/** /**
* A triangle mesh that deforms according to a bone hierarchy. * A triangle mesh that deforms according to a bone hierarchy.
@@ -67,10 +68,10 @@ public class SkinMesh extends ModelMesh
{ {
/** The number of vertices in this weight group. */ /** The number of vertices in this weight group. */
public int vertexCount; public int vertexCount;
/** The bones influencing this group. */ /** The bones influencing this group. */
public Bone[] bones; public Bone[] bones;
/** The array of interleaved weights (of length <code>vertexCount * /** The array of interleaved weights (of length <code>vertexCount *
* boneIndices.length</code>): weights for first vertex, weights for * boneIndices.length</code>): weights for first vertex, weights for
* second, etc. */ * second, etc. */
@@ -92,13 +93,13 @@ public class SkinMesh extends ModelMesh
wgroup.weights = weights; wgroup.weights = weights;
return wgroup; return wgroup;
} }
// documentation inherited // documentation inherited
public Class getClassTag () public Class getClassTag ()
{ {
return getClass(); return getClass();
} }
// documentation inherited // documentation inherited
public void read (JMEImporter im) public void read (JMEImporter im)
throws IOException throws IOException
@@ -119,7 +120,7 @@ public class SkinMesh extends ModelMesh
capsule.write(bones, "bones", null); capsule.write(bones, "bones", null);
capsule.write(weights, "weights", null); capsule.write(weights, "weights", null);
} }
private static final long serialVersionUID = 1; private static final long serialVersionUID = 1;
} }
@@ -129,24 +130,24 @@ public class SkinMesh extends ModelMesh
{ {
/** The node that defines the bone's position. */ /** The node that defines the bone's position. */
public ModelNode node; public ModelNode node;
/** The inverse of the bone's model space reference transform. */ /** The inverse of the bone's model space reference transform. */
public transient Matrix4f invRefTransform; public transient Matrix4f invRefTransform;
/** The bone's current transform in model space. */ /** The bone's current transform in model space. */
public transient Matrix4f transform; public transient Matrix4f transform;
public Bone (ModelNode node) public Bone (ModelNode node)
{ {
this(); this();
this.node = node; this.node = node;
} }
public Bone () public Bone ()
{ {
transform = new Matrix4f(); transform = new Matrix4f();
} }
/** /**
* Rebinds this bone for a prototype instance. * Rebinds this bone for a prototype instance.
* *
@@ -159,13 +160,13 @@ public class SkinMesh extends ModelMesh
bone.transform = new Matrix4f(); bone.transform = new Matrix4f();
return bone; return bone;
} }
// documentation inherited // documentation inherited
public Class getClassTag () public Class getClassTag ()
{ {
return getClass(); return getClass();
} }
// documentation inherited // documentation inherited
public void read (JMEImporter im) public void read (JMEImporter im)
throws IOException throws IOException
@@ -181,17 +182,17 @@ public class SkinMesh extends ModelMesh
OutputCapsule capsule = ex.getCapsule(this); OutputCapsule capsule = ex.getCapsule(this);
capsule.write(node, "node", null); capsule.write(node, "node", null);
} }
private static final long serialVersionUID = 1; private static final long serialVersionUID = 1;
} }
/** /**
* No-arg constructor for deserialization. * No-arg constructor for deserialization.
*/ */
public SkinMesh () public SkinMesh ()
{ {
} }
/** /**
* Creates an empty mesh. * Creates an empty mesh.
*/ */
@@ -199,7 +200,7 @@ public class SkinMesh extends ModelMesh
{ {
super(name); super(name);
} }
/** /**
* Sets the array of weight groups that determine how bones affect * Sets the array of weight groups that determine how bones affect
* each vertex. * each vertex.
@@ -207,7 +208,7 @@ public class SkinMesh extends ModelMesh
public void setWeightGroups (WeightGroup[] weightGroups) public void setWeightGroups (WeightGroup[] weightGroups)
{ {
_weightGroups = weightGroups; _weightGroups = weightGroups;
// compile a list of all referenced bones // compile a list of all referenced bones
HashSet<Bone> bones = new HashSet<Bone>(); HashSet<Bone> bones = new HashSet<Bone>();
for (WeightGroup group : weightGroups) { for (WeightGroup group : weightGroups) {
@@ -215,18 +216,18 @@ public class SkinMesh extends ModelMesh
} }
_bones = bones.toArray(new Bone[bones.size()]); _bones = bones.toArray(new Bone[bones.size()]);
} }
@Override // documentation inherited @Override // documentation inherited
public void reconstruct ( public void reconstruct (
FloatBuffer vertices, FloatBuffer normals, FloatBuffer colors, FloatBuffer vertices, FloatBuffer normals, FloatBuffer colors,
FloatBuffer textures, IntBuffer indices) FloatBuffer textures, IntBuffer indices)
{ {
super.reconstruct(vertices, normals, colors, textures, indices); super.reconstruct(vertices, normals, colors, textures, indices);
// initialize the quantized frame table // initialize the quantized frame table
_frames = new HashIntMap<Object>(); _frames = new HashIntMap<Object>();
} }
@Override // documentation inherited @Override // documentation inherited
public Spatial putClone (Spatial store, Model.CloneCreator properties) public Spatial putClone (Spatial store, Model.CloneCreator properties)
{ {
@@ -264,7 +265,7 @@ public class SkinMesh extends ModelMesh
mstore._nbuf = new float[_nbuf.length]; mstore._nbuf = new float[_nbuf.length];
return mstore; return mstore;
} }
@Override // documentation inherited @Override // documentation inherited
public void read (JMEImporter im) public void read (JMEImporter im)
throws IOException throws IOException
@@ -274,7 +275,7 @@ public class SkinMesh extends ModelMesh
setWeightGroups(ArrayUtil.copy(capsule.readSavableArray( setWeightGroups(ArrayUtil.copy(capsule.readSavableArray(
"weightGroups", null), new WeightGroup[0])); "weightGroups", null), new WeightGroup[0]));
} }
@Override // documentation inherited @Override // documentation inherited
public void write (JMEExporter ex) public void write (JMEExporter ex)
throws IOException throws IOException
@@ -283,7 +284,7 @@ public class SkinMesh extends ModelMesh
OutputCapsule capsule = ex.getCapsule(this); OutputCapsule capsule = ex.getCapsule(this);
capsule.write(_weightGroups, "weightGroups", null); capsule.write(_weightGroups, "weightGroups", null);
} }
@Override // documentation inherited @Override // documentation inherited
public void expandModelBounds () public void expandModelBounds ()
{ {
@@ -292,14 +293,14 @@ public class SkinMesh extends ModelMesh
updateModelBound(); updateModelBound();
getBatch(0).getModelBound().mergeLocal(obound); getBatch(0).getModelBound().mergeLocal(obound);
} }
@Override // documentation inherited @Override // documentation inherited
public void setReferenceTransforms () public void setReferenceTransforms ()
{ {
_invRefTransform = new Matrix4f(); _invRefTransform = new Matrix4f();
if (parent instanceof ModelNode) { if (parent instanceof ModelNode) {
Matrix4f transform = new Matrix4f(); Matrix4f transform = new Matrix4f();
ModelNode.setTransform(getLocalTranslation(), getLocalRotation(), JmeUtil.setTransform(getLocalTranslation(), getLocalRotation(),
getLocalScale(), transform); getLocalScale(), transform);
((ModelNode)parent).getModelTransform().mult(transform, ((ModelNode)parent).getModelTransform().mult(transform,
_invRefTransform); _invRefTransform);
@@ -310,7 +311,7 @@ public class SkinMesh extends ModelMesh
_invRefTransform.mult(bone.node.getModelTransform()).invert(); _invRefTransform.mult(bone.node.getModelTransform()).invert();
} }
} }
@Override // documentation inherited @Override // documentation inherited
public void lockStaticMeshes ( public void lockStaticMeshes (
Renderer renderer, boolean useVBOs, boolean useDisplayLists) Renderer renderer, boolean useVBOs, boolean useDisplayLists)
@@ -325,14 +326,14 @@ public class SkinMesh extends ModelMesh
} }
_useDisplayLists = useDisplayLists && !_translucent; _useDisplayLists = useDisplayLists && !_translucent;
} }
@Override // documentation inherited @Override // documentation inherited
public void storeMeshFrame (int frameId, boolean blend) public void storeMeshFrame (int frameId, boolean blend)
{ {
_storeFrameId = frameId; _storeFrameId = frameId;
_storeBlend = blend; _storeBlend = blend;
} }
@Override // documentation inherited @Override // documentation inherited
public void setMeshFrame (int frameId) public void setMeshFrame (int frameId)
{ {
@@ -346,7 +347,7 @@ public class SkinMesh extends ModelMesh
getBatch(0).updateRenderState(); getBatch(0).updateRenderState();
} }
} }
@Override // documentation inherited @Override // documentation inherited
public void blendMeshFrames (int frameId1, int frameId2, float alpha) public void blendMeshFrames (int frameId1, int frameId2, float alpha)
{ {
@@ -359,7 +360,7 @@ public class SkinMesh extends ModelMesh
nbuf.rewind(); nbuf.rewind();
nbuf.put(_nbuf); nbuf.put(_nbuf);
} }
@Override // documentation inherited @Override // documentation inherited
public void updateWorldData (float time) public void updateWorldData (float time)
{ {
@@ -373,7 +374,7 @@ public class SkinMesh extends ModelMesh
bone.transform); bone.transform);
bone.transform.multLocal(bone.invRefTransform); bone.transform.multLocal(bone.invRefTransform);
} }
// deform the mesh according to the positions of the bones (this code // deform the mesh according to the positions of the bones (this code
// is ugly as sin because it's optimized at a low level) // is ugly as sin because it's optimized at a low level)
Bone[] bones; Bone[] bones;
@@ -397,11 +398,11 @@ public class SkinMesh extends ModelMesh
for (kk = 0; kk < bones.length; kk++) { for (kk = 0; kk < bones.length; kk++) {
m = bones[kk].transform; m = bones[kk].transform;
weight = weights[ww++]; weight = weights[ww++];
vx += (ovx*m.m00 + ovy*m.m01 + ovz*m.m02 + m.m03) * weight; vx += (ovx*m.m00 + ovy*m.m01 + ovz*m.m02 + m.m03) * weight;
vy += (ovx*m.m10 + ovy*m.m11 + ovz*m.m12 + m.m13) * weight; vy += (ovx*m.m10 + ovy*m.m11 + ovz*m.m12 + m.m13) * weight;
vz += (ovx*m.m20 + ovy*m.m21 + ovz*m.m22 + m.m23) * weight; vz += (ovx*m.m20 + ovy*m.m21 + ovz*m.m22 + m.m23) * weight;
nx += (onx*m.m00 + ony*m.m01 + onz*m.m02) * weight; nx += (onx*m.m00 + ony*m.m01 + onz*m.m02) * weight;
ny += (onx*m.m10 + ony*m.m11 + onz*m.m12) * weight; ny += (onx*m.m10 + ony*m.m11 + onz*m.m12) * weight;
nz += (onx*m.m20 + ony*m.m21 + onz*m.m22) * weight; nz += (onx*m.m20 + ony*m.m21 + onz*m.m22) * weight;
@@ -414,7 +415,7 @@ public class SkinMesh extends ModelMesh
_nbuf[bidx++] = nz; _nbuf[bidx++] = nz;
} }
} }
// if skinning in real time, copy the data from arrays to buffers; // if skinning in real time, copy the data from arrays to buffers;
// otherwise, store the mesh as an animation frame // otherwise, store the mesh as an animation frame
if (_storeFrameId == 0) { if (_storeFrameId == 0) {
@@ -428,7 +429,7 @@ public class SkinMesh extends ModelMesh
_storeFrameId = -1; _storeFrameId = -1;
} }
} }
/** /**
* Stores the current frame data for later use. * Stores the current frame data for later use.
*/ */
@@ -465,12 +466,12 @@ public class SkinMesh extends ModelMesh
_frames.put(_storeFrameId, tbatch); _frames.put(_storeFrameId, tbatch);
} }
} }
@Override // documentation inherited @Override // documentation inherited
protected void storeOriginalBuffers () protected void storeOriginalBuffers ()
{ {
super.storeOriginalBuffers(); super.storeOriginalBuffers();
FloatBuffer vbuf = getVertexBuffer(0), nbuf = getNormalBuffer(0); FloatBuffer vbuf = getVertexBuffer(0), nbuf = getNormalBuffer(0);
vbuf.rewind(); vbuf.rewind();
nbuf.rewind(); nbuf.rewind();
@@ -479,19 +480,19 @@ public class SkinMesh extends ModelMesh
_vbuf = new float[_ovbuf.length]; _vbuf = new float[_ovbuf.length];
_nbuf = new float[_onbuf.length]; _nbuf = new float[_onbuf.length];
} }
/** A stored frame used for linear blending. */ /** A stored frame used for linear blending. */
protected static class BlendFrame protected static class BlendFrame
{ {
/** The skinned vertex and normal values. */ /** The skinned vertex and normal values. */
public float[] vbuf, nbuf; public float[] vbuf, nbuf;
public BlendFrame (float[] vbuf, float[] nbuf) public BlendFrame (float[] vbuf, float[] nbuf)
{ {
this.vbuf = vbuf; this.vbuf = vbuf;
this.nbuf = nbuf; this.nbuf = nbuf;
} }
public void blend ( public void blend (
BlendFrame next, float alpha, float[] rvbuf, float[] rnbuf) BlendFrame next, float alpha, float[] rvbuf, float[] rnbuf)
{ {
@@ -502,26 +503,26 @@ public class SkinMesh extends ModelMesh
rnbuf[ii] = nbuf[ii] * ialpha + nnbuf[ii] * alpha; rnbuf[ii] = nbuf[ii] * ialpha + nnbuf[ii] * alpha;
} }
} }
} }
/** Pre-skinned {@link TriangleBatch}es or {@link BlendFrame}s shared /** Pre-skinned {@link TriangleBatch}es or {@link BlendFrame}s shared
* between all instances corresponding to frame ids from * between all instances corresponding to frame ids from
* {@link #storeAnimationFrame}. */ * {@link #storeAnimationFrame}. */
protected HashIntMap<Object> _frames; protected HashIntMap<Object> _frames;
/** Whether or to use display lists if VBOs are unavailable for quantized /** Whether or to use display lists if VBOs are unavailable for quantized
* meshes. */ * meshes. */
protected boolean _useDisplayLists; protected boolean _useDisplayLists;
/** The inverse of the model space reference transform. */ /** The inverse of the model space reference transform. */
protected Matrix4f _invRefTransform; protected Matrix4f _invRefTransform;
/** The groups of vertices influenced by different sets of bones. */ /** The groups of vertices influenced by different sets of bones. */
protected WeightGroup[] _weightGroups; protected WeightGroup[] _weightGroups;
/** The bones referenced by the weight groups. */ /** The bones referenced by the weight groups. */
protected Bone[] _bones; protected Bone[] _bones;
/** The original (undeformed) vertex and normal buffers and the deformed /** The original (undeformed) vertex and normal buffers and the deformed
* versions. */ * versions. */
protected float[] _onbuf, _ovbuf, _nbuf; protected float[] _onbuf, _ovbuf, _nbuf;
@@ -530,12 +531,12 @@ public class SkinMesh extends ModelMesh
* and skin the mesh as normal. If -1, a frame has been stored and thus * and skin the mesh as normal. If -1, a frame has been stored and thus
* skinning should only take place when further frames are requested. */ * skinning should only take place when further frames are requested. */
protected int _storeFrameId; protected int _storeFrameId;
/** Whether or not the stored frame id will be used for blending. */ /** Whether or not the stored frame id will be used for blending. */
protected boolean _storeBlend; protected boolean _storeBlend;
/** A dummy mesh that simply hold transformation values. */ /** A dummy mesh that simply hold transformation values. */
protected static final TriMesh DUMMY_MESH = new TriMesh(); protected static final TriMesh DUMMY_MESH = new TriMesh();
private static final long serialVersionUID = 1; private static final long serialVersionUID = 1;
} }
@@ -26,15 +26,19 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Properties; import java.util.Properties;
import com.jme.math.Matrix4f;
import com.jme.math.Quaternion; import com.jme.math.Quaternion;
import com.jme.math.Vector3f; import com.jme.math.Vector3f;
import com.jme.scene.Controller; import com.jme.scene.Controller;
import com.jme.scene.Node;
import com.jme.scene.Spatial; import com.jme.scene.Spatial;
import com.threerings.jme.Log; import com.threerings.jme.Log;
import com.threerings.jme.model.Model; import com.threerings.jme.model.Model;
import com.threerings.jme.util.JmeUtil; import com.threerings.jme.util.JmeUtil;
import com.threerings.jme.tools.ModelDef.TransformNode;
/** /**
* A basic representation for keyframe animations. * A basic representation for keyframe animations.
*/ */
@@ -42,35 +46,39 @@ public class AnimationDef
{ {
/** The rate of the animation in frames per second. */ /** The rate of the animation in frames per second. */
public int frameRate; public int frameRate;
/** A single frame of the animation. */ /** A single frame of the animation. */
public static class FrameDef public static class FrameDef
{ {
/** Transform for affected nodes. */ /** Transform for affected nodes. */
public ArrayList<TransformDef> transforms = public ArrayList<TransformDef> transforms =
new ArrayList<TransformDef>(); new ArrayList<TransformDef>();
public void addTransform (TransformDef transform) public void addTransform (TransformDef transform)
{ {
transforms.add(transform); transforms.add(transform);
} }
/** Adds all transform targets in this frame to the supplied set. */ /** Adds all transform targets in this frame to the supplied sets. */
public void addTransformTargets ( public void addTransformTargets (
HashMap<String, Spatial> nodes, HashSet<Spatial> targets) HashMap<String, Spatial> nodes, HashMap<String, TransformNode> tnodes,
HashSet<Spatial> staticTargets, HashSet<Spatial> transformTargets)
{ {
for (int ii = 0, nn = transforms.size(); ii < nn; ii++) { for (int ii = 0, nn = transforms.size(); ii < nn; ii++) {
String name = transforms.get(ii).name; String name = transforms.get(ii).name;
Spatial target = nodes.get(name); Spatial target = nodes.get(name);
if (target != null) { if (target == null) {
targets.add(target); continue;
}
TransformNode tnode = tnodes.get(name);
if (tnode.baseLocalTransform != null) {
staticTargets.add(target);
} else { } else {
Log.debug("Missing animation target [name=" + name + transformTargets.add(target);
"].");
} }
} }
} }
/** Returns the array of transforms for this frame. */ /** Returns the array of transforms for this frame. */
public Model.Transform[] getTransforms (Spatial[] targets) public Model.Transform[] getTransforms (Spatial[] targets)
{ {
@@ -81,7 +89,7 @@ public class AnimationDef
} }
return mtransforms; return mtransforms;
} }
/** Returns the transform for the supplied target. */ /** Returns the transform for the supplied target. */
protected Model.Transform getTransform (Spatial target) protected Model.Transform getTransform (Spatial target)
{ {
@@ -95,18 +103,18 @@ public class AnimationDef
return null; return null;
} }
} }
/** A transform for a single node. */ /** A transform for a single node. */
public static class TransformDef public static class TransformDef
{ {
/** The name of the affected node. */ /** The name of the affected node. */
public String name; public String name;
/** The transformation parameters. */ /** The transformation parameters. */
public float[] translation; public float[] translation;
public float[] rotation; public float[] rotation;
public float[] scale; public float[] scale;
/** Returns the live transform object. */ /** Returns the live transform object. */
public Model.Transform getTransform () public Model.Transform getTransform ()
{ {
@@ -117,15 +125,36 @@ public class AnimationDef
new Vector3f(scale[0], scale[1], scale[2])); new Vector3f(scale[0], scale[1], scale[2]));
} }
} }
/** The individual frames of the animation. */ /** The individual frames of the animation. */
public ArrayList<FrameDef> frames = new ArrayList<FrameDef>(); public ArrayList<FrameDef> frames = new ArrayList<FrameDef>();
public void addFrame (FrameDef frame) public void addFrame (FrameDef frame)
{ {
frames.add(frame); frames.add(frame);
} }
/**
* Runs through each frame of the animation, updating the transforms of the preprocessing
* node to keep track of which transforms diverge from their original states.
*/
public void filterTransforms (Node root, HashMap<String, TransformNode> nodes)
{
for (FrameDef frame : frames) {
for (TransformDef transform : frame.transforms) {
TransformNode node = nodes.get(transform.name);
if (node != null) {
node.setLocalTransform(
transform.translation, transform.rotation, transform.scale);
}
}
root.updateGeometricState(0f, true);
for (TransformNode node : nodes.values()) {
node.cullDivergentTransforms();
}
}
}
/** /**
* Creates the "live" animation object that will be serialized with the * Creates the "live" animation object that will be serialized with the
* object. * object.
@@ -134,27 +163,30 @@ public class AnimationDef
* @param nodes the nodes in the model, mapped by name * @param nodes the nodes in the model, mapped by name
*/ */
public Model.Animation createAnimation ( public Model.Animation createAnimation (
Properties props, HashMap<String, Spatial> nodes) Properties props, HashMap<String, Spatial> nodes, HashMap<String, TransformNode> tnodes)
{ {
// find all affected nodes // find all affected nodes
HashSet<Spatial> targets = new HashSet<Spatial>(); HashSet<Spatial> staticTargets = new HashSet<Spatial>(),
transformTargets = new HashSet<Spatial>();
for (int ii = 0, nn = frames.size(); ii < nn; ii++) { for (int ii = 0, nn = frames.size(); ii < nn; ii++) {
frames.get(ii).addTransformTargets(nodes, targets); frames.get(ii).addTransformTargets(nodes, tnodes, staticTargets, transformTargets);
} }
// create and configure the animation // create and configure the animation
Model.Animation anim = new Model.Animation(); Model.Animation anim = new Model.Animation();
anim.frameRate = frameRate; anim.frameRate = frameRate;
anim.repeatType = JmeUtil.parseRepeatType(props.getProperty("repeat_type"), anim.repeatType = JmeUtil.parseRepeatType(props.getProperty("repeat_type"),
Controller.RT_CLAMP); Controller.RT_CLAMP);
// collect all transforms // collect all transforms
anim.transformTargets = targets.toArray(new Spatial[targets.size()]); anim.staticTargets = staticTargets.toArray(new Spatial[staticTargets.size()]);
anim.transforms = new Model.Transform[frames.size()][targets.size()]; anim.transformTargets = transformTargets.toArray(new Spatial[transformTargets.size()]);
anim.transforms = new Model.Transform[frames.size()][transformTargets.size()];
for (int ii = 0; ii < anim.transforms.length; ii++) { for (int ii = 0; ii < anim.transforms.length; ii++) {
anim.transforms[ii] = anim.transforms[ii] =
frames.get(ii).getTransforms(anim.transformTargets); frames.get(ii).getTransforms(anim.transformTargets);
} }
return anim; return anim;
} }
} }
@@ -32,6 +32,7 @@ import java.util.HashMap;
import java.util.Properties; import java.util.Properties;
import java.util.logging.Level; import java.util.logging.Level;
import com.jme.scene.Node;
import com.jme.scene.Spatial; import com.jme.scene.Spatial;
import com.jme.util.LoggingSystem; import com.jme.util.LoggingSystem;
import com.jmex.model.XMLparser.Converters.DummyDisplaySystem; import com.jmex.model.XMLparser.Converters.DummyDisplaySystem;
@@ -43,6 +44,7 @@ import com.threerings.jme.model.Model;
import com.threerings.jme.model.ModelMesh; import com.threerings.jme.model.ModelMesh;
import com.threerings.jme.model.ModelNode; import com.threerings.jme.model.ModelNode;
import com.threerings.jme.model.SkinMesh; import com.threerings.jme.model.SkinMesh;
import com.threerings.jme.tools.ModelDef.TransformNode;
import com.threerings.jme.tools.xml.AnimationParser; import com.threerings.jme.tools.xml.AnimationParser;
import com.threerings.jme.tools.xml.ModelParser; import com.threerings.jme.tools.xml.ModelParser;
@@ -106,18 +108,32 @@ public class CompileModel
System.out.println("Compiling to " + target + "..."); System.out.println("Compiling to " + target + "...");
// load the model content // parse the model and animations
ModelDef mdef = _mparser.parseModel(content.toString()); ModelDef mdef = _mparser.parseModel(content.toString());
AnimationDef[] adefs = new AnimationDef[anims.length];
for (int ii = 0; ii < adefs.length; ii++) {
System.out.println(" Adding " + afiles[ii] + "...");
adefs[ii] = _aparser.parseAnimation(afiles[ii].toString());
}
// preprocess the model and animations to determine which nodes never move, which never
// move within an animation, and which never move with respect to others
HashMap<String, TransformNode> tnodes = new HashMap<String, TransformNode>();
Node troot = mdef.createTransformTree(props, tnodes);
for (AnimationDef adef : adefs) {
adef.filterTransforms(troot, tnodes);
}
mdef.mergeSpatials(tnodes);
// load the model content
HashMap<String, Spatial> nodes = new HashMap<String, Spatial>(); HashMap<String, Spatial> nodes = new HashMap<String, Spatial>();
Model model = mdef.createModel(props, nodes); Model model = mdef.createModel(props, nodes);
model.initPrototype(); model.initPrototype();
// load the animations, if any // load the animations, if any
for (int ii = 0; ii < anims.length; ii++) { for (int ii = 0; ii < anims.length; ii++) {
System.out.println(" Adding " + afiles[ii] + "..."); model.addAnimation(anims[ii], adefs[ii].createAnimation(
AnimationDef adef = _aparser.parseAnimation(afiles[ii].toString()); PropertiesUtil.getSubProperties(props, anims[ii]), nodes, tnodes));
model.addAnimation(anims[ii], adef.createAnimation(
PropertiesUtil.getSubProperties(props, anims[ii]), nodes));
} }
// write and return the model // write and return the model
+292 -58
View File
@@ -39,13 +39,18 @@ import java.util.Set;
import com.jme.bounding.BoundingBox; import com.jme.bounding.BoundingBox;
import com.jme.bounding.BoundingSphere; import com.jme.bounding.BoundingSphere;
import com.jme.math.FastMath; import com.jme.math.FastMath;
import com.jme.math.Matrix4f;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f; import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.scene.Spatial; import com.jme.scene.Spatial;
import com.jme.scene.batch.GeomBatch; import com.jme.scene.batch.GeomBatch;
import com.jme.util.geom.BufferUtils; import com.jme.util.geom.BufferUtils;
import com.samskivert.util.ObjectUtil;
import com.samskivert.util.PropertiesUtil; import com.samskivert.util.PropertiesUtil;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import com.samskivert.util.Tuple;
import com.threerings.jme.Log; import com.threerings.jme.Log;
import com.threerings.jme.model.Model; import com.threerings.jme.model.Model;
@@ -53,6 +58,7 @@ import com.threerings.jme.model.ModelController;
import com.threerings.jme.model.ModelMesh; import com.threerings.jme.model.ModelMesh;
import com.threerings.jme.model.ModelNode; import com.threerings.jme.model.ModelNode;
import com.threerings.jme.model.SkinMesh; import com.threerings.jme.model.SkinMesh;
import com.threerings.jme.util.JmeUtil;
import com.threerings.jme.util.SpatialVisitor; import com.threerings.jme.util.SpatialVisitor;
/** /**
@@ -75,6 +81,21 @@ public class ModelDef
public float[] rotation; public float[] rotation;
public float[] scale; public float[] scale;
/**
* Stores the names of all bones referenced by this spatial in the supplied set.
*/
public void getBoneNames (HashSet<String> bones)
{
// nothing by default
}
/** Checks whether it's possible (disregarding issues of transformation) to merge
* the specified spatial into this one. */
public abstract boolean canMerge (Properties props, SpatialDef other);
/** Merges another spatial into this one. */
public abstract void merge (SpatialDef other, Matrix4f xform);
/** Returns a JME node for this definition. */ /** Returns a JME node for this definition. */
public Spatial getSpatial (Properties props) public Spatial getSpatial (Properties props)
{ {
@@ -155,6 +176,42 @@ public class ModelDef
tcoords = tcoords || vertex.tcoords != null; tcoords = tcoords || vertex.tcoords != null;
} }
// documentation inherited
public boolean canMerge (Properties props, SpatialDef other)
{
if (getClass() != other.getClass()) {
return false; // require exact class match
}
TriMeshDef omesh = (TriMeshDef)other;
return solid == omesh.solid && transparent == omesh.transparent &&
ObjectUtil.equals(texture, omesh.texture) &&
PropertiesUtil.getSubProperties(props, name).equals(
PropertiesUtil.getSubProperties(props, omesh.name));
}
// documentation inherited
public void merge (SpatialDef other, Matrix4f xform)
{
TriMeshDef omesh = (TriMeshDef)other;
// prepend the inverse of the offset transformation
xform = getOffsetTransform().invertLocal().multLocal(xform);
// and append the other's offset
xform.multLocal(omesh.getOffsetTransform());
// extract the rotation to transform normals
Quaternion xrot = xform.toRotationQuat();
// transform the vertices and add them in
for (Vertex vertex : omesh.vertices) {
vertex.transform(xform, xrot);
}
for (int idx : omesh.indices) {
addVertex(omesh.vertices.get(idx));
}
}
// documentation inherited // documentation inherited
public Spatial createSpatial (Properties props) public Spatial createSpatial (Properties props)
{ {
@@ -167,6 +224,24 @@ public class ModelDef
return node; return node;
} }
/** Gets the matrix representing the offset transform. */
protected Matrix4f getOffsetTransform ()
{
Vector3f otrans = new Vector3f(), oscale = new Vector3f(1f, 1f, 1f);
Quaternion orot = new Quaternion();
if (offsetTranslation != null) {
otrans.set(offsetTranslation[0], offsetTranslation[1], offsetTranslation[2]);
}
if (offsetRotation != null) {
orot.set(offsetRotation[0], offsetRotation[1], offsetRotation[2],
offsetRotation[3]);
}
if (offsetScale != null) {
oscale.set(offsetScale[0], offsetScale[1], offsetScale[2]);
}
return JmeUtil.setTransform(otrans, orot, oscale, new Matrix4f());
}
/** Creates the mesh to attach to the node. */ /** Creates the mesh to attach to the node. */
protected ModelMesh createMesh () protected ModelMesh createMesh ()
{ {
@@ -229,6 +304,14 @@ public class ModelDef
/** A triangle mesh that deforms according to bone positions. */ /** A triangle mesh that deforms according to bone positions. */
public static class SkinMeshDef extends TriMeshDef public static class SkinMeshDef extends TriMeshDef
{ {
@Override // documentation inherited
public void getBoneNames (HashSet<String> bones)
{
for (Vertex vertex : vertices) {
bones.addAll(((SkinVertex)vertex).boneWeights.keySet());
}
}
@Override // documentation inherited @Override // documentation inherited
protected ModelMesh createMesh () protected ModelMesh createMesh ()
{ {
@@ -250,6 +333,7 @@ public class ModelDef
HashMap<String, SkinMesh.Bone> bones = HashMap<String, SkinMesh.Bone> bones =
new HashMap<String, SkinMesh.Bone>(); new HashMap<String, SkinMesh.Bone>();
int ii = 0; int ii = 0;
int mweights = 0, tweights = 0;
for (Map.Entry<Set<String>, WeightGroupDef> entry : for (Map.Entry<Set<String>, WeightGroupDef> entry :
_groups.entrySet()) { _groups.entrySet()) {
SkinMesh.WeightGroup wgroup = new SkinMesh.WeightGroup(); SkinMesh.WeightGroup wgroup = new SkinMesh.WeightGroup();
@@ -267,6 +351,8 @@ public class ModelDef
wgroup.bones[jj++] = bone; wgroup.bones[jj++] = bone;
} }
wgroup.weights = toArray(entry.getValue().weights); wgroup.weights = toArray(entry.getValue().weights);
tweights += wgroup.bones.length;
mweights = Math.max(wgroup.bones.length, mweights);
wgroups[ii++] = wgroup; wgroups[ii++] = wgroup;
} }
((SkinMesh)_mesh).setWeightGroups(wgroups); ((SkinMesh)_mesh).setWeightGroups(wgroups);
@@ -315,6 +401,18 @@ public class ModelDef
/** A generic node. */ /** A generic node. */
public static class NodeDef extends SpatialDef public static class NodeDef extends SpatialDef
{ {
// documentation inherited
public boolean canMerge (Properties props, SpatialDef other)
{
return false;
}
// documentation inherited
public void merge (SpatialDef other, Matrix4f xform)
{
throw new UnsupportedOperationException();
}
// documentation inherited // documentation inherited
public Spatial createSpatial (Properties props) public Spatial createSpatial (Properties props)
{ {
@@ -329,6 +427,21 @@ public class ModelDef
public float[] normal; public float[] normal;
public float[] tcoords; public float[] tcoords;
public void transform (Matrix4f xform, Quaternion xrot)
{
Vector3f xvec = new Vector3f(location[0], location[1], location[2]);
xform.mult(xvec, xvec);
location[0] = xvec.x;
location[1] = xvec.y;
location[2] = xvec.z;
xvec.set(normal[0], normal[1], normal[2]);
xrot.mult(xvec, xvec);
normal[0] = xvec.x;
normal[1] = xvec.y;
normal[2] = xvec.z;
}
public void setInBuffers ( public void setInBuffers (
FloatBuffer vbuf, FloatBuffer nbuf, FloatBuffer tbuf) FloatBuffer vbuf, FloatBuffer nbuf, FloatBuffer tbuf)
{ {
@@ -418,6 +531,99 @@ public class ModelDef
public ArrayList<Float> weights = new ArrayList<Float>(); public ArrayList<Float> weights = new ArrayList<Float>();
} }
/** Contains the transform of a node for preprocessing. */
public static class TransformNode extends Node
{
/** The source definition. */
public SpatialDef spatial;
/** If true, this node is referenced by name (as a bone or parent) and cannot be merged
* into another. */
public boolean referenced;
/** If true, this node is a controller target; nodes beneath it can only be merged with
* other descendants. */
public boolean controlled;
/** The node's current local transform. */
public Matrix4f localTransform = new Matrix4f();
/** The node's current world space transform. */
public Matrix4f worldTransform = new Matrix4f();
/** The node's local transform in the original model, or <code>null</code> if the node's
* transform has diverged from the original. */
public Matrix4f baseLocalTransform;
/** The relative transforms between this and all other loosely compatible nodes not yet
* eliminated. As soon as the relative transform diverges in the course of preprocessing
* an animation, the node/transform pair is removed from the list. */
public ArrayList<Tuple<TransformNode, Matrix4f>> relativeTransforms;
public TransformNode (SpatialDef spatial)
{
super(spatial.name);
this.spatial = spatial;
setLocalTransform(spatial.translation, spatial.rotation, spatial.scale);
}
public void setLocalTransform (float[] translation, float[] rotation, float[] scale)
{
getLocalTranslation().set(translation[0], translation[1], translation[2]);
getLocalRotation().set(rotation[0], rotation[1], rotation[2], rotation[3]);
getLocalScale().set(scale[0], scale[1], scale[2]);
JmeUtil.setTransform(
getLocalTranslation(), getLocalRotation(), getLocalScale(), localTransform);
}
@Override // documentation inherited
public void updateWorldVectors ()
{
super.updateWorldVectors();
JmeUtil.setTransform(
getWorldTranslation(), getWorldRotation(), getWorldScale(), worldTransform);
}
public boolean canMerge (Properties props, TransformNode onode)
{
// nodes must have same controlled ancestor
return !onode.referenced && spatial.canMerge(props, onode.spatial) &&
getControlledAncestor() == onode.getControlledAncestor();
}
protected Node getControlledAncestor ()
{
Node ref = this;
while (ref instanceof TransformNode && !((TransformNode)ref).controlled) {
ref = ref.getParent();
}
return ref;
}
public void cullDivergentTransforms ()
{
if (baseLocalTransform != null && !epsilonEquals(localTransform, baseLocalTransform)) {
baseLocalTransform = null;
}
for (Iterator<Tuple<TransformNode, Matrix4f>> it = relativeTransforms.iterator();
it.hasNext(); ) {
Tuple<TransformNode, Matrix4f> tuple = it.next();
if (!epsilonEquals(getRelativeTransform(tuple.left), tuple.right)) {
it.remove();
}
}
}
public Matrix4f getRelativeTransform (TransformNode other)
{
// return the matrix that takes vertices from the space of the other node
// into the space of this one
Matrix4f inv = new Matrix4f();
worldTransform.invert(inv);
return inv.mult(other.worldTransform);
}
}
/** The meshes and bones comprising the model. */ /** The meshes and bones comprising the model. */
public ArrayList<SpatialDef> spatials = new ArrayList<SpatialDef>(); public ArrayList<SpatialDef> spatials = new ArrayList<SpatialDef>();
@@ -428,6 +634,85 @@ public class ModelDef
spatial); spatial);
} }
/**
* Creates and returns a transform tree representing the model for preprocessing.
*/
public Node createTransformTree (Properties props, HashMap<String, TransformNode> nodes)
{
// create the nodes and map them by name
for (SpatialDef spatial : spatials) {
nodes.put(spatial.name, new TransformNode(spatial));
}
// resolve the parents and collect the names of the bones
Node root = new Node("root");
HashSet<String> bones = new HashSet<String>();
for (TransformNode node : nodes.values()) {
if (node.spatial.parent == null) {
root.attachChild(node);
} else {
TransformNode pnode = nodes.get(node.spatial.parent);
if (pnode != null) {
pnode.attachChild(node);
pnode.referenced = true;
}
}
node.spatial.getBoneNames(bones);
}
// mark the bones as referenced
for (String name : bones) {
TransformNode node = nodes.get(name);
if (node != null) {
node.referenced = true;
}
}
// mark the controlled nodes
String[] controllers = StringUtil.parseStringArray(props.getProperty("controllers", ""));
for (String controller : controllers) {
Properties subProps = PropertiesUtil.getSubProperties(props, controller);
TransformNode node = nodes.get(subProps.getProperty("node", controller));
if (node != null) {
node.referenced = node.controlled = true;
}
}
// store the base transforms and relative transforms for merge candidates
root.updateGeometricState(0f, true);
for (TransformNode node : nodes.values()) {
node.baseLocalTransform = new Matrix4f(node.localTransform);
node.relativeTransforms = new ArrayList<Tuple<TransformNode, Matrix4f>>();
for (TransformNode onode : nodes.values()) {
if (node == onode || !node.canMerge(props, onode)) {
continue;
}
node.relativeTransforms.add(new Tuple<TransformNode, Matrix4f>(
onode, node.getRelativeTransform(onode)));
}
}
return root;
}
/**
* Merges compatible meshes that retain the same relative transform throughout all animations.
*/
public void mergeSpatials (HashMap<String, TransformNode> nodes)
{
for (TransformNode node : nodes.values()) {
if (!spatials.contains(node.spatial)) {
continue;
}
for (Tuple<TransformNode, Matrix4f> tuple : node.relativeTransforms) {
if (spatials.contains(tuple.left.spatial)) {
node.spatial.merge(tuple.left.spatial, tuple.right);
spatials.remove(tuple.left.spatial);
}
}
}
}
/** /**
* Creates the model node defined herein. * Creates the model node defined herein.
* *
@@ -475,11 +760,6 @@ public class ModelDef
} }
} }
// in non-animated models, merge meshes with the same attributes
if (StringUtil.isBlank(props.getProperty("animations"))) {
mergeEquivalentMeshes(model, nodes, referenced);
}
// get rid of any nodes that serve no purpose // get rid of any nodes that serve no purpose
pruneUnusedNodes(model, nodes, referenced); pruneUnusedNodes(model, nodes, referenced);
@@ -526,63 +806,17 @@ public class ModelDef
return referenced.contains(node) || hasValidChildren; return referenced.contains(node) || hasValidChildren;
} }
/** Merges meshes with the same attributes. */ /** Determines whether a pair of matrices are "close enough" to equal. */
protected void mergeEquivalentMeshes ( public static boolean epsilonEquals (Matrix4f m1, Matrix4f m2)
Model model, HashMap<String, Spatial> nodes, final HashSet<Spatial> referenced)
{ {
final HashMap<Object, ArrayList<ModelMesh>> meshes = for (int ii = 0; ii < 4; ii++) {
new HashMap<Object, ArrayList<ModelMesh>>(); for (int jj = 0; jj < 4; jj++) {
new SpatialVisitor<ModelMesh>(ModelMesh.class) { if (FastMath.abs(m1.get(ii, jj) - m2.get(ii, jj)) > 0.0001f) {
public void traverse (Spatial spatial) { return false;
// cut out when we encounter referenced nodes
if (!referenced.contains(spatial)) {
spatial.updateWorldVectors();
super.traverse(spatial);
} }
} }
protected void visit (ModelMesh mesh) {
// make sure we don't try this with skinned meshes
if (mesh instanceof SkinMesh) {
return;
}
Object key = mesh.getAttributeKey();
ArrayList<ModelMesh> list = meshes.get(key);
if (list == null) {
meshes.put(key, list = new ArrayList<ModelMesh>());
}
list.add(mesh);
}
}.traverse(model);
for (ArrayList<ModelMesh> list : meshes.values()) {
// the first in the list becomes the base
ModelMesh base = list.get(0);
flattenTransforms(base);
model.attachChild(base);
// the rest are merged with it
for (int ii = 1, nn = list.size(); ii < nn; ii++) {
ModelMesh mesh = list.get(ii);
flattenTransforms(mesh);
mesh.getParent().detachChild(mesh);
base.merge(mesh);
}
// update the model bound and recenter
base.updateModelBound();
base.centerVertices();
} }
} return true;
/** Transforms the mesh's vertices and normals into model coordinates. */
protected static void flattenTransforms (ModelMesh mesh)
{
GeomBatch batch = mesh.getBatch(0);
batch.getWorldCoords(batch.getVertexBuffer());
batch.getWorldNormals(batch.getNormalBuffer());
mesh.getLocalTranslation().zero();
mesh.getLocalRotation().loadIdentity();
mesh.getLocalScale().set(Vector3f.UNIT_XYZ);
} }
/** Converts a boxed Integer list to an unboxed int array. */ /** Converts a boxed Integer list to an unboxed int array. */
@@ -30,6 +30,8 @@ import java.nio.IntBuffer;
import org.lwjgl.opengl.ARBShaderObjects; import org.lwjgl.opengl.ARBShaderObjects;
import com.jme.math.Matrix4f;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f; import com.jme.math.Vector3f;
import com.jme.scene.Controller; import com.jme.scene.Controller;
import com.jme.scene.state.GLSLShaderObjectsState; import com.jme.scene.state.GLSLShaderObjectsState;
@@ -111,6 +113,31 @@ public class JmeUtil
} }
} }
/**
* Sets a matrix to the transform defined by the given translation,
* rotation, and scale values.
*/
public static Matrix4f setTransform (
Vector3f translation, Quaternion rotation, Vector3f scale, Matrix4f result)
{
result.setRotationQuaternion(rotation);
result.setTranslation(translation);
result.m00 *= scale.x;
result.m01 *= scale.y;
result.m02 *= scale.z;
result.m10 *= scale.x;
result.m11 *= scale.y;
result.m12 *= scale.z;
result.m20 *= scale.x;
result.m21 *= scale.y;
result.m22 *= scale.z;
return result;
}
/** /**
* Attempts to parse a string containing an axis: either "x", "y", or "z", or three * Attempts to parse a string containing an axis: either "x", "y", or "z", or three
* comma-delimited values representing an axis vector. The value returned may be one of * comma-delimited values representing an axis vector. The value returned may be one of