diff --git a/src/java/com/threerings/jme/model/BoneNode.java b/src/java/com/threerings/jme/model/BoneNode.java
new file mode 100644
index 000000000..f83c6580e
--- /dev/null
+++ b/src/java/com/threerings/jme/model/BoneNode.java
@@ -0,0 +1,99 @@
+//
+// $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 com.jme.scene.Node;
+
+import com.jme.math.Quaternion;
+import com.jme.math.TransformMatrix;
+import com.jme.math.Vector3f;
+
+import com.threerings.jme.Log;
+
+/**
+ * Represents a bone in a scene. Bones can have simple meshes as children,
+ * or they can be used to deform {@link SkinMesh}es.
+ */
+public class BoneNode extends ModelNode
+{
+ /**
+ * No-arg constructor for deserialization.
+ */
+ public BoneNode ()
+ {
+ }
+
+ /**
+ * Default constructor.
+ *
+ * @param name the name of the node
+ */
+ public BoneNode (String name)
+ {
+ super(name);
+ }
+
+ @Override // documentation inherited
+ public void setReferenceTransforms ()
+ {
+ super.setReferenceTransforms();
+
+ // store the inverse of the reference transform
+ _invRefTransform.set(worldRotation, worldTranslation);
+ _invRefTransform.setScale(worldScale);
+ _invRefTransform.inverse();
+ }
+
+ @Override // documentation inherited
+ public void updateWorldVectors ()
+ {
+ super.updateWorldVectors();
+ _skinTransform.set(worldRotation, worldTranslation);
+ _skinTransform.setScale(worldScale);
+ _skinTransform.multLocal(_invRefTransform, _tempStore);
+ }
+
+ /**
+ * Returns a reference to the matrix that transforms vertices in the
+ * reference position to vertices in the current position.
+ */
+ public TransformMatrix getSkinTransform ()
+ {
+ return _skinTransform;
+ }
+
+ /** The inverse of the bone's world space reference transform. */
+ protected TransformMatrix _invRefTransform = new TransformMatrix();
+
+ /** The bone's current skin transform. */
+ protected TransformMatrix _skinTransform = new TransformMatrix();
+
+ /** A temporary vector for transformations. */
+ protected Vector3f _tempStore = new Vector3f();
+
+ private static final long serialVersionUID = 1;
+}
diff --git a/src/java/com/threerings/jme/model/Model.java b/src/java/com/threerings/jme/model/Model.java
new file mode 100644
index 000000000..51b9d53e4
--- /dev/null
+++ b/src/java/com/threerings/jme/model/Model.java
@@ -0,0 +1,122 @@
+//
+// $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.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+
+import java.nio.channels.FileChannel;
+
+import com.threerings.jme.Log;
+
+/**
+ * The base node for models.
+ */
+public class Model extends ModelNode
+{
+ /**
+ * Attempts to read a model from the specified file.
+ *
+ * @param map if true, map buffers into memory directly from the
+ * file
+ */
+ public static Model readFromFile (File file, boolean map)
+ throws IOException
+ {
+ // read the serialized model and its children
+ FileInputStream fis = new FileInputStream(file);
+ ObjectInputStream ois = new ObjectInputStream(fis);
+ Model model;
+ try {
+ model = (Model)ois.readObject();
+ } catch (ClassNotFoundException e) {
+ Log.warning("Encountered unknown class [error=" + e + "].");
+ return null;
+ }
+
+ // then either read or map the buffers
+ FileChannel fc = fis.getChannel();
+ if (map) {
+ long pos = fc.position();
+ model.sliceBuffers(fc.map(FileChannel.MapMode.READ_ONLY,
+ pos, fc.size() - pos));
+ } else {
+ model.readBuffers(fc);
+ }
+ ois.close();
+ return model;
+ }
+
+ /**
+ * No-arg constructor for deserialization.
+ */
+ public Model ()
+ {
+ }
+
+ /**
+ * Standard constructor.
+ */
+ public Model (String name)
+ {
+ super(name);
+ }
+
+ /**
+ * Writes this model out to a file.
+ */
+ public void writeToFile (File file)
+ throws IOException
+ {
+ // start out by writing this node and its children
+ FileOutputStream fos = new FileOutputStream(file);
+ ObjectOutputStream oos = new ObjectOutputStream(fos);
+ oos.writeObject(this);
+
+ // now traverse the scene graph appending buffers to the
+ // end of the file
+ writeBuffers(fos.getChannel());
+ oos.close();
+ }
+
+ @Override // documentation inherited
+ public void writeExternal (ObjectOutput out)
+ throws IOException
+ {
+ super.writeExternal(out);
+ }
+
+ @Override // documentation inherited
+ public void readExternal (ObjectInput in)
+ throws IOException
+ {
+ super.readExternal(in);
+ }
+
+ 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
new file mode 100644
index 000000000..12a5d5e9c
--- /dev/null
+++ b/src/java/com/threerings/jme/model/ModelMesh.java
@@ -0,0 +1,263 @@
+//
+// $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.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+
+import com.jme.math.Quaternion;
+import com.jme.math.Vector3f;
+import com.jme.scene.TriMesh;
+
+import com.threerings.jme.Log;
+
+/**
+ * A {@link TriMesh} with a serialization mechanism tailored to stored models.
+ */
+public class ModelMesh extends TriMesh
+ implements Externalizable, ModelSpatial
+{
+ /**
+ * No-arg constructor for deserialization.
+ */
+ public ModelMesh ()
+ {
+ }
+
+ /**
+ * Creates a mesh with no vertex data.
+ */
+ public ModelMesh (String name)
+ {
+ super(name);
+ }
+
+ /**
+ * Creates and populates a mesh.
+ */
+ public ModelMesh (
+ String name, FloatBuffer vertices, FloatBuffer normals,
+ FloatBuffer colors, FloatBuffer tcoords, IntBuffer indices)
+ {
+ super(name, vertices, normals, colors, tcoords, indices);
+ }
+
+ /**
+ * Sets the buffers as {@link ByteBuffer}s, because we can't create byte
+ * views of non-byte buffers.
+ */
+ public void reconstruct (
+ ByteBuffer vertices, ByteBuffer normals, ByteBuffer colors,
+ ByteBuffer textures, ByteBuffer indices)
+ {
+ reconstruct(
+ vertices == null ? null : vertices.asFloatBuffer(),
+ normals == null ? null : normals.asFloatBuffer(),
+ colors == null ? null : colors.asFloatBuffer(),
+ textures == null ? null : textures.asFloatBuffer(),
+ indices == null ? null : indices.asIntBuffer());
+ _vertexByteBuffer = vertices;
+ _normalByteBuffer = normals;
+ _colorByteBuffer = colors;
+ _textureByteBuffer = textures;
+ _indexByteBuffer = indices;
+ }
+
+ // documentation inherited
+ public void reconstruct (
+ FloatBuffer vertices, FloatBuffer normals, FloatBuffer colors,
+ FloatBuffer textures, IntBuffer indices)
+ {
+ super.reconstruct(vertices, normals, colors, textures, indices);
+
+ _vertexBufferSize = (vertices == null) ? 0 : vertices.capacity();
+ _normalBufferSize = (normals == null) ? 0 : normals.capacity();
+ _colorBufferSize = (colors == null) ? 0 : colors.capacity();
+ _textureBufferSize = (textures == null) ? 0 : textures.capacity();
+ _indexBufferSize = (indices == null) ? 0 : indices.capacity();
+ }
+
+ // documentation inherited
+ public void reconstruct (
+ FloatBuffer vertices, FloatBuffer normals, FloatBuffer colors,
+ FloatBuffer textures)
+ {
+ super.reconstruct(vertices, normals, colors, textures);
+
+ _vertexBufferSize = (vertices == null) ? 0 : vertices.capacity();
+ _normalBufferSize = (normals == null) ? 0 : normals.capacity();
+ _colorBufferSize = (colors == null) ? 0 : colors.capacity();
+ _textureBufferSize = (textures == null) ? 0 : textures.capacity();
+ }
+
+ // documentation inherited from interface Externalizable
+ public void writeExternal (ObjectOutput out)
+ throws IOException
+ {
+ out.writeUTF(getName());
+ out.writeObject(getLocalTranslation());
+ out.writeObject(getLocalRotation());
+ out.writeObject(getLocalScale());
+ out.writeInt(_vertexBufferSize);
+ out.writeInt(_normalBufferSize);
+ out.writeInt(_colorBufferSize);
+ out.writeInt(_textureBufferSize);
+ out.writeInt(_indexBufferSize);
+ }
+
+ // documentation inherited from interface Externalizable
+ public void readExternal (ObjectInput in)
+ throws IOException
+ {
+ setName(in.readUTF());
+ try {
+ setLocalTranslation((Vector3f)in.readObject());
+ setLocalRotation((Quaternion)in.readObject());
+ setLocalScale((Vector3f)in.readObject());
+
+ } catch (ClassNotFoundException e) {
+ Log.warning("Encounted unknown class [mesh=" + getName() +
+ ", exception=" + e + "].");
+ }
+ _vertexBufferSize = in.readInt();
+ _normalBufferSize = in.readInt();
+ _colorBufferSize = in.readInt();
+ _textureBufferSize = in.readInt();
+ _indexBufferSize = in.readInt();
+ }
+
+ // documentation inherited from interface ModelSpatial
+ public void writeBuffers (FileChannel out)
+ throws IOException
+ {
+ if (_vertexBufferSize > 0) {
+ _vertexByteBuffer.rewind();
+ out.write(_vertexByteBuffer);
+ }
+ if (_normalBufferSize > 0) {
+ _normalByteBuffer.rewind();
+ out.write(_normalByteBuffer);
+ }
+ if (_colorBufferSize > 0) {
+ _colorByteBuffer.rewind();
+ out.write(_colorByteBuffer);
+ }
+ if (_textureBufferSize > 0) {
+ _indexByteBuffer.rewind();
+ out.write(_indexByteBuffer);
+ }
+ if (_indexBufferSize > 0) {
+ _indexByteBuffer.rewind();
+ out.write(_indexByteBuffer);
+ }
+ }
+
+ // documentation inherited from interface ModelSpatial
+ public void readBuffers (FileChannel in)
+ throws IOException
+ {
+ ByteBuffer vbbuf = null, nbbuf = null, cbbuf = null, tbbuf = null,
+ ibbuf = null;
+ if (_vertexBufferSize > 0) {
+ vbbuf = ByteBuffer.allocateDirect(_vertexBufferSize*3*4);
+ in.read(vbbuf);
+ vbbuf.rewind();
+ }
+ if (_normalBufferSize > 0) {
+ nbbuf = ByteBuffer.allocateDirect(_normalBufferSize*3*4);
+ in.read(nbbuf);
+ nbbuf.rewind();
+ }
+ if (_colorBufferSize > 0) {
+ cbbuf = ByteBuffer.allocateDirect(_colorBufferSize*4*4);
+ in.read(cbbuf);
+ cbbuf.rewind();
+ }
+ if (_textureBufferSize > 0) {
+ tbbuf = ByteBuffer.allocateDirect(_textureBufferSize*2*4);
+ in.read(tbbuf);
+ tbbuf.rewind();
+ }
+ if (_indexBufferSize > 0) {
+ ibbuf = ByteBuffer.allocateDirect(_indexBufferSize*4);
+ in.read(ibbuf);
+ ibbuf.rewind();
+ }
+ reconstruct(vbbuf, nbbuf, cbbuf, tbbuf, ibbuf);
+ }
+
+ // documentation inherited from interface ModelSpatial
+ public void sliceBuffers (MappedByteBuffer map)
+ {
+ ByteBuffer vbbuf = null, nbbuf = null, cbbuf = null, tbbuf = null,
+ ibbuf = null;
+ if (_vertexBufferSize > 0) {
+ int npos = map.position() + _vertexBufferSize*3*4;
+ map.limit(npos);
+ vbbuf = map.slice();
+ map.position(npos);
+ }
+ if (_normalBufferSize > 0) {
+ int npos = map.position() + _normalBufferSize*3*4;
+ map.limit(npos);
+ nbbuf = map.slice();
+ map.position(npos);
+ }
+ if (_colorBufferSize > 0) {
+ int npos = map.position() + _colorBufferSize*4*4;
+ map.limit(npos);
+ cbbuf = map.slice();
+ map.position(npos);
+ }
+ if (_textureBufferSize > 0) {
+ int npos = map.position() + _textureBufferSize*2*4;
+ map.limit(npos);
+ tbbuf = map.slice();
+ map.position(npos);
+ }
+ if (_indexBufferSize > 0) {
+ int npos = map.position() + _indexBufferSize*4;
+ map.limit(npos);
+ ibbuf = map.slice();
+ map.position(npos);
+ }
+ reconstruct(vbbuf, nbbuf, cbbuf, tbbuf, ibbuf);
+ }
+
+ /** The sizes of the various buffers (zero for null). */
+ protected int _vertexBufferSize, _normalBufferSize, _colorBufferSize,
+ _textureBufferSize, _indexBufferSize;
+
+ /** The backing byte buffers for the various buffers. */
+ protected ByteBuffer _vertexByteBuffer, _normalByteBuffer,
+ _colorByteBuffer, _textureByteBuffer, _indexByteBuffer;
+
+ private static final long serialVersionUID = 1;
+}
diff --git a/src/java/com/threerings/jme/model/ModelNode.java b/src/java/com/threerings/jme/model/ModelNode.java
new file mode 100644
index 000000000..56ff69233
--- /dev/null
+++ b/src/java/com/threerings/jme/model/ModelNode.java
@@ -0,0 +1,139 @@
+//
+// $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.DataOutput;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+
+import java.util.ArrayList;
+
+import com.jme.math.Quaternion;
+import com.jme.math.Vector3f;
+import com.jme.scene.Node;
+import com.jme.scene.Spatial;
+
+import com.threerings.jme.Log;
+
+/**
+ * A {@link Node} with a serialization mechanism tailored to stored models.
+ */
+public class ModelNode extends Node
+ implements Externalizable, ModelSpatial
+{
+ /**
+ * No-arg constructor for deserialization.
+ */
+ public ModelNode ()
+ {
+ }
+
+ /**
+ * Standard constructor.
+ */
+ public ModelNode (String name)
+ {
+ super(name);
+ }
+
+ /**
+ * Recursively sets the reference transforms for any {@link BoneNode}s in
+ * the model.
+ */
+ public void setReferenceTransforms ()
+ {
+ for (Object child : getChildren()) {
+ if (child instanceof ModelNode) {
+ ((ModelNode)child).setReferenceTransforms();
+ }
+ }
+ }
+
+ // documentation inherited from interface Externalizable
+ public void writeExternal (ObjectOutput out)
+ throws IOException
+ {
+ out.writeUTF(getName());
+ out.writeObject(getLocalTranslation());
+ out.writeObject(getLocalRotation());
+ out.writeObject(getLocalScale());
+ out.writeObject(getChildren());
+ }
+
+ // documentation inherited from interface Externalizable
+ public void readExternal (ObjectInput in)
+ throws IOException
+ {
+ setName(in.readUTF());
+ try {
+ setLocalTranslation((Vector3f)in.readObject());
+ setLocalRotation((Quaternion)in.readObject());
+ setLocalScale((Vector3f)in.readObject());
+ ArrayList children = (ArrayList)in.readObject();
+ for (int ii = 0, nn = children.size(); ii < nn; ii++) {
+ attachChild((Spatial)children.get(ii));
+ }
+ } catch (ClassNotFoundException e) {
+ Log.warning("Encounted unknown class [node=" + getName() +
+ ", exception=" + e + "].");
+ }
+ }
+
+ // documentation inherited from interface ModelSpatial
+ public void writeBuffers (FileChannel out)
+ throws IOException
+ {
+ for (Object child : getChildren()) {
+ if (child instanceof ModelSpatial) {
+ ((ModelSpatial)child).writeBuffers(out);
+ }
+ }
+ }
+
+ // documentation inherited from interface ModelSpatial
+ public void readBuffers (FileChannel in)
+ throws IOException
+ {
+ for (Object child : getChildren()) {
+ if (child instanceof ModelSpatial) {
+ ((ModelSpatial)child).readBuffers(in);
+ }
+ }
+ }
+
+ // documentation inherited from interface ModelSpatial
+ public void sliceBuffers (MappedByteBuffer map)
+ {
+ for (Object child : getChildren()) {
+ if (child instanceof ModelSpatial) {
+ ((ModelSpatial)child).sliceBuffers(map);
+ }
+ }
+ }
+
+ private static final long serialVersionUID = 1;
+}
diff --git a/src/java/com/threerings/jme/model/ModelSpatial.java b/src/java/com/threerings/jme/model/ModelSpatial.java
new file mode 100644
index 000000000..5f8848f09
--- /dev/null
+++ b/src/java/com/threerings/jme/model/ModelSpatial.java
@@ -0,0 +1,51 @@
+//
+// $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.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+
+/**
+ * Contains method common to both {@link ModelNode}s and {@link ModelMesh}es.
+ */
+public interface ModelSpatial
+{
+ /**
+ * Recursively writes any data buffers to the output channel.
+ */
+ public void writeBuffers (FileChannel out)
+ throws IOException;
+
+ /**
+ * Recursively reads any data buffers from the input channel.
+ */
+ public void readBuffers (FileChannel in)
+ throws IOException;
+
+ /**
+ * Recursively slices any data buffers from the buffer map.
+ */
+ public void sliceBuffers (MappedByteBuffer map);
+}
diff --git a/src/java/com/threerings/jme/model/SkinMesh.java b/src/java/com/threerings/jme/model/SkinMesh.java
new file mode 100644
index 000000000..44cc6fbe2
--- /dev/null
+++ b/src/java/com/threerings/jme/model/SkinMesh.java
@@ -0,0 +1,137 @@
+//
+// $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.io.Serializable;
+
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+import com.threerings.jme.Log;
+
+/**
+ * A triangle mesh that deforms according to a bone hierarchy.
+ */
+public class SkinMesh extends ModelMesh
+{
+ /** Represents the vertex weights of a group of vertices influenced by the
+ * same set of bones. */
+ public static class WeightGroup
+ implements Serializable
+ {
+ /** The indices of the affected vertices. */
+ public int[] indices;
+
+ /** The bones influencing this group. */
+ public BoneNode[] bones;
+
+ /** The array of interleaved weights (of length indices.length *
+ * bones.length): weights for first vertex, weights for second,
+ * etc. */
+ public float[] weights;
+
+ private static final long serialVersionUID = 1;
+ }
+
+ /**
+ * No-arg constructor for deserialization.
+ */
+ public SkinMesh ()
+ {
+ }
+
+ /**
+ * Creates an empty mesh.
+ */
+ public SkinMesh (String name)
+ {
+ super(name);
+ }
+
+ /**
+ * Creates and populates a mesh.
+ */
+ public SkinMesh (
+ String name, FloatBuffer vertices, FloatBuffer normals,
+ FloatBuffer color, FloatBuffer texture, IntBuffer indices)
+ {
+ super(name, vertices, normals, color, texture, indices);
+ }
+
+ /**
+ * Sets the weight groups that determine how vertices are affected by
+ * bones.
+ */
+ public void setWeightGroups (WeightGroup[] weightGroups)
+ {
+ _weightGroups = weightGroups;
+ }
+
+ /**
+ * Returns a reference to the array of weight groups.
+ */
+ public WeightGroup[] getWeightGroups ()
+ {
+ return _weightGroups;
+ }
+
+ @Override // documentation inherited
+ public void writeExternal (ObjectOutput out)
+ throws IOException
+ {
+ super.writeExternal(out);
+ out.writeObject(_weightGroups);
+ }
+
+ @Override // documentation inherited
+ public void readExternal (ObjectInput in)
+ throws IOException
+ {
+ super.readExternal(in);
+ try {
+ _weightGroups = (WeightGroup[])in.readObject();
+ } catch (ClassNotFoundException e) {
+ Log.warning("Encounted unknown class [mesh=" + getName() +
+ ", exception=" + e + "].");
+ }
+ }
+
+ @Override // documentation inherited
+ public void updateWorldData (float time)
+ {
+ super.updateWorldData(time);
+
+ // deform the mesh according to the positions of the bones
+ for (int ii = 0; ii < _weightGroups.length; ii++) {
+
+ }
+ }
+
+ /** The groups of vertices influenced by different sets of bones. */
+ protected WeightGroup[] _weightGroups;
+
+ private static final long serialVersionUID = 1;
+}
diff --git a/src/java/com/threerings/jme/tools/AnimationDef.java b/src/java/com/threerings/jme/tools/AnimationDef.java
new file mode 100644
index 000000000..788143251
--- /dev/null
+++ b/src/java/com/threerings/jme/tools/AnimationDef.java
@@ -0,0 +1,62 @@
+//
+// $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.tools;
+
+import java.util.ArrayList;
+
+/**
+ * A basic representation for keyframe animations.
+ */
+public class AnimationDef
+{
+ /** A single frame of the animation. */
+ public static class Frame
+ {
+ /** Transforms for affected nodes. */
+ public ArrayList transforms = new ArrayList();
+
+ public void addTransform (Transform transform)
+ {
+ transforms.add(transform);
+ }
+ }
+
+ /** A transform for a single node. */
+ public static class Transform
+ {
+ /** The name of the node to transform. */
+ public String name;
+
+ /** The transformation parameters. */
+ public float[] translation;
+ public float[] rotation;
+ public float[] scale;
+ }
+
+ /** The individual frames of the animation. */
+ public ArrayList frames = new ArrayList();
+
+ public void addFrame (Frame frame)
+ {
+ frames.add(frame);
+ }
+}
diff --git a/src/java/com/threerings/jme/tools/CompileModelTask.java b/src/java/com/threerings/jme/tools/CompileModelTask.java
new file mode 100644
index 000000000..ff213dc50
--- /dev/null
+++ b/src/java/com/threerings/jme/tools/CompileModelTask.java
@@ -0,0 +1,106 @@
+//
+// $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.tools;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+
+import java.util.ArrayList;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.FileSet;
+
+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.ModelParser;
+
+/**
+ * An ant task for compiling 3D models defined in XML to fast-loading binary
+ * files.
+ */
+public class CompileModelTask extends Task
+{
+ public void addFileset (FileSet set)
+ {
+ _filesets.add(set);
+ }
+
+ public void execute ()
+ throws BuildException
+ {
+ for (int ii = 0, nn = _filesets.size(); ii < nn; ii++) {
+ FileSet fs = _filesets.get(ii);
+ DirectoryScanner ds = fs.getDirectoryScanner(getProject());
+ File fromDir = fs.getDir(getProject());
+ String[] srcFiles = ds.getIncludedFiles();
+
+ for (int f = 0; f < srcFiles.length; f++) {
+ File cfile = new File(fromDir, srcFiles[f]);
+ String target = srcFiles[f];
+ int didx = target.lastIndexOf(".");
+ target = (didx == -1) ? target : target.substring(0, didx);
+ target += ".dat";
+ compileModel(cfile, new File(fromDir, target));
+ }
+ }
+ }
+
+ protected void compileModel (File source, File target)
+ {
+ if (source.lastModified() < target.lastModified()) {
+ return;
+ }
+ System.out.println("Compiling " + source + "...");
+
+ ModelDef mdef;
+ try {
+ mdef = _mparser.parseModel(source.toString());
+
+ } catch (Exception e) {
+ System.err.println("Error parsing '" + source + "': " + e);
+ return;
+ }
+
+ Model model = mdef.createModel("model");
+ try {
+ FileOutputStream fos = new FileOutputStream(target);
+ new ObjectOutputStream(fos).writeObject(model);
+
+ } catch (IOException e) {
+ System.err.println("Error writing '" + target + "': " + e);
+ }
+ }
+
+ /** A list of filesets that contain XML models. */
+ protected ArrayList _filesets = new ArrayList();
+
+ /** A parser for the model definitions. */
+ protected ModelParser _mparser = new ModelParser();
+}
diff --git a/src/java/com/threerings/jme/tools/ModelDef.java b/src/java/com/threerings/jme/tools/ModelDef.java
new file mode 100644
index 000000000..ac89c7b6c
--- /dev/null
+++ b/src/java/com/threerings/jme/tools/ModelDef.java
@@ -0,0 +1,364 @@
+//
+// $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.tools;
+
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+import com.jme.scene.Spatial;
+import com.jme.util.geom.BufferUtils;
+
+import com.threerings.jme.Log;
+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;
+
+/**
+ * An intermediate representation for model nodes using in XML parsing.
+ */
+public class ModelDef
+{
+ /** The base class of nodes in the model. */
+ public abstract static class SpatialDef
+ {
+ /** The node's name. */
+ public String name;
+
+ /** The name of the node's parent. */
+ public String parent;
+
+ /** The node's transformation. */
+ public float[] translation;
+ public float[] rotation;
+ public float[] scale;
+
+ /** Returns a JME node for this definition. */
+ public Spatial getSpatial ()
+ {
+ if (_spatial == null) {
+ _spatial = createSpatial();
+ setTransform();
+ }
+ return _spatial;
+ }
+
+ /** Sets the transform of the created node. */
+ protected void setTransform ()
+ {
+ _spatial.getLocalTranslation().set(translation[0], translation[1],
+ translation[2]);
+ _spatial.getLocalRotation().set(rotation[0], rotation[1],
+ rotation[2], rotation[3]);
+ _spatial.getLocalScale().set(scale[0], scale[1], scale[2]);
+ }
+
+ /** Creates a JME node for this definition. */
+ public abstract Spatial createSpatial ();
+
+ /** Resolves any name references using the supplied map. */
+ public void resolveReferences (HashMap nodes)
+ {
+ Spatial pnode = nodes.get(parent);
+ if (pnode instanceof ModelNode) {
+ ((ModelNode)pnode).attachChild(_spatial);
+ } else if (parent != null) {
+ Log.warning("Missing or invalid parent node [spatial=" +
+ name + ", parent=" + parent + "].");
+ }
+ }
+
+ /** The JME node created for this definition. */
+ protected Spatial _spatial;
+ }
+
+ /** A rigid triangle mesh. */
+ public static class TriMeshDef extends SpatialDef
+ {
+ /** The vertices of the mesh. */
+ public ArrayList vertices = new ArrayList();
+
+ /** The triangle indices. */
+ public ArrayList indices = new ArrayList();
+
+ /** Whether or not any of the vertices have texture coordinates. */
+ public boolean tcoords;
+
+ public void addVertex (Vertex vertex)
+ {
+ int idx = vertices.indexOf(vertex);
+ if (idx != -1) {
+ indices.add(idx);
+ } else {
+ indices.add(vertices.size());
+ vertices.add(vertex);
+ }
+ tcoords = tcoords || vertex.tcoords != null;
+ }
+
+ // documentation inherited
+ public Spatial createSpatial ()
+ {
+ return setBuffers(new ModelMesh(name));
+ }
+
+ /** Sets the mesh's geometry buffers and returns it. */
+ protected ModelMesh setBuffers (ModelMesh mmesh)
+ {
+ int vsize = vertices.size();
+ ByteBuffer vbbuf = ByteBuffer.allocateDirect(vsize*3*4),
+ nbbuf = ByteBuffer.allocateDirect(vsize*3*4),
+ tbbuf = tcoords ? ByteBuffer.allocateDirect(vsize*2*4) : null,
+ ibbuf = ByteBuffer.allocateDirect(indices.size()*4);
+ FloatBuffer vbuf = vbbuf.asFloatBuffer(),
+ nbuf = nbbuf.asFloatBuffer(),
+ tbuf = tcoords ? tbbuf.asFloatBuffer() : null;
+ for (int ii = 0; ii < vsize; ii++) {
+ vertices.get(ii).setInBuffers(vbuf, nbuf, tbuf);
+ }
+ IntBuffer ibuf = ibbuf.asIntBuffer();
+ for (int ii = 0, nn = indices.size(); ii < nn; ii++) {
+ ibuf.put(indices.get(ii));
+ }
+ mmesh.reconstruct(vbbuf, nbbuf, null, tbbuf, ibbuf);
+ return mmesh;
+ }
+ }
+
+ /** A triangle mesh that deforms according to bone positions. */
+ public static class SkinMeshDef extends TriMeshDef
+ {
+ // documentation inherited
+ public Spatial createSpatial ()
+ {
+ return setBuffers(new SkinMesh(name));
+ }
+
+ @Override // documentation inherited
+ public void resolveReferences (HashMap nodes)
+ {
+ super.resolveReferences(nodes);
+
+ // divide the vertices up by weight groups
+ HashMap, WeightGroupDef> groups =
+ new HashMap, WeightGroupDef>();
+ for (int ii = 0, nn = vertices.size(); ii < nn; ii++) {
+ SkinVertex svertex = (SkinVertex)vertices.get(ii);
+ HashSet bones = svertex.getBoneNames();
+ WeightGroupDef group = groups.get(bones);
+ if (group == null) {
+ groups.put(bones, group = new WeightGroupDef());
+ }
+ group.indices.add(ii);
+ for (String bone : bones) {
+ group.weights.add(svertex.getWeight(bone));
+ }
+ }
+
+ // resolve names and set in mesh
+ SkinMesh.WeightGroup[] wgroups =
+ new SkinMesh.WeightGroup[groups.size()];
+ int ii = 0;
+ for (Map.Entry, WeightGroupDef> entry :
+ groups.entrySet()) {
+ SkinMesh.WeightGroup wgroup = new SkinMesh.WeightGroup();
+ wgroup.indices = toArray(entry.getValue().indices);
+ wgroup.bones = new BoneNode[entry.getKey().size()];
+ int jj = 0;
+ for (String bone : entry.getKey()) {
+ Spatial node = nodes.get(bone);
+ if (!(node instanceof BoneNode)) {
+ Log.warning("Missing or invalid bone reference " +
+ "[mesh=" + name + ", bone=" + bone + "].");
+ return;
+ }
+ wgroup.bones[jj++] = (BoneNode)node;
+ }
+ wgroup.weights = toArray(entry.getValue().weights);
+ wgroups[ii++] = wgroup;
+ }
+ ((SkinMesh)_spatial).setWeightGroups(wgroups);
+ }
+ }
+
+ /** A generic node. */
+ public static class NodeDef extends SpatialDef
+ {
+ // documentation inherited
+ public Spatial createSpatial ()
+ {
+ return new ModelNode(name);
+ }
+ }
+
+ /** A bone that influences skinned meshes. */
+ public static class BoneNodeDef extends NodeDef
+ {
+ // documentation inherited
+ public Spatial createSpatial ()
+ {
+ return new BoneNode(name);
+ }
+ }
+
+ /** A basic vertex. */
+ public static class Vertex
+ {
+ public float[] location;
+ public float[] normal;
+ public float[] tcoords;
+
+ public void setInBuffers (
+ FloatBuffer vbuf, FloatBuffer nbuf, FloatBuffer tbuf)
+ {
+ vbuf.put(location);
+ nbuf.put(normal);
+ if (tbuf != null) {
+ tbuf.put(tcoords);
+ }
+ }
+
+ public boolean equals (Object obj)
+ {
+ Vertex overt = (Vertex)obj;
+ return Arrays.equals(location, overt.location) &&
+ Arrays.equals(normal, overt.normal) &&
+ Arrays.equals(tcoords, overt.tcoords);
+ }
+ }
+
+ /** A vertex influenced by a number of bones. */
+ public static class SkinVertex extends Vertex
+ {
+ /** The bones influencing the vertex. */
+ public ArrayList boneWeights = new ArrayList();
+
+ public void addBoneWeight (BoneWeight weight)
+ {
+ boneWeights.add(weight);
+ }
+
+ /** Returns the set of names of the bones influencing this vertex. */
+ public HashSet getBoneNames ()
+ {
+ HashSet names = new HashSet();
+ for (BoneWeight bweight : boneWeights) {
+ names.add(bweight.bone);
+ }
+ return names;
+ }
+
+ /** Returns the weight of the named bone. */
+ public float getWeight (String bone)
+ {
+ for (BoneWeight bweight : boneWeights) {
+ if (bweight.bone.equals(bone)) {
+ return bweight.weight;
+ }
+ }
+ return 0f;
+ }
+ }
+
+ /** The influence of a bone on a vertex. */
+ public static class BoneWeight
+ {
+ /** The name of the influencing bone. */
+ public String bone;
+
+ /** The amount of influence. */
+ public float weight;
+ }
+
+ /** A group of vertices influenced by the same bone. */
+ public static class WeightGroupDef
+ {
+ /** The indices of the affected vertex. */
+ public ArrayList indices = new ArrayList();
+
+ /** The interleaved vertex weights. */
+ public ArrayList weights = new ArrayList();
+ }
+
+ /** The meshes and bones comprising the model. */
+ public ArrayList spatials = new ArrayList();
+
+ public void addSpatial (SpatialDef spatial)
+ {
+ spatials.add(spatial);
+ }
+
+ /**
+ * Creates the model node defined herein.
+ */
+ public Model createModel (String name)
+ {
+ Model model = new Model(name);
+
+ // 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();
+ nodes.put(spatial.getName(), spatial);
+ }
+
+ // then go through again, resolving any name references and attaching
+ // root children
+ for (int ii = 0, nn = spatials.size(); ii < nn; ii++) {
+ SpatialDef sdef = spatials.get(ii);
+ sdef.resolveReferences(nodes);
+ if (sdef.getSpatial().getParent() == null) {
+ model.attachChild(sdef.getSpatial());
+ }
+ }
+
+ return model;
+ }
+
+ /** Converts a boxed Integer list to an unboxed int array. */
+ protected static int[] toArray (ArrayList list)
+ {
+ int[] array = new int[list.size()];
+ for (int ii = 0, nn = list.size(); ii < nn; ii++) {
+ array[ii] = list.get(ii);
+ }
+ return array;
+ }
+
+ /** Converts a boxed Float list to an unboxed float array. */
+ protected static float[] toArray (ArrayList list)
+ {
+ float[] array = new float[list.size()];
+ for (int ii = 0, nn = list.size(); ii < nn; ii++) {
+ array[ii] = list.get(ii);
+ }
+ return array;
+ }
+}
diff --git a/src/java/com/threerings/jme/tools/xml/AnimationParser.java b/src/java/com/threerings/jme/tools/xml/AnimationParser.java
new file mode 100644
index 000000000..f8f348563
--- /dev/null
+++ b/src/java/com/threerings/jme/tools/xml/AnimationParser.java
@@ -0,0 +1,86 @@
+//
+// $Id: SceneParser.java 3749 2005-11-09 04:00:16Z mdb $
+//
+// Narya library - tools for developing networked games
+// Copyright (C) 2002-2004 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.tools.xml;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import org.xml.sax.SAXException;
+import org.apache.commons.digester.Digester;
+
+import com.samskivert.xml.SetPropertyFieldsRule;
+
+import com.threerings.jme.tools.AnimationDef;
+
+/**
+ * Parses XML files containing animations.
+ */
+public class AnimationParser
+{
+ public AnimationParser ()
+ {
+ // create and configure our digester
+ _digester = new Digester();
+
+ // add the rules
+ String anim = "animation";
+ _digester.addObjectCreate(anim, AnimationDef.class.getName());
+ _digester.addSetNext(anim, "setAnimation",
+ AnimationDef.class.getName());
+
+ String frame = anim + "/frame";
+ _digester.addObjectCreate(frame, AnimationDef.Frame.class.getName());
+ _digester.addSetNext(frame, "addFrame",
+ AnimationDef.Frame.class.getName());
+
+ String xform = frame + "/transform";
+ _digester.addObjectCreate(xform,
+ AnimationDef.Transform.class.getName());
+ _digester.addRule(xform, new SetPropertyFieldsRule());
+ _digester.addSetNext(xform, "addTransform",
+ AnimationDef.Transform.class.getName());
+ }
+
+ /**
+ * Parses the XML file at the specified path into an animation
+ * definition.
+ */
+ public AnimationDef parseAnimation (String path)
+ throws IOException, SAXException
+ {
+ _animation = null;
+ _digester.push(this);
+ _digester.parse(new FileInputStream(path));
+ return _animation;
+ }
+
+ /**
+ * Called by the parser once the animation is parsed.
+ */
+ public void setAnimation (AnimationDef animation)
+ {
+ _animation = animation;
+ }
+
+ protected Digester _digester;
+ protected AnimationDef _animation;
+}
diff --git a/src/java/com/threerings/jme/tools/xml/ModelParser.java b/src/java/com/threerings/jme/tools/xml/ModelParser.java
new file mode 100644
index 000000000..a73964f17
--- /dev/null
+++ b/src/java/com/threerings/jme/tools/xml/ModelParser.java
@@ -0,0 +1,113 @@
+//
+// $Id: SceneParser.java 3749 2005-11-09 04:00:16Z mdb $
+//
+// Narya library - tools for developing networked games
+// Copyright (C) 2002-2004 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.tools.xml;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import org.xml.sax.SAXException;
+import org.apache.commons.digester.Digester;
+
+import com.samskivert.xml.SetPropertyFieldsRule;
+
+import com.threerings.jme.tools.ModelDef;
+
+/**
+ * Parses XML files containing 3D models.
+ */
+public class ModelParser
+{
+ public ModelParser ()
+ {
+ // create and configure our digester
+ _digester = new Digester();
+
+ // add the rules
+ String model = "model";
+ _digester.addObjectCreate(model, ModelDef.class.getName());
+ _digester.addSetNext(model, "setModel", ModelDef.class.getName());
+
+ String tmesh = model + "/triMesh";
+ _digester.addObjectCreate(tmesh, ModelDef.TriMeshDef.class.getName());
+ _digester.addRule(tmesh, new SetPropertyFieldsRule());
+ _digester.addSetNext(tmesh, "addSpatial",
+ ModelDef.SpatialDef.class.getName());
+
+ String smesh = model + "/skinMesh";
+ _digester.addObjectCreate(smesh,
+ ModelDef.SkinMeshDef.class.getName());
+ _digester.addRule(smesh, new SetPropertyFieldsRule());
+ _digester.addSetNext(smesh, "addSpatial",
+ ModelDef.SpatialDef.class.getName());
+
+ String node = model + "/node";
+ _digester.addObjectCreate(node, ModelDef.NodeDef.class.getName());
+ _digester.addRule(node, new SetPropertyFieldsRule());
+ _digester.addSetNext(node, "addSpatial",
+ ModelDef.SpatialDef.class.getName());
+
+ String bnode = model + "/boneNode";
+ _digester.addObjectCreate(bnode, ModelDef.BoneNodeDef.class.getName());
+ _digester.addRule(bnode, new SetPropertyFieldsRule());
+ _digester.addSetNext(bnode, "addSpatial",
+ ModelDef.SpatialDef.class.getName());
+
+ String vertex = tmesh + "/vertex";
+ _digester.addObjectCreate(vertex, ModelDef.Vertex.class.getName());
+ _digester.addObjectCreate(smesh + "/vertex",
+ ModelDef.SkinVertex.class.getName());
+ _digester.addRule("*/vertex", new SetPropertyFieldsRule());
+
+ String bweight = smesh + "/vertex/boneWeight";
+ _digester.addObjectCreate(bweight,
+ ModelDef.BoneWeight.class.getName());
+ _digester.addRule(bweight, new SetPropertyFieldsRule());
+ _digester.addSetNext(bweight, "addBoneWeight",
+ ModelDef.BoneWeight.class.getName());
+
+ _digester.addSetNext("*/vertex", "addVertex",
+ ModelDef.Vertex.class.getName());
+ }
+
+ /**
+ * Parses the XML file at the specified path into a model definition.
+ */
+ public ModelDef parseModel (String path)
+ throws IOException, SAXException
+ {
+ _model = null;
+ _digester.push(this);
+ _digester.parse(new FileInputStream(path));
+ return _model;
+ }
+
+ /**
+ * Called by the parser once the model is parsed.
+ */
+ public void setModel (ModelDef model)
+ {
+ _model = model;
+ }
+
+ protected Digester _digester;
+ protected ModelDef _model;
+}
diff --git a/src/ms/TRAnimationExporter.mcr b/src/ms/TRAnimationExporter.mcr
new file mode 100644
index 000000000..59ba38dc5
--- /dev/null
+++ b/src/ms/TRAnimationExporter.mcr
@@ -0,0 +1,92 @@
+--
+-- $Id$
+--
+-- Narya library - tools for developing networked games
+-- Copyright (C) 2002-2004 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
+
+macroScript TRAnimationExporter category:"File" \
+ buttonText:"Export Animation as XML..." toolTip:"Export Animation as XML" (
+
+ -- Writes a point3-valued attribute to the file
+ fn writePoint3Attr attr p outFile =
+ (
+ format "%=\"%, %, %\"" attr p.x p.y p.z to:outFile
+ )
+
+ -- Writes a quat-valued attribute to the file
+ fn writeQuatAttr attr q outFile =
+ (
+ format "%=\"%, %, %, %\"" attr q.x q.y q.z q.w to:outFile
+ )
+
+ -- Writes a single node transform
+ fn writeTransform node outFile = (
+ format " \n" to:outFile
+ )
+
+ -- Writes a single animation frame
+ fn writeFrame nodes outFile = (
+ format " \n" to:outFile
+ for node in nodes do in coordsys parent (
+ writeTransform node outFile
+ )
+ format " \n\n" to:outFile
+ )
+
+ -- Writes animation to the named file
+ fn writeAnimation fileName =
+ (
+ outFile = createfile fileName
+ format "\n\n" to:outFile
+ format "\n\n" to:outFile
+ local nodes
+ if selection.count > 0 then (
+ nodes = selection
+ ) else (
+ nodes = objects
+ )
+ for i = animationRange.start to animationRange.end do at time i (
+ writeFrame nodes outFile
+ )
+ format "\n" to:outFile
+ close outFile
+ )
+
+ --
+ -- Main entry point
+ --
+
+ -- Get the target filename
+ persistent global xmlAnimFileName
+ local fileName
+ if (xmlAnimFileName == undefined) then (
+ fileName = maxFilePath + (getFilenameFile maxFileName) + ".xml"
+ ) else (
+ fileName = xmlAnimFileName
+ )
+ fileName = getSaveFileName caption:"Select File to Export" \
+ filename:fileName types:"XML Animations (*.XML)|*.xml|All|*.*"
+ if fileName != undefined do (
+ xmlAnimFileName = fileName
+ writeAnimation fileName
+ )
+)
diff --git a/src/ms/TRModelExporter.mcr b/src/ms/TRModelExporter.mcr
new file mode 100644
index 000000000..07cc9b199
--- /dev/null
+++ b/src/ms/TRModelExporter.mcr
@@ -0,0 +1,172 @@
+--
+-- $Id$
+--
+-- Narya library - tools for developing networked games
+-- Copyright (C) 2002-2004 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
+
+macroScript TRModelExporter category:"File" \
+ buttonText:"Export Model as XML..." toolTip:"Export Model as XML" (
+
+ -- Writes a point3-valued attribute to the file
+ fn writePoint3Attr attr p outFile =
+ (
+ format "%=\"%, %, %\"" attr p.x p.y p.z to:outFile
+ )
+
+ -- Writes a quat-valued attribute to the file
+ fn writeQuatAttr attr q outFile =
+ (
+ format "%=\"%, %, %, %\"" attr q.x q.y q.z q.w to:outFile
+ )
+
+ -- Writes a skinned vertex's bone weights to the file
+ fn writeSkinBoneWeights skin vidx outFile =
+ (
+ weightCount = skinOps.getVertexWeightCount skin vidx
+ for i = 1 to weightCount do (
+ bone = skinOps.getBoneName skin (skinOps.getVertexWeightBoneID \
+ skin vidx i) 0
+ weight = skinOps.getVertexWeight skin vidx i
+ format " \n" bone \
+ weight to:outFile
+ )
+ )
+
+ -- Writes a physiqued vertex's bone weights to the file
+ fn writePhysiqueBoneWeights node vidx outFile =
+ (
+ boneCount = physiqueOps.getVertexBoneCount node vidx
+ for i = 1 to boneCount do (
+ bone = physiqueOps.getVertexBone node vidx i
+ weight = physiqueOps.getVertexWeight node vidx i
+ format " \n" bone.name \
+ weight to:outFile
+ )
+ )
+
+ -- Writes a mesh's vertices to the file
+ fn writeVertices mesh outFile =
+ (
+ for i = 1 to mesh.numfaces do (
+ face = getFace mesh i
+ local tvface
+ if mesh.numtverts > 0 do (
+ tvface = getTVFace mesh i
+ )
+ for i = 1 to 3 do (
+ format " \n" to:outFile
+ if isProperty mesh #skin then (
+ writeSkinBoneWeights mesh.skin face[i] outFile
+ ) else (
+ writePhysiqueBoneWeights mesh face[i] outFile
+ )
+ format " \n" to:outFile
+ ) else (
+ format "/>\n" to:outFile
+ )
+ )
+ )
+ )
+
+ -- Writes a node to the file
+ fn writeNode node outFile =
+ (
+ local kind
+ isMesh = false
+ if isKindOf node Editable_Mesh then (
+ isMesh = true
+ if isProperty node #skin or isProperty node #physique then (
+ kind = "skinMesh"
+ ) else (
+ kind = "triMesh"
+ )
+ ) else if isKindOf node BoneGeometry then (
+ kind = "boneNode"
+ ) else (
+ kind = "node"
+ )
+ format " <% name=\"%\"" kind node.name to:outFile
+ if node.parent != undefined do (
+ format " parent=\"%\"" node.parent.name to:outFile
+ )
+ in coordsys parent (
+ writePoint3Attr " translation" node.transform.translationPart \
+ outFile
+ writeQuatAttr " rotation" (inverse node.transform.rotationPart) \
+ outFile
+ writePoint3Attr " scale" node.transform.scalePart outFile
+ )
+ if isMesh then (
+ format ">\n" to:outFile
+ if isProperty node #skin do (
+ modPanel.setCurrentObject node.skin node:node ui:true
+ )
+ writeVertices node outFile
+ format " %>\n\n" kind to:outFile
+
+ ) else (
+ format "\>\n\n" to:outFile
+ )
+ )
+
+ -- Writes the model to the named file
+ fn writeModel fileName =
+ (
+ outFile = createfile fileName
+ format "\n\n" to:outFile
+ format "\n\n" to:outFile
+ local nodes
+ if selection.count > 0 then (
+ nodes = selection
+ ) else (
+ nodes = objects
+ )
+ for node in nodes do (
+ writeNode node outFile
+ )
+ format "\n" to:outFile
+ close outFile
+ )
+
+ --
+ -- Main entry point
+ --
+
+ -- Get the target filename
+ persistent global xmlModelFileName
+ local fileName
+ if (xmlModelFileName == undefined) then (
+ fileName = maxFilePath + (getFilenameFile maxFileName) + ".xml"
+ ) else (
+ fileName = xmlModelFileName
+ )
+ fileName = getSaveFileName caption:"Select File to Export" \
+ filename:fileName types:"XML Models (*.XML)|*.xml|All|*.*"
+ if fileName != undefined do (
+ xmlModelFileName = fileName
+ writeModel fileName
+ )
+)