diff --git a/src/java/com/threerings/jme/model/Model.java b/src/java/com/threerings/jme/model/Model.java
index cb0a63088..3923e3f93 100644
--- a/src/java/com/threerings/jme/model/Model.java
+++ b/src/java/com/threerings/jme/model/Model.java
@@ -106,6 +106,9 @@ public class Model extends ModelNode
/** The animation transforms (one transform per target per frame). */
public transient Transform[][] transforms;
+ /** Uniquely identifies this animation within the model. */
+ public transient int animId;
+
/**
* Returns this animation's duration in seconds.
*/
@@ -130,6 +133,7 @@ public class Model extends ModelNode
anim.transformTargets[ii] =
(Spatial)pnodes.get(transformTargets[ii]);
}
+ anim.animId = animId;
return anim;
}
@@ -353,6 +357,13 @@ public class Model extends ModelNode
*/
public void initPrototype ()
{
+ // assign identifiers to the animations
+ if (_anims != null) {
+ int nextId = 1;
+ for (Animation anim : _anims.values()) {
+ anim.animId = nextId++;
+ }
+ }
setReferenceTransforms();
cullInvisibleNodes();
initInstance();
@@ -396,6 +407,28 @@ public class Model extends ModelNode
updateWorldData(0f);
}
+ /**
+ * Sets the resolution at which to quantize animations over time.
+ * This should be set on the prototype before any animations are
+ * started or any instances are created.
+ *
+ * @param the temporal quantization rate in frames per second, or
+ * 0 to disable quantization
+ */
+ public void setAnimationResolution (float resolution)
+ {
+ _animResolution = resolution;
+ }
+
+ /**
+ * Returns the resolution at which animations are quantized over time,
+ * or 0 if quantization is disabled.
+ */
+ public float getAnimationResolution ()
+ {
+ return _animResolution;
+ }
+
/**
* Returns the names of the model's animations.
*/
@@ -654,6 +687,7 @@ public class Model extends ModelNode
mstore._anims = new HashMap();
}
mstore._pnodes = (HashMap)properties.originalToCopy.clone();
+ mstore._animResolution = _animResolution;
return mstore;
}
@@ -731,19 +765,35 @@ public class Model extends ModelNode
*/
protected void updateAnimation (float time)
{
+ float res = (_animResolution == 0f) ?
+ 0f : (_anim.frameRate / _animResolution);
+ if (_elapsed < res && _elapsed > 0f) {
+ _elapsed += (time * _anim.frameRate);
+ return;
+ }
+
// advance the frame counter if necessary
while (_elapsed > 1f) {
advanceFrameCounter();
_elapsed -= 1f;
}
+ float qelapsed = (res == 0f) ?
+ _elapsed : (res * (int)(_elapsed / res));
- // update the target transforms if not outside the view frustum
+ // update the target transforms and animation frame if not outside the
+ // view frustum
if (!_outside) {
Spatial[] targets = _anim.transformTargets;
Transform[] xforms = _anim.transforms[_fidx],
nxforms = _anim.transforms[_nidx];
for (int ii = 0; ii < targets.length; ii++) {
- xforms[ii].blend(nxforms[ii], _elapsed, targets[ii]);
+ xforms[ii].blend(nxforms[ii], qelapsed, targets[ii]);
+ }
+
+ if (res != 0f) {
+ int frameId = (_anim.animId << 16) |
+ (int)((_fidx + _elapsed)/res);
+ setAnimationFrame(frameId);
}
}
@@ -822,6 +872,9 @@ public class Model extends ModelNode
* instances. */
protected CloneCreator _ccreator;
+ /** The resolution of the model's animations in frames per second. */
+ protected float _animResolution;
+
/** For instances, maps prototype nodes to their corresponding instance
* nodes. */
protected HashMap _pnodes;
@@ -927,8 +980,6 @@ public class Model extends ModelNode
/** The name of the animation cancelled. */
protected String _name;
}
-
-
-
+
private static final long serialVersionUID = 1;
}
diff --git a/src/java/com/threerings/jme/model/ModelMesh.java b/src/java/com/threerings/jme/model/ModelMesh.java
index 40915e39d..cc8f5a21f 100644
--- a/src/java/com/threerings/jme/model/ModelMesh.java
+++ b/src/java/com/threerings/jme/model/ModelMesh.java
@@ -342,6 +342,12 @@ public class ModelMesh extends TriMesh
}
}
+ // documentation inherited from interface ModelSpatial
+ public void setAnimationFrame (int frameId)
+ {
+ // no-op
+ }
+
// documentation inherited from interface ModelSpatial
public void writeBuffers (FileChannel out)
throws IOException
diff --git a/src/java/com/threerings/jme/model/ModelNode.java b/src/java/com/threerings/jme/model/ModelNode.java
index ccc2841fd..73209a58a 100644
--- a/src/java/com/threerings/jme/model/ModelNode.java
+++ b/src/java/com/threerings/jme/model/ModelNode.java
@@ -248,6 +248,17 @@ public class ModelNode extends Node
}
}
+ // documentation inherited from interface ModelSpatial
+ public void setAnimationFrame (int frameId)
+ {
+ for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
+ Spatial child = getChild(ii);
+ if (child instanceof ModelSpatial) {
+ ((ModelSpatial)child).setAnimationFrame(frameId);
+ }
+ }
+ }
+
// documentation inherited from interface ModelSpatial
public void writeBuffers (FileChannel out)
throws IOException
diff --git a/src/java/com/threerings/jme/model/ModelSpatial.java b/src/java/com/threerings/jme/model/ModelSpatial.java
index 4f7efec53..c4ce1203b 100644
--- a/src/java/com/threerings/jme/model/ModelSpatial.java
+++ b/src/java/com/threerings/jme/model/ModelSpatial.java
@@ -63,6 +63,17 @@ public interface ModelSpatial
*/
public void resolveTextures (TextureProvider tprov);
+ /**
+ * Recursively sets the quantized animation frame. For skinned meshes,
+ * the first time the frame is set causes the current skin to be stored
+ * in a table shared by all instances. At subsequent times, the stored
+ * skin is displayed.
+ *
+ * @param frameId the frame id, which uniquely identifies one frame of
+ * one animation
+ */
+ public void setAnimationFrame (int frameId);
+
/**
* Creates or populates and returns a clone of this object using the given
* clone properties.
diff --git a/src/java/com/threerings/jme/model/SkinMesh.java b/src/java/com/threerings/jme/model/SkinMesh.java
index 943cdc02b..b652d7764 100644
--- a/src/java/com/threerings/jme/model/SkinMesh.java
+++ b/src/java/com/threerings/jme/model/SkinMesh.java
@@ -40,9 +40,15 @@ import com.jme.math.Matrix4f;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.Spatial;
+import com.jme.scene.TriMesh;
import com.jme.scene.VBOInfo;
+import com.jme.scene.batch.SharedBatch;
+import com.jme.scene.batch.TriangleBatch;
+import com.jme.system.DisplaySystem;
import com.jme.util.geom.BufferUtils;
+import com.samskivert.util.HashIntMap;
+
import com.threerings.jme.Log;
/**
@@ -171,6 +177,9 @@ public class SkinMesh extends ModelMesh
// store the current buffers as the originals
storeOriginalBuffers();
+
+ // initialize the quantized frame table
+ _frames = new HashIntMap();
}
@Override // documentation inherited
@@ -198,6 +207,7 @@ public class SkinMesh extends ModelMesh
properties.addProperty("vertices");
properties.addProperty("normals");
properties.addProperty("displaylistid");
+ mstore._frames = _frames;
mstore._bones = new Bone[_bones.length];
HashMap bmap = new HashMap();
for (int ii = 0; ii < _bones.length; ii++) {
@@ -263,6 +273,14 @@ public class SkinMesh extends ModelMesh
vboinfo.setVBOIndexEnabled(true);
setVBOInfo(vboinfo);
}
+ _useDisplayLists = useDisplayLists;
+ }
+
+ @Override // documentation inherited
+ public void setAnimationFrame (int frameId)
+ {
+ // switch to the specified frame id on next update
+ _updateFrameId = frameId;
}
@Override // documentation inherited
@@ -287,6 +305,51 @@ public class SkinMesh extends ModelMesh
if (_weightGroups == null) {
return;
}
+ // determine if we are using/updating a quantized frame
+ TriangleBatch lockBatch = null;
+ if (_updateFrameId > 0) {
+ if (_currentFrameId == _updateFrameId) {
+ return;
+ }
+ TriangleBatch batch = getBatch(0),
+ tbatch = _frames.get(_updateFrameId);
+ boolean reskin = false;
+ if (tbatch == null) {
+ _frames.put(_updateFrameId, tbatch = new TriangleBatch());
+ tbatch.setParentGeom(DUMMY_MESH);
+ tbatch.setColorBuffer(batch.getColorBuffer());
+ tbatch.setTextureBuffer(batch.getTextureBuffer(0), 0);
+ tbatch.setIndexBuffer(batch.getIndexBuffer());
+ tbatch.setVertexBuffer(
+ BufferUtils.createFloatBuffer(_ovbuf.length));
+ tbatch.setNormalBuffer(
+ BufferUtils.createFloatBuffer(_onbuf.length));
+ VBOInfo ovboinfo = batch.getVBOInfo();
+ if (ovboinfo != null) {
+ VBOInfo vboinfo = new VBOInfo(true);
+ vboinfo.setVBOIndexEnabled(true);
+ vboinfo.setVBOColorID(ovboinfo.getVBOColorID());
+ vboinfo.setVBOTextureID(0, ovboinfo.getVBOTextureID(0));
+ vboinfo.setVBOIndexID(ovboinfo.getVBOIndexID());
+ tbatch.setVBOInfo(vboinfo);
+ } else if (_useDisplayLists) {
+ lockBatch = tbatch;
+ }
+ reskin = true;
+ }
+ if (batch instanceof SharedBatch) {
+ ((SharedBatch)batch).setTarget(tbatch);
+ } else {
+ clearBatches();
+ addBatch(new SharedBatch(tbatch));
+ getBatch(0).updateRenderState();
+ }
+ _currentFrameId = _updateFrameId;
+ if (!reskin) {
+ return;
+ }
+ }
+
// update the bone transforms
_modelTransform.invert(_transform);
for (Bone bone : _bones) {
@@ -341,6 +404,12 @@ public class SkinMesh extends ModelMesh
vbuf.put(_vbuf);
nbuf.rewind();
nbuf.put(_nbuf);
+
+ // compile the target batch to a display list if requested
+ if (lockBatch != null) {
+ lockBatch.lockMeshes(
+ DisplaySystem.getDisplaySystem().getRenderer());
+ }
}
/**
@@ -357,6 +426,10 @@ public class SkinMesh extends ModelMesh
_nbuf = new float[_onbuf.length];
}
+ /** Pre-skinned meshes shared between all instances corresponding to
+ * frame ids from {@link #setAnimationFrame}. */
+ protected HashIntMap _frames;
+
/** The groups of vertices influenced by different sets of bones. */
protected WeightGroup[] _weightGroups;
@@ -367,11 +440,21 @@ public class SkinMesh extends ModelMesh
* versions. */
protected float[] _ovbuf, _onbuf, _vbuf, _nbuf;
+ /** The desired and current frame id, or -1 for continuous animation. */
+ protected int _updateFrameId = -1, _currentFrameId;
+
+ /** Whether or to use display lists if VBOs are unavailable for quantized
+ * meshes. */
+ protected boolean _useDisplayLists;
+
/** The node's transform in model space. */
protected Matrix4f _modelTransform = new Matrix4f();
/** Working transform. */
protected Matrix4f _transform = new Matrix4f();
+ /** A dummy mesh that simply hold transformation values. */
+ protected static final TriMesh DUMMY_MESH = new TriMesh();
+
private static final long serialVersionUID = 1;
}