Changed animations to allow support for morph style (linear blend

between deformed meshes) and flipbook style (simple switch between 
deformed meshes) in addition to skin style (meshes deformed in real 
time).  Currently, the resolution is the same as the animation frame 
rate.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4176 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Andrzej Kapolka
2006-06-06 23:00:43 +00:00
parent 51eb40d77f
commit b7ca8f50f4
7 changed files with 291 additions and 106 deletions
+5
View File
@@ -16,6 +16,11 @@ m.view_bounds = Show Bounds
m.view_normals = Show Normals
m.view_light = Rotate Light...
m.animation_mode = Animation Mode
m.mode_flipbook = Flipbook
m.mode_morph = Morph
m.mode_skin = Skin
m.load_title = Select Model to Load
m.load_filter = Model Files (*.properties, *.dat)
+77 -32
View File
@@ -63,6 +63,11 @@ import com.threerings.jme.Log;
*/
public class Model extends ModelNode
{
/** 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
@@ -109,6 +114,9 @@ public class Model extends ModelNode
/** 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.
*/
@@ -134,9 +142,32 @@ public class Model extends ModelNode
(Spatial)pnodes.get(transformTargets[ii]);
}
anim.animId = animId;
anim.stored = stored;
return anim;
}
/**
* Applies the transforms for a frame of this animation.
*/
public void applyFrame (int fidx)
{
Transform[] xforms = transforms[fidx];
for (int ii = 0; ii < transformTargets.length; ii++) {
xforms[ii].apply(transformTargets[ii]);
}
}
/**
* Blends the transforms between two frames of this animation.
*/
public void blendFrames (int fidx, int nidx, float alpha)
{
Transform[] xforms = transforms[fidx], nxforms = transforms[nidx];
for (int ii = 0; ii < transformTargets.length; ii++) {
xforms[ii].blend(nxforms[ii], alpha, transformTargets[ii]);
}
}
private void writeObject (ObjectOutputStream out)
throws IOException
{
@@ -357,11 +388,12 @@ public class Model extends ModelNode
*/
public void initPrototype ()
{
// assign identifiers to the animations
// initialize shared transient animation state
if (_anims != null) {
int nextId = 1;
for (Animation anim : _anims.values()) {
anim.animId = nextId++;
anim.stored = new boolean[anim.transforms.length];
}
}
setReferenceTransforms();
@@ -408,25 +440,21 @@ public class Model extends ModelNode
}
/**
* 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
* <code>0</code> to disable quantization
* Sets the animation mode to use for this model. This should be set
* on the prototype before any animations are started or any instances
* are created.
*/
public void setAnimationResolution (float resolution)
public void setAnimationMode (AnimationMode mode)
{
_animResolution = resolution;
_animMode = mode;
}
/**
* Returns the resolution at which animations are quantized over time,
* or <code>0</code> if quantization is disabled.
* Returns the animation mode configured for this model.
*/
public float getAnimationResolution ()
public AnimationMode getAnimationMode ()
{
return _animResolution;
return _animMode;
}
/**
@@ -687,7 +715,7 @@ public class Model extends ModelNode
mstore._anims = new HashMap<String, Animation>();
}
mstore._pnodes = (HashMap)properties.originalToCopy.clone();
mstore._animResolution = _animResolution;
mstore._animMode = _animMode;
return mstore;
}
@@ -765,9 +793,9 @@ public class Model extends ModelNode
*/
protected void updateAnimation (float time)
{
float res = (_animResolution == 0f) ?
0f : (_anim.frameRate / _animResolution);
if (_elapsed < res && _elapsed > 0f) {
// no need to update between frames for flipbook animation
if (_animMode == AnimationMode.FLIPBOOK && _elapsed > 0f &&
_elapsed < 1f) {
_elapsed += (time * _anim.frameRate);
return;
}
@@ -777,23 +805,40 @@ public class Model extends ModelNode
advanceFrameCounter();
_elapsed -= 1f;
}
float qelapsed = (res == 0f) ?
_elapsed : (res * (int)(_elapsed / res));
// 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], qelapsed, targets[ii]);
}
if (res != 0f) {
int frameId = (_anim.animId << 16) |
(int)((_fidx + _elapsed)/res);
setAnimationFrame(frameId);
if (_animMode == AnimationMode.FLIPBOOK) {
int frameId = (_anim.animId << 16) | _fidx;
_anim.applyFrame(_fidx);
if (!_anim.stored[_fidx]) {
storeMeshFrame(frameId, false);
updateWorldData(0f);
_anim.stored[_fidx] = true;
}
setMeshFrame(frameId);
} else if (_animMode == AnimationMode.MORPH) {
int frameId1 = (_anim.animId << 16) | _fidx,
frameId2 = (_anim.animId << 16) | _nidx;
if (!_anim.stored[_fidx]) {
storeMeshFrame(frameId1, true);
_anim.applyFrame(_fidx);
updateWorldData(0f);
_anim.stored[_fidx] = true;
}
if (!_anim.stored[_nidx]) {
storeMeshFrame(frameId2, true);
_anim.applyFrame(_nidx);
updateWorldData(0f);
_anim.stored[_nidx] = true;
}
_anim.blendFrames(_fidx, _nidx, _elapsed);
blendMeshFrames(frameId1, frameId2, _elapsed);
} else { // _animMode == AnimationMode.SKIN
_anim.blendFrames(_fidx, _nidx, _elapsed);
}
}
@@ -872,8 +917,8 @@ public class Model extends ModelNode
* instances. */
protected CloneCreator _ccreator;
/** The resolution of the model's animations in frames per second. */
protected float _animResolution;
/** The animation mode to use for this model. */
protected AnimationMode _animMode;
/** For instances, maps prototype nodes to their corresponding instance
* nodes. */
@@ -343,7 +343,19 @@ public class ModelMesh extends TriMesh
}
// documentation inherited from interface ModelSpatial
public void setAnimationFrame (int frameId)
public void storeMeshFrame (int frameId, boolean blend)
{
// no-op
}
// documentation inherited from interface ModelSpatial
public void setMeshFrame (int frameId)
{
// no-op
}
// documentation inherited from interface ModelSpatial
public void blendMeshFrames (int frameId1, int frameId2, float alpha)
{
// no-op
}
@@ -249,12 +249,35 @@ public class ModelNode extends Node
}
// documentation inherited from interface ModelSpatial
public void setAnimationFrame (int frameId)
public void storeMeshFrame (int frameId, boolean blend)
{
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
Spatial child = getChild(ii);
if (child instanceof ModelSpatial) {
((ModelSpatial)child).setAnimationFrame(frameId);
((ModelSpatial)child).storeMeshFrame(frameId, blend);
}
}
}
// documentation inherited from interface ModelSpatial
public void setMeshFrame (int frameId)
{
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
Spatial child = getChild(ii);
if (child instanceof ModelSpatial) {
((ModelSpatial)child).setMeshFrame(frameId);
}
}
}
// documentation inherited from interface ModelSpatial
public void blendMeshFrames (int frameId1, int frameId2, float alpha)
{
for (int ii = 0, nn = getQuantity(); ii < nn; ii++) {
Spatial child = getChild(ii);
if (child instanceof ModelSpatial) {
((ModelSpatial)child).blendMeshFrames(
frameId1, frameId2, alpha);
}
}
}
@@ -64,15 +64,28 @@ 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.
* Recursively requests that the current state of all skinned meshes be
* stored as an animation frame on the next update.
*
* @param frameId the frame id, which uniquely identifies one frame of
* one animation
* @param blend whether or not the stored frames will be retrieved by
* calls to {@link #blendAnimationFrames} as opposed to
* {@link #setAnimationFrames}
*/
public void setAnimationFrame (int frameId);
public void storeMeshFrame (int frameId, boolean blend);
/**
* Recursively switches all skinned meshes to a stored animation frame for
* the next update.
*/
public void setMeshFrame (int frameId);
/**
* Recursively blends all skinned meshes between two stored animation
* frames for the next update.
*/
public void blendMeshFrames (int frameId1, int frameId2, float alpha);
/**
* Creates or populates and returns a clone of this object using the given
+110 -66
View File
@@ -179,7 +179,7 @@ public class SkinMesh extends ModelMesh
storeOriginalBuffers();
// initialize the quantized frame table
_frames = new HashIntMap<TriangleBatch>();
_frames = new HashIntMap<Object>();
}
@Override // documentation inherited
@@ -278,10 +278,37 @@ public class SkinMesh extends ModelMesh
}
@Override // documentation inherited
public void setAnimationFrame (int frameId)
public void storeMeshFrame (int frameId, boolean blend)
{
// switch to the specified frame id on next update
_updateFrameId = frameId;
_storeFrameId = frameId;
_storeBlend = blend;
}
@Override // documentation inherited
public void setMeshFrame (int frameId)
{
TriangleBatch batch = getBatch(0),
tbatch = (TriangleBatch)_frames.get(frameId);
if (batch instanceof SharedBatch) {
((SharedBatch)batch).setTarget(tbatch);
} else {
clearBatches();
addBatch(new SharedBatch(tbatch));
getBatch(0).updateRenderState();
}
}
@Override // documentation inherited
public void blendMeshFrames (int frameId1, int frameId2, float alpha)
{
BlendFrame frame1 = (BlendFrame)_frames.get(frameId1),
frame2 = (BlendFrame)_frames.get(frameId2);
frame1.blend(frame2, alpha, _vbuf, _nbuf);
FloatBuffer vbuf = getVertexBuffer(0), nbuf = getNormalBuffer(0);
vbuf.rewind();
vbuf.put(_vbuf);
nbuf.rewind();
nbuf.put(_nbuf);
}
@Override // documentation inherited
@@ -303,54 +330,9 @@ public class SkinMesh extends ModelMesh
public void updateWorldData (float time)
{
super.updateWorldData(time);
if (_weightGroups == null) {
if (_weightGroups == null || _storeFrameId == -1) {
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) {
@@ -399,17 +381,49 @@ public class SkinMesh extends ModelMesh
}
}
// copy it from array to buffer
FloatBuffer vbuf = getVertexBuffer(0), nbuf = getNormalBuffer(0);
vbuf.rewind();
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());
// if skinning in real time, copy the data from arrays to buffers;
// otherwise, store the mesh as an animation frame
if (_storeFrameId == 0) {
FloatBuffer vbuf = getVertexBuffer(0), nbuf = getNormalBuffer(0);
vbuf.rewind();
vbuf.put(_vbuf);
nbuf.rewind();
nbuf.put(_nbuf);
} else {
storeFrame();
_storeFrameId = -1;
}
}
/**
* Stores the current frame data for later use.
*/
protected void storeFrame ()
{
if (_storeBlend) {
_frames.put(_storeFrameId, new BlendFrame(
(float[])_vbuf.clone(), (float[])_nbuf.clone()));
} else {
TriangleBatch batch = getBatch(0), 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(_vbuf));
tbatch.setNormalBuffer(BufferUtils.createFloatBuffer(_nbuf));
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) {
tbatch.lockMeshes(
DisplaySystem.getDisplaySystem().getRenderer());
}
_frames.put(_storeFrameId, tbatch);
}
}
@@ -427,9 +441,34 @@ 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<TriangleBatch> _frames;
/** 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)
{
float[] nvbuf = next.vbuf, nnbuf = next.nbuf;
float ialpha = 1f - alpha;
for (int ii = 0, nn = vbuf.length; ii < nn; ii++) {
rvbuf[ii] = vbuf[ii] * ialpha + nvbuf[ii] * alpha;
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. */
@@ -445,8 +484,13 @@ 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;
/** The frame id to store on the next update. If 0, don't store any frame
* 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;
/** The node's transform in model space. */
protected Matrix4f _modelTransform = new Matrix4f();
@@ -46,6 +46,8 @@ import java.util.logging.Level;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.ButtonModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
@@ -59,6 +61,7 @@ import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JSlider;
import javax.swing.KeyStroke;
import javax.swing.event.ChangeEvent;
@@ -205,6 +208,38 @@ public class ModelViewer extends JmeCanvasApp
view.add(_normals);
view.addSeparator();
JMenu amode = new JMenu(_msg.get("m.animation_mode"));
final JRadioButtonMenuItem flipbook =
new JRadioButtonMenuItem(_msg.get("m.mode_flipbook")),
morph = new JRadioButtonMenuItem(_msg.get("m.mode_morph")),
skin = new JRadioButtonMenuItem(_msg.get("m.mode_skin"));
ButtonGroup mgroup = new ButtonGroup() {
public void setSelected (ButtonModel model, boolean b) {
super.setSelected(model, b);
if (b) {
if (flipbook.isSelected()) {
_animMode = Model.AnimationMode.FLIPBOOK;
} else if (morph.isSelected()) {
_animMode = Model.AnimationMode.MORPH;
} else {
_animMode = Model.AnimationMode.SKIN;
}
if (_loaded != null) {
loadModel(_loaded); // reload
}
}
}
};
mgroup.add(flipbook);
mgroup.add(morph);
mgroup.add(skin);
mgroup.setSelected(skin.getModel(), true);
amode.add(skin);
amode.add(morph);
amode.add(flipbook);
view.add(amode);
view.addSeparator();
Action rlight = new AbstractAction(_msg.get("m.view_light")) {
public void actionPerformed (ActionEvent e) {
if (_rldialog == null) {
@@ -447,6 +482,7 @@ public class ModelViewer extends JmeCanvasApp
throw new Exception(_msg.get("m.invalid_type"));
}
_status.setText(_msg.get("m.loaded_model", fpath));
_loaded = file;
} catch (Exception e) {
e.printStackTrace();
@@ -494,6 +530,7 @@ public class ModelViewer extends JmeCanvasApp
_ctx.getGeometry().detachChild(_model);
}
_ctx.getGeometry().attachChild(_model = model);
_model.setAnimationMode(_animMode);
_model.lockStaticMeshes(_ctx.getRenderer(), true, true);
// resolve the textures from the file's directory
@@ -558,6 +595,9 @@ public class ModelViewer extends JmeCanvasApp
/** The path of the initial model to load. */
protected String _path;
/** The last model successfully loaded. */
protected File _loaded;
/** The viewer frame. */
protected JFrame _frame;
@@ -582,6 +622,9 @@ public class ModelViewer extends JmeCanvasApp
/** The model file chooser. */
protected JFileChooser _chooser;
/** The desired animation mode. */
protected Model.AnimationMode _animMode;
/** The light rotation dialog. */
protected RotateLightDialog _rldialog;