diff --git a/build.xml b/build.xml
index d701b9b2a..e0368558c 100644
--- a/build.xml
+++ b/build.xml
@@ -243,6 +243,7 @@
+
diff --git a/rsrc/i18n/jme/viewer.properties b/rsrc/i18n/jme/viewer.properties
new file mode 100644
index 000000000..06093f2be
--- /dev/null
+++ b/rsrc/i18n/jme/viewer.properties
@@ -0,0 +1,19 @@
+#
+# $Id$
+#
+# Translation strings for ModelViewer messages.
+
+m.title = Three Rings Model Viewer
+
+m.file_menu = File
+m.file_load = Load Model...
+m.file_quit = Quit
+
+m.load_title = Select Model to Load
+m.load_filter = Model Files (*.properties, *.dat)
+
+m.compiling_model = Compiling {0}...
+m.loading_model = Loading {0}...
+m.loaded_model = Loaded {0}
+m.load_error = Error loading {0}: {1}
+m.invalid_type = Invalid file type.
diff --git a/src/java/com/threerings/jme/camera/CameraHandler.java b/src/java/com/threerings/jme/camera/CameraHandler.java
index a9d84e72d..b59c0698e 100644
--- a/src/java/com/threerings/jme/camera/CameraHandler.java
+++ b/src/java/com/threerings/jme/camera/CameraHandler.java
@@ -89,13 +89,13 @@ public class CameraHandler
}
/**
- * Configures the minimum and maximum z-axis elevation allowed for the
+ * Configures the minimum and maximum zoom values allowed for the
* camera.
*/
- public void setZoomLimits (float minZ, float maxZ)
+ public void setZoomLimits (float minZoom, float maxZoom)
{
- _minZ = minZ;
- _maxZ = maxZ;
+ _minZoom = minZoom;
+ _maxZoom = maxZoom;
}
/**
@@ -230,6 +230,12 @@ public class CameraHandler
public void zoomCamera (float distance)
{
Vector3f loc = _camera.getLocation();
+ float dist = -1f * _ground.normal.dot(loc) /
+ _ground.normal.dot(_camera.getDirection()),
+ ndist = Math.min(Math.max(dist + distance, _minZoom), _maxZoom);
+ if ((distance = ndist - dist) == 0f) {
+ return;
+ }
loc.subtractLocal(_camera.getDirection().mult(distance, _temp));
setLocation(loc);
}
@@ -247,6 +253,17 @@ public class CameraHandler
// which we're going to orbit
Vector3f direction = _camera.getLocation().subtract(spot);
+ // if we're rotating around the left vector, impose tilt limits
+ if (axis == _camera.getLeft()) {
+ float angle = FastMath.asin(_ground.normal.dot(direction) /
+ direction.length());
+ float nangle = Math.min(Math.max(angle + deltaAngle, _minTilt),
+ _maxTilt);
+ if ((deltaAngle = nangle - angle) == 0f) {
+ return;
+ }
+ }
+
// create a rotation matrix
_rotm.fromAxisAngle(axis, deltaAngle);
@@ -383,6 +400,7 @@ public class CameraHandler
protected float _minX = -Float.MAX_VALUE, _maxX = Float.MAX_VALUE;
protected float _minY = -Float.MAX_VALUE, _maxY = Float.MAX_VALUE;
protected float _minZ = -Float.MAX_VALUE, _maxZ = Float.MAX_VALUE;
+ protected float _minZoom = 0f, _maxZoom = Float.MAX_VALUE;
protected float _minTilt = -Float.MAX_VALUE, _maxTilt = Float.MAX_VALUE;
protected Vector3f _rxdir = new Vector3f(1, 0, 0);
diff --git a/src/java/com/threerings/jme/model/BoneNode.java b/src/java/com/threerings/jme/model/BoneNode.java
index f83c6580e..f6c0c93cd 100644
--- a/src/java/com/threerings/jme/model/BoneNode.java
+++ b/src/java/com/threerings/jme/model/BoneNode.java
@@ -21,7 +21,6 @@
package com.threerings.jme.model;
-import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
diff --git a/src/java/com/threerings/jme/model/Model.java b/src/java/com/threerings/jme/model/Model.java
index 51b9d53e4..fc4cccf22 100644
--- a/src/java/com/threerings/jme/model/Model.java
+++ b/src/java/com/threerings/jme/model/Model.java
@@ -30,8 +30,12 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
+import java.nio.ByteOrder;
+import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
+import java.util.Properties;
+
import com.threerings.jme.Log;
/**
@@ -63,8 +67,10 @@ public class Model extends ModelNode
FileChannel fc = fis.getChannel();
if (map) {
long pos = fc.position();
- model.sliceBuffers(fc.map(FileChannel.MapMode.READ_ONLY,
- pos, fc.size() - pos));
+ MappedByteBuffer buf = fc.map(FileChannel.MapMode.READ_ONLY,
+ pos, fc.size() - pos);
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+ model.sliceBuffers(buf);
} else {
model.readBuffers(fc);
}
@@ -82,9 +88,18 @@ public class Model extends ModelNode
/**
* Standard constructor.
*/
- public Model (String name)
+ public Model (String name, Properties props)
{
super(name);
+ _props = props;
+ }
+
+ /**
+ * Returns a reference to the properties of the model.
+ */
+ public Properties getProperties ()
+ {
+ return _props;
}
/**
@@ -109,14 +124,19 @@ public class Model extends ModelNode
throws IOException
{
super.writeExternal(out);
+ out.writeObject(_props);
}
@Override // documentation inherited
public void readExternal (ObjectInput in)
- throws IOException
+ throws IOException, ClassNotFoundException
{
super.readExternal(in);
+ _props = (Properties)in.readObject();
}
+ /** The model properties. */
+ protected Properties _props;
+
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 12a5d5e9c..fc3eefdee 100644
--- a/src/java/com/threerings/jme/model/ModelMesh.java
+++ b/src/java/com/threerings/jme/model/ModelMesh.java
@@ -27,11 +27,16 @@ import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
+import java.util.Properties;
+
+import com.jme.bounding.BoundingBox;
+import com.jme.bounding.BoundingSphere;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.TriMesh;
@@ -49,6 +54,7 @@ public class ModelMesh extends TriMesh
*/
public ModelMesh ()
{
+ super("mesh");
}
/**
@@ -69,6 +75,15 @@ public class ModelMesh extends TriMesh
super(name, vertices, normals, colors, tcoords, indices);
}
+ /**
+ * Configures this mesh based on the given (sub-)properties.
+ */
+ public void configure (Properties props)
+ {
+ _boundingType = "sphere".equals(props.getProperty("bound")) ?
+ SPHERE_BOUND : BOX_BOUND;
+ }
+
/**
* Sets the buffers as {@link ByteBuffer}s, because we can't create byte
* views of non-byte buffers.
@@ -88,6 +103,13 @@ public class ModelMesh extends TriMesh
_colorByteBuffer = colors;
_textureByteBuffer = textures;
_indexByteBuffer = indices;
+
+ if (_boundingType == BOX_BOUND) {
+ setModelBound(new BoundingBox());
+ } else { // _boundingType == SPHERE_BOUND
+ setModelBound(new BoundingSphere());
+ }
+ updateModelBound();
}
// documentation inherited
@@ -130,27 +152,23 @@ public class ModelMesh extends TriMesh
out.writeInt(_colorBufferSize);
out.writeInt(_textureBufferSize);
out.writeInt(_indexBufferSize);
+ out.writeInt(_boundingType);
}
// documentation inherited from interface Externalizable
public void readExternal (ObjectInput in)
- throws IOException
+ throws IOException, ClassNotFoundException
{
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 + "].");
- }
+ setLocalTranslation((Vector3f)in.readObject());
+ setLocalRotation((Quaternion)in.readObject());
+ setLocalScale((Vector3f)in.readObject());
_vertexBufferSize = in.readInt();
_normalBufferSize = in.readInt();
_colorBufferSize = in.readInt();
_textureBufferSize = in.readInt();
_indexBufferSize = in.readInt();
+ _boundingType = in.readInt();
}
// documentation inherited from interface ModelSpatial
@@ -159,23 +177,33 @@ public class ModelMesh extends TriMesh
{
if (_vertexBufferSize > 0) {
_vertexByteBuffer.rewind();
+ convertOrder(_vertexByteBuffer, ByteOrder.LITTLE_ENDIAN);
out.write(_vertexByteBuffer);
+ convertOrder(_vertexByteBuffer, ByteOrder.nativeOrder());
}
if (_normalBufferSize > 0) {
_normalByteBuffer.rewind();
+ convertOrder(_normalByteBuffer, ByteOrder.LITTLE_ENDIAN);
out.write(_normalByteBuffer);
+ convertOrder(_normalByteBuffer, ByteOrder.nativeOrder());
}
if (_colorBufferSize > 0) {
_colorByteBuffer.rewind();
+ convertOrder(_colorByteBuffer, ByteOrder.LITTLE_ENDIAN);
out.write(_colorByteBuffer);
+ convertOrder(_colorByteBuffer, ByteOrder.nativeOrder());
}
if (_textureBufferSize > 0) {
- _indexByteBuffer.rewind();
- out.write(_indexByteBuffer);
+ _textureByteBuffer.rewind();
+ convertOrder(_textureByteBuffer, ByteOrder.LITTLE_ENDIAN);
+ out.write(_textureByteBuffer);
+ convertOrder(_textureByteBuffer, ByteOrder.nativeOrder());
}
if (_indexBufferSize > 0) {
_indexByteBuffer.rewind();
+ convertOrder(_indexByteBuffer, ByteOrder.LITTLE_ENDIAN);
out.write(_indexByteBuffer);
+ convertOrder(_indexByteBuffer, ByteOrder.nativeOrder());
}
}
@@ -185,30 +213,36 @@ public class ModelMesh extends TriMesh
{
ByteBuffer vbbuf = null, nbbuf = null, cbbuf = null, tbbuf = null,
ibbuf = null;
+ ByteOrder le = ByteOrder.LITTLE_ENDIAN;
if (_vertexBufferSize > 0) {
- vbbuf = ByteBuffer.allocateDirect(_vertexBufferSize*3*4);
+ vbbuf = ByteBuffer.allocateDirect(_vertexBufferSize*4).order(le);
in.read(vbbuf);
vbbuf.rewind();
+ convertOrder(vbbuf, ByteOrder.nativeOrder());
}
if (_normalBufferSize > 0) {
- nbbuf = ByteBuffer.allocateDirect(_normalBufferSize*3*4);
+ nbbuf = ByteBuffer.allocateDirect(_normalBufferSize*4).order(le);
in.read(nbbuf);
nbbuf.rewind();
+ convertOrder(nbbuf, ByteOrder.nativeOrder());
}
if (_colorBufferSize > 0) {
- cbbuf = ByteBuffer.allocateDirect(_colorBufferSize*4*4);
+ cbbuf = ByteBuffer.allocateDirect(_colorBufferSize*4).order(le);
in.read(cbbuf);
cbbuf.rewind();
+ convertOrder(cbbuf, ByteOrder.nativeOrder());
}
if (_textureBufferSize > 0) {
- tbbuf = ByteBuffer.allocateDirect(_textureBufferSize*2*4);
+ tbbuf = ByteBuffer.allocateDirect(_textureBufferSize*4).order(le);
in.read(tbbuf);
tbbuf.rewind();
+ convertOrder(tbbuf, ByteOrder.nativeOrder());
}
if (_indexBufferSize > 0) {
- ibbuf = ByteBuffer.allocateDirect(_indexBufferSize*4);
+ ibbuf = ByteBuffer.allocateDirect(_indexBufferSize*4).order(le);
in.read(ibbuf);
ibbuf.rewind();
+ convertOrder(ibbuf, ByteOrder.nativeOrder());
}
reconstruct(vbbuf, nbbuf, cbbuf, tbbuf, ibbuf);
}
@@ -218,26 +252,27 @@ public class ModelMesh extends TriMesh
{
ByteBuffer vbbuf = null, nbbuf = null, cbbuf = null, tbbuf = null,
ibbuf = null;
+ int total = 0;
if (_vertexBufferSize > 0) {
- int npos = map.position() + _vertexBufferSize*3*4;
+ int npos = map.position() + _vertexBufferSize*4;
map.limit(npos);
vbbuf = map.slice();
map.position(npos);
}
if (_normalBufferSize > 0) {
- int npos = map.position() + _normalBufferSize*3*4;
+ int npos = map.position() + _normalBufferSize*4;
map.limit(npos);
nbbuf = map.slice();
map.position(npos);
}
if (_colorBufferSize > 0) {
- int npos = map.position() + _colorBufferSize*4*4;
+ int npos = map.position() + _colorBufferSize*4;
map.limit(npos);
cbbuf = map.slice();
map.position(npos);
}
if (_textureBufferSize > 0) {
- int npos = map.position() + _textureBufferSize*2*4;
+ int npos = map.position() + _textureBufferSize*4;
map.limit(npos);
tbbuf = map.slice();
map.position(npos);
@@ -251,6 +286,21 @@ public class ModelMesh extends TriMesh
reconstruct(vbbuf, nbbuf, cbbuf, tbbuf, ibbuf);
}
+ /**
+ * Imposes the specified order on the given buffer of 32 bit values.
+ */
+ protected void convertOrder (ByteBuffer buf, ByteOrder order)
+ {
+ if (buf.order() == order) {
+ return;
+ }
+ IntBuffer obuf = buf.asIntBuffer(),
+ nbuf = buf.order(order).asIntBuffer();
+ while (obuf.hasRemaining()) {
+ nbuf.put(obuf.get());
+ }
+ }
+
/** The sizes of the various buffers (zero for null). */
protected int _vertexBufferSize, _normalBufferSize, _colorBufferSize,
_textureBufferSize, _indexBufferSize;
@@ -259,5 +309,14 @@ public class ModelMesh extends TriMesh
protected ByteBuffer _vertexByteBuffer, _normalByteBuffer,
_colorByteBuffer, _textureByteBuffer, _indexByteBuffer;
+ /** The type of bounding volume that this mesh should use. */
+ protected int _boundingType;
+
+ /** Indicates that this mesh should use a bounding box. */
+ protected static final int BOX_BOUND = 0;
+
+ /** Indicates that this mesh should use a bounding sphere. */
+ protected static final int SPHERE_BOUND = 1;
+
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
index 56ff69233..c6fc60b56 100644
--- a/src/java/com/threerings/jme/model/ModelNode.java
+++ b/src/java/com/threerings/jme/model/ModelNode.java
@@ -50,6 +50,7 @@ public class ModelNode extends Node
*/
public ModelNode ()
{
+ super("node");
}
/**
@@ -86,23 +87,18 @@ public class ModelNode extends Node
// documentation inherited from interface Externalizable
public void readExternal (ObjectInput in)
- throws IOException
+ throws IOException, ClassNotFoundException
{
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 + "].");
+ 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));
}
}
-
+
// documentation inherited from interface ModelSpatial
public void writeBuffers (FileChannel out)
throws IOException
diff --git a/src/java/com/threerings/jme/model/SkinMesh.java b/src/java/com/threerings/jme/model/SkinMesh.java
index 44cc6fbe2..2f914836b 100644
--- a/src/java/com/threerings/jme/model/SkinMesh.java
+++ b/src/java/com/threerings/jme/model/SkinMesh.java
@@ -21,7 +21,6 @@
package com.threerings.jme.model;
-import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
@@ -108,21 +107,19 @@ public class SkinMesh extends ModelMesh
@Override // documentation inherited
public void readExternal (ObjectInput in)
- throws IOException
+ throws IOException, ClassNotFoundException
{
super.readExternal(in);
- try {
- _weightGroups = (WeightGroup[])in.readObject();
- } catch (ClassNotFoundException e) {
- Log.warning("Encounted unknown class [mesh=" + getName() +
- ", exception=" + e + "].");
- }
+ _weightGroups = (WeightGroup[])in.readObject();
}
@Override // documentation inherited
public void updateWorldData (float time)
{
super.updateWorldData(time);
+ if (_weightGroups == null) {
+ return;
+ }
// deform the mesh according to the positions of the bones
for (int ii = 0; ii < _weightGroups.length; ii++) {
diff --git a/src/java/com/threerings/jme/tools/CompileModelTask.java b/src/java/com/threerings/jme/tools/CompileModelTask.java
index ff213dc50..302d30127 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.Properties;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
@@ -47,6 +48,42 @@ import com.threerings.jme.tools.xml.ModelParser;
*/
public class CompileModelTask extends Task
{
+ /**
+ * Loads the model described by the given properties file and compiles it
+ * to a .dat file in the same directory.
+ *
+ * @return the loaded model, or null if the compiled version
+ * is up-to-date
+ */
+ public static Model compileModel (File source)
+ throws Exception
+ {
+ String spath = source.toString();
+ int didx = spath.lastIndexOf('.');
+ 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;
+ }
+ System.out.println("Compiling " + source.getParent() + "...");
+
+ // load the model properties
+ Properties props = new Properties();
+ FileInputStream in = new FileInputStream(source);
+ props.load(in);
+ in.close();
+
+ // load the model content
+ ModelDef mdef = _mparser.parseModel(content.toString());
+ Model model = mdef.createModel(props);
+
+ // write and return the model
+ model.writeToFile(target);
+ return model;
+ }
+
public void addFileset (FileSet set)
{
_filesets.add(set);
@@ -62,45 +99,19 @@ public class CompileModelTask extends Task
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));
+ File source = new File(fromDir, srcFiles[f]);
+ try {
+ compileModel(source);
+ } catch (Exception e) {
+ System.err.println("Error compiling " + source + ": " + e);
+ }
}
}
}
-
- 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();
+ protected static ModelParser _mparser = new ModelParser();
}
diff --git a/src/java/com/threerings/jme/tools/ModelDef.java b/src/java/com/threerings/jme/tools/ModelDef.java
index ac89c7b6c..93d99f46c 100644
--- a/src/java/com/threerings/jme/tools/ModelDef.java
+++ b/src/java/com/threerings/jme/tools/ModelDef.java
@@ -22,6 +22,7 @@
package com.threerings.jme.tools;
import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
@@ -30,10 +31,14 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+import java.util.Properties;
+import com.jme.math.FastMath;
import com.jme.scene.Spatial;
import com.jme.util.geom.BufferUtils;
+import com.samskivert.util.PropertiesUtil;
+
import com.threerings.jme.Log;
import com.threerings.jme.model.BoneNode;
import com.threerings.jme.model.Model;
@@ -61,10 +66,10 @@ public class ModelDef
public float[] scale;
/** Returns a JME node for this definition. */
- public Spatial getSpatial ()
+ public Spatial getSpatial (Properties props)
{
if (_spatial == null) {
- _spatial = createSpatial();
+ _spatial = createSpatial(props);
setTransform();
}
return _spatial;
@@ -81,7 +86,7 @@ public class ModelDef
}
/** Creates a JME node for this definition. */
- public abstract Spatial createSpatial ();
+ public abstract Spatial createSpatial (Properties props);
/** Resolves any name references using the supplied map. */
public void resolveReferences (HashMap nodes)
@@ -124,19 +129,25 @@ public class ModelDef
}
// documentation inherited
- public Spatial createSpatial ()
+ public Spatial createSpatial (Properties props)
{
- return setBuffers(new ModelMesh(name));
+ return configure(new ModelMesh(name), props);
}
- /** Sets the mesh's geometry buffers and returns it. */
- protected ModelMesh setBuffers (ModelMesh mmesh)
+ /** Configures the new mesh and returns it. */
+ protected ModelMesh configure (ModelMesh mmesh, Properties props)
{
+ // configure using sub-properties
+ mmesh.configure(PropertiesUtil.getSubProperties(props, name, ""));
+
+ // set the various buffers
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);
+ ByteOrder no = ByteOrder.nativeOrder();
+ ByteBuffer vbbuf = ByteBuffer.allocateDirect(vsize*3*4).order(no),
+ nbbuf = ByteBuffer.allocateDirect(vsize*3*4).order(no),
+ tbbuf = tcoords ?
+ ByteBuffer.allocateDirect(vsize*2*4).order(no) : null,
+ ibbuf = ByteBuffer.allocateDirect(indices.size()*4).order(no);
FloatBuffer vbuf = vbbuf.asFloatBuffer(),
nbuf = nbbuf.asFloatBuffer(),
tbuf = tcoords ? tbbuf.asFloatBuffer() : null;
@@ -156,9 +167,9 @@ public class ModelDef
public static class SkinMeshDef extends TriMeshDef
{
// documentation inherited
- public Spatial createSpatial ()
+ public Spatial createSpatial (Properties props)
{
- return setBuffers(new SkinMesh(name));
+ return configure(new SkinMesh(name), props);
}
@Override // documentation inherited
@@ -167,17 +178,17 @@ public class ModelDef
super.resolveReferences(nodes);
// divide the vertices up by weight groups
- HashMap, WeightGroupDef> groups =
- new HashMap, WeightGroupDef>();
+ 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();
+ HashSet bones = svertex.getBonesAndNormalize(nodes);
WeightGroupDef group = groups.get(bones);
if (group == null) {
groups.put(bones, group = new WeightGroupDef());
}
group.indices.add(ii);
- for (String bone : bones) {
+ for (BoneNode bone : bones) {
group.weights.add(svertex.getWeight(bone));
}
}
@@ -186,21 +197,12 @@ public class ModelDef
SkinMesh.WeightGroup[] wgroups =
new SkinMesh.WeightGroup[groups.size()];
int ii = 0;
- for (Map.Entry, WeightGroupDef> entry :
+ 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;
- }
+ HashSet bones = entry.getKey();
+ wgroup.bones = bones.toArray(new BoneNode[bones.size()]);
wgroup.weights = toArray(entry.getValue().weights);
wgroups[ii++] = wgroup;
}
@@ -212,7 +214,7 @@ public class ModelDef
public static class NodeDef extends SpatialDef
{
// documentation inherited
- public Spatial createSpatial ()
+ public Spatial createSpatial (Properties props)
{
return new ModelNode(name);
}
@@ -222,7 +224,7 @@ public class ModelDef
public static class BoneNodeDef extends NodeDef
{
// documentation inherited
- public Spatial createSpatial ()
+ public Spatial createSpatial (Properties props)
{
return new BoneNode(name);
}
@@ -239,7 +241,17 @@ public class ModelDef
FloatBuffer vbuf, FloatBuffer nbuf, FloatBuffer tbuf)
{
vbuf.put(location);
+
+ // make sure the normal is normalized
+ float nlen = FastMath.sqrt(normal[0]*normal[0] +
+ normal[1]*normal[1] + normal[2]*normal[2]);
+ if (nlen != 1f) {
+ normal[0] /= nlen;
+ normal[1] /= nlen;
+ normal[2] /= nlen;
+ }
nbuf.put(normal);
+
if (tbuf != null) {
tbuf.put(tcoords);
}
@@ -262,24 +274,39 @@ public class ModelDef
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);
+ if (weight.weight > 0f) {
+ boneWeights.add(weight);
}
- return names;
}
- /** Returns the weight of the named bone. */
- public float getWeight (String bone)
+ /** Finds the bone nodes influencing this vertex and normalizes the
+ * weights. */
+ public HashSet getBonesAndNormalize (
+ HashMap nodes)
{
+ HashSet bones = new HashSet();
+ float totalWeight = 0f;
for (BoneWeight bweight : boneWeights) {
- if (bweight.bone.equals(bone)) {
+ Spatial node = nodes.get(bweight.bone);
+ if (node instanceof BoneNode) {
+ bones.add((BoneNode)node);
+ totalWeight += bweight.weight;
+ }
+ }
+ if (totalWeight > 0f && totalWeight < 1f) {
+ for (BoneWeight bweight : boneWeights) {
+ bweight.weight /= totalWeight;
+ }
+ }
+ return bones;
+ }
+
+ /** Returns the weight of the given bone. */
+ public float getWeight (BoneNode bone)
+ {
+ String name = bone.getName();
+ for (BoneWeight bweight : boneWeights) {
+ if (bweight.bone.equals(name)) {
return bweight.weight;
}
}
@@ -318,14 +345,14 @@ public class ModelDef
/**
* Creates the model node defined herein.
*/
- public Model createModel (String name)
+ public Model createModel (Properties props)
{
- Model model = new Model(name);
+ 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();
+ Spatial spatial = spatials.get(ii).getSpatial(props);
nodes.put(spatial.getName(), spatial);
}
@@ -334,12 +361,12 @@ public class ModelDef
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());
+ if (sdef.getSpatial(props).getParent() == null) {
+ model.attachChild(sdef.getSpatial(props));
}
}
- return model;
+ return model;
}
/** Converts a boxed Integer list to an unboxed int array. */
diff --git a/src/java/com/threerings/jme/tools/ModelViewer.java b/src/java/com/threerings/jme/tools/ModelViewer.java
new file mode 100644
index 000000000..c38836284
--- /dev/null
+++ b/src/java/com/threerings/jme/tools/ModelViewer.java
@@ -0,0 +1,387 @@
+//
+// $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.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.util.logging.Level;
+
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.KeyStroke;
+import javax.swing.filechooser.FileFilter;
+
+import com.jme.light.DirectionalLight;
+import com.jme.math.FastMath;
+import com.jme.math.Vector3f;
+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.util.LoggingSystem;
+
+import com.samskivert.swing.GroupLayout;
+import com.samskivert.util.Config;
+
+import com.threerings.util.MessageBundle;
+import com.threerings.util.MessageManager;
+
+import com.threerings.jme.JmeCanvasApp;
+import com.threerings.jme.model.Model;
+
+/**
+ * A simple viewer application that allows users to examine models and their
+ * animations by loading them from their uncompiled .properties /
+ * .xml representations or their compiled .dat
+ * representations.
+ */
+public class ModelViewer extends JmeCanvasApp
+{
+ public static void main (String[] args)
+ {
+ new ModelViewer(args.length > 0 ? args[0] : null);
+ }
+
+ /**
+ * Creates and initializes an instance of the model viewer application.
+ *
+ * @param path the path of the model to view, or null for
+ * none
+ */
+ public ModelViewer (String path)
+ {
+ super(1024, 768);
+ _msg = new MessageManager("rsrc.i18n").getBundle("jme.viewer");
+ _path = path;
+
+ JPopupMenu.setDefaultLightWeightPopupEnabled(false);
+
+ _frame = new JFrame(_msg.get("m.title"));
+ _frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+ JMenuBar menu = new JMenuBar();
+ _frame.setJMenuBar(menu);
+
+ JMenu file = new JMenu(_msg.get("m.file_menu"));
+ menu.add(file);
+ Action load = new AbstractAction(_msg.get("m.file_load")) {
+ public void actionPerformed (ActionEvent e) {
+ showLoadDialog();
+ }
+ };
+ load.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_L);
+ load.putValue(Action.ACCELERATOR_KEY,
+ KeyStroke.getKeyStroke(KeyEvent.VK_L, KeyEvent.CTRL_MASK));
+ file.add(load);
+
+ file.addSeparator();
+ Action quit = new AbstractAction(_msg.get("m.file_quit")) {
+ public void actionPerformed (ActionEvent e) {
+ System.exit(0);
+ }
+ };
+ quit.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_Q);
+ quit.putValue(Action.ACCELERATOR_KEY,
+ KeyStroke.getKeyStroke(KeyEvent.VK_Q, KeyEvent.CTRL_MASK));
+ file.add(quit);
+
+ _frame.getContentPane().add(getCanvas(), BorderLayout.CENTER);
+
+ _controls = GroupLayout.makeVBox(GroupLayout.NONE, GroupLayout.TOP);
+ _controls.setPreferredSize(new Dimension(100, 100));
+ _frame.getContentPane().add(_controls, BorderLayout.EAST);
+
+ _status = new JLabel(" ");
+ _status.setHorizontalAlignment(JLabel.LEFT);
+ _frame.getContentPane().add(_status, BorderLayout.SOUTH);
+
+ _frame.pack();
+ _frame.setVisible(true);
+
+ LoggingSystem.getLoggingSystem().setLevel(Level.WARNING);
+
+ run();
+ }
+
+ @Override // documentation inherited
+ public boolean init ()
+ {
+ if (!super.init()) {
+ return false;
+ }
+ if (_path != null) {
+ loadModel(new File(_path));
+ }
+ return true;
+ }
+
+ @Override // documentation inherited
+ protected void initInput ()
+ {
+ super.initInput();
+
+ _camhand.setTiltLimits(FastMath.PI / 16.0f, FastMath.PI * 7.0f / 16.0f);
+ _camhand.setZoomLimits(1f, 100f);
+ _camhand.tiltCamera(-FastMath.PI * 7.0f / 16.0f);
+
+ MouseOrbiter orbiter = new MouseOrbiter();
+ _canvas.addMouseListener(orbiter);
+ _canvas.addMouseMotionListener(orbiter);
+ _canvas.addMouseWheelListener(orbiter);
+ }
+
+ @Override // documentation inherited
+ protected void initRoot ()
+ {
+ super.initRoot();
+
+ // set a default material
+ MaterialState mstate = _ctx.getRenderer().createMaterialState();
+ mstate.getDiffuse().set(ColorRGBA.white);
+ mstate.getAmbient().set(ColorRGBA.white);
+ _ctx.getGeometry().setRenderState(mstate);
+
+ // create a grid on the XY plane to provide some reference
+ Vector3f[] points = new Vector3f[GRID_SIZE*2 + GRID_SIZE*2];
+ float halfLength = (GRID_SIZE - 1) * GRID_SPACING / 2;
+ int idx = 0;
+ for (int xx = 0; xx < GRID_SIZE; xx++) {
+ points[idx++] = new Vector3f(
+ -halfLength + xx*GRID_SPACING, -halfLength, 0f);
+ points[idx++] = new Vector3f(
+ -halfLength + xx*GRID_SPACING, +halfLength, 0f);
+ }
+ for (int yy = 0; yy < GRID_SIZE; yy++) {
+ points[idx++] = new Vector3f(
+ -halfLength, -halfLength + yy*GRID_SPACING, 0f);
+ points[idx++] = new Vector3f(
+ +halfLength, -halfLength + yy*GRID_SPACING, 0f);
+
+ }
+ Line grid = new Line("grid", points, null, null, null);
+ grid.getDefaultColor().set(0.25f, 0.25f, 0.25f, 1f);
+ grid.setLightCombineMode(LightState.OFF);
+ _ctx.getGeometry().attachChild(grid);
+ grid.updateRenderState();
+ }
+
+ @Override // documentation inherited
+ protected void initLighting ()
+ {
+ DirectionalLight dlight = new DirectionalLight();
+ dlight.setEnabled(true);
+ dlight.getDirection().set(-1f, 0f, -1f).normalizeLocal();
+ dlight.getAmbient().set(0.25f, 0.25f, 0.25f, 1f);
+
+ LightState lstate = _ctx.getRenderer().createLightState();
+ lstate.attach(dlight);
+ _ctx.getGeometry().setRenderState(lstate);
+ _ctx.getGeometry().setLightCombineMode(LightState.REPLACE);
+ }
+
+ /**
+ * Shows the load model dialog.
+ */
+ protected void showLoadDialog ()
+ {
+ if (_chooser == null) {
+ _chooser = new JFileChooser();
+ _chooser.setDialogTitle(_msg.get("m.load_title"));
+ _chooser.setFileFilter(new FileFilter() {
+ public boolean accept (File file) {
+ if (file.isDirectory()) {
+ return true;
+ }
+ String path = file.toString();
+ return path.endsWith(".properties") ||
+ path.endsWith(".dat");
+ }
+ public String getDescription () {
+ return _msg.get("m.load_filter");
+ }
+ });
+ File dir = new File(_config.getValue("dir", "."));
+ if (dir.exists()) {
+ _chooser.setCurrentDirectory(dir);
+ }
+ }
+ if (_chooser.showOpenDialog(_frame) == JFileChooser.APPROVE_OPTION) {
+ loadModel(_chooser.getSelectedFile());
+ }
+ _config.setValue("dir", _chooser.getCurrentDirectory().toString());
+ }
+
+ /**
+ * Attempts to load a model from the specified location.
+ */
+ protected void loadModel (File file)
+ {
+ String fpath = file.toString();
+ try {
+ if (fpath.endsWith(".properties")) {
+ compileModel(file);
+ } else if (fpath.endsWith(".dat")) {
+ loadCompiledModel(file);
+ } else {
+ throw new Exception(_msg.get("m.invalid_type"));
+ }
+ _status.setText(_msg.get("m.loaded_model", fpath));
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ _status.setText(_msg.get("m.load_error", fpath, e));
+ }
+ }
+
+ /**
+ * Attempts to compile and load a model.
+ */
+ protected void compileModel (File file)
+ throws Exception
+ {
+ _status.setText(_msg.get("m.compiling_model", file));
+ Model model = CompileModelTask.compileModel(file);
+ if (model != null) {
+ setModel(model);
+ return;
+ }
+ // if compileModel returned null, the .dat file is up-to-date
+ String fpath = file.toString();
+ int didx = fpath.lastIndexOf('.');
+ fpath = (didx == -1) ? fpath : fpath.substring(0, didx);
+ loadCompiledModel(new File(fpath + ".dat"));
+ }
+
+ /**
+ * Attempts to load a model that has already been compiled.
+ */
+ protected void loadCompiledModel (File file)
+ throws IOException
+ {
+ _status.setText(_msg.get("m.loading_model", file));
+ setModel(Model.readFromFile(file, false));
+ }
+
+ /**
+ * Sets the model once it's been loaded.
+ */
+ protected void setModel (Model model)
+ {
+ if (_model != null) {
+ _ctx.getGeometry().detachChild(_model);
+ }
+ _ctx.getGeometry().attachChild(_model = model);
+ _model.setLocalScale(0.04f);
+ _model.updateRenderState();
+ }
+
+ /** The translation bundle. */
+ protected MessageBundle _msg;
+
+ /** The path of the initial model to load. */
+ protected String _path;
+
+ /** The viewer frame. */
+ protected JFrame _frame;
+
+ /** The control panel. */
+ protected JPanel _controls;
+
+ /** The status bar. */
+ protected JLabel _status;
+
+ /** The model file chooser. */
+ protected JFileChooser _chooser;
+
+ /** The currently loaded model. */
+ protected Model _model;
+
+ /** Moves the camera using mouse input. */
+ protected class MouseOrbiter extends MouseAdapter
+ implements MouseMotionListener, MouseWheelListener
+ {
+ @Override // documentation inherited
+ public void mousePressed (MouseEvent e)
+ {
+ _mloc.setLocation(e.getX(), e.getY());
+ }
+
+ // documentation inherited from interface MouseMotionListener
+ public void mouseMoved (MouseEvent e)
+ {
+ }
+
+ // documentation inherited from interface MouseMotionListener
+ public void mouseDragged (MouseEvent e)
+ {
+ int dx = e.getX() - _mloc.x, dy = e.getY() - _mloc.y;
+ _mloc.setLocation(e.getX(), e.getY());
+ if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
+ _camhand.tiltCamera(dy * FastMath.PI / 1000);
+ _camhand.orbitCamera(-dx * FastMath.PI / 1000);
+ } else {
+ _camhand.zoomCamera(dy);
+ }
+ }
+
+ // documentation inherited from interface MouseWheelListener
+ public void mouseWheelMoved (MouseWheelEvent e)
+ {
+ _camhand.zoomCamera(e.getWheelRotation() * 10f);
+ }
+
+ /** The last recorded position of the mouse cursor. */
+ protected Point _mloc = new Point();
+ }
+
+ /** The app configuration. */
+ protected static Config _config =
+ new Config("com/threerings/jme/tools/ModelViewer");
+
+ /** The number of lines on the grid in each direction. */
+ protected static final int GRID_SIZE = 32;
+
+ /** The spacing between lines on the grid. */
+ protected static final float GRID_SPACING = 2.5f;
+}
diff --git a/src/java/com/threerings/jme/tools/xml/ModelParser.java b/src/java/com/threerings/jme/tools/xml/ModelParser.java
index a73964f17..460e592b1 100644
--- a/src/java/com/threerings/jme/tools/xml/ModelParser.java
+++ b/src/java/com/threerings/jme/tools/xml/ModelParser.java
@@ -71,21 +71,23 @@ public class ModelParser
_digester.addSetNext(bnode, "addSpatial",
ModelDef.SpatialDef.class.getName());
- String vertex = tmesh + "/vertex";
+ String vertex = tmesh + "/vertex", svertex = smesh + "/vertex";
_digester.addObjectCreate(vertex, ModelDef.Vertex.class.getName());
- _digester.addObjectCreate(smesh + "/vertex",
+ _digester.addObjectCreate(svertex,
ModelDef.SkinVertex.class.getName());
- _digester.addRule("*/vertex", new SetPropertyFieldsRule());
-
+ _digester.addRule(vertex, new SetPropertyFieldsRule());
+ _digester.addRule(svertex, new SetPropertyFieldsRule());
+ _digester.addSetNext(vertex, "addVertex",
+ ModelDef.Vertex.class.getName());
+ _digester.addSetNext(svertex, "addVertex",
+ ModelDef.Vertex.class.getName());
+
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());
}
/**
diff --git a/src/ms/TRModelExporter.mcr b/src/ms/TRModelExporter.mcr
index 07cc9b199..dc4abb0f9 100644
--- a/src/ms/TRModelExporter.mcr
+++ b/src/ms/TRModelExporter.mcr
@@ -68,7 +68,7 @@ macroScript TRModelExporter category:"File" \
if mesh.numtverts > 0 do (
tvface = getTVFace mesh i
)
- for i = 1 to 3 do (
+ for i = 1 to 3 do in coordsys local (
format " \n" to:outFile
if isProperty node #skin do (
- modPanel.setCurrentObject node.skin node:node ui:true
+ setCommandPanelTaskMode mode:#modify
+ modPanel.setCurrentObject node.skin node:node
)
writeVertices node outFile
format " %>\n\n" kind to:outFile
) else (
- format "\>\n\n" to:outFile
+ format "/>\n\n" to:outFile
)
)