Merge meshes in animated models by finding ones that maintain the same

relative position throughout all animations.  Also, don't include 
transforms in the animations for nodes that never move.


git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@218 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
Andrzej Kapolka
2007-05-01 23:36:23 +00:00
parent 86298ca832
commit 072926f814
8 changed files with 620 additions and 390 deletions
+23 -9
View File
@@ -111,6 +111,9 @@ public class Model extends ModelNode
* {@link Controller#RT_CYCLE}, or {@link Controller#RT_WRAP}). */ * {@link Controller#RT_CYCLE}, or {@link Controller#RT_WRAP}). */
public int repeatType; public int repeatType;
/** Any nodes visible that never move within the model. */
public Spatial[] staticTargets;
/** The transformation targets of the animation. */ /** The transformation targets of the animation. */
public Spatial[] transformTargets; public Spatial[] transformTargets;
@@ -141,12 +144,9 @@ public class Model extends ModelNode
Animation anim = new Animation(); Animation anim = new Animation();
anim.frameRate = frameRate; anim.frameRate = frameRate;
anim.repeatType = repeatType; anim.repeatType = repeatType;
anim.staticTargets = rebind(staticTargets, pnodes);
anim.transformTargets = rebind(transformTargets, pnodes);
anim.transforms = transforms; anim.transforms = transforms;
anim.transformTargets = new Spatial[transformTargets.length];
for (int ii = 0; ii < transformTargets.length; ii++) {
anim.transformTargets[ii] =
(Spatial)pnodes.get(transformTargets[ii]);
}
anim.animId = animId; anim.animId = animId;
anim.stored = stored; anim.stored = stored;
return anim; return anim;
@@ -187,6 +187,8 @@ public class Model extends ModelNode
InputCapsule capsule = im.getCapsule(this); InputCapsule capsule = im.getCapsule(this);
frameRate = capsule.readInt("frameRate", 0); frameRate = capsule.readInt("frameRate", 0);
repeatType = capsule.readInt("repeatType", Controller.RT_CLAMP); repeatType = capsule.readInt("repeatType", Controller.RT_CLAMP);
staticTargets = ArrayUtil.copy(capsule.readSavableArray(
"staticTargets", null), new Spatial[0]);
transformTargets = ArrayUtil.copy(capsule.readSavableArray( transformTargets = ArrayUtil.copy(capsule.readSavableArray(
"transformTargets", null), new Spatial[0]); "transformTargets", null), new Spatial[0]);
FloatBuffer pxforms = capsule.readFloatBuffer("transforms", null); FloatBuffer pxforms = capsule.readFloatBuffer("transforms", null);
@@ -208,9 +210,10 @@ public class Model extends ModelNode
OutputCapsule capsule = ex.getCapsule(this); OutputCapsule capsule = ex.getCapsule(this);
capsule.write(frameRate, "frameRate", 0); capsule.write(frameRate, "frameRate", 0);
capsule.write(repeatType, "repeatType", Controller.RT_CLAMP); capsule.write(repeatType, "repeatType", Controller.RT_CLAMP);
capsule.write(staticTargets, "staticTargets", null);
capsule.write(transformTargets, "transformTargets", null); capsule.write(transformTargets, "transformTargets", null);
FloatBuffer pxforms = FloatBuffer.allocate(transforms.length * FloatBuffer pxforms = FloatBuffer.allocate(
transformTargets.length * Transform.PACKED_SIZE); transforms.length * transformTargets.length * Transform.PACKED_SIZE);
for (Transform[] frame : transforms) { for (Transform[] frame : transforms) {
for (Transform xform : frame) { for (Transform xform : frame) {
xform.writeToBuffer(pxforms); xform.writeToBuffer(pxforms);
@@ -220,6 +223,15 @@ public class Model extends ModelNode
capsule.write(pxforms, "transforms", null); capsule.write(pxforms, "transforms", null);
} }
protected Spatial[] rebind (Spatial[] targets, HashMap pnodes)
{
Spatial[] ntargets = new Spatial[targets.length];
for (int ii = 0; ii < targets.length; ii++) {
ntargets[ii] = (Spatial)pnodes.get(targets[ii]);
}
return ntargets;
}
private static final long serialVersionUID = 1; private static final long serialVersionUID = 1;
} }
@@ -534,9 +546,11 @@ public class Model extends ModelNode
_animObservers.apply(new AnimCancelledOp(_animName)); _animObservers.apply(new AnimCancelledOp(_animName));
} }
// first cull all model nodes, then re-activate the ones in the // first cull all model nodes, then re-activate the ones in the target lists
// target list
cullModelNodes(); cullModelNodes();
for (Spatial target : anim.staticTargets) {
((ModelNode)target).updateCullMode();
}
for (Spatial target : anim.transformTargets) { for (Spatial target : anim.transformTargets) {
((ModelNode)target).updateCullMode(); ((ModelNode)target).updateCullMode();
} }
@@ -144,51 +144,6 @@ public class ModelMesh extends TriMesh
Boolean.parseBoolean(tprops.getProperty("translucent")); Boolean.parseBoolean(tprops.getProperty("translucent"));
} }
/**
* Returns an opaque object representing this mesh's attributes. The
* object will implement {@link Object#hashCode} and {@link Object#equals}
* so that meshes with the same attributes will have identical keys.
*/
public Object getAttributeKey ()
{
return new AttributeKey(_textureKey, _textures, _sphereMapped,
_filterMode, _mipMapMode, _compress, _emissiveMap, _emissive, _additive,
_solid, _transparent, _alphaThreshold, _translucent);
}
/**
* Merges another mesh into this one. The mesh is assumed to be in the same coordinate system
* and have the same attributes.
*/
public void merge (ModelMesh mesh)
{
TriangleBatch batch = (TriangleBatch)getBatch(0);
TriangleBatch mbatch = (TriangleBatch)mesh.getBatch(0);
int vcount = batch.getVertexCount();
batch.setVertexBuffer(concatenate(batch.getVertexBuffer(), mbatch.getVertexBuffer()));
batch.setNormalBuffer(concatenate(batch.getNormalBuffer(), mbatch.getNormalBuffer()));
if (batch.getColorBuffer() != null && mbatch.getColorBuffer() != null) {
batch.setColorBuffer(concatenate(batch.getColorBuffer(), mbatch.getColorBuffer()));
}
batch.setTextureBuffer(concatenate(
batch.getTextureBuffer(0), mbatch.getTextureBuffer(0)), 0);
for (int ii = 1, nn = getTextureCount(); ii < nn; ii++) {
batch.setTextureBuffer(batch.getTextureBuffer(0), ii);
}
IntBuffer ibuf = batch.getIndexBuffer(), mibuf = mbatch.getIndexBuffer();
IntBuffer nibuf = BufferUtils.createIntBuffer(ibuf.capacity() + mibuf.capacity());
ibuf.clear();
nibuf.put(ibuf);
mibuf.clear();
while (mibuf.hasRemaining()) {
nibuf.put(mibuf.get() + vcount);
}
nibuf.clear();
batch.setIndexBuffer(nibuf);
}
/** /**
* Adjusts the vertices and the transform of the mesh so that the mesh's * Adjusts the vertices and the transform of the mesh so that the mesh's
* position lies at the center of its bounding volume. * position lies at the center of its bounding volume.
@@ -819,30 +774,6 @@ public class ModelMesh extends TriMesh
new RenderState[RenderState.RS_MAX_STATE]; new RenderState[RenderState.RS_MAX_STATE];
} }
/** A wrapper around an array of attributes that uses {@link Arrays#deepHashCode} and
* {@link Arrays#deepEquals} for hashing/comparison. */
protected static class AttributeKey
{
public AttributeKey (Object... attrs)
{
_attrs = attrs;
}
@Override // documentation inherited
public int hashCode ()
{
return Arrays.deepHashCode(_attrs);
}
@Override // documentation inherited
public boolean equals (Object other)
{
return Arrays.deepEquals(_attrs, ((AttributeKey)other)._attrs);
}
protected Object[] _attrs;
}
/** The name of the texture specified in the model file, which acts as a /** The name of the texture specified in the model file, which acts as a
* property key and a default value. */ * property key and a default value. */
protected String _textureKey; protected String _textureKey;
@@ -41,6 +41,7 @@ import com.jme.util.export.InputCapsule;
import com.jme.util.export.OutputCapsule; import com.jme.util.export.OutputCapsule;
import com.threerings.jme.Log; import com.threerings.jme.Log;
import com.threerings.jme.util.JmeUtil;
/** /**
* A {@link Node} with a serialization mechanism tailored to stored models. * A {@link Node} with a serialization mechanism tailored to stored models.
@@ -123,7 +124,7 @@ public class ModelNode extends Node
{ {
super.updateWorldVectors(); super.updateWorldVectors();
if (parent instanceof ModelNode) { if (parent instanceof ModelNode) {
setTransform(getLocalTranslation(), getLocalRotation(), JmeUtil.setTransform(getLocalTranslation(), getLocalRotation(),
getLocalScale(), _localTransform); getLocalScale(), _localTransform);
((ModelNode)parent).getModelTransform().mult(_localTransform, ((ModelNode)parent).getModelTransform().mult(_localTransform,
_modelTransform); _modelTransform);
@@ -390,32 +391,6 @@ public class ModelNode extends Node
CULL_INHERIT : CULL_ALWAYS); CULL_INHERIT : CULL_ALWAYS);
} }
/**
* Sets a matrix to the transform defined by the given translation,
* rotation, and scale values.
*/
protected static Matrix4f setTransform (
Vector3f translation, Quaternion rotation, Vector3f scale,
Matrix4f result)
{
result.setRotationQuaternion(rotation);
result.setTranslation(translation);
result.m00 *= scale.x;
result.m01 *= scale.y;
result.m02 *= scale.z;
result.m10 *= scale.x;
result.m11 *= scale.y;
result.m12 *= scale.z;
result.m20 *= scale.x;
result.m21 *= scale.y;
result.m22 *= scale.z;
return result;
}
/** The node's transform in local and model space. */ /** The node's transform in local and model space. */
protected Matrix4f _localTransform = new Matrix4f(), protected Matrix4f _localTransform = new Matrix4f(),
_modelTransform = new Matrix4f(); _modelTransform = new Matrix4f();
@@ -54,6 +54,7 @@ import com.samskivert.util.ArrayUtil;
import com.samskivert.util.HashIntMap; import com.samskivert.util.HashIntMap;
import com.threerings.jme.Log; import com.threerings.jme.Log;
import com.threerings.jme.util.JmeUtil;
/** /**
* A triangle mesh that deforms according to a bone hierarchy. * A triangle mesh that deforms according to a bone hierarchy.
@@ -299,7 +300,7 @@ public class SkinMesh extends ModelMesh
_invRefTransform = new Matrix4f(); _invRefTransform = new Matrix4f();
if (parent instanceof ModelNode) { if (parent instanceof ModelNode) {
Matrix4f transform = new Matrix4f(); Matrix4f transform = new Matrix4f();
ModelNode.setTransform(getLocalTranslation(), getLocalRotation(), JmeUtil.setTransform(getLocalTranslation(), getLocalRotation(),
getLocalScale(), transform); getLocalScale(), transform);
((ModelNode)parent).getModelTransform().mult(transform, ((ModelNode)parent).getModelTransform().mult(transform,
_invRefTransform); _invRefTransform);
@@ -26,15 +26,19 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Properties; import java.util.Properties;
import com.jme.math.Matrix4f;
import com.jme.math.Quaternion; import com.jme.math.Quaternion;
import com.jme.math.Vector3f; import com.jme.math.Vector3f;
import com.jme.scene.Controller; import com.jme.scene.Controller;
import com.jme.scene.Node;
import com.jme.scene.Spatial; import com.jme.scene.Spatial;
import com.threerings.jme.Log; import com.threerings.jme.Log;
import com.threerings.jme.model.Model; import com.threerings.jme.model.Model;
import com.threerings.jme.util.JmeUtil; import com.threerings.jme.util.JmeUtil;
import com.threerings.jme.tools.ModelDef.TransformNode;
/** /**
* A basic representation for keyframe animations. * A basic representation for keyframe animations.
*/ */
@@ -55,18 +59,22 @@ public class AnimationDef
transforms.add(transform); transforms.add(transform);
} }
/** Adds all transform targets in this frame to the supplied set. */ /** Adds all transform targets in this frame to the supplied sets. */
public void addTransformTargets ( public void addTransformTargets (
HashMap<String, Spatial> nodes, HashSet<Spatial> targets) HashMap<String, Spatial> nodes, HashMap<String, TransformNode> tnodes,
HashSet<Spatial> staticTargets, HashSet<Spatial> transformTargets)
{ {
for (int ii = 0, nn = transforms.size(); ii < nn; ii++) { for (int ii = 0, nn = transforms.size(); ii < nn; ii++) {
String name = transforms.get(ii).name; String name = transforms.get(ii).name;
Spatial target = nodes.get(name); Spatial target = nodes.get(name);
if (target != null) { if (target == null) {
targets.add(target); continue;
}
TransformNode tnode = tnodes.get(name);
if (tnode.baseLocalTransform != null) {
staticTargets.add(target);
} else { } else {
Log.debug("Missing animation target [name=" + name + transformTargets.add(target);
"].");
} }
} }
} }
@@ -126,6 +134,27 @@ public class AnimationDef
frames.add(frame); frames.add(frame);
} }
/**
* Runs through each frame of the animation, updating the transforms of the preprocessing
* node to keep track of which transforms diverge from their original states.
*/
public void filterTransforms (Node root, HashMap<String, TransformNode> nodes)
{
for (FrameDef frame : frames) {
for (TransformDef transform : frame.transforms) {
TransformNode node = nodes.get(transform.name);
if (node != null) {
node.setLocalTransform(
transform.translation, transform.rotation, transform.scale);
}
}
root.updateGeometricState(0f, true);
for (TransformNode node : nodes.values()) {
node.cullDivergentTransforms();
}
}
}
/** /**
* Creates the "live" animation object that will be serialized with the * Creates the "live" animation object that will be serialized with the
* object. * object.
@@ -134,12 +163,13 @@ public class AnimationDef
* @param nodes the nodes in the model, mapped by name * @param nodes the nodes in the model, mapped by name
*/ */
public Model.Animation createAnimation ( public Model.Animation createAnimation (
Properties props, HashMap<String, Spatial> nodes) Properties props, HashMap<String, Spatial> nodes, HashMap<String, TransformNode> tnodes)
{ {
// find all affected nodes // find all affected nodes
HashSet<Spatial> targets = new HashSet<Spatial>(); HashSet<Spatial> staticTargets = new HashSet<Spatial>(),
transformTargets = new HashSet<Spatial>();
for (int ii = 0, nn = frames.size(); ii < nn; ii++) { for (int ii = 0, nn = frames.size(); ii < nn; ii++) {
frames.get(ii).addTransformTargets(nodes, targets); frames.get(ii).addTransformTargets(nodes, tnodes, staticTargets, transformTargets);
} }
// create and configure the animation // create and configure the animation
@@ -149,12 +179,14 @@ public class AnimationDef
Controller.RT_CLAMP); Controller.RT_CLAMP);
// collect all transforms // collect all transforms
anim.transformTargets = targets.toArray(new Spatial[targets.size()]); anim.staticTargets = staticTargets.toArray(new Spatial[staticTargets.size()]);
anim.transforms = new Model.Transform[frames.size()][targets.size()]; anim.transformTargets = transformTargets.toArray(new Spatial[transformTargets.size()]);
anim.transforms = new Model.Transform[frames.size()][transformTargets.size()];
for (int ii = 0; ii < anim.transforms.length; ii++) { for (int ii = 0; ii < anim.transforms.length; ii++) {
anim.transforms[ii] = anim.transforms[ii] =
frames.get(ii).getTransforms(anim.transformTargets); frames.get(ii).getTransforms(anim.transformTargets);
} }
return anim; return anim;
} }
} }
@@ -32,6 +32,7 @@ import java.util.HashMap;
import java.util.Properties; import java.util.Properties;
import java.util.logging.Level; import java.util.logging.Level;
import com.jme.scene.Node;
import com.jme.scene.Spatial; import com.jme.scene.Spatial;
import com.jme.util.LoggingSystem; import com.jme.util.LoggingSystem;
import com.jmex.model.XMLparser.Converters.DummyDisplaySystem; import com.jmex.model.XMLparser.Converters.DummyDisplaySystem;
@@ -43,6 +44,7 @@ import com.threerings.jme.model.Model;
import com.threerings.jme.model.ModelMesh; import com.threerings.jme.model.ModelMesh;
import com.threerings.jme.model.ModelNode; import com.threerings.jme.model.ModelNode;
import com.threerings.jme.model.SkinMesh; import com.threerings.jme.model.SkinMesh;
import com.threerings.jme.tools.ModelDef.TransformNode;
import com.threerings.jme.tools.xml.AnimationParser; import com.threerings.jme.tools.xml.AnimationParser;
import com.threerings.jme.tools.xml.ModelParser; import com.threerings.jme.tools.xml.ModelParser;
@@ -106,18 +108,32 @@ public class CompileModel
System.out.println("Compiling to " + target + "..."); System.out.println("Compiling to " + target + "...");
// load the model content // parse the model and animations
ModelDef mdef = _mparser.parseModel(content.toString()); ModelDef mdef = _mparser.parseModel(content.toString());
AnimationDef[] adefs = new AnimationDef[anims.length];
for (int ii = 0; ii < adefs.length; ii++) {
System.out.println(" Adding " + afiles[ii] + "...");
adefs[ii] = _aparser.parseAnimation(afiles[ii].toString());
}
// preprocess the model and animations to determine which nodes never move, which never
// move within an animation, and which never move with respect to others
HashMap<String, TransformNode> tnodes = new HashMap<String, TransformNode>();
Node troot = mdef.createTransformTree(props, tnodes);
for (AnimationDef adef : adefs) {
adef.filterTransforms(troot, tnodes);
}
mdef.mergeSpatials(tnodes);
// load the model content
HashMap<String, Spatial> nodes = new HashMap<String, Spatial>(); HashMap<String, Spatial> nodes = new HashMap<String, Spatial>();
Model model = mdef.createModel(props, nodes); Model model = mdef.createModel(props, nodes);
model.initPrototype(); model.initPrototype();
// load the animations, if any // load the animations, if any
for (int ii = 0; ii < anims.length; ii++) { for (int ii = 0; ii < anims.length; ii++) {
System.out.println(" Adding " + afiles[ii] + "..."); model.addAnimation(anims[ii], adefs[ii].createAnimation(
AnimationDef adef = _aparser.parseAnimation(afiles[ii].toString()); PropertiesUtil.getSubProperties(props, anims[ii]), nodes, tnodes));
model.addAnimation(anims[ii], adef.createAnimation(
PropertiesUtil.getSubProperties(props, anims[ii]), nodes));
} }
// write and return the model // write and return the model
+292 -58
View File
@@ -39,13 +39,18 @@ import java.util.Set;
import com.jme.bounding.BoundingBox; import com.jme.bounding.BoundingBox;
import com.jme.bounding.BoundingSphere; import com.jme.bounding.BoundingSphere;
import com.jme.math.FastMath; import com.jme.math.FastMath;
import com.jme.math.Matrix4f;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f; import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.scene.Spatial; import com.jme.scene.Spatial;
import com.jme.scene.batch.GeomBatch; import com.jme.scene.batch.GeomBatch;
import com.jme.util.geom.BufferUtils; import com.jme.util.geom.BufferUtils;
import com.samskivert.util.ObjectUtil;
import com.samskivert.util.PropertiesUtil; import com.samskivert.util.PropertiesUtil;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import com.samskivert.util.Tuple;
import com.threerings.jme.Log; import com.threerings.jme.Log;
import com.threerings.jme.model.Model; import com.threerings.jme.model.Model;
@@ -53,6 +58,7 @@ import com.threerings.jme.model.ModelController;
import com.threerings.jme.model.ModelMesh; import com.threerings.jme.model.ModelMesh;
import com.threerings.jme.model.ModelNode; import com.threerings.jme.model.ModelNode;
import com.threerings.jme.model.SkinMesh; import com.threerings.jme.model.SkinMesh;
import com.threerings.jme.util.JmeUtil;
import com.threerings.jme.util.SpatialVisitor; import com.threerings.jme.util.SpatialVisitor;
/** /**
@@ -75,6 +81,21 @@ public class ModelDef
public float[] rotation; public float[] rotation;
public float[] scale; public float[] scale;
/**
* Stores the names of all bones referenced by this spatial in the supplied set.
*/
public void getBoneNames (HashSet<String> bones)
{
// nothing by default
}
/** Checks whether it's possible (disregarding issues of transformation) to merge
* the specified spatial into this one. */
public abstract boolean canMerge (Properties props, SpatialDef other);
/** Merges another spatial into this one. */
public abstract void merge (SpatialDef other, Matrix4f xform);
/** Returns a JME node for this definition. */ /** Returns a JME node for this definition. */
public Spatial getSpatial (Properties props) public Spatial getSpatial (Properties props)
{ {
@@ -155,6 +176,42 @@ public class ModelDef
tcoords = tcoords || vertex.tcoords != null; tcoords = tcoords || vertex.tcoords != null;
} }
// documentation inherited
public boolean canMerge (Properties props, SpatialDef other)
{
if (getClass() != other.getClass()) {
return false; // require exact class match
}
TriMeshDef omesh = (TriMeshDef)other;
return solid == omesh.solid && transparent == omesh.transparent &&
ObjectUtil.equals(texture, omesh.texture) &&
PropertiesUtil.getSubProperties(props, name).equals(
PropertiesUtil.getSubProperties(props, omesh.name));
}
// documentation inherited
public void merge (SpatialDef other, Matrix4f xform)
{
TriMeshDef omesh = (TriMeshDef)other;
// prepend the inverse of the offset transformation
xform = getOffsetTransform().invertLocal().multLocal(xform);
// and append the other's offset
xform.multLocal(omesh.getOffsetTransform());
// extract the rotation to transform normals
Quaternion xrot = xform.toRotationQuat();
// transform the vertices and add them in
for (Vertex vertex : omesh.vertices) {
vertex.transform(xform, xrot);
}
for (int idx : omesh.indices) {
addVertex(omesh.vertices.get(idx));
}
}
// documentation inherited // documentation inherited
public Spatial createSpatial (Properties props) public Spatial createSpatial (Properties props)
{ {
@@ -167,6 +224,24 @@ public class ModelDef
return node; return node;
} }
/** Gets the matrix representing the offset transform. */
protected Matrix4f getOffsetTransform ()
{
Vector3f otrans = new Vector3f(), oscale = new Vector3f(1f, 1f, 1f);
Quaternion orot = new Quaternion();
if (offsetTranslation != null) {
otrans.set(offsetTranslation[0], offsetTranslation[1], offsetTranslation[2]);
}
if (offsetRotation != null) {
orot.set(offsetRotation[0], offsetRotation[1], offsetRotation[2],
offsetRotation[3]);
}
if (offsetScale != null) {
oscale.set(offsetScale[0], offsetScale[1], offsetScale[2]);
}
return JmeUtil.setTransform(otrans, orot, oscale, new Matrix4f());
}
/** Creates the mesh to attach to the node. */ /** Creates the mesh to attach to the node. */
protected ModelMesh createMesh () protected ModelMesh createMesh ()
{ {
@@ -229,6 +304,14 @@ public class ModelDef
/** A triangle mesh that deforms according to bone positions. */ /** A triangle mesh that deforms according to bone positions. */
public static class SkinMeshDef extends TriMeshDef public static class SkinMeshDef extends TriMeshDef
{ {
@Override // documentation inherited
public void getBoneNames (HashSet<String> bones)
{
for (Vertex vertex : vertices) {
bones.addAll(((SkinVertex)vertex).boneWeights.keySet());
}
}
@Override // documentation inherited @Override // documentation inherited
protected ModelMesh createMesh () protected ModelMesh createMesh ()
{ {
@@ -250,6 +333,7 @@ public class ModelDef
HashMap<String, SkinMesh.Bone> bones = HashMap<String, SkinMesh.Bone> bones =
new HashMap<String, SkinMesh.Bone>(); new HashMap<String, SkinMesh.Bone>();
int ii = 0; int ii = 0;
int mweights = 0, tweights = 0;
for (Map.Entry<Set<String>, WeightGroupDef> entry : for (Map.Entry<Set<String>, WeightGroupDef> entry :
_groups.entrySet()) { _groups.entrySet()) {
SkinMesh.WeightGroup wgroup = new SkinMesh.WeightGroup(); SkinMesh.WeightGroup wgroup = new SkinMesh.WeightGroup();
@@ -267,6 +351,8 @@ public class ModelDef
wgroup.bones[jj++] = bone; wgroup.bones[jj++] = bone;
} }
wgroup.weights = toArray(entry.getValue().weights); wgroup.weights = toArray(entry.getValue().weights);
tweights += wgroup.bones.length;
mweights = Math.max(wgroup.bones.length, mweights);
wgroups[ii++] = wgroup; wgroups[ii++] = wgroup;
} }
((SkinMesh)_mesh).setWeightGroups(wgroups); ((SkinMesh)_mesh).setWeightGroups(wgroups);
@@ -315,6 +401,18 @@ public class ModelDef
/** A generic node. */ /** A generic node. */
public static class NodeDef extends SpatialDef public static class NodeDef extends SpatialDef
{ {
// documentation inherited
public boolean canMerge (Properties props, SpatialDef other)
{
return false;
}
// documentation inherited
public void merge (SpatialDef other, Matrix4f xform)
{
throw new UnsupportedOperationException();
}
// documentation inherited // documentation inherited
public Spatial createSpatial (Properties props) public Spatial createSpatial (Properties props)
{ {
@@ -329,6 +427,21 @@ public class ModelDef
public float[] normal; public float[] normal;
public float[] tcoords; public float[] tcoords;
public void transform (Matrix4f xform, Quaternion xrot)
{
Vector3f xvec = new Vector3f(location[0], location[1], location[2]);
xform.mult(xvec, xvec);
location[0] = xvec.x;
location[1] = xvec.y;
location[2] = xvec.z;
xvec.set(normal[0], normal[1], normal[2]);
xrot.mult(xvec, xvec);
normal[0] = xvec.x;
normal[1] = xvec.y;
normal[2] = xvec.z;
}
public void setInBuffers ( public void setInBuffers (
FloatBuffer vbuf, FloatBuffer nbuf, FloatBuffer tbuf) FloatBuffer vbuf, FloatBuffer nbuf, FloatBuffer tbuf)
{ {
@@ -418,6 +531,99 @@ public class ModelDef
public ArrayList<Float> weights = new ArrayList<Float>(); public ArrayList<Float> weights = new ArrayList<Float>();
} }
/** Contains the transform of a node for preprocessing. */
public static class TransformNode extends Node
{
/** The source definition. */
public SpatialDef spatial;
/** If true, this node is referenced by name (as a bone or parent) and cannot be merged
* into another. */
public boolean referenced;
/** If true, this node is a controller target; nodes beneath it can only be merged with
* other descendants. */
public boolean controlled;
/** The node's current local transform. */
public Matrix4f localTransform = new Matrix4f();
/** The node's current world space transform. */
public Matrix4f worldTransform = new Matrix4f();
/** The node's local transform in the original model, or <code>null</code> if the node's
* transform has diverged from the original. */
public Matrix4f baseLocalTransform;
/** The relative transforms between this and all other loosely compatible nodes not yet
* eliminated. As soon as the relative transform diverges in the course of preprocessing
* an animation, the node/transform pair is removed from the list. */
public ArrayList<Tuple<TransformNode, Matrix4f>> relativeTransforms;
public TransformNode (SpatialDef spatial)
{
super(spatial.name);
this.spatial = spatial;
setLocalTransform(spatial.translation, spatial.rotation, spatial.scale);
}
public void setLocalTransform (float[] translation, float[] rotation, float[] scale)
{
getLocalTranslation().set(translation[0], translation[1], translation[2]);
getLocalRotation().set(rotation[0], rotation[1], rotation[2], rotation[3]);
getLocalScale().set(scale[0], scale[1], scale[2]);
JmeUtil.setTransform(
getLocalTranslation(), getLocalRotation(), getLocalScale(), localTransform);
}
@Override // documentation inherited
public void updateWorldVectors ()
{
super.updateWorldVectors();
JmeUtil.setTransform(
getWorldTranslation(), getWorldRotation(), getWorldScale(), worldTransform);
}
public boolean canMerge (Properties props, TransformNode onode)
{
// nodes must have same controlled ancestor
return !onode.referenced && spatial.canMerge(props, onode.spatial) &&
getControlledAncestor() == onode.getControlledAncestor();
}
protected Node getControlledAncestor ()
{
Node ref = this;
while (ref instanceof TransformNode && !((TransformNode)ref).controlled) {
ref = ref.getParent();
}
return ref;
}
public void cullDivergentTransforms ()
{
if (baseLocalTransform != null && !epsilonEquals(localTransform, baseLocalTransform)) {
baseLocalTransform = null;
}
for (Iterator<Tuple<TransformNode, Matrix4f>> it = relativeTransforms.iterator();
it.hasNext(); ) {
Tuple<TransformNode, Matrix4f> tuple = it.next();
if (!epsilonEquals(getRelativeTransform(tuple.left), tuple.right)) {
it.remove();
}
}
}
public Matrix4f getRelativeTransform (TransformNode other)
{
// return the matrix that takes vertices from the space of the other node
// into the space of this one
Matrix4f inv = new Matrix4f();
worldTransform.invert(inv);
return inv.mult(other.worldTransform);
}
}
/** The meshes and bones comprising the model. */ /** The meshes and bones comprising the model. */
public ArrayList<SpatialDef> spatials = new ArrayList<SpatialDef>(); public ArrayList<SpatialDef> spatials = new ArrayList<SpatialDef>();
@@ -428,6 +634,85 @@ public class ModelDef
spatial); spatial);
} }
/**
* Creates and returns a transform tree representing the model for preprocessing.
*/
public Node createTransformTree (Properties props, HashMap<String, TransformNode> nodes)
{
// create the nodes and map them by name
for (SpatialDef spatial : spatials) {
nodes.put(spatial.name, new TransformNode(spatial));
}
// resolve the parents and collect the names of the bones
Node root = new Node("root");
HashSet<String> bones = new HashSet<String>();
for (TransformNode node : nodes.values()) {
if (node.spatial.parent == null) {
root.attachChild(node);
} else {
TransformNode pnode = nodes.get(node.spatial.parent);
if (pnode != null) {
pnode.attachChild(node);
pnode.referenced = true;
}
}
node.spatial.getBoneNames(bones);
}
// mark the bones as referenced
for (String name : bones) {
TransformNode node = nodes.get(name);
if (node != null) {
node.referenced = true;
}
}
// mark the controlled nodes
String[] controllers = StringUtil.parseStringArray(props.getProperty("controllers", ""));
for (String controller : controllers) {
Properties subProps = PropertiesUtil.getSubProperties(props, controller);
TransformNode node = nodes.get(subProps.getProperty("node", controller));
if (node != null) {
node.referenced = node.controlled = true;
}
}
// store the base transforms and relative transforms for merge candidates
root.updateGeometricState(0f, true);
for (TransformNode node : nodes.values()) {
node.baseLocalTransform = new Matrix4f(node.localTransform);
node.relativeTransforms = new ArrayList<Tuple<TransformNode, Matrix4f>>();
for (TransformNode onode : nodes.values()) {
if (node == onode || !node.canMerge(props, onode)) {
continue;
}
node.relativeTransforms.add(new Tuple<TransformNode, Matrix4f>(
onode, node.getRelativeTransform(onode)));
}
}
return root;
}
/**
* Merges compatible meshes that retain the same relative transform throughout all animations.
*/
public void mergeSpatials (HashMap<String, TransformNode> nodes)
{
for (TransformNode node : nodes.values()) {
if (!spatials.contains(node.spatial)) {
continue;
}
for (Tuple<TransformNode, Matrix4f> tuple : node.relativeTransforms) {
if (spatials.contains(tuple.left.spatial)) {
node.spatial.merge(tuple.left.spatial, tuple.right);
spatials.remove(tuple.left.spatial);
}
}
}
}
/** /**
* Creates the model node defined herein. * Creates the model node defined herein.
* *
@@ -475,11 +760,6 @@ public class ModelDef
} }
} }
// in non-animated models, merge meshes with the same attributes
if (StringUtil.isBlank(props.getProperty("animations"))) {
mergeEquivalentMeshes(model, nodes, referenced);
}
// get rid of any nodes that serve no purpose // get rid of any nodes that serve no purpose
pruneUnusedNodes(model, nodes, referenced); pruneUnusedNodes(model, nodes, referenced);
@@ -526,63 +806,17 @@ public class ModelDef
return referenced.contains(node) || hasValidChildren; return referenced.contains(node) || hasValidChildren;
} }
/** Merges meshes with the same attributes. */ /** Determines whether a pair of matrices are "close enough" to equal. */
protected void mergeEquivalentMeshes ( public static boolean epsilonEquals (Matrix4f m1, Matrix4f m2)
Model model, HashMap<String, Spatial> nodes, final HashSet<Spatial> referenced)
{ {
final HashMap<Object, ArrayList<ModelMesh>> meshes = for (int ii = 0; ii < 4; ii++) {
new HashMap<Object, ArrayList<ModelMesh>>(); for (int jj = 0; jj < 4; jj++) {
new SpatialVisitor<ModelMesh>(ModelMesh.class) { if (FastMath.abs(m1.get(ii, jj) - m2.get(ii, jj)) > 0.0001f) {
public void traverse (Spatial spatial) { return false;
// cut out when we encounter referenced nodes
if (!referenced.contains(spatial)) {
spatial.updateWorldVectors();
super.traverse(spatial);
} }
} }
protected void visit (ModelMesh mesh) {
// make sure we don't try this with skinned meshes
if (mesh instanceof SkinMesh) {
return;
}
Object key = mesh.getAttributeKey();
ArrayList<ModelMesh> list = meshes.get(key);
if (list == null) {
meshes.put(key, list = new ArrayList<ModelMesh>());
}
list.add(mesh);
}
}.traverse(model);
for (ArrayList<ModelMesh> list : meshes.values()) {
// the first in the list becomes the base
ModelMesh base = list.get(0);
flattenTransforms(base);
model.attachChild(base);
// the rest are merged with it
for (int ii = 1, nn = list.size(); ii < nn; ii++) {
ModelMesh mesh = list.get(ii);
flattenTransforms(mesh);
mesh.getParent().detachChild(mesh);
base.merge(mesh);
}
// update the model bound and recenter
base.updateModelBound();
base.centerVertices();
} }
} return true;
/** Transforms the mesh's vertices and normals into model coordinates. */
protected static void flattenTransforms (ModelMesh mesh)
{
GeomBatch batch = mesh.getBatch(0);
batch.getWorldCoords(batch.getVertexBuffer());
batch.getWorldNormals(batch.getNormalBuffer());
mesh.getLocalTranslation().zero();
mesh.getLocalRotation().loadIdentity();
mesh.getLocalScale().set(Vector3f.UNIT_XYZ);
} }
/** Converts a boxed Integer list to an unboxed int array. */ /** Converts a boxed Integer list to an unboxed int array. */
@@ -30,6 +30,8 @@ import java.nio.IntBuffer;
import org.lwjgl.opengl.ARBShaderObjects; import org.lwjgl.opengl.ARBShaderObjects;
import com.jme.math.Matrix4f;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f; import com.jme.math.Vector3f;
import com.jme.scene.Controller; import com.jme.scene.Controller;
import com.jme.scene.state.GLSLShaderObjectsState; import com.jme.scene.state.GLSLShaderObjectsState;
@@ -111,6 +113,31 @@ public class JmeUtil
} }
} }
/**
* Sets a matrix to the transform defined by the given translation,
* rotation, and scale values.
*/
public static Matrix4f setTransform (
Vector3f translation, Quaternion rotation, Vector3f scale, Matrix4f result)
{
result.setRotationQuaternion(rotation);
result.setTranslation(translation);
result.m00 *= scale.x;
result.m01 *= scale.y;
result.m02 *= scale.z;
result.m10 *= scale.x;
result.m11 *= scale.y;
result.m12 *= scale.z;
result.m20 *= scale.x;
result.m21 *= scale.y;
result.m22 *= scale.z;
return result;
}
/** /**
* Attempts to parse a string containing an axis: either "x", "y", or "z", or three * Attempts to parse a string containing an axis: either "x", "y", or "z", or three
* comma-delimited values representing an axis vector. The value returned may be one of * comma-delimited values representing an axis vector. The value returned may be one of