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; }