From 3c6ac7ac676ae11c877965e43aaf6eb7b3700441 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Fri, 14 Apr 2006 03:26:50 +0000 Subject: [PATCH] Further progress: texturing, compiling animations. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4020 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- rsrc/i18n/jme/viewer.properties | 3 + src/java/com/threerings/jme/model/Model.java | 176 ++++++++++++++++++ .../com/threerings/jme/model/ModelMesh.java | 81 +++++++- .../com/threerings/jme/model/ModelNode.java | 12 +- .../threerings/jme/model/ModelSpatial.java | 5 + .../threerings/jme/model/TextureProvider.java | 35 ++++ .../threerings/jme/tools/AnimationDef.java | 104 ++++++++++- .../jme/tools/CompileModelTask.java | 45 ++++- .../com/threerings/jme/tools/ModelDef.java | 42 ++++- .../com/threerings/jme/tools/ModelViewer.java | 83 ++++++++- .../jme/tools/xml/AnimationParser.java | 9 +- 11 files changed, 560 insertions(+), 35 deletions(-) create mode 100644 src/java/com/threerings/jme/model/TextureProvider.java diff --git a/rsrc/i18n/jme/viewer.properties b/rsrc/i18n/jme/viewer.properties index 06093f2be..0ace712e9 100644 --- a/rsrc/i18n/jme/viewer.properties +++ b/rsrc/i18n/jme/viewer.properties @@ -9,6 +9,9 @@ m.file_menu = File m.file_load = Load Model... m.file_quit = Quit +m.anim_menu = Animations +m.anim_stop = Stop + m.load_title = Select Model to Load m.load_filter = Model Files (*.properties, *.dat) diff --git a/src/java/com/threerings/jme/model/Model.java b/src/java/com/threerings/jme/model/Model.java index fc4cccf22..f28d8ed22 100644 --- a/src/java/com/threerings/jme/model/Model.java +++ b/src/java/com/threerings/jme/model/Model.java @@ -21,6 +21,7 @@ package com.threerings.jme.model; +import java.io.Externalizable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -29,13 +30,19 @@ import java.io.ObjectInput; import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; +import java.io.Serializable; import java.nio.ByteOrder; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; +import java.util.HashMap; import java.util.Properties; +import com.jme.math.Quaternion; +import com.jme.math.Vector3f; +import com.jme.scene.Spatial; + import com.threerings.jme.Log; /** @@ -43,6 +50,97 @@ import com.threerings.jme.Log; */ public class Model extends ModelNode { + /** An animation for the model. */ + public static class Animation + implements Serializable + { + /** The transformation targets of the animation. */ + public Spatial[] transformTargets; + + /** The animation transforms (one transform per target per frame). */ + public transient Transform[][] transforms; + + private void writeObject (ObjectOutputStream out) + throws IOException + { + out.defaultWriteObject(); + out.writeInt(transforms.length); + for (int ii = 0; ii < transforms.length; ii++) { + for (int jj = 0; jj < transformTargets.length; jj++) { + transforms[ii][jj].writeExternal(out); + } + } + } + + private void readObject (ObjectInputStream in) + throws IOException, ClassNotFoundException + { + in.defaultReadObject(); + transforms = new Transform[in.readInt()][transformTargets.length]; + for (int ii = 0; ii < transforms.length; ii++) { + for (int jj = 0; jj < transformTargets.length; jj++) { + transforms[ii][jj] = new Transform(new Vector3f(), + new Quaternion(), new Vector3f()); + transforms[ii][jj].readExternal(in); + } + } + } + + private static final long serialVersionUID = 1; + } + + /** A frame element that manipulates the target's transform. */ + public static final class Transform + implements Externalizable + { + public Transform ( + Vector3f translation, Quaternion rotation, Vector3f scale) + { + _translation = translation; + _rotation = rotation; + _scale = scale; + } + + /** + * Blends between this transform and the next, applying the result to + * the given target. + * + * @param alpha the blend factor: 0.0 for entirely this frame, 1.0 for + * entirely the next + */ + public void blend (Transform next, float alpha, Spatial target) + { + target.getLocalTranslation().interpolate(_translation, + next._translation, alpha); + target.getLocalRotation().slerp(_rotation, next._rotation, alpha); + target.getLocalScale().interpolate(_scale, next._scale, alpha); + } + + // documentation inherited from interface Externalizable + public void writeExternal (ObjectOutput out) + throws IOException + { + _translation.writeExternal(out); + _rotation.writeExternal(out); + _scale.writeExternal(out); + } + + // documentation inherited from interface Externalizable + public void readExternal (ObjectInput in) + throws IOException, ClassNotFoundException + { + _translation.readExternal(in); + _rotation.readExternal(in); + _scale.readExternal(in); + } + + /** The transform at this frame. */ + protected Vector3f _translation, _scale; + protected Quaternion _rotation; + + private static final long serialVersionUID = 1; + } + /** * Attempts to read a model from the specified file. * @@ -102,6 +200,48 @@ public class Model extends ModelNode return _props; } + /** + * Adds an animation to the model's library. + */ + public void addAnimation (String name, Animation anim) + { + if (_anims == null) { + _anims = new HashMap(); + } + _anims.put(name, anim); + } + + /** + * Returns the names of the model's animations. + */ + public String[] getAnimations () + { + return (_anims == null) ? new String[0] : + _anims.keySet().toArray(new String[_anims.size()]); + } + + /** + * Starts the named animation. + */ + public void startAnimation (String name) + { + _anim = _anims.get(name); + if (_anim == null) { + Log.warning("Requested unknown animation [name=" + + name + "]."); + return; + } + _fidx = -1; + } + + /** + * Stops the currently running animation. + */ + public void stopAnimation () + { + _anim = null; + } + /** * Writes this model out to a file. */ @@ -125,6 +265,7 @@ public class Model extends ModelNode { super.writeExternal(out); out.writeObject(_props); + out.writeObject(_anims); } @Override // documentation inherited @@ -133,10 +274,45 @@ public class Model extends ModelNode { super.readExternal(in); _props = (Properties)in.readObject(); + _anims = (HashMap)in.readObject(); + } + + @Override // documentation inherited + public void updateWorldData (float time) + { + if (_anim != null) { + updateAnimation(time); + } + + // update children + super.updateWorldData(time); + } + + /** + * Updates the model's state according to the current animation. + */ + protected void updateAnimation (float time) + { + } /** The model properties. */ protected Properties _props; + /** The model animations. */ + protected HashMap _anims; + + /** The currently running animation, or null for none. */ + protected Animation _anim; + + /** The last frame index. */ + protected int _fidx; + + /** The time corresponding to the last frame. */ + protected float _ftime; + + /** Identifies a transform frame element. */ + protected static final byte TRANSFORM_ELEMENT = 0; + 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 fc3eefdee..84c8e05d1 100644 --- a/src/java/com/threerings/jme/model/ModelMesh.java +++ b/src/java/com/threerings/jme/model/ModelMesh.java @@ -39,7 +39,13 @@ import com.jme.bounding.BoundingBox; import com.jme.bounding.BoundingSphere; import com.jme.math.Quaternion; import com.jme.math.Vector3f; +import com.jme.renderer.Renderer; import com.jme.scene.TriMesh; +import com.jme.scene.state.AlphaState; +import com.jme.scene.state.CullState; +import com.jme.scene.state.TextureState; +import com.jme.scene.state.ZBufferState; +import com.jme.system.DisplaySystem; import com.threerings.jme.Log; @@ -82,11 +88,15 @@ public class ModelMesh extends TriMesh { _boundingType = "sphere".equals(props.getProperty("bound")) ? SPHERE_BOUND : BOX_BOUND; + _texture = props.getProperty("texture"); + _solid = !"false".equals(props.getProperty("solid")); + _transparent = "true".equals(props.getProperty("transparent")); } /** * Sets the buffers as {@link ByteBuffer}s, because we can't create byte - * views of non-byte buffers. + * views of non-byte buffers. This method is where the model is + * initialized after loading. */ public void reconstruct ( ByteBuffer vertices, ByteBuffer normals, ByteBuffer colors, @@ -104,12 +114,28 @@ public class ModelMesh extends TriMesh _textureByteBuffer = textures; _indexByteBuffer = indices; + // initialize the model if we're displaying + if (DisplaySystem.getDisplaySystem() == null) { + return; + } if (_boundingType == BOX_BOUND) { setModelBound(new BoundingBox()); } else { // _boundingType == SPHERE_BOUND setModelBound(new BoundingSphere()); } updateModelBound(); + + if (_backCull == null) { + initSharedStates(); + } + if (_solid) { + setRenderState(_backCull); + } + if (_transparent) { + setRenderQueueMode(Renderer.QUEUE_TRANSPARENT); + setRenderState(_blendAlpha); + setRenderState(_overlayZBuffer); + } } // documentation inherited @@ -153,6 +179,9 @@ public class ModelMesh extends TriMesh out.writeInt(_textureBufferSize); out.writeInt(_indexBufferSize); out.writeInt(_boundingType); + out.writeObject(_texture); + out.writeBoolean(_solid); + out.writeBoolean(_transparent); } // documentation inherited from interface Externalizable @@ -169,6 +198,20 @@ public class ModelMesh extends TriMesh _textureBufferSize = in.readInt(); _indexBufferSize = in.readInt(); _boundingType = in.readInt(); + _texture = (String)in.readObject(); + _solid = in.readBoolean(); + _transparent = in.readBoolean(); + } + + // documentation inherited from interface ModelSpatial + public void resolveTextures (TextureProvider tprov) + { + if (_texture != null) { + TextureState tstate = tprov.getTexture(_texture); + if (tstate != null) { + setRenderState(tstate); + } + } } // documentation inherited from interface ModelSpatial @@ -289,7 +332,7 @@ public class ModelMesh extends TriMesh /** * Imposes the specified order on the given buffer of 32 bit values. */ - protected void convertOrder (ByteBuffer buf, ByteOrder order) + protected static void convertOrder (ByteBuffer buf, ByteOrder order) { if (buf.order() == order) { return; @@ -301,6 +344,22 @@ public class ModelMesh extends TriMesh } } + /** + * Initializes the states shared between all models. Requires an active + * display. + */ + protected static void initSharedStates () + { + Renderer renderer = DisplaySystem.getDisplaySystem().getRenderer(); + _backCull = renderer.createCullState(); + _backCull.setCullMode(CullState.CS_BACK); + _blendAlpha = renderer.createAlphaState(); + _blendAlpha.setBlendEnabled(true); + _overlayZBuffer = renderer.createZBufferState(); + _overlayZBuffer.setFunction(ZBufferState.CF_LEQUAL); + _overlayZBuffer.setWritable(false); + } + /** The sizes of the various buffers (zero for null). */ protected int _vertexBufferSize, _normalBufferSize, _colorBufferSize, _textureBufferSize, _indexBufferSize; @@ -312,6 +371,24 @@ public class ModelMesh extends TriMesh /** The type of bounding volume that this mesh should use. */ protected int _boundingType; + /** The name of this model's texture, or null for none. */ + protected String _texture; + + /** Whether or not this mesh can enable back-face culling. */ + protected boolean _solid; + + /** Whether or not this mesh must be rendered as transparent. */ + protected boolean _transparent; + + /** The shared state for back-face culling. */ + protected static CullState _backCull; + + /** The shared state for alpha blending. */ + protected static AlphaState _blendAlpha; + + /** The shared state for checking, but not writing to, the z buffer. */ + protected static ZBufferState _overlayZBuffer; + /** Indicates that this mesh should use a bounding box. */ protected static final int BOX_BOUND = 0; diff --git a/src/java/com/threerings/jme/model/ModelNode.java b/src/java/com/threerings/jme/model/ModelNode.java index c6fc60b56..f4246443d 100644 --- a/src/java/com/threerings/jme/model/ModelNode.java +++ b/src/java/com/threerings/jme/model/ModelNode.java @@ -98,7 +98,17 @@ public class ModelNode extends Node attachChild((Spatial)children.get(ii)); } } - + + // documentation inherited from interface ModelSpatial + public void resolveTextures (TextureProvider tprov) + { + for (Object child : getChildren()) { + if (child instanceof ModelSpatial) { + ((ModelSpatial)child).resolveTextures(tprov); + } + } + } + // 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 5f8848f09..d3681a1b2 100644 --- a/src/java/com/threerings/jme/model/ModelSpatial.java +++ b/src/java/com/threerings/jme/model/ModelSpatial.java @@ -32,6 +32,11 @@ import java.nio.channels.FileChannel; */ public interface ModelSpatial { + /** + * Recursively resolves texture references using the given provider. + */ + public void resolveTextures (TextureProvider tprov); + /** * Recursively writes any data buffers to the output channel. */ diff --git a/src/java/com/threerings/jme/model/TextureProvider.java b/src/java/com/threerings/jme/model/TextureProvider.java new file mode 100644 index 000000000..3b1126b5f --- /dev/null +++ b/src/java/com/threerings/jme/model/TextureProvider.java @@ -0,0 +1,35 @@ +// +// $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 com.jme.scene.state.TextureState; + +/** + * Provides a means for models to resolve their texture references. + */ +public interface TextureProvider +{ + /** + * Returns a texture state containing the named texture. + */ + public TextureState getTexture (String name); +} diff --git a/src/java/com/threerings/jme/tools/AnimationDef.java b/src/java/com/threerings/jme/tools/AnimationDef.java index 788143251..9e6960320 100644 --- a/src/java/com/threerings/jme/tools/AnimationDef.java +++ b/src/java/com/threerings/jme/tools/AnimationDef.java @@ -22,6 +22,16 @@ package com.threerings.jme.tools; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Properties; + +import com.jme.math.Quaternion; +import com.jme.math.Vector3f; +import com.jme.scene.Spatial; + +import com.threerings.jme.Log; +import com.threerings.jme.model.Model; /** * A basic representation for keyframe animations. @@ -29,34 +39,112 @@ import java.util.ArrayList; public class AnimationDef { /** A single frame of the animation. */ - public static class Frame + public static class FrameDef { - /** Transforms for affected nodes. */ - public ArrayList transforms = new ArrayList(); + /** Transform for affected nodes. */ + public ArrayList transforms = + new ArrayList(); - public void addTransform (Transform transform) + public void addTransform (TransformDef transform) { transforms.add(transform); } + + /** Adds all transform targets in this frame to the supplied set. */ + public void addTransformTargets ( + HashMap nodes, HashSet targets) + { + for (int ii = 0, nn = transforms.size(); ii < nn; ii++) { + String name = transforms.get(ii).name; + Spatial target = nodes.get(name); + if (target != null) { + targets.add(target); + } else { + Log.warning("Missing animation target [name=" + name + + "]."); + } + } + } + + /** Returns the array of transforms for this frame. */ + public Model.Transform[] getTransforms (Spatial[] targets) + { + Model.Transform[] mtransforms = + new Model.Transform[targets.length]; + for (int ii = 0; ii < targets.length; ii++) { + mtransforms[ii] = getTransform(targets[ii]); + } + return mtransforms; + } + + /** Returns the transform for the supplied target. */ + protected Model.Transform getTransform (Spatial target) + { + String name = target.getName(); + for (int ii = 0, nn = transforms.size(); ii < nn; ii++) { + TransformDef transform = transforms.get(ii); + if (name.equals(transform.name)) { + return transform.getTransform(); + } + } + return null; + } } /** A transform for a single node. */ - public static class Transform + public static class TransformDef { - /** The name of the node to transform. */ + /** The name of the affected node. */ public String name; /** The transformation parameters. */ public float[] translation; public float[] rotation; public float[] scale; + + /** Returns the live transform object. */ + public Model.Transform getTransform () + { + return new Model.Transform( + new Vector3f(translation[0], translation[1], translation[2]), + new Quaternion(rotation[0], rotation[1], rotation[2], + rotation[3]), + new Vector3f(scale[0], scale[1], scale[2])); + } } /** The individual frames of the animation. */ - public ArrayList frames = new ArrayList(); + public ArrayList frames = new ArrayList(); - public void addFrame (Frame frame) + public void addFrame (FrameDef frame) { frames.add(frame); } + + /** + * Creates the "live" animation object that will be serialized with the + * object. + * + * @param props the model properties + * @param nodes the nodes in the model, mapped by name + */ + public Model.Animation createAnimation ( + Properties props, HashMap nodes) + { + // find all affected nodes + HashSet targets = new HashSet(); + for (int ii = 0, nn = frames.size(); ii < nn; ii++) { + frames.get(ii).addTransformTargets(nodes, targets); + } + + // collect all transforms + Model.Animation anim = new Model.Animation(); + anim.transformTargets = targets.toArray(new Spatial[targets.size()]); + anim.transforms = new Model.Transform[frames.size()][targets.size()]; + for (int ii = 0; ii < anim.transforms.length; ii++) { + anim.transforms[ii] = + frames.get(ii).getTransforms(anim.transformTargets); + } + return anim; + } } diff --git a/src/java/com/threerings/jme/tools/CompileModelTask.java b/src/java/com/threerings/jme/tools/CompileModelTask.java index 302d30127..f10d4a6cb 100644 --- a/src/java/com/threerings/jme/tools/CompileModelTask.java +++ b/src/java/com/threerings/jme/tools/CompileModelTask.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.io.ObjectOutputStream; import java.util.ArrayList; +import java.util.HashMap; import java.util.Properties; import org.apache.tools.ant.BuildException; @@ -35,11 +36,16 @@ import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.Task; import org.apache.tools.ant.types.FileSet; +import com.jme.scene.Spatial; + +import com.samskivert.util.StringUtil; + import com.threerings.jme.model.BoneNode; import com.threerings.jme.model.Model; import com.threerings.jme.model.ModelMesh; import com.threerings.jme.model.ModelNode; import com.threerings.jme.model.SkinMesh; +import com.threerings.jme.tools.xml.AnimationParser; import com.threerings.jme.tools.xml.ModelParser; /** @@ -63,21 +69,45 @@ public class CompileModelTask extends Task String root = (didx == -1) ? spath : spath.substring(0, didx); File content = new File(root + ".xml"), target = new File(root + ".dat"); - if (source.lastModified() < target.lastModified() && - content.lastModified() < target.lastModified()) { - return null; + boolean needsUpdate = false; + if (source.lastModified() >= target.lastModified() || + content.lastModified() >= target.lastModified()) { + needsUpdate = true; } - System.out.println("Compiling " + source.getParent() + "..."); - + // load the model properties Properties props = new Properties(); FileInputStream in = new FileInputStream(source); props.load(in); in.close(); + // locate the animations, if any + String[] actions = + StringUtil.parseStringArray(props.getProperty("actions", "")); + File[] afiles = new File[actions.length]; + File dir = source.getParentFile(); + for (int ii = 0; ii < actions.length; ii++) { + afiles[ii] = new File(dir, actions[ii] + ".xml"); + if (afiles[ii].lastModified() >= target.lastModified()) { + needsUpdate = true; + } + } + if (!needsUpdate) { + return null; + } + System.out.println("Compiling " + source.getParent() + "..."); + // load the model content ModelDef mdef = _mparser.parseModel(content.toString()); - Model model = mdef.createModel(props); + HashMap nodes = new HashMap(); + Model model = mdef.createModel(props, nodes); + + // load the actions, if any + for (int ii = 0; ii < actions.length; ii++) { + System.out.println(" Adding " + afiles[ii] + "..."); + AnimationDef adef = _aparser.parseAnimation(afiles[ii].toString()); + model.addAnimation(actions[ii], adef.createAnimation(props, nodes)); + } // write and return the model model.writeToFile(target); @@ -114,4 +144,7 @@ public class CompileModelTask extends Task /** A parser for the model definitions. */ protected static ModelParser _mparser = new ModelParser(); + + /** A parser for the animation definitions. */ + protected static AnimationParser _aparser = new AnimationParser(); } diff --git a/src/java/com/threerings/jme/tools/ModelDef.java b/src/java/com/threerings/jme/tools/ModelDef.java index 93d99f46c..8499f9b25 100644 --- a/src/java/com/threerings/jme/tools/ModelDef.java +++ b/src/java/com/threerings/jme/tools/ModelDef.java @@ -69,7 +69,7 @@ public class ModelDef public Spatial getSpatial (Properties props) { if (_spatial == null) { - _spatial = createSpatial(props); + _spatial = createSpatial(new NodeProperties(props, name)); setTransform(); } return _spatial; @@ -137,8 +137,8 @@ public class ModelDef /** Configures the new mesh and returns it. */ protected ModelMesh configure (ModelMesh mmesh, Properties props) { - // configure using sub-properties - mmesh.configure(PropertiesUtil.getSubProperties(props, name, "")); + // configure using properties + mmesh.configure(props); // set the various buffers int vsize = vertices.size(); @@ -344,13 +344,15 @@ public class ModelDef /** * Creates the model node defined herein. + * + * @param props the properties of the model + * @param nodes a node map to populate */ - public Model createModel (Properties props) + public Model createModel (Properties props, HashMap nodes) { Model model = new Model(props.getProperty("name", "model"), props); // start by creating the spatials and mapping them to their names - HashMap nodes = new HashMap(); for (int ii = 0, nn = spatials.size(); ii < nn; ii++) { Spatial spatial = spatials.get(ii).getSpatial(props); nodes.put(spatial.getName(), spatial); @@ -388,4 +390,34 @@ public class ModelDef } return array; } + + /** A wrapper for the model properties providing access to the properties + * of a node within the model. */ + protected static class NodeProperties extends Properties + { + public NodeProperties (Properties mprops, String name) + { + _mprops = mprops; + _prefix = name + "."; + } + + @Override // documentation inherited + public String getProperty (String key) + { + return getProperty(key, null); + } + + @Override // documentation inherited + public String getProperty (String key, String defaultValue) + { + return _mprops.getProperty(_prefix + key, + _mprops.getProperty(key, defaultValue)); + } + + /** The properties of the model. */ + protected Properties _mprops; + + /** The node prefix. */ + protected String _prefix; + } } diff --git a/src/java/com/threerings/jme/tools/ModelViewer.java b/src/java/com/threerings/jme/tools/ModelViewer.java index c38836284..07a8c5749 100644 --- a/src/java/com/threerings/jme/tools/ModelViewer.java +++ b/src/java/com/threerings/jme/tools/ModelViewer.java @@ -37,10 +37,12 @@ import java.awt.event.WindowEvent; import java.io.File; import java.io.IOException; +import java.util.HashMap; import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.Action; +import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; @@ -52,6 +54,7 @@ import javax.swing.JPopupMenu; import javax.swing.KeyStroke; import javax.swing.filechooser.FileFilter; +import com.jme.image.Texture; import com.jme.light.DirectionalLight; import com.jme.math.FastMath; import com.jme.math.Vector3f; @@ -59,7 +62,9 @@ import com.jme.renderer.ColorRGBA; import com.jme.scene.Line; import com.jme.scene.state.LightState; import com.jme.scene.state.MaterialState; +import com.jme.scene.state.TextureState; import com.jme.util.LoggingSystem; +import com.jme.util.TextureManager; import com.samskivert.swing.GroupLayout; import com.samskivert.util.Config; @@ -68,7 +73,9 @@ import com.threerings.util.MessageBundle; import com.threerings.util.MessageManager; import com.threerings.jme.JmeCanvasApp; +import com.threerings.jme.Log; import com.threerings.jme.model.Model; +import com.threerings.jme.model.TextureProvider; /** * A simple viewer application that allows users to examine models and their @@ -104,6 +111,7 @@ public class ModelViewer extends JmeCanvasApp _frame.setJMenuBar(menu); JMenu file = new JMenu(_msg.get("m.file_menu")); + file.setMnemonic(KeyEvent.VK_F); menu.add(file); Action load = new AbstractAction(_msg.get("m.file_load")) { public void actionPerformed (ActionEvent e) { @@ -126,11 +134,20 @@ public class ModelViewer extends JmeCanvasApp KeyStroke.getKeyStroke(KeyEvent.VK_Q, KeyEvent.CTRL_MASK)); file.add(quit); - _frame.getContentPane().add(getCanvas(), BorderLayout.CENTER); + _amenu = new JMenu(_msg.get("m.anim_menu")); + _amenu.setMnemonic(KeyEvent.VK_A); + menu.add(_amenu); + _amenu.setVisible(false); + _stop = new AbstractAction(_msg.get("m.anim_stop")) { + public void actionPerformed (ActionEvent e) { + _model.stopAnimation(); + } + }; + _stop.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_S); + _stop.putValue(Action.ACCELERATOR_KEY, + KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_MASK)); - _controls = GroupLayout.makeVBox(GroupLayout.NONE, GroupLayout.TOP); - _controls.setPreferredSize(new Dimension(100, 100)); - _frame.getContentPane().add(_controls, BorderLayout.EAST); + _frame.getContentPane().add(getCanvas(), BorderLayout.CENTER); _status = new JLabel(" "); _status.setHorizontalAlignment(JLabel.LEFT); @@ -283,7 +300,7 @@ public class ModelViewer extends JmeCanvasApp _status.setText(_msg.get("m.compiling_model", file)); Model model = CompileModelTask.compileModel(file); if (model != null) { - setModel(model); + setModel(model, file); return; } // if compileModel returned null, the .dat file is up-to-date @@ -300,20 +317,65 @@ public class ModelViewer extends JmeCanvasApp throws IOException { _status.setText(_msg.get("m.loading_model", file)); - setModel(Model.readFromFile(file, false)); + setModel(Model.readFromFile(file, false), file); } /** * Sets the model once it's been loaded. + * + * @param file the file from which the model was loaded */ - protected void setModel (Model model) + protected void setModel (Model model, File file) { if (_model != null) { _ctx.getGeometry().detachChild(_model); } _ctx.getGeometry().attachChild(_model = model); _model.setLocalScale(0.04f); + + // resolve the textures from the file's directory + final File dir = file.getParentFile(); + _model.resolveTextures(new TextureProvider() { + public TextureState getTexture (String name) { + TextureState tstate = _tstates.get(name); + if (tstate == null) { + File file = new File(dir, name); + Texture tex = TextureManager.loadTexture(file.toString(), + Texture.MM_LINEAR_LINEAR, Texture.FM_LINEAR); + if (tex == null) { + Log.warning("Couldn't find texture [path=" + file + + "]."); + return null; + } + tstate = _ctx.getRenderer().createTextureState(); + tstate.setTexture(tex); + _tstates.put(name, tstate); + } + return tstate; + } + protected HashMap _tstates = + new HashMap(); + }); _model.updateRenderState(); + + // create buttons for the model's animations + String[] anims = _model.getAnimations(); + _amenu.removeAll(); + if (anims.length == 0) { + _amenu.setVisible(false); + return; + } + _amenu.setVisible(true); + for (int ii = 0; ii < anims.length; ii++) { + final String anim = anims[ii]; + _amenu.add(new AbstractAction(anim) { + public void actionPerformed (ActionEvent e) { + _model.startAnimation(anim); + } + }); + } + _amenu.addSeparator(); + _amenu.add(_stop); } /** The translation bundle. */ @@ -325,8 +387,11 @@ public class ModelViewer extends JmeCanvasApp /** The viewer frame. */ protected JFrame _frame; - /** The control panel. */ - protected JPanel _controls; + /** The animation menu. */ + protected JMenu _amenu; + + /** The stop animation action. */ + protected Action _stop; /** The status bar. */ protected JLabel _status; diff --git a/src/java/com/threerings/jme/tools/xml/AnimationParser.java b/src/java/com/threerings/jme/tools/xml/AnimationParser.java index f8f348563..4d2f10d7a 100644 --- a/src/java/com/threerings/jme/tools/xml/AnimationParser.java +++ b/src/java/com/threerings/jme/tools/xml/AnimationParser.java @@ -48,16 +48,17 @@ public class AnimationParser AnimationDef.class.getName()); String frame = anim + "/frame"; - _digester.addObjectCreate(frame, AnimationDef.Frame.class.getName()); + _digester.addObjectCreate(frame, + AnimationDef.FrameDef.class.getName()); _digester.addSetNext(frame, "addFrame", - AnimationDef.Frame.class.getName()); + AnimationDef.FrameDef.class.getName()); String xform = frame + "/transform"; _digester.addObjectCreate(xform, - AnimationDef.Transform.class.getName()); + AnimationDef.TransformDef.class.getName()); _digester.addRule(xform, new SetPropertyFieldsRule()); _digester.addSetNext(xform, "addTransform", - AnimationDef.Transform.class.getName()); + AnimationDef.TransformDef.class.getName()); } /**