Skinning optimizations: order vertices by weight groups, compute mesh

space bone transforms only once per mesh per bone (rather than each time 
the bone appears in a weight group), low level fiddling.  Also changed 
the exporters to export everything, regardless of what's selected.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@4081 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Andrzej Kapolka
2006-05-02 04:27:49 +00:00
parent 28659209cd
commit 2728af5059
5 changed files with 200 additions and 128 deletions
@@ -329,6 +329,7 @@ public class Model extends ModelNode
for (int ii = 0; ii < anim.transformTargets.length; ii++) { for (int ii = 0; ii < anim.transformTargets.length; ii++) {
oxforms[ii].apply(anim.transformTargets[ii]); oxforms[ii].apply(anim.transformTargets[ii]);
} }
updateWorldData(0f);
} }
/** /**
+136 -87
View File
@@ -23,6 +23,7 @@ package com.threerings.jme.model;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInput; import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput; import java.io.ObjectOutput;
import java.io.Serializable; import java.io.Serializable;
@@ -30,7 +31,9 @@ import java.nio.ByteBuffer;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
import java.nio.IntBuffer; import java.nio.IntBuffer;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import com.jme.bounding.BoundingVolume; import com.jme.bounding.BoundingVolume;
import com.jme.math.Matrix4f; import com.jme.math.Matrix4f;
@@ -53,36 +56,77 @@ public class SkinMesh extends ModelMesh
public static class WeightGroup public static class WeightGroup
implements Serializable implements Serializable
{ {
/** The indices of the affected vertices. */ /** The number of vertices in this weight group. */
public int[] indices; public int vertexCount;
/** The bones influencing this group. */ /** The bones influencing this group. */
public ModelNode[] bones; public Bone[] bones;
/** The array of interleaved weights (of length <code>indices.length * /** The array of interleaved weights (of length <code>vertexCount *
* bones.length</code>): weights for first vertex, weights for second, * boneIndices.length</code>): weights for first vertex, weights for
* etc. */ * second, etc. */
public float[] weights; public float[] weights;
/** The inverses of the bones' mesh space reference transforms. */
public transient Matrix4f[] invRefTransforms;
/** /**
* Rebinds this weight group for a prototype instance. * Rebinds this weight group for a prototype instance.
* *
* @param bmap the mapping from prototype to instance bones
*/
public WeightGroup rebind (HashMap<Bone, Bone> bmap)
{
WeightGroup wgroup = new WeightGroup();
wgroup.vertexCount = vertexCount;
wgroup.bones = new Bone[bones.length];
for (int ii = 0; ii < bones.length; ii++) {
wgroup.bones[ii] = bmap.get(bones[ii]);
}
wgroup.weights = weights;
return wgroup;
}
private static final long serialVersionUID = 1;
}
/** Represents a bone that influences the mesh. */
public static class Bone
implements Serializable
{
/** The node that defines the bone's position. */
public ModelNode node;
/** The inverse of the bone's model space reference transform. */
public transient Matrix4f invRefTransform;
/** The bone's current transform in model space. */
public transient Matrix4f transform;
public Bone (ModelNode node)
{
this.node = node;
transform = new Matrix4f();
}
/**
* Rebinds this bone for a prototype instance.
*
* @param pnodes a mapping from prototype nodes to instance nodes * @param pnodes a mapping from prototype nodes to instance nodes
*/ */
public WeightGroup rebind (HashMap pnodes) public Bone rebind (HashMap pnodes)
{ {
WeightGroup group = new WeightGroup(); Bone bone = new Bone((ModelNode)pnodes.get(node));
group.indices = indices; bone.invRefTransform = invRefTransform;
group.weights = weights; bone.transform = new Matrix4f();
group.invRefTransforms = invRefTransforms; return bone;
group.bones = new ModelNode[bones.length]; }
for (int ii = 0; ii < bones.length; ii++) {
group.bones[ii] = (ModelNode)pnodes.get(bones[ii]); /**
} * Initializes the bone's transient state.
return group; */
private void readObject (ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
transform = new Matrix4f();
} }
private static final long serialVersionUID = 1; private static final long serialVersionUID = 1;
@@ -104,20 +148,19 @@ public class SkinMesh extends ModelMesh
} }
/** /**
* Sets the weight groups that determine how vertices are affected by * Sets the array of weight groups that determine how bones affect
* bones. * each vertex.
*/ */
public void setWeightGroups (WeightGroup[] weightGroups) public void setWeightGroups (WeightGroup[] weightGroups)
{ {
_weightGroups = weightGroups; _weightGroups = weightGroups;
}
/** // compile a list of all referenced bones
* Returns a reference to the array of weight groups. HashSet<Bone> bones = new HashSet<Bone>();
*/ for (WeightGroup group : weightGroups) {
public WeightGroup[] getWeightGroups () Collections.addAll(bones, group.bones);
{ }
return _weightGroups; _bones = bones.toArray(new Bone[bones.size()]);
} }
@Override // documentation inherited @Override // documentation inherited
@@ -127,18 +170,15 @@ public class SkinMesh extends ModelMesh
{ {
super.reconstruct(vertices, normals, colors, textures, indices); super.reconstruct(vertices, normals, colors, textures, indices);
// replace the vertex and normal buffers with working buffers // store the current buffers as the originals
setVertexBuffer(BufferUtils.clone(_ovbuf = getVertexBuffer())); storeOriginalBuffers();
setNormalBuffer(BufferUtils.clone(_onbuf = getNormalBuffer()));
} }
@Override // documentation inherited @Override // documentation inherited
public void centerVertices () public void centerVertices ()
{ {
super.centerVertices(); super.centerVertices();
_ovbuf.rewind(); storeOriginalBuffers();
getVertexBuffer().rewind();
_ovbuf.put(getVertexBuffer());
} }
@Override // documentation inherited @Override // documentation inherited
@@ -159,10 +199,15 @@ public class SkinMesh extends ModelMesh
properties.addProperty("vertices"); properties.addProperty("vertices");
properties.addProperty("normals"); properties.addProperty("normals");
properties.addProperty("displaylistid"); properties.addProperty("displaylistid");
mstore._bones = new Bone[_bones.length];
HashMap<Bone, Bone> bmap = new HashMap<Bone, Bone>();
for (int ii = 0; ii < _bones.length; ii++) {
bmap.put(_bones[ii], mstore._bones[ii] =
_bones[ii].rebind(properties.originalToCopy));
}
mstore._weightGroups = new WeightGroup[_weightGroups.length]; mstore._weightGroups = new WeightGroup[_weightGroups.length];
for (int ii = 0; ii < _weightGroups.length; ii++) { for (int ii = 0; ii < _weightGroups.length; ii++) {
mstore._weightGroups[ii] = mstore._weightGroups[ii] = _weightGroups[ii].rebind(bmap);
_weightGroups[ii].rebind(properties.originalToCopy);
} }
mstore._ovbuf = _ovbuf; mstore._ovbuf = _ovbuf;
mstore._onbuf = _onbuf; mstore._onbuf = _onbuf;
@@ -182,7 +227,7 @@ public class SkinMesh extends ModelMesh
throws IOException, ClassNotFoundException throws IOException, ClassNotFoundException
{ {
super.readExternal(in); super.readExternal(in);
_weightGroups = (WeightGroup[])in.readObject(); setWeightGroups((WeightGroup[])in.readObject());
} }
@Override // documentation inherited @Override // documentation inherited
@@ -198,13 +243,9 @@ public class SkinMesh extends ModelMesh
{ {
updateWorldVectors(); updateWorldVectors();
_modelTransform.invert(_transform); _modelTransform.invert(_transform);
for (int ii = 0; ii < _weightGroups.length; ii++) { for (Bone bone : _bones) {
WeightGroup group = _weightGroups[ii]; bone.invRefTransform =
group.invRefTransforms = new Matrix4f[group.bones.length]; _transform.mult(bone.node.getModelTransform()).invert();
for (int jj = 0; jj < group.bones.length; jj++) {
group.invRefTransforms[jj] = _transform.mult(
group.bones[jj].getModelTransform()).invertLocal();
}
} }
} }
@@ -244,53 +285,66 @@ public class SkinMesh extends ModelMesh
if (_weightGroups == null) { if (_weightGroups == null) {
return; return;
} }
// update the bone transforms
_modelTransform.invert(_transform); _modelTransform.invert(_transform);
for (Bone bone : _bones) {
_transform.mult(bone.node.getModelTransform(), bone.transform);
bone.transform.multLocal(bone.invRefTransform);
}
// deform the mesh according to the positions of the bones // deform the mesh according to the positions of the bones (this code
ModelNode[] bones; // is ugly as sin because it's optimized at a low level)
int idx; Bone[] bones;
int[] indices; int vertexCount;
float[] weights; float[] weights;
Matrix4f xform; Matrix4f m;
Matrix4f[] invRefXforms; float weight, ovx, ovy, ovz, onx, ony, onz, vx, vy, vz, nx, ny, nz;
float weight;
FloatBuffer vbuf = getVertexBuffer(), nbuf = getNormalBuffer(); FloatBuffer vbuf = getVertexBuffer(), nbuf = getNormalBuffer();
for (int ii = 0; ii < _weightGroups.length; ii++) { vbuf.rewind();
nbuf.rewind();
for (int ii = 0, bidx = 0; ii < _weightGroups.length; ii++) {
vertexCount = _weightGroups[ii].vertexCount;
bones = _weightGroups[ii].bones; bones = _weightGroups[ii].bones;
invRefXforms = _weightGroups[ii].invRefTransforms;
if (_transforms == null || _transforms.length < bones.length) {
_transforms = new Matrix4f[bones.length];
}
for (int jj = 0; jj < bones.length; jj++) {
if (_transforms[jj] == null) {
_transforms[jj] = new Matrix4f();
}
_transform.mult(bones[jj].getModelTransform(),
_transforms[jj]);
_transforms[jj].multLocal(invRefXforms[jj]);
}
indices = _weightGroups[ii].indices;
weights = _weightGroups[ii].weights; weights = _weightGroups[ii].weights;
for (int jj = 0, ww = 0; jj < indices.length; jj++) { for (int jj = 0, ww = 0; jj < vertexCount; jj++) {
idx = indices[jj]; ovx = _ovbuf[bidx];
BufferUtils.populateFromBuffer(_overtex, _ovbuf, idx); ovy = _ovbuf[bidx + 1];
BufferUtils.populateFromBuffer(_onormal, _onbuf, idx); ovz = _ovbuf[bidx + 2];
_vertex.zero(); onx = _onbuf[bidx++];
_normal.zero(); ony = _onbuf[bidx++];
onz = _onbuf[bidx++];
vx = vy = vz = 0f;
nx = ny = nz = 0f;
for (int kk = 0; kk < bones.length; kk++) { for (int kk = 0; kk < bones.length; kk++) {
xform = _transforms[kk]; m = bones[kk].transform;
weight = weights[ww++]; weight = weights[ww++];
_vertex.addLocal(
xform.mult(_overtex, _tmp).multLocal(weight)); vx += (ovx*m.m00 + ovy*m.m01 + ovz*m.m02 + m.m03) * weight;
_normal.addLocal( vy += (ovx*m.m10 + ovy*m.m11 + ovz*m.m12 + m.m13) * weight;
multNormal(_onormal, xform, _tmp).multLocal(weight)); vz += (ovx*m.m20 + ovy*m.m21 + ovz*m.m22 + m.m23) * weight;
nx += (onx*m.m00 + ony*m.m01 + onz*m.m02) * weight;
ny += (onx*m.m10 + ony*m.m11 + onz*m.m12) * weight;
nz += (onx*m.m20 + ony*m.m21 + onz*m.m22) * weight;
} }
BufferUtils.setInBuffer(_vertex, vbuf, idx); vbuf.put(vx); vbuf.put(vy); vbuf.put(vz);
BufferUtils.setInBuffer(_normal, nbuf, idx); nbuf.put(nx); nbuf.put(ny); nbuf.put(nz);
} }
} }
} }
/**
* Stores the current vertex and normal buffers for later deformation.
*/
protected void storeOriginalBuffers ()
{
FloatBuffer vbuf = getVertexBuffer(), nbuf = getNormalBuffer();
vbuf.rewind();
nbuf.rewind();
FloatBuffer.wrap(_ovbuf = new float[vbuf.capacity()]).put(vbuf);
FloatBuffer.wrap(_onbuf = new float[nbuf.capacity()]).put(nbuf);
}
/** /**
* Multiplies a normal vector by the 3x3 rotation submatrix of the given * Multiplies a normal vector by the 3x3 rotation submatrix of the given
* matrix. * matrix.
@@ -311,20 +365,15 @@ public class SkinMesh extends ModelMesh
/** The groups of vertices influenced by different sets of bones. */ /** The groups of vertices influenced by different sets of bones. */
protected WeightGroup[] _weightGroups; protected WeightGroup[] _weightGroups;
/** The bones referenced by the weight groups. */
protected Bone[] _bones;
/** The original (undeformed) vertex and normal buffers. */ /** The original (undeformed) vertex and normal buffers. */
protected FloatBuffer _ovbuf, _onbuf; protected float[] _ovbuf, _onbuf;
/** The node's transform in model space. */ /** The node's transform in model space. */
protected Matrix4f _modelTransform = new Matrix4f(); protected Matrix4f _modelTransform = new Matrix4f();
/** A working array for transforms. */
protected Matrix4f[] _transforms;
/** Working vectors. */
protected Vector3f _overtex = new Vector3f(), _vertex = new Vector3f(),
_onormal = new Vector3f(), _normal = new Vector3f(),
_tmp = new Vector3f();
/** Working transform. */ /** Working transform. */
protected Matrix4f _transform = new Matrix4f(); protected Matrix4f _transform = new Matrix4f();
+60 -24
View File
@@ -28,11 +28,13 @@ import java.nio.IntBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set;
import com.jme.bounding.BoundingBox; import com.jme.bounding.BoundingBox;
import com.jme.bounding.BoundingSphere; import com.jme.bounding.BoundingSphere;
@@ -244,38 +246,72 @@ public class ModelDef
return; return;
} }
// divide the vertices up by weight groups // create and set the final weight groups
HashMap<HashSet<ModelNode>, WeightGroupDef> groups =
new HashMap<HashSet<ModelNode>, WeightGroupDef>();
for (int ii = 0, nn = vertices.size(); ii < nn; ii++) {
SkinVertex svertex = (SkinVertex)vertices.get(ii);
HashSet<ModelNode> bones = svertex.getBones(nodes);
WeightGroupDef group = groups.get(bones);
if (group == null) {
groups.put(bones, group = new WeightGroupDef());
}
group.indices.add(ii);
for (ModelNode bone : bones) {
group.weights.add(svertex.getWeight(bone));
}
}
// resolve names and set in mesh
SkinMesh.WeightGroup[] wgroups = SkinMesh.WeightGroup[] wgroups =
new SkinMesh.WeightGroup[groups.size()]; new SkinMesh.WeightGroup[_groups.size()];
HashMap<String, SkinMesh.Bone> bones =
new HashMap<String, SkinMesh.Bone>();
int ii = 0; int ii = 0;
for (Map.Entry<HashSet<ModelNode>, 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();
wgroup.indices = toArray(entry.getValue().indices); wgroup.vertexCount = entry.getValue().indices.size();
HashSet<ModelNode> bones = entry.getKey(); wgroup.bones = new SkinMesh.Bone[entry.getKey().size()];
referenced.addAll(bones); int jj = 0;
wgroup.bones = bones.toArray(new ModelNode[bones.size()]); for (String bname : entry.getKey()) {
SkinMesh.Bone bone = bones.get(bname);
if (bone == null) {
Spatial node = nodes.get(bname);
bones.put(bname,
bone = new SkinMesh.Bone((ModelNode)node));
referenced.add(node);
}
wgroup.bones[jj++] = bone;
}
wgroup.weights = toArray(entry.getValue().weights); wgroup.weights = toArray(entry.getValue().weights);
wgroups[ii++] = wgroup; wgroups[ii++] = wgroup;
} }
((SkinMesh)_mesh).setWeightGroups(wgroups); ((SkinMesh)_mesh).setWeightGroups(wgroups);
} }
@Override // documentation inherited
protected void configureMesh (Properties props)
{
// divide the vertices up by weight groups
_groups = new HashMap<Set<String>, WeightGroupDef>();
for (int ii = 0, nn = vertices.size(); ii < nn; ii++) {
SkinVertex svertex = (SkinVertex)vertices.get(ii);
Set<String> bones = svertex.boneWeights.keySet();
WeightGroupDef group = _groups.get(bones);
if (group == null) {
_groups.put(bones, group = new WeightGroupDef());
}
group.indices.add(ii);
for (String bone : bones) {
group.weights.add(svertex.boneWeights.get(bone).weight);
}
}
// reorder the vertices by group
ArrayList<Vertex> overts = vertices;
vertices = new ArrayList<Vertex>();
int[] imap = new int[overts.size()];
for (Map.Entry<Set<String>, WeightGroupDef> entry :
_groups.entrySet()) {
for (int idx : entry.getValue().indices) {
imap[idx] = vertices.size();
vertices.add(overts.get(idx));
}
}
for (int ii = 0, nn = indices.size(); ii < nn; ii++) {
indices.set(ii, imap[indices.get(ii)]);
}
super.configureMesh(props);
}
/** The intermediate weight groups, mapped by bone names. */
protected HashMap<Set<String>, WeightGroupDef> _groups;
} }
/** A generic node. */ /** A generic node. */
+1 -7
View File
@@ -62,14 +62,8 @@ macroScript TRAnimationExporter category:"File" \
outFile = createfile fileName outFile = createfile fileName
format "<?xml version=\"1.0\" standalone=\"yes\"?>\n\n" to:outFile format "<?xml version=\"1.0\" standalone=\"yes\"?>\n\n" to:outFile
format "<animation frameRate=\"%\">\n\n" frameRate to:outFile format "<animation frameRate=\"%\">\n\n" frameRate to:outFile
local nodes
if selection.count > 0 then (
nodes = selection
) else (
nodes = objects
)
for t = animationRange.start to animationRange.end do at time t ( for t = animationRange.start to animationRange.end do at time t (
writeFrame nodes outFile writeFrame objects outFile
) )
format "</animation>\n" to:outFile format "</animation>\n" to:outFile
close outFile close outFile
+1 -9
View File
@@ -149,17 +149,9 @@ macroScript TRModelExporter category:"File" \
outFile = createfile fileName outFile = createfile fileName
format "<?xml version=\"1.0\" standalone=\"yes\"?>\n\n" to:outFile format "<?xml version=\"1.0\" standalone=\"yes\"?>\n\n" to:outFile
format "<model>\n\n" to:outFile format "<model>\n\n" to:outFile
local nodes for node in objects do in coordsys world (
oldsel = selection as array
if selection.count > 0 then (
nodes = selection
) else (
nodes = objects
)
for node in nodes do in coordsys world (
writeNode node outFile writeNode node outFile
) )
select oldsel
format "</model>\n" to:outFile format "</model>\n" to:outFile
close outFile close outFile
) )