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:
@@ -329,6 +329,7 @@ public class Model extends ModelNode
|
||||
for (int ii = 0; ii < anim.transformTargets.length; ii++) {
|
||||
oxforms[ii].apply(anim.transformTargets[ii]);
|
||||
}
|
||||
updateWorldData(0f);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,6 +23,7 @@ package com.threerings.jme.model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutput;
|
||||
import java.io.Serializable;
|
||||
|
||||
@@ -30,7 +31,9 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
import com.jme.bounding.BoundingVolume;
|
||||
import com.jme.math.Matrix4f;
|
||||
@@ -53,40 +56,81 @@ public class SkinMesh extends ModelMesh
|
||||
public static class WeightGroup
|
||||
implements Serializable
|
||||
{
|
||||
/** The indices of the affected vertices. */
|
||||
public int[] indices;
|
||||
/** The number of vertices in this weight group. */
|
||||
public int vertexCount;
|
||||
|
||||
/** The bones influencing this group. */
|
||||
public ModelNode[] bones;
|
||||
public Bone[] bones;
|
||||
|
||||
/** The array of interleaved weights (of length <code>indices.length *
|
||||
* bones.length</code>): weights for first vertex, weights for second,
|
||||
* etc. */
|
||||
/** The array of interleaved weights (of length <code>vertexCount *
|
||||
* boneIndices.length</code>): weights for first vertex, weights for
|
||||
* second, etc. */
|
||||
public float[] weights;
|
||||
|
||||
/** The inverses of the bones' mesh space reference transforms. */
|
||||
public transient Matrix4f[] invRefTransforms;
|
||||
|
||||
|
||||
/**
|
||||
* Rebinds this weight group for a prototype instance.
|
||||
*
|
||||
* @param pnodes a mapping from prototype nodes to instance nodes
|
||||
* @param bmap the mapping from prototype to instance bones
|
||||
*/
|
||||
public WeightGroup rebind (HashMap pnodes)
|
||||
public WeightGroup rebind (HashMap<Bone, Bone> bmap)
|
||||
{
|
||||
WeightGroup group = new WeightGroup();
|
||||
group.indices = indices;
|
||||
group.weights = weights;
|
||||
group.invRefTransforms = invRefTransforms;
|
||||
group.bones = new ModelNode[bones.length];
|
||||
WeightGroup wgroup = new WeightGroup();
|
||||
wgroup.vertexCount = vertexCount;
|
||||
wgroup.bones = new Bone[bones.length];
|
||||
for (int ii = 0; ii < bones.length; ii++) {
|
||||
group.bones[ii] = (ModelNode)pnodes.get(bones[ii]);
|
||||
wgroup.bones[ii] = bmap.get(bones[ii]);
|
||||
}
|
||||
return group;
|
||||
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
|
||||
*/
|
||||
public Bone rebind (HashMap pnodes)
|
||||
{
|
||||
Bone bone = new Bone((ModelNode)pnodes.get(node));
|
||||
bone.invRefTransform = invRefTransform;
|
||||
bone.transform = new Matrix4f();
|
||||
return bone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the bone's transient state.
|
||||
*/
|
||||
private void readObject (ObjectInputStream in)
|
||||
throws IOException, ClassNotFoundException
|
||||
{
|
||||
in.defaultReadObject();
|
||||
transform = new Matrix4f();
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* No-arg constructor for deserialization.
|
||||
@@ -104,20 +148,19 @@ public class SkinMesh extends ModelMesh
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the weight groups that determine how vertices are affected by
|
||||
* bones.
|
||||
* Sets the array of weight groups that determine how bones affect
|
||||
* each vertex.
|
||||
*/
|
||||
public void setWeightGroups (WeightGroup[] weightGroups)
|
||||
{
|
||||
_weightGroups = weightGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the array of weight groups.
|
||||
*/
|
||||
public WeightGroup[] getWeightGroups ()
|
||||
{
|
||||
return _weightGroups;
|
||||
|
||||
// compile a list of all referenced bones
|
||||
HashSet<Bone> bones = new HashSet<Bone>();
|
||||
for (WeightGroup group : weightGroups) {
|
||||
Collections.addAll(bones, group.bones);
|
||||
}
|
||||
_bones = bones.toArray(new Bone[bones.size()]);
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
@@ -127,18 +170,15 @@ public class SkinMesh extends ModelMesh
|
||||
{
|
||||
super.reconstruct(vertices, normals, colors, textures, indices);
|
||||
|
||||
// replace the vertex and normal buffers with working buffers
|
||||
setVertexBuffer(BufferUtils.clone(_ovbuf = getVertexBuffer()));
|
||||
setNormalBuffer(BufferUtils.clone(_onbuf = getNormalBuffer()));
|
||||
// store the current buffers as the originals
|
||||
storeOriginalBuffers();
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
public void centerVertices ()
|
||||
{
|
||||
super.centerVertices();
|
||||
_ovbuf.rewind();
|
||||
getVertexBuffer().rewind();
|
||||
_ovbuf.put(getVertexBuffer());
|
||||
storeOriginalBuffers();
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
@@ -159,10 +199,15 @@ public class SkinMesh extends ModelMesh
|
||||
properties.addProperty("vertices");
|
||||
properties.addProperty("normals");
|
||||
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];
|
||||
for (int ii = 0; ii < _weightGroups.length; ii++) {
|
||||
mstore._weightGroups[ii] =
|
||||
_weightGroups[ii].rebind(properties.originalToCopy);
|
||||
mstore._weightGroups[ii] = _weightGroups[ii].rebind(bmap);
|
||||
}
|
||||
mstore._ovbuf = _ovbuf;
|
||||
mstore._onbuf = _onbuf;
|
||||
@@ -182,7 +227,7 @@ public class SkinMesh extends ModelMesh
|
||||
throws IOException, ClassNotFoundException
|
||||
{
|
||||
super.readExternal(in);
|
||||
_weightGroups = (WeightGroup[])in.readObject();
|
||||
setWeightGroups((WeightGroup[])in.readObject());
|
||||
}
|
||||
|
||||
@Override // documentation inherited
|
||||
@@ -198,13 +243,9 @@ public class SkinMesh extends ModelMesh
|
||||
{
|
||||
updateWorldVectors();
|
||||
_modelTransform.invert(_transform);
|
||||
for (int ii = 0; ii < _weightGroups.length; ii++) {
|
||||
WeightGroup group = _weightGroups[ii];
|
||||
group.invRefTransforms = new Matrix4f[group.bones.length];
|
||||
for (int jj = 0; jj < group.bones.length; jj++) {
|
||||
group.invRefTransforms[jj] = _transform.mult(
|
||||
group.bones[jj].getModelTransform()).invertLocal();
|
||||
}
|
||||
for (Bone bone : _bones) {
|
||||
bone.invRefTransform =
|
||||
_transform.mult(bone.node.getModelTransform()).invert();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,53 +285,66 @@ public class SkinMesh extends ModelMesh
|
||||
if (_weightGroups == null) {
|
||||
return;
|
||||
}
|
||||
// update the bone transforms
|
||||
_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
|
||||
ModelNode[] bones;
|
||||
int idx;
|
||||
int[] indices;
|
||||
// deform the mesh according to the positions of the bones (this code
|
||||
// is ugly as sin because it's optimized at a low level)
|
||||
Bone[] bones;
|
||||
int vertexCount;
|
||||
float[] weights;
|
||||
Matrix4f xform;
|
||||
Matrix4f[] invRefXforms;
|
||||
float weight;
|
||||
Matrix4f m;
|
||||
float weight, ovx, ovy, ovz, onx, ony, onz, vx, vy, vz, nx, ny, nz;
|
||||
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;
|
||||
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;
|
||||
for (int jj = 0, ww = 0; jj < indices.length; jj++) {
|
||||
idx = indices[jj];
|
||||
BufferUtils.populateFromBuffer(_overtex, _ovbuf, idx);
|
||||
BufferUtils.populateFromBuffer(_onormal, _onbuf, idx);
|
||||
_vertex.zero();
|
||||
_normal.zero();
|
||||
for (int jj = 0, ww = 0; jj < vertexCount; jj++) {
|
||||
ovx = _ovbuf[bidx];
|
||||
ovy = _ovbuf[bidx + 1];
|
||||
ovz = _ovbuf[bidx + 2];
|
||||
onx = _onbuf[bidx++];
|
||||
ony = _onbuf[bidx++];
|
||||
onz = _onbuf[bidx++];
|
||||
vx = vy = vz = 0f;
|
||||
nx = ny = nz = 0f;
|
||||
for (int kk = 0; kk < bones.length; kk++) {
|
||||
xform = _transforms[kk];
|
||||
m = bones[kk].transform;
|
||||
weight = weights[ww++];
|
||||
_vertex.addLocal(
|
||||
xform.mult(_overtex, _tmp).multLocal(weight));
|
||||
_normal.addLocal(
|
||||
multNormal(_onormal, xform, _tmp).multLocal(weight));
|
||||
|
||||
vx += (ovx*m.m00 + ovy*m.m01 + ovz*m.m02 + m.m03) * weight;
|
||||
vy += (ovx*m.m10 + ovy*m.m11 + ovz*m.m12 + m.m13) * 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);
|
||||
BufferUtils.setInBuffer(_normal, nbuf, idx);
|
||||
vbuf.put(vx); vbuf.put(vy); vbuf.put(vz);
|
||||
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
|
||||
* matrix.
|
||||
@@ -311,20 +365,15 @@ public class SkinMesh extends ModelMesh
|
||||
/** The groups of vertices influenced by different sets of bones. */
|
||||
protected WeightGroup[] _weightGroups;
|
||||
|
||||
/** The bones referenced by the weight groups. */
|
||||
protected Bone[] _bones;
|
||||
|
||||
/** The original (undeformed) vertex and normal buffers. */
|
||||
protected FloatBuffer _ovbuf, _onbuf;
|
||||
protected float[] _ovbuf, _onbuf;
|
||||
|
||||
/** The node's transform in model space. */
|
||||
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. */
|
||||
protected Matrix4f _transform = new Matrix4f();
|
||||
|
||||
|
||||
@@ -28,11 +28,13 @@ import java.nio.IntBuffer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import com.jme.bounding.BoundingBox;
|
||||
import com.jme.bounding.BoundingSphere;
|
||||
@@ -244,38 +246,72 @@ public class ModelDef
|
||||
return;
|
||||
}
|
||||
|
||||
// divide the vertices up by 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
|
||||
// create and set the final weight groups
|
||||
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;
|
||||
for (Map.Entry<HashSet<ModelNode>, WeightGroupDef> entry :
|
||||
groups.entrySet()) {
|
||||
for (Map.Entry<Set<String>, WeightGroupDef> entry :
|
||||
_groups.entrySet()) {
|
||||
SkinMesh.WeightGroup wgroup = new SkinMesh.WeightGroup();
|
||||
wgroup.indices = toArray(entry.getValue().indices);
|
||||
HashSet<ModelNode> bones = entry.getKey();
|
||||
referenced.addAll(bones);
|
||||
wgroup.bones = bones.toArray(new ModelNode[bones.size()]);
|
||||
wgroup.vertexCount = entry.getValue().indices.size();
|
||||
wgroup.bones = new SkinMesh.Bone[entry.getKey().size()];
|
||||
int jj = 0;
|
||||
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);
|
||||
wgroups[ii++] = wgroup;
|
||||
}
|
||||
((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. */
|
||||
|
||||
@@ -62,14 +62,8 @@ macroScript TRAnimationExporter category:"File" \
|
||||
outFile = createfile fileName
|
||||
format "<?xml version=\"1.0\" standalone=\"yes\"?>\n\n" 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 (
|
||||
writeFrame nodes outFile
|
||||
writeFrame objects outFile
|
||||
)
|
||||
format "</animation>\n" to:outFile
|
||||
close outFile
|
||||
|
||||
@@ -149,17 +149,9 @@ macroScript TRModelExporter category:"File" \
|
||||
outFile = createfile fileName
|
||||
format "<?xml version=\"1.0\" standalone=\"yes\"?>\n\n" to:outFile
|
||||
format "<model>\n\n" to:outFile
|
||||
local nodes
|
||||
oldsel = selection as array
|
||||
if selection.count > 0 then (
|
||||
nodes = selection
|
||||
) else (
|
||||
nodes = objects
|
||||
)
|
||||
for node in nodes do in coordsys world (
|
||||
for node in objects do in coordsys world (
|
||||
writeNode node outFile
|
||||
)
|
||||
select oldsel
|
||||
format "</model>\n" to:outFile
|
||||
close outFile
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user