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}). */
public int repeatType;
/** Any nodes visible that never move within the model. */
public Spatial[] staticTargets;
/** The transformation targets of the animation. */
public Spatial[] transformTargets;
@@ -141,12 +144,9 @@ public class Model extends ModelNode
Animation anim = new Animation();
anim.frameRate = frameRate;
anim.repeatType = repeatType;
anim.staticTargets = rebind(staticTargets, pnodes);
anim.transformTargets = rebind(transformTargets, pnodes);
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.stored = stored;
return anim;
@@ -187,6 +187,8 @@ public class Model extends ModelNode
InputCapsule capsule = im.getCapsule(this);
frameRate = capsule.readInt("frameRate", 0);
repeatType = capsule.readInt("repeatType", Controller.RT_CLAMP);
staticTargets = ArrayUtil.copy(capsule.readSavableArray(
"staticTargets", null), new Spatial[0]);
transformTargets = ArrayUtil.copy(capsule.readSavableArray(
"transformTargets", null), new Spatial[0]);
FloatBuffer pxforms = capsule.readFloatBuffer("transforms", null);
@@ -208,9 +210,10 @@ public class Model extends ModelNode
OutputCapsule capsule = ex.getCapsule(this);
capsule.write(frameRate, "frameRate", 0);
capsule.write(repeatType, "repeatType", Controller.RT_CLAMP);
capsule.write(staticTargets, "staticTargets", null);
capsule.write(transformTargets, "transformTargets", null);
FloatBuffer pxforms = FloatBuffer.allocate(transforms.length *
transformTargets.length * Transform.PACKED_SIZE);
FloatBuffer pxforms = FloatBuffer.allocate(
transforms.length * transformTargets.length * Transform.PACKED_SIZE);
for (Transform[] frame : transforms) {
for (Transform xform : frame) {
xform.writeToBuffer(pxforms);
@@ -220,6 +223,15 @@ public class Model extends ModelNode
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;
}
@@ -534,9 +546,11 @@ public class Model extends ModelNode
_animObservers.apply(new AnimCancelledOp(_animName));
}
// first cull all model nodes, then re-activate the ones in the
// target list
// first cull all model nodes, then re-activate the ones in the target lists
cullModelNodes();
for (Spatial target : anim.staticTargets) {
((ModelNode)target).updateCullMode();
}
for (Spatial target : anim.transformTargets) {
((ModelNode)target).updateCullMode();
}
@@ -144,51 +144,6 @@ public class ModelMesh extends TriMesh
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
* position lies at the center of its bounding volume.
@@ -819,30 +774,6 @@ public class ModelMesh extends TriMesh
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
* property key and a default value. */
protected String _textureKey;
@@ -41,6 +41,7 @@ import com.jme.util.export.InputCapsule;
import com.jme.util.export.OutputCapsule;
import com.threerings.jme.Log;
import com.threerings.jme.util.JmeUtil;
/**
* A {@link Node} with a serialization mechanism tailored to stored models.
@@ -123,7 +124,7 @@ public class ModelNode extends Node
{
super.updateWorldVectors();
if (parent instanceof ModelNode) {
setTransform(getLocalTranslation(), getLocalRotation(),
JmeUtil.setTransform(getLocalTranslation(), getLocalRotation(),
getLocalScale(), _localTransform);
((ModelNode)parent).getModelTransform().mult(_localTransform,
_modelTransform);
@@ -390,32 +391,6 @@ public class ModelNode extends Node
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. */
protected Matrix4f _localTransform = new Matrix4f(),
_modelTransform = new Matrix4f();
@@ -54,6 +54,7 @@ import com.samskivert.util.ArrayUtil;
import com.samskivert.util.HashIntMap;
import com.threerings.jme.Log;
import com.threerings.jme.util.JmeUtil;
/**
* A triangle mesh that deforms according to a bone hierarchy.
@@ -299,7 +300,7 @@ public class SkinMesh extends ModelMesh
_invRefTransform = new Matrix4f();
if (parent instanceof ModelNode) {
Matrix4f transform = new Matrix4f();
ModelNode.setTransform(getLocalTranslation(), getLocalRotation(),
JmeUtil.setTransform(getLocalTranslation(), getLocalRotation(),
getLocalScale(), transform);
((ModelNode)parent).getModelTransform().mult(transform,
_invRefTransform);
@@ -26,15 +26,19 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Properties;
import com.jme.math.Matrix4f;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.Controller;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.threerings.jme.Log;
import com.threerings.jme.model.Model;
import com.threerings.jme.util.JmeUtil;
import com.threerings.jme.tools.ModelDef.TransformNode;
/**
* A basic representation for keyframe animations.
*/
@@ -55,18 +59,22 @@ public class AnimationDef
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 (
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++) {
String name = transforms.get(ii).name;
Spatial target = nodes.get(name);
if (target != null) {
targets.add(target);
if (target == null) {
continue;
}
TransformNode tnode = tnodes.get(name);
if (tnode.baseLocalTransform != null) {
staticTargets.add(target);
} else {
Log.debug("Missing animation target [name=" + name +
"].");
transformTargets.add(target);
}
}
}
@@ -126,6 +134,27 @@ public class AnimationDef
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
* object.
@@ -134,12 +163,13 @@ public class AnimationDef
* @param nodes the nodes in the model, mapped by name
*/
public Model.Animation createAnimation (
Properties props, HashMap<String, Spatial> nodes)
Properties props, HashMap<String, Spatial> nodes, HashMap<String, TransformNode> tnodes)
{
// 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++) {
frames.get(ii).addTransformTargets(nodes, targets);
frames.get(ii).addTransformTargets(nodes, tnodes, staticTargets, transformTargets);
}
// create and configure the animation
@@ -149,12 +179,14 @@ public class AnimationDef
Controller.RT_CLAMP);
// collect all transforms
anim.transformTargets = targets.toArray(new Spatial[targets.size()]);
anim.transforms = new Model.Transform[frames.size()][targets.size()];
anim.staticTargets = staticTargets.toArray(new Spatial[staticTargets.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++) {
anim.transforms[ii] =
frames.get(ii).getTransforms(anim.transformTargets);
}
return anim;
}
}
@@ -32,6 +32,7 @@ import java.util.HashMap;
import java.util.Properties;
import java.util.logging.Level;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.util.LoggingSystem;
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.ModelNode;
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.ModelParser;
@@ -106,18 +108,32 @@ public class CompileModel
System.out.println("Compiling to " + target + "...");
// load the model content
// parse the model and animations
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>();
Model model = mdef.createModel(props, nodes);
model.initPrototype();
// load the animations, if any
for (int ii = 0; ii < anims.length; ii++) {
System.out.println(" Adding " + afiles[ii] + "...");
AnimationDef adef = _aparser.parseAnimation(afiles[ii].toString());
model.addAnimation(anims[ii], adef.createAnimation(
PropertiesUtil.getSubProperties(props, anims[ii]), nodes));
model.addAnimation(anims[ii], adefs[ii].createAnimation(
PropertiesUtil.getSubProperties(props, anims[ii]), nodes, tnodes));
}
// 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.BoundingSphere;
import com.jme.math.FastMath;
import com.jme.math.Matrix4f;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.batch.GeomBatch;
import com.jme.util.geom.BufferUtils;
import com.samskivert.util.ObjectUtil;
import com.samskivert.util.PropertiesUtil;
import com.samskivert.util.StringUtil;
import com.samskivert.util.Tuple;
import com.threerings.jme.Log;
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.ModelNode;
import com.threerings.jme.model.SkinMesh;
import com.threerings.jme.util.JmeUtil;
import com.threerings.jme.util.SpatialVisitor;
/**
@@ -75,6 +81,21 @@ public class ModelDef
public float[] rotation;
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. */
public Spatial getSpatial (Properties props)
{
@@ -155,6 +176,42 @@ public class ModelDef
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
public Spatial createSpatial (Properties props)
{
@@ -167,6 +224,24 @@ public class ModelDef
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. */
protected ModelMesh createMesh ()
{
@@ -229,6 +304,14 @@ public class ModelDef
/** A triangle mesh that deforms according to bone positions. */
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
protected ModelMesh createMesh ()
{
@@ -250,6 +333,7 @@ public class ModelDef
HashMap<String, SkinMesh.Bone> bones =
new HashMap<String, SkinMesh.Bone>();
int ii = 0;
int mweights = 0, tweights = 0;
for (Map.Entry<Set<String>, WeightGroupDef> entry :
_groups.entrySet()) {
SkinMesh.WeightGroup wgroup = new SkinMesh.WeightGroup();
@@ -267,6 +351,8 @@ public class ModelDef
wgroup.bones[jj++] = bone;
}
wgroup.weights = toArray(entry.getValue().weights);
tweights += wgroup.bones.length;
mweights = Math.max(wgroup.bones.length, mweights);
wgroups[ii++] = wgroup;
}
((SkinMesh)_mesh).setWeightGroups(wgroups);
@@ -315,6 +401,18 @@ public class ModelDef
/** A generic node. */
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
public Spatial createSpatial (Properties props)
{
@@ -329,6 +427,21 @@ public class ModelDef
public float[] normal;
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 (
FloatBuffer vbuf, FloatBuffer nbuf, FloatBuffer tbuf)
{
@@ -418,6 +531,99 @@ public class ModelDef
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. */
public ArrayList<SpatialDef> spatials = new ArrayList<SpatialDef>();
@@ -428,6 +634,85 @@ public class ModelDef
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.
*
@@ -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
pruneUnusedNodes(model, nodes, referenced);
@@ -526,63 +806,17 @@ public class ModelDef
return referenced.contains(node) || hasValidChildren;
}
/** Merges meshes with the same attributes. */
protected void mergeEquivalentMeshes (
Model model, HashMap<String, Spatial> nodes, final HashSet<Spatial> referenced)
/** Determines whether a pair of matrices are "close enough" to equal. */
public static boolean epsilonEquals (Matrix4f m1, Matrix4f m2)
{
final HashMap<Object, ArrayList<ModelMesh>> meshes =
new HashMap<Object, ArrayList<ModelMesh>>();
new SpatialVisitor<ModelMesh>(ModelMesh.class) {
public void traverse (Spatial spatial) {
// cut out when we encounter referenced nodes
if (!referenced.contains(spatial)) {
spatial.updateWorldVectors();
super.traverse(spatial);
for (int ii = 0; ii < 4; ii++) {
for (int jj = 0; jj < 4; jj++) {
if (FastMath.abs(m1.get(ii, jj) - m2.get(ii, jj)) > 0.0001f) {
return false;
}
}
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();
}
}
/** 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);
return true;
}
/** Converts a boxed Integer list to an unboxed int array. */
@@ -30,6 +30,8 @@ import java.nio.IntBuffer;
import org.lwjgl.opengl.ARBShaderObjects;
import com.jme.math.Matrix4f;
import com.jme.math.Quaternion;
import com.jme.math.Vector3f;
import com.jme.scene.Controller;
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
* comma-delimited values representing an axis vector. The value returned may be one of