Further progress: texturing, compiling animations.
git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4020 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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<String, Animation>();
|
||||
}
|
||||
_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<String, Animation>)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<String, Animation> _anims;
|
||||
|
||||
/** The currently running animation, or <code>null</code> 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;
|
||||
}
|
||||
|
||||
@@ -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 <code>null</code>). */
|
||||
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 <code>null</code> 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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<Transform> transforms = new ArrayList<Transform>();
|
||||
/** Transform for affected nodes. */
|
||||
public ArrayList<TransformDef> transforms =
|
||||
new ArrayList<TransformDef>();
|
||||
|
||||
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<String, Spatial> nodes, HashSet<Spatial> 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<Frame> frames = new ArrayList<Frame>();
|
||||
public ArrayList<FrameDef> frames = new ArrayList<FrameDef>();
|
||||
|
||||
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<String, Spatial> nodes)
|
||||
{
|
||||
// find all affected nodes
|
||||
HashSet<Spatial> targets = new HashSet<Spatial>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, Spatial> nodes = new HashMap<String, Spatial>();
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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<String, Spatial> nodes)
|
||||
{
|
||||
Model model = new Model(props.getProperty("name", "model"), props);
|
||||
|
||||
// start by creating the spatials and mapping them to their names
|
||||
HashMap<String, Spatial> nodes = new HashMap<String, Spatial>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, TextureState> _tstates =
|
||||
new HashMap<String, TextureState>();
|
||||
});
|
||||
_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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user