Further progress on model revamp. Now loading and viewing static
models. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4017 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -243,6 +243,7 @@
|
|||||||
<jar destfile="${deploy.dir}/${app.name}-jme.jar">
|
<jar destfile="${deploy.dir}/${app.name}-jme.jar">
|
||||||
<fileset dir="${classes.dir}" includes="com/threerings/jme/**"/>
|
<fileset dir="${classes.dir}" includes="com/threerings/jme/**"/>
|
||||||
<fileset dir="${classes.dir}" includes="rsrc/media/jme/**"/>
|
<fileset dir="${classes.dir}" includes="rsrc/media/jme/**"/>
|
||||||
|
<fileset dir="${classes.dir}" includes="rsrc/i18n/jme/**"/>
|
||||||
</jar>
|
</jar>
|
||||||
<jar destfile="${deploy.dir}/${app.name}-openal.jar">
|
<jar destfile="${deploy.dir}/${app.name}-openal.jar">
|
||||||
<fileset dir="${classes.dir}" includes="com/threerings/openal/**"/>
|
<fileset dir="${classes.dir}" includes="com/threerings/openal/**"/>
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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.
|
* camera.
|
||||||
*/
|
*/
|
||||||
public void setZoomLimits (float minZ, float maxZ)
|
public void setZoomLimits (float minZoom, float maxZoom)
|
||||||
{
|
{
|
||||||
_minZ = minZ;
|
_minZoom = minZoom;
|
||||||
_maxZ = maxZ;
|
_maxZoom = maxZoom;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -230,6 +230,12 @@ public class CameraHandler
|
|||||||
public void zoomCamera (float distance)
|
public void zoomCamera (float distance)
|
||||||
{
|
{
|
||||||
Vector3f loc = _camera.getLocation();
|
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));
|
loc.subtractLocal(_camera.getDirection().mult(distance, _temp));
|
||||||
setLocation(loc);
|
setLocation(loc);
|
||||||
}
|
}
|
||||||
@@ -247,6 +253,17 @@ public class CameraHandler
|
|||||||
// which we're going to orbit
|
// which we're going to orbit
|
||||||
Vector3f direction = _camera.getLocation().subtract(spot);
|
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
|
// create a rotation matrix
|
||||||
_rotm.fromAxisAngle(axis, deltaAngle);
|
_rotm.fromAxisAngle(axis, deltaAngle);
|
||||||
|
|
||||||
@@ -383,6 +400,7 @@ public class CameraHandler
|
|||||||
protected float _minX = -Float.MAX_VALUE, _maxX = Float.MAX_VALUE;
|
protected float _minX = -Float.MAX_VALUE, _maxX = Float.MAX_VALUE;
|
||||||
protected float _minY = -Float.MAX_VALUE, _maxY = 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 _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 float _minTilt = -Float.MAX_VALUE, _maxTilt = Float.MAX_VALUE;
|
||||||
|
|
||||||
protected Vector3f _rxdir = new Vector3f(1, 0, 0);
|
protected Vector3f _rxdir = new Vector3f(1, 0, 0);
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
|
|
||||||
package com.threerings.jme.model;
|
package com.threerings.jme.model;
|
||||||
|
|
||||||
import java.io.Externalizable;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInput;
|
import java.io.ObjectInput;
|
||||||
import java.io.ObjectOutput;
|
import java.io.ObjectOutput;
|
||||||
|
|||||||
@@ -30,8 +30,12 @@ import java.io.ObjectInputStream;
|
|||||||
import java.io.ObjectOutput;
|
import java.io.ObjectOutput;
|
||||||
import java.io.ObjectOutputStream;
|
import java.io.ObjectOutputStream;
|
||||||
|
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.MappedByteBuffer;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
import com.threerings.jme.Log;
|
import com.threerings.jme.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,8 +67,10 @@ public class Model extends ModelNode
|
|||||||
FileChannel fc = fis.getChannel();
|
FileChannel fc = fis.getChannel();
|
||||||
if (map) {
|
if (map) {
|
||||||
long pos = fc.position();
|
long pos = fc.position();
|
||||||
model.sliceBuffers(fc.map(FileChannel.MapMode.READ_ONLY,
|
MappedByteBuffer buf = fc.map(FileChannel.MapMode.READ_ONLY,
|
||||||
pos, fc.size() - pos));
|
pos, fc.size() - pos);
|
||||||
|
buf.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
model.sliceBuffers(buf);
|
||||||
} else {
|
} else {
|
||||||
model.readBuffers(fc);
|
model.readBuffers(fc);
|
||||||
}
|
}
|
||||||
@@ -82,9 +88,18 @@ public class Model extends ModelNode
|
|||||||
/**
|
/**
|
||||||
* Standard constructor.
|
* Standard constructor.
|
||||||
*/
|
*/
|
||||||
public Model (String name)
|
public Model (String name, Properties props)
|
||||||
{
|
{
|
||||||
super(name);
|
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
|
throws IOException
|
||||||
{
|
{
|
||||||
super.writeExternal(out);
|
super.writeExternal(out);
|
||||||
|
out.writeObject(_props);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // documentation inherited
|
@Override // documentation inherited
|
||||||
public void readExternal (ObjectInput in)
|
public void readExternal (ObjectInput in)
|
||||||
throws IOException
|
throws IOException, ClassNotFoundException
|
||||||
{
|
{
|
||||||
super.readExternal(in);
|
super.readExternal(in);
|
||||||
|
_props = (Properties)in.readObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The model properties. */
|
||||||
|
protected Properties _props;
|
||||||
|
|
||||||
private static final long serialVersionUID = 1;
|
private static final long serialVersionUID = 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,11 +27,16 @@ import java.io.ObjectInput;
|
|||||||
import java.io.ObjectOutput;
|
import java.io.ObjectOutput;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
import java.nio.FloatBuffer;
|
import java.nio.FloatBuffer;
|
||||||
import java.nio.IntBuffer;
|
import java.nio.IntBuffer;
|
||||||
import java.nio.MappedByteBuffer;
|
import java.nio.MappedByteBuffer;
|
||||||
import java.nio.channels.FileChannel;
|
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.Quaternion;
|
||||||
import com.jme.math.Vector3f;
|
import com.jme.math.Vector3f;
|
||||||
import com.jme.scene.TriMesh;
|
import com.jme.scene.TriMesh;
|
||||||
@@ -49,6 +54,7 @@ public class ModelMesh extends TriMesh
|
|||||||
*/
|
*/
|
||||||
public ModelMesh ()
|
public ModelMesh ()
|
||||||
{
|
{
|
||||||
|
super("mesh");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,6 +75,15 @@ public class ModelMesh extends TriMesh
|
|||||||
super(name, vertices, normals, colors, tcoords, indices);
|
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
|
* Sets the buffers as {@link ByteBuffer}s, because we can't create byte
|
||||||
* views of non-byte buffers.
|
* views of non-byte buffers.
|
||||||
@@ -88,6 +103,13 @@ public class ModelMesh extends TriMesh
|
|||||||
_colorByteBuffer = colors;
|
_colorByteBuffer = colors;
|
||||||
_textureByteBuffer = textures;
|
_textureByteBuffer = textures;
|
||||||
_indexByteBuffer = indices;
|
_indexByteBuffer = indices;
|
||||||
|
|
||||||
|
if (_boundingType == BOX_BOUND) {
|
||||||
|
setModelBound(new BoundingBox());
|
||||||
|
} else { // _boundingType == SPHERE_BOUND
|
||||||
|
setModelBound(new BoundingSphere());
|
||||||
|
}
|
||||||
|
updateModelBound();
|
||||||
}
|
}
|
||||||
|
|
||||||
// documentation inherited
|
// documentation inherited
|
||||||
@@ -130,27 +152,23 @@ public class ModelMesh extends TriMesh
|
|||||||
out.writeInt(_colorBufferSize);
|
out.writeInt(_colorBufferSize);
|
||||||
out.writeInt(_textureBufferSize);
|
out.writeInt(_textureBufferSize);
|
||||||
out.writeInt(_indexBufferSize);
|
out.writeInt(_indexBufferSize);
|
||||||
|
out.writeInt(_boundingType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// documentation inherited from interface Externalizable
|
// documentation inherited from interface Externalizable
|
||||||
public void readExternal (ObjectInput in)
|
public void readExternal (ObjectInput in)
|
||||||
throws IOException
|
throws IOException, ClassNotFoundException
|
||||||
{
|
{
|
||||||
setName(in.readUTF());
|
setName(in.readUTF());
|
||||||
try {
|
setLocalTranslation((Vector3f)in.readObject());
|
||||||
setLocalTranslation((Vector3f)in.readObject());
|
setLocalRotation((Quaternion)in.readObject());
|
||||||
setLocalRotation((Quaternion)in.readObject());
|
setLocalScale((Vector3f)in.readObject());
|
||||||
setLocalScale((Vector3f)in.readObject());
|
|
||||||
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
Log.warning("Encounted unknown class [mesh=" + getName() +
|
|
||||||
", exception=" + e + "].");
|
|
||||||
}
|
|
||||||
_vertexBufferSize = in.readInt();
|
_vertexBufferSize = in.readInt();
|
||||||
_normalBufferSize = in.readInt();
|
_normalBufferSize = in.readInt();
|
||||||
_colorBufferSize = in.readInt();
|
_colorBufferSize = in.readInt();
|
||||||
_textureBufferSize = in.readInt();
|
_textureBufferSize = in.readInt();
|
||||||
_indexBufferSize = in.readInt();
|
_indexBufferSize = in.readInt();
|
||||||
|
_boundingType = in.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
// documentation inherited from interface ModelSpatial
|
// documentation inherited from interface ModelSpatial
|
||||||
@@ -159,23 +177,33 @@ public class ModelMesh extends TriMesh
|
|||||||
{
|
{
|
||||||
if (_vertexBufferSize > 0) {
|
if (_vertexBufferSize > 0) {
|
||||||
_vertexByteBuffer.rewind();
|
_vertexByteBuffer.rewind();
|
||||||
|
convertOrder(_vertexByteBuffer, ByteOrder.LITTLE_ENDIAN);
|
||||||
out.write(_vertexByteBuffer);
|
out.write(_vertexByteBuffer);
|
||||||
|
convertOrder(_vertexByteBuffer, ByteOrder.nativeOrder());
|
||||||
}
|
}
|
||||||
if (_normalBufferSize > 0) {
|
if (_normalBufferSize > 0) {
|
||||||
_normalByteBuffer.rewind();
|
_normalByteBuffer.rewind();
|
||||||
|
convertOrder(_normalByteBuffer, ByteOrder.LITTLE_ENDIAN);
|
||||||
out.write(_normalByteBuffer);
|
out.write(_normalByteBuffer);
|
||||||
|
convertOrder(_normalByteBuffer, ByteOrder.nativeOrder());
|
||||||
}
|
}
|
||||||
if (_colorBufferSize > 0) {
|
if (_colorBufferSize > 0) {
|
||||||
_colorByteBuffer.rewind();
|
_colorByteBuffer.rewind();
|
||||||
|
convertOrder(_colorByteBuffer, ByteOrder.LITTLE_ENDIAN);
|
||||||
out.write(_colorByteBuffer);
|
out.write(_colorByteBuffer);
|
||||||
|
convertOrder(_colorByteBuffer, ByteOrder.nativeOrder());
|
||||||
}
|
}
|
||||||
if (_textureBufferSize > 0) {
|
if (_textureBufferSize > 0) {
|
||||||
_indexByteBuffer.rewind();
|
_textureByteBuffer.rewind();
|
||||||
out.write(_indexByteBuffer);
|
convertOrder(_textureByteBuffer, ByteOrder.LITTLE_ENDIAN);
|
||||||
|
out.write(_textureByteBuffer);
|
||||||
|
convertOrder(_textureByteBuffer, ByteOrder.nativeOrder());
|
||||||
}
|
}
|
||||||
if (_indexBufferSize > 0) {
|
if (_indexBufferSize > 0) {
|
||||||
_indexByteBuffer.rewind();
|
_indexByteBuffer.rewind();
|
||||||
|
convertOrder(_indexByteBuffer, ByteOrder.LITTLE_ENDIAN);
|
||||||
out.write(_indexByteBuffer);
|
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,
|
ByteBuffer vbbuf = null, nbbuf = null, cbbuf = null, tbbuf = null,
|
||||||
ibbuf = null;
|
ibbuf = null;
|
||||||
|
ByteOrder le = ByteOrder.LITTLE_ENDIAN;
|
||||||
if (_vertexBufferSize > 0) {
|
if (_vertexBufferSize > 0) {
|
||||||
vbbuf = ByteBuffer.allocateDirect(_vertexBufferSize*3*4);
|
vbbuf = ByteBuffer.allocateDirect(_vertexBufferSize*4).order(le);
|
||||||
in.read(vbbuf);
|
in.read(vbbuf);
|
||||||
vbbuf.rewind();
|
vbbuf.rewind();
|
||||||
|
convertOrder(vbbuf, ByteOrder.nativeOrder());
|
||||||
}
|
}
|
||||||
if (_normalBufferSize > 0) {
|
if (_normalBufferSize > 0) {
|
||||||
nbbuf = ByteBuffer.allocateDirect(_normalBufferSize*3*4);
|
nbbuf = ByteBuffer.allocateDirect(_normalBufferSize*4).order(le);
|
||||||
in.read(nbbuf);
|
in.read(nbbuf);
|
||||||
nbbuf.rewind();
|
nbbuf.rewind();
|
||||||
|
convertOrder(nbbuf, ByteOrder.nativeOrder());
|
||||||
}
|
}
|
||||||
if (_colorBufferSize > 0) {
|
if (_colorBufferSize > 0) {
|
||||||
cbbuf = ByteBuffer.allocateDirect(_colorBufferSize*4*4);
|
cbbuf = ByteBuffer.allocateDirect(_colorBufferSize*4).order(le);
|
||||||
in.read(cbbuf);
|
in.read(cbbuf);
|
||||||
cbbuf.rewind();
|
cbbuf.rewind();
|
||||||
|
convertOrder(cbbuf, ByteOrder.nativeOrder());
|
||||||
}
|
}
|
||||||
if (_textureBufferSize > 0) {
|
if (_textureBufferSize > 0) {
|
||||||
tbbuf = ByteBuffer.allocateDirect(_textureBufferSize*2*4);
|
tbbuf = ByteBuffer.allocateDirect(_textureBufferSize*4).order(le);
|
||||||
in.read(tbbuf);
|
in.read(tbbuf);
|
||||||
tbbuf.rewind();
|
tbbuf.rewind();
|
||||||
|
convertOrder(tbbuf, ByteOrder.nativeOrder());
|
||||||
}
|
}
|
||||||
if (_indexBufferSize > 0) {
|
if (_indexBufferSize > 0) {
|
||||||
ibbuf = ByteBuffer.allocateDirect(_indexBufferSize*4);
|
ibbuf = ByteBuffer.allocateDirect(_indexBufferSize*4).order(le);
|
||||||
in.read(ibbuf);
|
in.read(ibbuf);
|
||||||
ibbuf.rewind();
|
ibbuf.rewind();
|
||||||
|
convertOrder(ibbuf, ByteOrder.nativeOrder());
|
||||||
}
|
}
|
||||||
reconstruct(vbbuf, nbbuf, cbbuf, tbbuf, ibbuf);
|
reconstruct(vbbuf, nbbuf, cbbuf, tbbuf, ibbuf);
|
||||||
}
|
}
|
||||||
@@ -218,26 +252,27 @@ public class ModelMesh extends TriMesh
|
|||||||
{
|
{
|
||||||
ByteBuffer vbbuf = null, nbbuf = null, cbbuf = null, tbbuf = null,
|
ByteBuffer vbbuf = null, nbbuf = null, cbbuf = null, tbbuf = null,
|
||||||
ibbuf = null;
|
ibbuf = null;
|
||||||
|
int total = 0;
|
||||||
if (_vertexBufferSize > 0) {
|
if (_vertexBufferSize > 0) {
|
||||||
int npos = map.position() + _vertexBufferSize*3*4;
|
int npos = map.position() + _vertexBufferSize*4;
|
||||||
map.limit(npos);
|
map.limit(npos);
|
||||||
vbbuf = map.slice();
|
vbbuf = map.slice();
|
||||||
map.position(npos);
|
map.position(npos);
|
||||||
}
|
}
|
||||||
if (_normalBufferSize > 0) {
|
if (_normalBufferSize > 0) {
|
||||||
int npos = map.position() + _normalBufferSize*3*4;
|
int npos = map.position() + _normalBufferSize*4;
|
||||||
map.limit(npos);
|
map.limit(npos);
|
||||||
nbbuf = map.slice();
|
nbbuf = map.slice();
|
||||||
map.position(npos);
|
map.position(npos);
|
||||||
}
|
}
|
||||||
if (_colorBufferSize > 0) {
|
if (_colorBufferSize > 0) {
|
||||||
int npos = map.position() + _colorBufferSize*4*4;
|
int npos = map.position() + _colorBufferSize*4;
|
||||||
map.limit(npos);
|
map.limit(npos);
|
||||||
cbbuf = map.slice();
|
cbbuf = map.slice();
|
||||||
map.position(npos);
|
map.position(npos);
|
||||||
}
|
}
|
||||||
if (_textureBufferSize > 0) {
|
if (_textureBufferSize > 0) {
|
||||||
int npos = map.position() + _textureBufferSize*2*4;
|
int npos = map.position() + _textureBufferSize*4;
|
||||||
map.limit(npos);
|
map.limit(npos);
|
||||||
tbbuf = map.slice();
|
tbbuf = map.slice();
|
||||||
map.position(npos);
|
map.position(npos);
|
||||||
@@ -251,6 +286,21 @@ public class ModelMesh extends TriMesh
|
|||||||
reconstruct(vbbuf, nbbuf, cbbuf, tbbuf, ibbuf);
|
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 <code>null</code>). */
|
/** The sizes of the various buffers (zero for <code>null</code>). */
|
||||||
protected int _vertexBufferSize, _normalBufferSize, _colorBufferSize,
|
protected int _vertexBufferSize, _normalBufferSize, _colorBufferSize,
|
||||||
_textureBufferSize, _indexBufferSize;
|
_textureBufferSize, _indexBufferSize;
|
||||||
@@ -259,5 +309,14 @@ public class ModelMesh extends TriMesh
|
|||||||
protected ByteBuffer _vertexByteBuffer, _normalByteBuffer,
|
protected ByteBuffer _vertexByteBuffer, _normalByteBuffer,
|
||||||
_colorByteBuffer, _textureByteBuffer, _indexByteBuffer;
|
_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;
|
private static final long serialVersionUID = 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ public class ModelNode extends Node
|
|||||||
*/
|
*/
|
||||||
public ModelNode ()
|
public ModelNode ()
|
||||||
{
|
{
|
||||||
|
super("node");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,20 +87,15 @@ public class ModelNode extends Node
|
|||||||
|
|
||||||
// documentation inherited from interface Externalizable
|
// documentation inherited from interface Externalizable
|
||||||
public void readExternal (ObjectInput in)
|
public void readExternal (ObjectInput in)
|
||||||
throws IOException
|
throws IOException, ClassNotFoundException
|
||||||
{
|
{
|
||||||
setName(in.readUTF());
|
setName(in.readUTF());
|
||||||
try {
|
setLocalTranslation((Vector3f)in.readObject());
|
||||||
setLocalTranslation((Vector3f)in.readObject());
|
setLocalRotation((Quaternion)in.readObject());
|
||||||
setLocalRotation((Quaternion)in.readObject());
|
setLocalScale((Vector3f)in.readObject());
|
||||||
setLocalScale((Vector3f)in.readObject());
|
ArrayList children = (ArrayList)in.readObject();
|
||||||
ArrayList children = (ArrayList)in.readObject();
|
for (int ii = 0, nn = children.size(); ii < nn; ii++) {
|
||||||
for (int ii = 0, nn = children.size(); ii < nn; ii++) {
|
attachChild((Spatial)children.get(ii));
|
||||||
attachChild((Spatial)children.get(ii));
|
|
||||||
}
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
Log.warning("Encounted unknown class [node=" + getName() +
|
|
||||||
", exception=" + e + "].");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
|
|
||||||
package com.threerings.jme.model;
|
package com.threerings.jme.model;
|
||||||
|
|
||||||
import java.io.Externalizable;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInput;
|
import java.io.ObjectInput;
|
||||||
import java.io.ObjectOutput;
|
import java.io.ObjectOutput;
|
||||||
@@ -108,21 +107,19 @@ public class SkinMesh extends ModelMesh
|
|||||||
|
|
||||||
@Override // documentation inherited
|
@Override // documentation inherited
|
||||||
public void readExternal (ObjectInput in)
|
public void readExternal (ObjectInput in)
|
||||||
throws IOException
|
throws IOException, ClassNotFoundException
|
||||||
{
|
{
|
||||||
super.readExternal(in);
|
super.readExternal(in);
|
||||||
try {
|
_weightGroups = (WeightGroup[])in.readObject();
|
||||||
_weightGroups = (WeightGroup[])in.readObject();
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
Log.warning("Encounted unknown class [mesh=" + getName() +
|
|
||||||
", exception=" + e + "].");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // documentation inherited
|
@Override // documentation inherited
|
||||||
public void updateWorldData (float time)
|
public void updateWorldData (float time)
|
||||||
{
|
{
|
||||||
super.updateWorldData(time);
|
super.updateWorldData(time);
|
||||||
|
if (_weightGroups == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// deform the mesh according to the positions of the bones
|
// deform the mesh according to the positions of the bones
|
||||||
for (int ii = 0; ii < _weightGroups.length; ii++) {
|
for (int ii = 0; ii < _weightGroups.length; ii++) {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import java.io.IOException;
|
|||||||
import java.io.ObjectOutputStream;
|
import java.io.ObjectOutputStream;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
import org.apache.tools.ant.BuildException;
|
import org.apache.tools.ant.BuildException;
|
||||||
import org.apache.tools.ant.DirectoryScanner;
|
import org.apache.tools.ant.DirectoryScanner;
|
||||||
@@ -47,6 +48,42 @@ import com.threerings.jme.tools.xml.ModelParser;
|
|||||||
*/
|
*/
|
||||||
public class CompileModelTask extends Task
|
public class CompileModelTask extends Task
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Loads the model described by the given properties file and compiles it
|
||||||
|
* to a <code>.dat</code> file in the same directory.
|
||||||
|
*
|
||||||
|
* @return the loaded model, or <code>null</code> 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)
|
public void addFileset (FileSet set)
|
||||||
{
|
{
|
||||||
_filesets.add(set);
|
_filesets.add(set);
|
||||||
@@ -62,45 +99,19 @@ public class CompileModelTask extends Task
|
|||||||
String[] srcFiles = ds.getIncludedFiles();
|
String[] srcFiles = ds.getIncludedFiles();
|
||||||
|
|
||||||
for (int f = 0; f < srcFiles.length; f++) {
|
for (int f = 0; f < srcFiles.length; f++) {
|
||||||
File cfile = new File(fromDir, srcFiles[f]);
|
File source = new File(fromDir, srcFiles[f]);
|
||||||
String target = srcFiles[f];
|
try {
|
||||||
int didx = target.lastIndexOf(".");
|
compileModel(source);
|
||||||
target = (didx == -1) ? target : target.substring(0, didx);
|
} catch (Exception e) {
|
||||||
target += ".dat";
|
System.err.println("Error compiling " + source + ": " + e);
|
||||||
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. */
|
/** A list of filesets that contain XML models. */
|
||||||
protected ArrayList<FileSet> _filesets = new ArrayList<FileSet>();
|
protected ArrayList<FileSet> _filesets = new ArrayList<FileSet>();
|
||||||
|
|
||||||
/** A parser for the model definitions. */
|
/** A parser for the model definitions. */
|
||||||
protected ModelParser _mparser = new ModelParser();
|
protected static ModelParser _mparser = new ModelParser();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
package com.threerings.jme.tools;
|
package com.threerings.jme.tools;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
import java.nio.FloatBuffer;
|
import java.nio.FloatBuffer;
|
||||||
import java.nio.IntBuffer;
|
import java.nio.IntBuffer;
|
||||||
|
|
||||||
@@ -30,10 +31,14 @@ import java.util.Arrays;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import com.jme.math.FastMath;
|
||||||
import com.jme.scene.Spatial;
|
import com.jme.scene.Spatial;
|
||||||
import com.jme.util.geom.BufferUtils;
|
import com.jme.util.geom.BufferUtils;
|
||||||
|
|
||||||
|
import com.samskivert.util.PropertiesUtil;
|
||||||
|
|
||||||
import com.threerings.jme.Log;
|
import com.threerings.jme.Log;
|
||||||
import com.threerings.jme.model.BoneNode;
|
import com.threerings.jme.model.BoneNode;
|
||||||
import com.threerings.jme.model.Model;
|
import com.threerings.jme.model.Model;
|
||||||
@@ -61,10 +66,10 @@ public class ModelDef
|
|||||||
public float[] scale;
|
public float[] scale;
|
||||||
|
|
||||||
/** Returns a JME node for this definition. */
|
/** Returns a JME node for this definition. */
|
||||||
public Spatial getSpatial ()
|
public Spatial getSpatial (Properties props)
|
||||||
{
|
{
|
||||||
if (_spatial == null) {
|
if (_spatial == null) {
|
||||||
_spatial = createSpatial();
|
_spatial = createSpatial(props);
|
||||||
setTransform();
|
setTransform();
|
||||||
}
|
}
|
||||||
return _spatial;
|
return _spatial;
|
||||||
@@ -81,7 +86,7 @@ public class ModelDef
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Creates a JME node for this definition. */
|
/** 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. */
|
/** Resolves any name references using the supplied map. */
|
||||||
public void resolveReferences (HashMap<String, Spatial> nodes)
|
public void resolveReferences (HashMap<String, Spatial> nodes)
|
||||||
@@ -124,19 +129,25 @@ public class ModelDef
|
|||||||
}
|
}
|
||||||
|
|
||||||
// documentation inherited
|
// 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. */
|
/** Configures the new mesh and returns it. */
|
||||||
protected ModelMesh setBuffers (ModelMesh mmesh)
|
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();
|
int vsize = vertices.size();
|
||||||
ByteBuffer vbbuf = ByteBuffer.allocateDirect(vsize*3*4),
|
ByteOrder no = ByteOrder.nativeOrder();
|
||||||
nbbuf = ByteBuffer.allocateDirect(vsize*3*4),
|
ByteBuffer vbbuf = ByteBuffer.allocateDirect(vsize*3*4).order(no),
|
||||||
tbbuf = tcoords ? ByteBuffer.allocateDirect(vsize*2*4) : null,
|
nbbuf = ByteBuffer.allocateDirect(vsize*3*4).order(no),
|
||||||
ibbuf = ByteBuffer.allocateDirect(indices.size()*4);
|
tbbuf = tcoords ?
|
||||||
|
ByteBuffer.allocateDirect(vsize*2*4).order(no) : null,
|
||||||
|
ibbuf = ByteBuffer.allocateDirect(indices.size()*4).order(no);
|
||||||
FloatBuffer vbuf = vbbuf.asFloatBuffer(),
|
FloatBuffer vbuf = vbbuf.asFloatBuffer(),
|
||||||
nbuf = nbbuf.asFloatBuffer(),
|
nbuf = nbbuf.asFloatBuffer(),
|
||||||
tbuf = tcoords ? tbbuf.asFloatBuffer() : null;
|
tbuf = tcoords ? tbbuf.asFloatBuffer() : null;
|
||||||
@@ -156,9 +167,9 @@ public class ModelDef
|
|||||||
public static class SkinMeshDef extends TriMeshDef
|
public static class SkinMeshDef extends TriMeshDef
|
||||||
{
|
{
|
||||||
// documentation inherited
|
// documentation inherited
|
||||||
public Spatial createSpatial ()
|
public Spatial createSpatial (Properties props)
|
||||||
{
|
{
|
||||||
return setBuffers(new SkinMesh(name));
|
return configure(new SkinMesh(name), props);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // documentation inherited
|
@Override // documentation inherited
|
||||||
@@ -167,17 +178,17 @@ public class ModelDef
|
|||||||
super.resolveReferences(nodes);
|
super.resolveReferences(nodes);
|
||||||
|
|
||||||
// divide the vertices up by weight groups
|
// divide the vertices up by weight groups
|
||||||
HashMap<HashSet<String>, WeightGroupDef> groups =
|
HashMap<HashSet<BoneNode>, WeightGroupDef> groups =
|
||||||
new HashMap<HashSet<String>, WeightGroupDef>();
|
new HashMap<HashSet<BoneNode>, WeightGroupDef>();
|
||||||
for (int ii = 0, nn = vertices.size(); ii < nn; ii++) {
|
for (int ii = 0, nn = vertices.size(); ii < nn; ii++) {
|
||||||
SkinVertex svertex = (SkinVertex)vertices.get(ii);
|
SkinVertex svertex = (SkinVertex)vertices.get(ii);
|
||||||
HashSet<String> bones = svertex.getBoneNames();
|
HashSet<BoneNode> bones = svertex.getBonesAndNormalize(nodes);
|
||||||
WeightGroupDef group = groups.get(bones);
|
WeightGroupDef group = groups.get(bones);
|
||||||
if (group == null) {
|
if (group == null) {
|
||||||
groups.put(bones, group = new WeightGroupDef());
|
groups.put(bones, group = new WeightGroupDef());
|
||||||
}
|
}
|
||||||
group.indices.add(ii);
|
group.indices.add(ii);
|
||||||
for (String bone : bones) {
|
for (BoneNode bone : bones) {
|
||||||
group.weights.add(svertex.getWeight(bone));
|
group.weights.add(svertex.getWeight(bone));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,21 +197,12 @@ public class ModelDef
|
|||||||
SkinMesh.WeightGroup[] wgroups =
|
SkinMesh.WeightGroup[] wgroups =
|
||||||
new SkinMesh.WeightGroup[groups.size()];
|
new SkinMesh.WeightGroup[groups.size()];
|
||||||
int ii = 0;
|
int ii = 0;
|
||||||
for (Map.Entry<HashSet<String>, WeightGroupDef> entry :
|
for (Map.Entry<HashSet<BoneNode>, WeightGroupDef> entry :
|
||||||
groups.entrySet()) {
|
groups.entrySet()) {
|
||||||
SkinMesh.WeightGroup wgroup = new SkinMesh.WeightGroup();
|
SkinMesh.WeightGroup wgroup = new SkinMesh.WeightGroup();
|
||||||
wgroup.indices = toArray(entry.getValue().indices);
|
wgroup.indices = toArray(entry.getValue().indices);
|
||||||
wgroup.bones = new BoneNode[entry.getKey().size()];
|
HashSet<BoneNode> bones = entry.getKey();
|
||||||
int jj = 0;
|
wgroup.bones = bones.toArray(new BoneNode[bones.size()]);
|
||||||
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);
|
wgroup.weights = toArray(entry.getValue().weights);
|
||||||
wgroups[ii++] = wgroup;
|
wgroups[ii++] = wgroup;
|
||||||
}
|
}
|
||||||
@@ -212,7 +214,7 @@ public class ModelDef
|
|||||||
public static class NodeDef extends SpatialDef
|
public static class NodeDef extends SpatialDef
|
||||||
{
|
{
|
||||||
// documentation inherited
|
// documentation inherited
|
||||||
public Spatial createSpatial ()
|
public Spatial createSpatial (Properties props)
|
||||||
{
|
{
|
||||||
return new ModelNode(name);
|
return new ModelNode(name);
|
||||||
}
|
}
|
||||||
@@ -222,7 +224,7 @@ public class ModelDef
|
|||||||
public static class BoneNodeDef extends NodeDef
|
public static class BoneNodeDef extends NodeDef
|
||||||
{
|
{
|
||||||
// documentation inherited
|
// documentation inherited
|
||||||
public Spatial createSpatial ()
|
public Spatial createSpatial (Properties props)
|
||||||
{
|
{
|
||||||
return new BoneNode(name);
|
return new BoneNode(name);
|
||||||
}
|
}
|
||||||
@@ -239,7 +241,17 @@ public class ModelDef
|
|||||||
FloatBuffer vbuf, FloatBuffer nbuf, FloatBuffer tbuf)
|
FloatBuffer vbuf, FloatBuffer nbuf, FloatBuffer tbuf)
|
||||||
{
|
{
|
||||||
vbuf.put(location);
|
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);
|
nbuf.put(normal);
|
||||||
|
|
||||||
if (tbuf != null) {
|
if (tbuf != null) {
|
||||||
tbuf.put(tcoords);
|
tbuf.put(tcoords);
|
||||||
}
|
}
|
||||||
@@ -262,24 +274,39 @@ public class ModelDef
|
|||||||
|
|
||||||
public void addBoneWeight (BoneWeight weight)
|
public void addBoneWeight (BoneWeight weight)
|
||||||
{
|
{
|
||||||
boneWeights.add(weight);
|
if (weight.weight > 0f) {
|
||||||
}
|
boneWeights.add(weight);
|
||||||
|
|
||||||
/** Returns the set of names of the bones influencing this vertex. */
|
|
||||||
public HashSet<String> getBoneNames ()
|
|
||||||
{
|
|
||||||
HashSet<String> names = new HashSet<String>();
|
|
||||||
for (BoneWeight bweight : boneWeights) {
|
|
||||||
names.add(bweight.bone);
|
|
||||||
}
|
}
|
||||||
return names;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the weight of the named bone. */
|
/** Finds the bone nodes influencing this vertex and normalizes the
|
||||||
public float getWeight (String bone)
|
* weights. */
|
||||||
|
public HashSet<BoneNode> getBonesAndNormalize (
|
||||||
|
HashMap<String, Spatial> nodes)
|
||||||
{
|
{
|
||||||
|
HashSet<BoneNode> bones = new HashSet<BoneNode>();
|
||||||
|
float totalWeight = 0f;
|
||||||
for (BoneWeight bweight : boneWeights) {
|
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;
|
return bweight.weight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -318,14 +345,14 @@ public class ModelDef
|
|||||||
/**
|
/**
|
||||||
* Creates the model node defined herein.
|
* 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
|
// start by creating the spatials and mapping them to their names
|
||||||
HashMap<String, Spatial> nodes = new HashMap<String, Spatial>();
|
HashMap<String, Spatial> nodes = new HashMap<String, Spatial>();
|
||||||
for (int ii = 0, nn = spatials.size(); ii < nn; ii++) {
|
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);
|
nodes.put(spatial.getName(), spatial);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,8 +361,8 @@ public class ModelDef
|
|||||||
for (int ii = 0, nn = spatials.size(); ii < nn; ii++) {
|
for (int ii = 0, nn = spatials.size(); ii < nn; ii++) {
|
||||||
SpatialDef sdef = spatials.get(ii);
|
SpatialDef sdef = spatials.get(ii);
|
||||||
sdef.resolveReferences(nodes);
|
sdef.resolveReferences(nodes);
|
||||||
if (sdef.getSpatial().getParent() == null) {
|
if (sdef.getSpatial(props).getParent() == null) {
|
||||||
model.attachChild(sdef.getSpatial());
|
model.attachChild(sdef.getSpatial(props));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 <code>.properties</code> /
|
||||||
|
* <code>.xml</code> representations or their compiled <code>.dat</code>
|
||||||
|
* 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 <code>null</code> 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;
|
||||||
|
}
|
||||||
@@ -71,11 +71,16 @@ public class ModelParser
|
|||||||
_digester.addSetNext(bnode, "addSpatial",
|
_digester.addSetNext(bnode, "addSpatial",
|
||||||
ModelDef.SpatialDef.class.getName());
|
ModelDef.SpatialDef.class.getName());
|
||||||
|
|
||||||
String vertex = tmesh + "/vertex";
|
String vertex = tmesh + "/vertex", svertex = smesh + "/vertex";
|
||||||
_digester.addObjectCreate(vertex, ModelDef.Vertex.class.getName());
|
_digester.addObjectCreate(vertex, ModelDef.Vertex.class.getName());
|
||||||
_digester.addObjectCreate(smesh + "/vertex",
|
_digester.addObjectCreate(svertex,
|
||||||
ModelDef.SkinVertex.class.getName());
|
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";
|
String bweight = smesh + "/vertex/boneWeight";
|
||||||
_digester.addObjectCreate(bweight,
|
_digester.addObjectCreate(bweight,
|
||||||
@@ -83,9 +88,6 @@ public class ModelParser
|
|||||||
_digester.addRule(bweight, new SetPropertyFieldsRule());
|
_digester.addRule(bweight, new SetPropertyFieldsRule());
|
||||||
_digester.addSetNext(bweight, "addBoneWeight",
|
_digester.addSetNext(bweight, "addBoneWeight",
|
||||||
ModelDef.BoneWeight.class.getName());
|
ModelDef.BoneWeight.class.getName());
|
||||||
|
|
||||||
_digester.addSetNext("*/vertex", "addVertex",
|
|
||||||
ModelDef.Vertex.class.getName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ macroScript TRModelExporter category:"File" \
|
|||||||
if mesh.numtverts > 0 do (
|
if mesh.numtverts > 0 do (
|
||||||
tvface = getTVFace mesh i
|
tvface = getTVFace mesh i
|
||||||
)
|
)
|
||||||
for i = 1 to 3 do (
|
for i = 1 to 3 do in coordsys local (
|
||||||
format " <vertex" to:outFile
|
format " <vertex" to:outFile
|
||||||
writePoint3Attr " location" (getVert mesh face[i]) outFile
|
writePoint3Attr " location" (getVert mesh face[i]) outFile
|
||||||
writePoint3Attr " normal" (getNormal mesh face[i]) outFile
|
writePoint3Attr " normal" (getNormal mesh face[i]) outFile
|
||||||
@@ -103,7 +103,7 @@ macroScript TRModelExporter category:"File" \
|
|||||||
) else (
|
) else (
|
||||||
kind = "triMesh"
|
kind = "triMesh"
|
||||||
)
|
)
|
||||||
) else if isKindOf node BoneGeometry then (
|
) else if node.boneEnable then (
|
||||||
kind = "boneNode"
|
kind = "boneNode"
|
||||||
) else (
|
) else (
|
||||||
kind = "node"
|
kind = "node"
|
||||||
@@ -122,13 +122,14 @@ macroScript TRModelExporter category:"File" \
|
|||||||
if isMesh then (
|
if isMesh then (
|
||||||
format ">\n" to:outFile
|
format ">\n" to:outFile
|
||||||
if isProperty node #skin do (
|
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
|
writeVertices node outFile
|
||||||
format " </%>\n\n" kind to:outFile
|
format " </%>\n\n" kind to:outFile
|
||||||
|
|
||||||
) else (
|
) else (
|
||||||
format "\>\n\n" to:outFile
|
format "/>\n\n" to:outFile
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user