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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user