Merging the procedural animation controllers and emitters into Model

functionality, so that they will be visible in the model viewer.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4046 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Andrzej Kapolka
2006-04-21 23:10:23 +00:00
parent 7ce2b38ad5
commit f6608ea528
5 changed files with 438 additions and 28 deletions
+120 -24
View File
@@ -36,6 +36,7 @@ import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Properties;
@@ -45,6 +46,7 @@ import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.CloneCreator;
import com.jme.scene.Controller;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.threerings.jme.Log;
@@ -58,6 +60,13 @@ public class Model extends ModelNode
* for non-repeating animations) or cancelled. */
public interface AnimationObserver
{
/**
* Called when an animation has started.
*
* @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.
*
@@ -90,6 +99,14 @@ public class Model extends ModelNode
/** The animation transforms (one transform per target per frame). */
public transient Transform[][] transforms;
/**
* Returns this animation's duration in seconds.
*/
public float getDuration ()
{
return (float)transforms.length / frameRate;
}
/**
* Rebinds this animation for a prototype instance.
*
@@ -223,8 +240,8 @@ public class Model extends ModelNode
}
ois.close();
// set the reference transforms before any animations are applied
model.setReferenceTransforms();
// initialize the model as a prototype
model.initPrototype();
return model;
}
@@ -253,6 +270,16 @@ public class Model extends ModelNode
return _props;
}
/**
* Initializes this model as prototype. Only necessary when the prototype
* was not loaded through {@link #readFromFile}.
*/
public void initPrototype ()
{
setReferenceTransforms();
initInstance();
}
/**
* Adds an animation to the model's library. This should only be called by
* the model compiler.
@@ -268,38 +295,65 @@ public class Model extends ModelNode
/**
* Returns the names of the model's animations.
*/
public String[] getAnimations ()
public String[] getAnimationNames ()
{
if (_prototype != null) {
return _prototype.getAnimations();
return _prototype.getAnimationNames();
}
return (_anims == null) ? new String[0] :
_anims.keySet().toArray(new String[_anims.size()]);
}
/**
* Starts the named animation.
* Checks whether the unit has an animation with the given name.
*/
public void startAnimation (String name)
public boolean hasAnimation (String name)
{
if (_anim != null) {
stopAnimation();
if (_prototype != null) {
return _prototype.hasAnimation(name);
}
return (_anims == null) ? false : _anims.containsKey(name);
}
/**
* Starts the named animation.
*
* @return a reference to the started animation
*/
public Animation startAnimation (String name)
{
Animation anim = getAnimation(name);
if (anim == null) {
return null;
}
_anim = anim;
_animName = name;
_fidx = 0;
_nidx = 1;
_fdir = +1;
_elapsed = 0f;
_animObservers.apply(new AnimStartedOp(_animName));
return anim;
}
/**
* Gets a reference to the animation with the given name.
*/
public Animation getAnimation (String name)
{
Animation anim = _anims.get(name);
if (anim != null) {
startAnimation(name, anim);
return;
return anim;
}
if (_prototype != null) {
Animation panim = _prototype._anims.get(name);
if (panim != null) {
_anims.put(name, anim = panim.rebind(_pnodes));
startAnimation(name, anim);
return;
return anim;
}
}
Log.warning("Requested unknown animation [name=" +
name + "].");
Log.warning("Requested unknown animation [name=" + name + "].");
return null;
}
/**
@@ -347,6 +401,20 @@ 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
* node is created and added when this method is first called.
*/
public Node getEmissionNode ()
{
if (_emissionNode == null) {
attachChild(_emissionNode = new Node("emissions"));
_emissionNode.setTransformable(false);
}
return _emissionNode;
}
/**
* Writes this model out to a file.
*/
@@ -371,6 +439,7 @@ public class Model extends ModelNode
super.writeExternal(out);
out.writeObject(_props);
out.writeObject(_anims);
out.writeObject(getControllers());
}
@Override // documentation inherited
@@ -380,6 +449,10 @@ public class Model extends ModelNode
super.readExternal(in);
_props = (Properties)in.readObject();
_anims = (HashMap<String, Animation>)in.readObject();
ArrayList controllers = (ArrayList)in.readObject();
for (Object ctrl : controllers) {
addController((Controller)ctrl);
}
}
/**
@@ -410,7 +483,9 @@ public class Model extends ModelNode
_ccreator.addProperty("displaylistid");
_ccreator.addProperty("bound");
}
return (Model)_ccreator.createCopy();
Model instance = (Model)_ccreator.createCopy();
instance.initInstance();
return instance;
}
@Override // documentation inherited
@@ -441,18 +516,18 @@ public class Model extends ModelNode
// update children
super.updateWorldData(time);
}
/**
* Starts the supplied animation.
* Initializes the per-instance state of this model.
*/
protected void startAnimation (String name, Animation anim)
protected void initInstance ()
{
_anim = anim;
_animName = name;
_fidx = 0;
_nidx = 1;
_fdir = +1;
_elapsed = 0f;
// initialize the controllers
for (Object ctrl : getControllers()) {
if (ctrl instanceof ModelController) {
((ModelController)ctrl).init(this);
}
}
}
/**
@@ -541,10 +616,31 @@ public class Model extends ModelNode
/** The frame portion elapsed since the start of the current frame. */
protected float _elapsed;
/** The child node that contains the model's emissions in world space. */
protected Node _emissionNode;
/** 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>
{
public AnimStartedOp (String name)
{
_name = name;
}
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>
@@ -0,0 +1,147 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.model;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Collections;
import java.util.HashSet;
import java.util.Properties;
import com.jme.renderer.CloneCreator;
import com.jme.scene.Controller;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.samskivert.util.StringUtil;
/**
* The superclass of procedural animation controllers for models.
*/
public abstract class ModelController extends Controller
implements Externalizable
{
/**
* Configures this controller based on the supplied (sub-)properties and
* controller target.
*/
public void configure (Properties props, Spatial target)
{
_target = target;
String[] anims = StringUtil.parseStringArray(
props.getProperty("animations", ""));
if (anims.length == 0) {
return;
}
_animations = new HashSet<String>();
Collections.addAll(_animations, anims);
}
/**
* Initializes this controller.
*/
public void init (Model model)
{
model.addAnimationObserver(_animobs);
if (_animations != null) {
setActive(false);
}
}
@Override // documentation inherited
public Controller putClone (Controller store, CloneCreator properties)
{
if (store == null) {
return null;
}
ModelController mstore = (ModelController)store;
mstore._target = (Spatial)properties.originalToCopy.get(_target);
if (mstore._target == null) {
properties.originalToCopy.put(_target,
mstore._target = _target.putClone(null, properties));
}
return mstore;
}
// documentation inherited from interface Externalizable
public void writeExternal (ObjectOutput out)
throws IOException
{
out.writeObject(_target);
out.writeObject(_animations);
}
// documentation inherited from interface Externalizable
public void readExternal (ObjectInput in)
throws IOException, ClassNotFoundException
{
_target = (Spatial)in.readObject();
_animations = (HashSet<String>)in.readObject();
}
/**
* Called when an animation is started on the model.
*/
protected void animationStarted (String anim)
{
if (_animations != null && _animations.contains(anim)) {
setActive(true);
}
}
/**
* Called when an animation is stopped on the model.
*/
protected void animationStopped (String anim)
{
if (_animations != null) {
setActive(false);
}
}
/** The target to control. */
protected Spatial _target;
/** The animations for which this controller should be active, or
* <code>null</code> for all of them. */
protected HashSet<String> _animations;
/** Listens to the model's animation state. */
protected Model.AnimationObserver _animobs =
new Model.AnimationObserver() {
public boolean animationStarted (Model model, String anim) {
ModelController.this.animationStarted(anim);
return true;
}
public boolean animationCompleted (Model model, String anim) {
animationStopped(anim);
return true;
}
public boolean animationCancelled (Model model, String anim) {
animationStopped(anim);
return true;
}
};
}
@@ -0,0 +1,126 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2005 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.jme.model;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Properties;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.renderer.CloneCreator;
import com.jme.scene.Controller;
import com.jme.scene.Spatial;
import com.samskivert.util.StringUtil;
import com.threerings.jme.Log;
/**
* A procedural animation that rotates a node around at a constant angular
* velocity.
*/
public class Rotator extends ModelController
{
@Override // documentation inherited
public void configure (Properties props, Spatial target)
{
super.configure(props, target);
String axisstr = props.getProperty("axis", "x"),
rpsstr = props.getProperty("radpersec", "3.14");
if (axisstr.equalsIgnoreCase("x")) {
_axis = Vector3f.UNIT_X;
} else if (axisstr.equalsIgnoreCase("y")) {
_axis = Vector3f.UNIT_Y;
} else if (axisstr.equalsIgnoreCase("z")) {
_axis = Vector3f.UNIT_Z;
} else {
float[] axis = StringUtil.parseFloatArray(axisstr);
if (axis != null && axis.length == 3) {
_axis = new Vector3f(axis[0], axis[1],
axis[2]).normalizeLocal();
} else {
Log.warning("Invalid rotation axis [axis=" + axisstr + "].");
}
}
try {
_radpersec = Float.parseFloat(rpsstr);
} catch (NumberFormatException e) {
Log.warning("Invalid rotation rate [radpersec=" + rpsstr + "].");
}
}
// documentation inherited
public void update (float time)
{
if (!isActive()) {
return;
}
_rot.fromAngleNormalAxis(time * _radpersec, _axis);
_target.getLocalRotation().multLocal(_rot);
}
@Override // documentation inherited
public Controller putClone (Controller store, CloneCreator properties)
{
Rotator rstore;
if (store == null) {
rstore = new Rotator();
} else {
rstore = (Rotator)store;
}
super.putClone(rstore, properties);
rstore._axis = _axis;
rstore._radpersec = _radpersec;
return rstore;
}
// documentation inherited from interface Externalizable
public void writeExternal (ObjectOutput out)
throws IOException
{
super.writeExternal(out);
out.writeObject(_axis);
out.writeFloat(_radpersec);
}
// documentation inherited from interface Externalizable
public void readExternal (ObjectInput in)
throws IOException, ClassNotFoundException
{
super.readExternal(in);
_axis = (Vector3f)in.readObject();
_radpersec = in.readFloat();
}
/** The axis about which to rotate. */
protected Vector3f _axis;
/** The velocity at which to rotate in radians per second. */
protected float _radpersec;
/** A temporary quaternion. */
protected Quaternion _rot = new Quaternion();
}
@@ -38,9 +38,11 @@ import com.jme.scene.Spatial;
import com.jme.util.geom.BufferUtils;
import com.samskivert.util.PropertiesUtil;
import com.samskivert.util.StringUtil;
import com.threerings.jme.Log;
import com.threerings.jme.model.Model;
import com.threerings.jme.model.ModelController;
import com.threerings.jme.model.ModelMesh;
import com.threerings.jme.model.ModelNode;
import com.threerings.jme.model.SkinMesh;
@@ -358,9 +360,45 @@ public class ModelDef
}
}
// create any controllers listed
String[] controllers = StringUtil.parseStringArray(
props.getProperty("controllers", ""));
for (int ii = 0; ii < controllers.length; ii++) {
Spatial target = nodes.get(controllers[ii]);
if (target == null) {
Log.warning("Missing controller node [name=" +
controllers[ii] + "].");
continue;
}
ModelController ctrl = createController(
PropertiesUtil.getSubProperties(props, controllers[ii]),
target);
if (ctrl != null) {
model.addController(ctrl);
}
}
return model;
}
/** Creates, configures, and returns a model controller. */
protected ModelController createController (
Properties props, Spatial target)
{
// attempt to create an instance of the controller
ModelController ctrl;
String cname = props.getProperty("class", "");
try {
ctrl = (ModelController)Class.forName(cname).newInstance();
} catch (Exception e) {
Log.warning("Error instantiating controller [class=" + cname +
", error=" + e + "].");
return null;
}
ctrl.configure(props, target);
return ctrl;
}
/** Converts a boxed Integer list to an unboxed int array. */
protected static int[] toArray (ArrayList<Integer> list)
{
@@ -156,7 +156,6 @@ public class ModelViewer extends JmeCanvasApp
new AbstractAction(_msg.get("m.anim_start")) {
public void actionPerformed (ActionEvent e) {
_model.startAnimation((String)_animbox.getSelectedItem());
_animstop.setEnabled(true);
}
}));
_animctrls.add(_animstop = new JButton(
@@ -328,7 +327,7 @@ public class ModelViewer extends JmeCanvasApp
_status.setText(_msg.get("m.compiling_model", file));
Model model = CompileModelTask.compileModel(file);
if (model != null) {
model.setReferenceTransforms();
model.initPrototype();
setModel(model, file);
return;
}
@@ -388,7 +387,7 @@ public class ModelViewer extends JmeCanvasApp
_model.updateRenderState();
// configure the animation panel
String[] anims = _model.getAnimations();
String[] anims = _model.getAnimationNames();
if (anims.length == 0) {
_animctrls.setVisible(false);
return;
@@ -439,9 +438,13 @@ public class ModelViewer extends JmeCanvasApp
/** The currently loaded model. */
protected Model _model;
/** Disables the stop button when animations stop. */
/** Enables and disables the stop button when animations start and stop. */
protected Model.AnimationObserver _animobs =
new Model.AnimationObserver() {
public boolean animationStarted (Model model, String name) {
_animstop.setEnabled(true);
return true;
}
public boolean animationCompleted (Model model, String name) {
_animstop.setEnabled(false);
return true;