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
{
/** The supported types of animation in decreasing order of complexity. */
/** The supported types of animation in decreasing order of complexity. */
public enum AnimationMode {
SKIN, MORPH, FLIPBOOK
};
/** Lets listeners know when animations are completed (which only happens
* for non-repeating animations) or cancelled. */
public interface AnimationObserver
@@ -84,14 +84,14 @@ public class Model extends ModelNode
* @return true to remain on the observer list, false to remove self
*/
public boolean animationStarted (Model model, String anim);
/**
* Called when a non-repeating animation has finished.
*
* @return true to remain on the observer list, false to remove self
*/
public boolean animationCompleted (Model model, String anim);
/**
* Called when an animation has been cancelled.
*
@@ -99,30 +99,33 @@ public class Model extends ModelNode
*/
public boolean animationCancelled (Model model, String anim);
}
/** An animation for the model. */
public static class Animation
implements Savable
{
/** The rate of the animation in frames per second. */
public int frameRate;
/** The animation repeat type ({@link Controller#RT_CLAMP},
* {@link Controller#RT_CYCLE}, or {@link Controller#RT_WRAP}). */
public int repeatType;
/** Any nodes visible that never move within the model. */
public Spatial[] staticTargets;
/** The transformation targets of the animation. */
public Spatial[] transformTargets;
/** The animation transforms (one transform per target per frame). */
public transient Transform[][] transforms;
/** Uniquely identifies this animation within the model. */
public transient int animId;
/** For each frame, whether the frame has been stored in meshes. */
public transient boolean[] stored;
/**
* Returns this animation's duration in seconds.
*/
@@ -130,7 +133,7 @@ public class Model extends ModelNode
{
return (float)transforms.length / frameRate;
}
/**
* Rebinds this animation for a prototype instance.
*
@@ -141,17 +144,14 @@ public class Model extends ModelNode
Animation anim = new Animation();
anim.frameRate = frameRate;
anim.repeatType = repeatType;
anim.staticTargets = rebind(staticTargets, pnodes);
anim.transformTargets = rebind(transformTargets, pnodes);
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.stored = stored;
return anim;
}
/**
* Applies the transforms for a frame of this animation.
*/
@@ -162,7 +162,7 @@ public class Model extends ModelNode
xforms[ii].apply(transformTargets[ii]);
}
}
/**
* 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]);
}
}
// documentation inherited
public Class getClassTag ()
{
return getClass();
}
// documentation inherited
public void read (JMEImporter im)
throws IOException
@@ -187,6 +187,8 @@ public class Model extends ModelNode
InputCapsule capsule = im.getCapsule(this);
frameRate = capsule.readInt("frameRate", 0);
repeatType = capsule.readInt("repeatType", Controller.RT_CLAMP);
staticTargets = ArrayUtil.copy(capsule.readSavableArray(
"staticTargets", null), new Spatial[0]);
transformTargets = ArrayUtil.copy(capsule.readSavableArray(
"transformTargets", null), new Spatial[0]);
FloatBuffer pxforms = capsule.readFloatBuffer("transforms", null);
@@ -208,9 +210,10 @@ public class Model extends ModelNode
OutputCapsule capsule = ex.getCapsule(this);
capsule.write(frameRate, "frameRate", 0);
capsule.write(repeatType, "repeatType", Controller.RT_CLAMP);
capsule.write(staticTargets, "staticTargets", null);
capsule.write(transformTargets, "transformTargets", null);
FloatBuffer pxforms = FloatBuffer.allocate(transforms.length *
transformTargets.length * Transform.PACKED_SIZE);
FloatBuffer pxforms = FloatBuffer.allocate(
transforms.length * transformTargets.length * Transform.PACKED_SIZE);
for (Transform[] frame : transforms) {
for (Transform xform : frame) {
xform.writeToBuffer(pxforms);
@@ -219,16 +222,25 @@ public class Model extends ModelNode
pxforms.rewind();
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;
}
/** A frame element that manipulates the target's transform. */
public static final class Transform
{
/** The number of floats required to store a packed transform. */
public static final int PACKED_SIZE = 3 + 4 + 3;
public Transform (
Vector3f translation, Quaternion rotation, Vector3f scale)
{
@@ -236,7 +248,7 @@ public class Model extends ModelNode
_rotation = rotation;
_scale = scale;
}
public Transform (FloatBuffer buf)
{
_translation = new Vector3f(buf.get(), buf.get(), buf.get());
@@ -244,14 +256,14 @@ public class Model extends ModelNode
buf.get());
_scale = new Vector3f(buf.get(), buf.get(), buf.get());
}
public void apply (Spatial target)
{
target.getLocalTranslation().set(_translation);
target.getLocalRotation().set(_rotation);
target.getLocalScale().set(_scale);
}
/**
* Blends between this transform and the next, applying the result to
* the given target.
@@ -266,7 +278,7 @@ public class Model extends ModelNode
target.getLocalRotation().slerp(_rotation, next._rotation, alpha);
target.getLocalScale().interpolate(_scale, next._scale, alpha);
}
/**
* Writes this transform to the current position in the supplied
* buffer.
@@ -276,31 +288,31 @@ public class Model extends ModelNode
buf.put(_translation.x);
buf.put(_translation.y);
buf.put(_translation.z);
buf.put(_rotation.x);
buf.put(_rotation.y);
buf.put(_rotation.z);
buf.put(_rotation.w);
buf.put(_scale.x);
buf.put(_scale.y);
buf.put(_scale.z);
}
/** The transform at this frame. */
protected Vector3f _translation, _scale;
protected Quaternion _rotation;
}
/** Customized clone creator for models. */
public static class CloneCreator
{
/** A shared seed used to select textures consistently. */
public int random;
/** Maps original objects to their copies. */
public HashMap originalToCopy = new HashMap();
public CloneCreator (Model toCopy)
{
_toCopy = toCopy;
@@ -314,7 +326,7 @@ public class Model extends ModelNode
addProperty("displaylistid");
addProperty("bound");
}
/**
* Sets the named property.
*/
@@ -322,7 +334,7 @@ public class Model extends ModelNode
{
_properties.add(name);
}
/**
* Clears the named property.
*/
@@ -330,7 +342,7 @@ public class Model extends ModelNode
{
_properties.remove(name);
}
/**
* Checks whether the named property has been set.
*/
@@ -338,7 +350,7 @@ public class Model extends ModelNode
{
return _properties.contains(name);
}
/**
* Creates a new copy of the target model.
*/
@@ -349,14 +361,14 @@ public class Model extends ModelNode
originalToCopy.clear(); // make sure no references remain
return copy;
}
/** The model to copy. */
protected Model _toCopy;
/** The set of added properties. */
protected HashSet<String> _properties = new HashSet<String>();
}
/**
* Attempts to read a model from the specified file.
*/
@@ -367,20 +379,20 @@ public class Model extends ModelNode
FileInputStream fis = new FileInputStream(file);
Model model = (Model)BinaryImporter.getInstance().load(fis);
fis.close();
// initialize the model as a prototype
model.initPrototype();
return model;
}
/**
* No-arg constructor for deserialization.
*/
public Model ()
{
}
/**
* Standard constructor.
*/
@@ -389,7 +401,7 @@ public class Model extends ModelNode
super(name);
_props = props;
}
/**
* Returns a reference to the properties of the model.
*/
@@ -397,7 +409,7 @@ public class Model extends ModelNode
{
return _props;
}
/**
* Initializes this model as prototype. Only necessary when the prototype
* was not loaded through {@link #readFromFile}.
@@ -416,7 +428,7 @@ public class Model extends ModelNode
cullInvisibleNodes();
initInstance();
}
/**
* Returns the names of the model's variant configurations.
*/
@@ -424,7 +436,7 @@ public class Model extends ModelNode
{
return StringUtil.parseStringArray(_props.getProperty("variants", ""));
}
/**
* Adds an animation to the model's library. This should only be called by
* the model compiler.
@@ -435,7 +447,7 @@ public class Model extends ModelNode
_anims = new HashMap<String, Animation>();
}
_anims.put(name, anim);
// store the original transforms
Transform[] oxforms = new Transform[anim.transformTargets.length];
for (int ii = 0; ii < anim.transformTargets.length; ii++) {
@@ -445,7 +457,7 @@ public class Model extends ModelNode
new Quaternion(target.getLocalRotation()),
new Vector3f(target.getLocalScale()));
}
// run through every frame of the animation, expanding the bounding
// volumes of any deformable meshes
for (int ii = 0; ii < anim.transforms.length; ii++) {
@@ -455,14 +467,14 @@ public class Model extends ModelNode
updateWorldData(0f);
expandModelBounds();
}
// restore the original transforms
for (int ii = 0; ii < anim.transformTargets.length; ii++) {
oxforms[ii].apply(anim.transformTargets[ii]);
}
updateWorldData(0f);
}
/**
* Sets the animation mode to use for this model. This should be set
* on the prototype before any animations are started or any instances
@@ -472,7 +484,7 @@ public class Model extends ModelNode
{
_animMode = mode;
}
/**
* Returns the animation mode configured for this model.
*/
@@ -480,7 +492,7 @@ public class Model extends ModelNode
{
return _animMode;
}
/**
* Returns the names of the model's animations.
*/
@@ -492,7 +504,7 @@ public class Model extends ModelNode
return (_anims == null) ? new String[0] :
_anims.keySet().toArray(new String[_anims.size()]);
}
/**
* 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);
}
/**
* Starts the named animation.
*
@@ -533,14 +545,16 @@ public class Model extends ModelNode
if (_anim != null) {
_animObservers.apply(new AnimCancelledOp(_animName));
}
// first cull all model nodes, then re-activate the ones in the
// target list
// first cull all model nodes, then re-activate the ones in the target lists
cullModelNodes();
for (Spatial target : anim.staticTargets) {
((ModelNode)target).updateCullMode();
}
for (Spatial target : anim.transformTargets) {
((ModelNode)target).updateCullMode();
}
_paused = false;
_anim = anim;
_animName = name;
@@ -552,7 +566,7 @@ public class Model extends ModelNode
_animObservers.apply(new AnimStartedOp(_animName));
return anim.getDuration() / _animSpeed;
}
/**
* Fast-forwards the current animation by the given number of seconds.
*/
@@ -560,7 +574,7 @@ public class Model extends ModelNode
{
updateAnimation(time);
}
/**
* 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 + "].");
return null;
}
/**
* Returns a reference to the currently running animation, or
* <code>null</code> if no animation is running.
@@ -592,7 +606,7 @@ public class Model extends ModelNode
{
return _anim;
}
/**
* Stops the currently running animation.
*/
@@ -633,7 +647,7 @@ public class Model extends ModelNode
{
_fdir *= -1;
}
/**
* Sets the animation speed, which acts as a multiplier for the frame rate
* of each animation.
@@ -642,7 +656,7 @@ public class Model extends ModelNode
{
_animSpeed = speed;
}
/**
* Returns the currently configured animation speed.
*/
@@ -650,7 +664,7 @@ public class Model extends ModelNode
{
return _animSpeed;
}
/**
* Adds an animation observer.
*/
@@ -658,7 +672,7 @@ public class Model extends ModelNode
{
_animObservers.add(obs);
}
/**
* Removes an animation observer.
*/
@@ -666,7 +680,7 @@ public class Model extends ModelNode
{
_animObservers.remove(obs);
}
/**
* 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
@@ -685,7 +699,7 @@ public class Model extends ModelNode
}
return _emissionNode;
}
/**
* Writes this model out to a file.
*/
@@ -697,7 +711,7 @@ public class Model extends ModelNode
BinaryExporter.getInstance().save(this, fos);
fos.close();
}
@Override // documentation inherited
public void read (JMEImporter im)
throws IOException
@@ -729,7 +743,7 @@ public class Model extends ModelNode
}
}
}
@Override // documentation inherited
public void write (JMEExporter ex)
throws IOException
@@ -756,7 +770,7 @@ public class Model extends ModelNode
}
capsule.writeSavableArrayList(getControllers(), "controllers", null);
}
@Override // documentation inherited
public void resolveTextures (TextureProvider tprov)
{
@@ -767,7 +781,7 @@ public class Model extends ModelNode
}
}
}
/**
* Creates a new prototype using the given variant configuration.
* Use {@link #createInstance} on the returned prototype to create
@@ -788,7 +802,7 @@ public class Model extends ModelNode
}
prototype._prototype = null;
prototype._pnodes = null;
// reconfigure meshes with new variant type
if (variant != null) {
prototype._props = PropertiesUtil.getFilteredProperties(
@@ -802,10 +816,10 @@ public class Model extends ModelNode
}
return prototype;
}
/**
* Creates and returns a new instance of this model.
*/
*/
public Model createInstance ()
{
if (_prototype != null) {
@@ -838,7 +852,7 @@ public class Model extends ModelNode
}
lockInstance(targets);
}
@Override // documentation inherited
public Spatial putClone (Spatial store, CloneCreator properties)
{
@@ -866,7 +880,7 @@ public class Model extends ModelNode
mstore._animMode = _animMode;
return mstore;
}
@Override // documentation inherited
public void updateGeometricState (float time, boolean initiator)
{
@@ -875,13 +889,13 @@ public class Model extends ModelNode
// into view
boolean wasOutside = _outside;
_outside = isOutsideFrustum() && worldBound != null;
// slow evvvverything down by the animation speed
time *= _animSpeed;
if (_anim != null) {
updateAnimation(time);
}
// update controllers and children with accumulated time
_accum += time;
if (_outside) {
@@ -894,14 +908,14 @@ public class Model extends ModelNode
updateWorldVectors();
worldBound = _storedBound.transform(getWorldRotation(),
getWorldTranslation(), getWorldScale(), worldBound);
} else {
super.updateGeometricState(_shouldAccumulate ? _accum : time,
initiator);
_accum = 0f;
}
}
@Override // documentation inherited
public void onDraw (Renderer r)
{
@@ -912,7 +926,7 @@ public class Model extends ModelNode
updateWorldData(0f);
}
}
/**
* Transforms the current world bound into model space and stores it for
* when the model is offscreen.
@@ -928,10 +942,10 @@ public class Model extends ModelNode
getChild(ii).updateGeometricState(0f, false);
}
updateWorldBound();
_storedBound = worldBound.clone(_storedBound);
}
/**
* Determines whether this node was determined to be entirely outside the
* view frustum.
@@ -945,7 +959,7 @@ public class Model extends ModelNode
}
return false;
}
/**
* 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.
*/
@@ -972,7 +986,7 @@ public class Model extends ModelNode
_elapsed += (time * _anim.frameRate);
return;
}
// advance the frame counter if necessary
while (_elapsed > 1f) {
if (!_paused) {
@@ -982,13 +996,13 @@ public class Model extends ModelNode
_elapsed = 1f;
}
}
// update the target transforms and animation frame if not outside the
// view frustum
if (!_outside) {
updateMeshes();
updateMeshes();
}
// if the next index is the same as this one, we are finished
if (_fidx == _nidx && !_paused) {
_anim = null;
@@ -997,7 +1011,7 @@ public class Model extends ModelNode
}
_elapsed += (time * _anim.frameRate);
}
/**
* Advances the frame counter by one frame.
*/
@@ -1007,14 +1021,14 @@ public class Model extends ModelNode
int nframes = _anim.transforms.length;
if (_anim.repeatType == Controller.RT_CLAMP) {
_nidx = Math.max(0, Math.min(_nidx + _fdir, nframes - 1));
} else if (_anim.repeatType == Controller.RT_WRAP) {
// % is not a modulo operator, so is not guaranteed to be positive
_nidx = (_nidx + _fdir) % nframes;
if (_nidx < 0) {
_nidx += nframes;
}
} else { // _anim.repeatType == Controller.RT_CYCLE
if ((_nidx + _fdir) < 0 || (_nidx + _fdir) >= nframes) {
_fdir *= -1; // reverse direction
@@ -1022,7 +1036,7 @@ public class Model extends ModelNode
_nidx += _fdir;
}
}
/**
* 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;
}
setMeshFrame(frameId);
} else if (_animMode == AnimationMode.MORPH) {
int frameId1 = (_anim.animId << 16) | _fidx,
frameId2 = (_anim.animId << 16) | _nidx;
@@ -1055,74 +1069,74 @@ public class Model extends ModelNode
}
_anim.blendFrames(_fidx, _nidx, _elapsed);
blendMeshFrames(frameId1, frameId2, _elapsed);
} else { // _animMode == AnimationMode.SKIN
_anim.blendFrames(_fidx, _nidx, _elapsed);
}
}
/** A reference to the prototype, or <code>null</code> if this is a
* prototype. */
protected Model _prototype;
/** For prototype models, a customized clone creator used to generate
* instances. */
protected CloneCreator _ccreator;
/** The animation mode to use for this model. */
protected AnimationMode _animMode;
/** For instances, maps prototype nodes to their corresponding instance
* nodes. */
protected HashMap _pnodes;
/** The model properties. */
protected Properties _props;
/** The model animations. */
protected HashMap<String, Animation> _anims;
/** The currently running animation, or <code>null</code> for none. */
protected Animation _anim;
/** The name of the currently running animation, if any. */
protected String _animName;
/** The current animation speed multiplier. */
protected float _animSpeed = 1f;
/** The index of the current and next frames. */
protected int _fidx, _nidx;
/** The direction for wrapping animations (+1 forward, -1 backward). */
protected int _fdir;
/** The frame portion elapsed since the start of the current frame. */
protected float _elapsed;
/** The pause status of this model. */
protected boolean _paused;
/** The amount of update time accumulated while outside of view frustum. */
protected float _accum;
/** Whether or not we should accumulate update time while out of view. */
protected boolean _shouldAccumulate;
/** The child node that contains the model's emissions in world space. */
protected Node _emissionNode;
/** The model space bounding volume stored for use when the model is
* offscreen. */
protected BoundingVolume _storedBound;
/** Whether or not we were outside the frustum at the last update. */
protected boolean _outside;
/** Animation completion listeners. */
protected ObserverList<AnimationObserver> _animObservers =
new ObserverList<AnimationObserver>(ObserverList.FAST_UNSAFE_NOTIFY);
/** Used to notify observers of animation initiation. */
protected class AnimStartedOp
implements ObserverList.ObserverOp<AnimationObserver>
@@ -1131,17 +1145,17 @@ public class Model extends ModelNode
{
_name = name;
}
// documentation inherited from interface ObserverOp
public boolean apply (AnimationObserver obs)
{
return obs.animationStarted(Model.this, _name);
}
/** The name of the animation started. */
protected String _name;
}
/** Used to notify observers of animation completion. */
protected class AnimCompletedOp
implements ObserverList.ObserverOp<AnimationObserver>
@@ -1150,17 +1164,17 @@ public class Model extends ModelNode
{
_name = name;
}
// documentation inherited from interface ObserverOp
public boolean apply (AnimationObserver obs)
{
return obs.animationCompleted(Model.this, _name);
}
/** The name of the animation completed. */
protected String _name;
}
/** Used to notify observers of animation cancellation. */
protected class AnimCancelledOp
implements ObserverList.ObserverOp<AnimationObserver>
@@ -1169,13 +1183,13 @@ public class Model extends ModelNode
{
_name = name;
}
// documentation inherited from interface ObserverOp
public boolean apply (AnimationObserver obs)
{
return obs.animationCancelled(Model.this, _name);
}
/** The name of the animation cancelled. */
protected String _name;
}
@@ -144,51 +144,6 @@ public class ModelMesh extends TriMesh
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
* position lies at the center of its bounding volume.
@@ -819,30 +774,6 @@ public class ModelMesh extends TriMesh
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
* property key and a default value. */
protected String _textureKey;
@@ -41,6 +41,7 @@ import com.jme.util.export.InputCapsule;
import com.jme.util.export.OutputCapsule;
import com.threerings.jme.Log;
import com.threerings.jme.util.JmeUtil;
/**
* A {@link Node} with a serialization mechanism tailored to stored models.
@@ -55,7 +56,7 @@ public class ModelNode extends Node
{
super("node");
}
/**
* Standard constructor.
*/
@@ -63,14 +64,14 @@ public class ModelNode extends Node
{
super(name);
}
@Override // documentation inherited
public int hashCode ()
{
// hash on the name rather than the identity for consistent ordering
return getName().hashCode();
}
/**
* Recursively searches the scene graph rooted at this node for a
* node with the provided name.
@@ -90,7 +91,7 @@ public class ModelNode extends Node
}
return null;
}
/**
* Returns a reference to the model space transform of the node.
*/
@@ -98,7 +99,7 @@ public class ModelNode extends Node
{
return _modelTransform;
}
@Override // documentation inherited
public void updateWorldData (float time)
{
@@ -108,7 +109,7 @@ public class ModelNode extends Node
super.updateWorldData(time);
}
}
@Override // documentation inherited
public void updateWorldBound ()
{
@@ -117,22 +118,22 @@ public class ModelNode extends Node
super.updateWorldBound();
}
}
@Override // documentation inherited
public void updateWorldVectors ()
{
super.updateWorldVectors();
if (parent instanceof ModelNode) {
setTransform(getLocalTranslation(), getLocalRotation(),
JmeUtil.setTransform(getLocalTranslation(), getLocalRotation(),
getLocalScale(), _localTransform);
((ModelNode)parent).getModelTransform().mult(_localTransform,
_modelTransform);
} else {
_modelTransform.loadIdentity();
}
}
// documentation inherited from interface ModelSpatial
public Spatial putClone (Spatial store, Model.CloneCreator properties)
{
@@ -178,7 +179,7 @@ public class ModelNode extends Node
mstore._hasVisibleDescendants = _hasVisibleDescendants;
return mstore;
}
@Override // documentation inherited
public void read (JMEImporter im)
throws IOException
@@ -198,7 +199,7 @@ public class ModelNode extends Node
}
}
}
@Override // documentation inherited
public void write (JMEExporter ex)
throws IOException
@@ -210,7 +211,7 @@ public class ModelNode extends Node
capsule.write(getLocalScale(), "localScale", null);
capsule.writeSavableArrayList(getChildren(), "children", null);
}
// documentation inherited from interface ModelSpatial
public void expandModelBounds ()
{
@@ -221,7 +222,7 @@ public class ModelNode extends Node
}
}
}
// documentation inherited from interface ModelSpatial
public void setReferenceTransforms ()
{
@@ -233,7 +234,7 @@ public class ModelNode extends Node
}
}
}
// documentation inherited from interface ModelSpatial
public void lockStaticMeshes (
Renderer renderer, boolean useVBOs, boolean useDisplayLists)
@@ -246,7 +247,7 @@ public class ModelNode extends Node
}
}
}
// documentation inherited from interface ModelSpatial
public void resolveTextures (TextureProvider tprov)
{
@@ -257,7 +258,7 @@ public class ModelNode extends Node
}
}
}
// documentation inherited from interface ModelSpatial
public void storeMeshFrame (int frameId, boolean blend)
{
@@ -268,7 +269,7 @@ public class ModelNode extends Node
}
}
}
// documentation inherited from interface ModelSpatial
public void setMeshFrame (int frameId)
{
@@ -279,7 +280,7 @@ public class ModelNode extends Node
}
}
}
// documentation inherited from interface ModelSpatial
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
* descendants.
@@ -300,7 +301,7 @@ public class ModelNode extends Node
{
_forceCull = force;
}
/**
* Checks whether this node should be culled even if it has mesh
* descendants.
@@ -309,7 +310,7 @@ public class ModelNode extends Node
{
return _forceCull;
}
/**
* Sets the cull state of any nodes that do not contain geometric
* descendants to {@link CULL_ALWAYS} so that they don't waste
@@ -330,7 +331,7 @@ public class ModelNode extends Node
updateCullMode();
return _hasVisibleDescendants;
}
/**
* Locks the transforms and bounds of this instance with the assumption
* that the position will never change.
@@ -344,14 +345,14 @@ public class ModelNode extends Node
{
updateWorldVectors();
lockedMode |= LOCKED_TRANSFORMS;
boolean containsTargets = false;
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
Spatial child = getChild(ii);
if (targets.contains(child) || (child instanceof ModelNode &&
((ModelNode)child).lockInstance(targets))) {
containsTargets = true;
} else if (child instanceof ModelMesh) {
((ModelMesh)child).lockInstance();
}
@@ -364,7 +365,7 @@ public class ModelNode extends Node
return false;
}
}
/**
* Recursively culls all model nodes under this one in preparation for
* 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
* has not been specifically culled.
@@ -389,42 +390,16 @@ public class ModelNode extends Node
setCullMode((_hasVisibleDescendants && !_forceCull) ?
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. */
protected Matrix4f _localTransform = new Matrix4f(),
_modelTransform = new Matrix4f();
/** Whether or not this node has mesh descendants. */
protected boolean _hasVisibleDescendants;
/** If true, cull the node even if it has mesh descendants. */
protected boolean _forceCull;
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.threerings.jme.Log;
import com.threerings.jme.util.JmeUtil;
/**
* 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. */
public int vertexCount;
/** The bones influencing this group. */
public Bone[] bones;
/** The array of interleaved weights (of length <code>vertexCount *
* boneIndices.length</code>): weights for first vertex, weights for
* second, etc. */
@@ -92,13 +93,13 @@ public class SkinMesh extends ModelMesh
wgroup.weights = weights;
return wgroup;
}
// documentation inherited
public Class getClassTag ()
{
return getClass();
}
// documentation inherited
public void read (JMEImporter im)
throws IOException
@@ -119,7 +120,7 @@ public class SkinMesh extends ModelMesh
capsule.write(bones, "bones", null);
capsule.write(weights, "weights", null);
}
private static final long serialVersionUID = 1;
}
@@ -129,24 +130,24 @@ public class SkinMesh extends ModelMesh
{
/** The node that defines the bone's position. */
public ModelNode node;
/** The inverse of the bone's model space reference transform. */
public transient Matrix4f invRefTransform;
/** The bone's current transform in model space. */
public transient Matrix4f transform;
public Bone (ModelNode node)
{
this();
this.node = node;
}
public Bone ()
{
transform = new Matrix4f();
}
/**
* Rebinds this bone for a prototype instance.
*
@@ -159,13 +160,13 @@ public class SkinMesh extends ModelMesh
bone.transform = new Matrix4f();
return bone;
}
// documentation inherited
public Class getClassTag ()
{
return getClass();
}
// documentation inherited
public void read (JMEImporter im)
throws IOException
@@ -181,17 +182,17 @@ public class SkinMesh extends ModelMesh
OutputCapsule capsule = ex.getCapsule(this);
capsule.write(node, "node", null);
}
private static final long serialVersionUID = 1;
}
/**
* No-arg constructor for deserialization.
*/
public SkinMesh ()
{
}
/**
* Creates an empty mesh.
*/
@@ -199,7 +200,7 @@ public class SkinMesh extends ModelMesh
{
super(name);
}
/**
* Sets the array of weight groups that determine how bones affect
* each vertex.
@@ -207,7 +208,7 @@ public class SkinMesh extends ModelMesh
public void setWeightGroups (WeightGroup[] weightGroups)
{
_weightGroups = weightGroups;
// compile a list of all referenced bones
HashSet<Bone> bones = new HashSet<Bone>();
for (WeightGroup group : weightGroups) {
@@ -215,18 +216,18 @@ public class SkinMesh extends ModelMesh
}
_bones = bones.toArray(new Bone[bones.size()]);
}
@Override // documentation inherited
public void reconstruct (
FloatBuffer vertices, FloatBuffer normals, FloatBuffer colors,
FloatBuffer textures, IntBuffer indices)
{
super.reconstruct(vertices, normals, colors, textures, indices);
// initialize the quantized frame table
_frames = new HashIntMap<Object>();
}
@Override // documentation inherited
public Spatial putClone (Spatial store, Model.CloneCreator properties)
{
@@ -264,7 +265,7 @@ public class SkinMesh extends ModelMesh
mstore._nbuf = new float[_nbuf.length];
return mstore;
}
@Override // documentation inherited
public void read (JMEImporter im)
throws IOException
@@ -274,7 +275,7 @@ public class SkinMesh extends ModelMesh
setWeightGroups(ArrayUtil.copy(capsule.readSavableArray(
"weightGroups", null), new WeightGroup[0]));
}
@Override // documentation inherited
public void write (JMEExporter ex)
throws IOException
@@ -283,7 +284,7 @@ public class SkinMesh extends ModelMesh
OutputCapsule capsule = ex.getCapsule(this);
capsule.write(_weightGroups, "weightGroups", null);
}
@Override // documentation inherited
public void expandModelBounds ()
{
@@ -292,14 +293,14 @@ public class SkinMesh extends ModelMesh
updateModelBound();
getBatch(0).getModelBound().mergeLocal(obound);
}
@Override // documentation inherited
public void setReferenceTransforms ()
{
_invRefTransform = new Matrix4f();
if (parent instanceof ModelNode) {
Matrix4f transform = new Matrix4f();
ModelNode.setTransform(getLocalTranslation(), getLocalRotation(),
JmeUtil.setTransform(getLocalTranslation(), getLocalRotation(),
getLocalScale(), transform);
((ModelNode)parent).getModelTransform().mult(transform,
_invRefTransform);
@@ -310,7 +311,7 @@ public class SkinMesh extends ModelMesh
_invRefTransform.mult(bone.node.getModelTransform()).invert();
}
}
@Override // documentation inherited
public void lockStaticMeshes (
Renderer renderer, boolean useVBOs, boolean useDisplayLists)
@@ -325,14 +326,14 @@ public class SkinMesh extends ModelMesh
}
_useDisplayLists = useDisplayLists && !_translucent;
}
@Override // documentation inherited
public void storeMeshFrame (int frameId, boolean blend)
{
_storeFrameId = frameId;
_storeBlend = blend;
}
@Override // documentation inherited
public void setMeshFrame (int frameId)
{
@@ -346,7 +347,7 @@ public class SkinMesh extends ModelMesh
getBatch(0).updateRenderState();
}
}
@Override // documentation inherited
public void blendMeshFrames (int frameId1, int frameId2, float alpha)
{
@@ -359,7 +360,7 @@ public class SkinMesh extends ModelMesh
nbuf.rewind();
nbuf.put(_nbuf);
}
@Override // documentation inherited
public void updateWorldData (float time)
{
@@ -373,7 +374,7 @@ public class SkinMesh extends ModelMesh
bone.transform);
bone.transform.multLocal(bone.invRefTransform);
}
// deform the mesh according to the positions of the bones (this code
// is ugly as sin because it's optimized at a low level)
Bone[] bones;
@@ -397,11 +398,11 @@ public class SkinMesh extends ModelMesh
for (kk = 0; kk < bones.length; kk++) {
m = bones[kk].transform;
weight = weights[ww++];
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;
vz += (ovx*m.m20 + ovy*m.m21 + ovz*m.m22 + m.m23) * weight;
nx += (onx*m.m00 + ony*m.m01 + onz*m.m02) * weight;
ny += (onx*m.m10 + ony*m.m11 + onz*m.m12) * weight;
nz += (onx*m.m20 + ony*m.m21 + onz*m.m22) * weight;
@@ -414,7 +415,7 @@ public class SkinMesh extends ModelMesh
_nbuf[bidx++] = nz;
}
}
// if skinning in real time, copy the data from arrays to buffers;
// otherwise, store the mesh as an animation frame
if (_storeFrameId == 0) {
@@ -428,7 +429,7 @@ public class SkinMesh extends ModelMesh
_storeFrameId = -1;
}
}
/**
* Stores the current frame data for later use.
*/
@@ -465,12 +466,12 @@ public class SkinMesh extends ModelMesh
_frames.put(_storeFrameId, tbatch);
}
}
@Override // documentation inherited
protected void storeOriginalBuffers ()
{
super.storeOriginalBuffers();
FloatBuffer vbuf = getVertexBuffer(0), nbuf = getNormalBuffer(0);
vbuf.rewind();
nbuf.rewind();
@@ -479,19 +480,19 @@ public class SkinMesh extends ModelMesh
_vbuf = new float[_ovbuf.length];
_nbuf = new float[_onbuf.length];
}
/** A stored frame used for linear blending. */
protected static class BlendFrame
{
/** The skinned vertex and normal values. */
public float[] vbuf, nbuf;
public BlendFrame (float[] vbuf, float[] nbuf)
{
this.vbuf = vbuf;
this.nbuf = nbuf;
}
public void blend (
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;
}
}
}
}
/** Pre-skinned {@link TriangleBatch}es or {@link BlendFrame}s shared
* between all instances corresponding to frame ids from
* {@link #storeAnimationFrame}. */
protected HashIntMap<Object> _frames;
/** Whether or to use display lists if VBOs are unavailable for quantized
* meshes. */
protected boolean _useDisplayLists;
/** The inverse of the model space reference transform. */
protected Matrix4f _invRefTransform;
/** The groups of vertices influenced by different sets of bones. */
protected WeightGroup[] _weightGroups;
/** The bones referenced by the weight groups. */
protected Bone[] _bones;
/** The original (undeformed) vertex and normal buffers and the deformed
* versions. */
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
* skinning should only take place when further frames are requested. */
protected int _storeFrameId;
/** Whether or not the stored frame id will be used for blending. */
protected boolean _storeBlend;
/** A dummy mesh that simply hold transformation values. */
protected static final TriMesh DUMMY_MESH = new TriMesh();
private static final long serialVersionUID = 1;
}
@@ -26,15 +26,19 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Properties;
import com.jme.math.Matrix4f;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.Controller;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.threerings.jme.Log;
import com.threerings.jme.model.Model;
import com.threerings.jme.util.JmeUtil;
import com.threerings.jme.tools.ModelDef.TransformNode;
/**
* A basic representation for keyframe animations.
*/
@@ -42,35 +46,39 @@ public class AnimationDef
{
/** The rate of the animation in frames per second. */
public int frameRate;
/** A single frame of the animation. */
public static class FrameDef
{
/** Transform for affected nodes. */
public ArrayList<TransformDef> transforms =
new ArrayList<TransformDef>();
public void addTransform (TransformDef 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 (
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++) {
String name = transforms.get(ii).name;
Spatial target = nodes.get(name);
if (target != null) {
targets.add(target);
if (target == null) {
continue;
}
TransformNode tnode = tnodes.get(name);
if (tnode.baseLocalTransform != null) {
staticTargets.add(target);
} else {
Log.debug("Missing animation target [name=" + name +
"].");
transformTargets.add(target);
}
}
}
/** Returns the array of transforms for this frame. */
public Model.Transform[] getTransforms (Spatial[] targets)
{
@@ -81,7 +89,7 @@ public class AnimationDef
}
return mtransforms;
}
/** Returns the transform for the supplied target. */
protected Model.Transform getTransform (Spatial target)
{
@@ -95,18 +103,18 @@ public class AnimationDef
return null;
}
}
/** A transform for a single node. */
public static class TransformDef
{
/** The name of the affected node. */
public String name;
/** The transformation parameters. */
public float[] translation;
public float[] rotation;
public float[] scale;
/** Returns the live transform object. */
public Model.Transform getTransform ()
{
@@ -117,15 +125,36 @@ public class AnimationDef
new Vector3f(scale[0], scale[1], scale[2]));
}
}
/** The individual frames of the animation. */
public ArrayList<FrameDef> frames = new ArrayList<FrameDef>();
public void addFrame (FrameDef 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
* object.
@@ -134,27 +163,30 @@ public class AnimationDef
* @param nodes the nodes in the model, mapped by name
*/
public Model.Animation createAnimation (
Properties props, HashMap<String, Spatial> nodes)
Properties props, HashMap<String, Spatial> nodes, HashMap<String, TransformNode> tnodes)
{
// 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++) {
frames.get(ii).addTransformTargets(nodes, targets);
frames.get(ii).addTransformTargets(nodes, tnodes, staticTargets, transformTargets);
}
// create and configure the animation
Model.Animation anim = new Model.Animation();
anim.frameRate = frameRate;
anim.repeatType = JmeUtil.parseRepeatType(props.getProperty("repeat_type"),
Controller.RT_CLAMP);
// collect all transforms
anim.transformTargets = targets.toArray(new Spatial[targets.size()]);
anim.transforms = new Model.Transform[frames.size()][targets.size()];
anim.staticTargets = staticTargets.toArray(new Spatial[staticTargets.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++) {
anim.transforms[ii] =
frames.get(ii).getTransforms(anim.transformTargets);
}
return anim;
}
}
@@ -32,6 +32,7 @@ import java.util.HashMap;
import java.util.Properties;
import java.util.logging.Level;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.util.LoggingSystem;
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.ModelNode;
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.ModelParser;
@@ -106,18 +108,32 @@ public class CompileModel
System.out.println("Compiling to " + target + "...");
// load the model content
// parse the model and animations
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>();
Model model = mdef.createModel(props, nodes);
model.initPrototype();
// load the animations, if any
for (int ii = 0; ii < anims.length; ii++) {
System.out.println(" Adding " + afiles[ii] + "...");
AnimationDef adef = _aparser.parseAnimation(afiles[ii].toString());
model.addAnimation(anims[ii], adef.createAnimation(
PropertiesUtil.getSubProperties(props, anims[ii]), nodes));
model.addAnimation(anims[ii], adefs[ii].createAnimation(
PropertiesUtil.getSubProperties(props, anims[ii]), nodes, tnodes));
}
// 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.BoundingSphere;
import com.jme.math.FastMath;
import com.jme.math.Matrix4f;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.batch.GeomBatch;
import com.jme.util.geom.BufferUtils;
import com.samskivert.util.ObjectUtil;
import com.samskivert.util.PropertiesUtil;
import com.samskivert.util.StringUtil;
import com.samskivert.util.Tuple;
import com.threerings.jme.Log;
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.ModelNode;
import com.threerings.jme.model.SkinMesh;
import com.threerings.jme.util.JmeUtil;
import com.threerings.jme.util.SpatialVisitor;
/**
@@ -75,6 +81,21 @@ public class ModelDef
public float[] rotation;
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. */
public Spatial getSpatial (Properties props)
{
@@ -155,6 +176,42 @@ public class ModelDef
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
public Spatial createSpatial (Properties props)
{
@@ -167,6 +224,24 @@ public class ModelDef
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. */
protected ModelMesh createMesh ()
{
@@ -229,6 +304,14 @@ public class ModelDef
/** A triangle mesh that deforms according to bone positions. */
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
protected ModelMesh createMesh ()
{
@@ -250,6 +333,7 @@ public class ModelDef
HashMap<String, SkinMesh.Bone> bones =
new HashMap<String, SkinMesh.Bone>();
int ii = 0;
int mweights = 0, tweights = 0;
for (Map.Entry<Set<String>, WeightGroupDef> entry :
_groups.entrySet()) {
SkinMesh.WeightGroup wgroup = new SkinMesh.WeightGroup();
@@ -267,6 +351,8 @@ public class ModelDef
wgroup.bones[jj++] = bone;
}
wgroup.weights = toArray(entry.getValue().weights);
tweights += wgroup.bones.length;
mweights = Math.max(wgroup.bones.length, mweights);
wgroups[ii++] = wgroup;
}
((SkinMesh)_mesh).setWeightGroups(wgroups);
@@ -315,6 +401,18 @@ public class ModelDef
/** A generic node. */
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
public Spatial createSpatial (Properties props)
{
@@ -329,6 +427,21 @@ public class ModelDef
public float[] normal;
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 (
FloatBuffer vbuf, FloatBuffer nbuf, FloatBuffer tbuf)
{
@@ -418,6 +531,99 @@ public class ModelDef
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. */
public ArrayList<SpatialDef> spatials = new ArrayList<SpatialDef>();
@@ -428,6 +634,85 @@ public class ModelDef
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.
*
@@ -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
pruneUnusedNodes(model, nodes, referenced);
@@ -526,63 +806,17 @@ public class ModelDef
return referenced.contains(node) || hasValidChildren;
}
/** Merges meshes with the same attributes. */
protected void mergeEquivalentMeshes (
Model model, HashMap<String, Spatial> nodes, final HashSet<Spatial> referenced)
/** Determines whether a pair of matrices are "close enough" to equal. */
public static boolean epsilonEquals (Matrix4f m1, Matrix4f m2)
{
final HashMap<Object, ArrayList<ModelMesh>> meshes =
new HashMap<Object, ArrayList<ModelMesh>>();
new SpatialVisitor<ModelMesh>(ModelMesh.class) {
public void traverse (Spatial spatial) {
// cut out when we encounter referenced nodes
if (!referenced.contains(spatial)) {
spatial.updateWorldVectors();
super.traverse(spatial);
for (int ii = 0; ii < 4; ii++) {
for (int jj = 0; jj < 4; jj++) {
if (FastMath.abs(m1.get(ii, jj) - m2.get(ii, jj)) > 0.0001f) {
return false;
}
}
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();
}
}
/** 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);
return true;
}
/** Converts a boxed Integer list to an unboxed int array. */
@@ -30,6 +30,8 @@ import java.nio.IntBuffer;
import org.lwjgl.opengl.ARBShaderObjects;
import com.jme.math.Matrix4f;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.Controller;
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
* comma-delimited values representing an axis vector. The value returned may be one of