Use MPOs for skinning

fix-456
Kirill Vainer 8 years ago committed by Rémy Bouquet
parent 381d69ccb7
commit bf18ef3048
  1. 4
      jme3-core/src/main/java/com/jme3/animation/Skeleton.java
  2. 135
      jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java

@ -70,7 +70,7 @@ public final class Skeleton implements Savable, JmeCloneable {
public Skeleton(Bone[] boneList) { public Skeleton(Bone[] boneList) {
this.boneList = boneList; this.boneList = boneList;
List<Bone> rootBoneList = new ArrayList<Bone>(); List<Bone> rootBoneList = new ArrayList<>();
for (int i = boneList.length - 1; i >= 0; i--) { for (int i = boneList.length - 1; i >= 0; i--) {
Bone b = boneList[i]; Bone b = boneList[i];
if (b.getParent() == null) { if (b.getParent() == null) {
@ -289,6 +289,7 @@ public final class Skeleton implements Savable, JmeCloneable {
return sb.toString(); return sb.toString();
} }
@Override
public void read(JmeImporter im) throws IOException { public void read(JmeImporter im) throws IOException {
InputCapsule input = im.getCapsule(this); InputCapsule input = im.getCapsule(this);
@ -308,6 +309,7 @@ public final class Skeleton implements Savable, JmeCloneable {
} }
} }
@Override
public void write(JmeExporter ex) throws IOException { public void write(JmeExporter ex) throws IOException {
OutputCapsule output = ex.getCapsule(this); OutputCapsule output = ex.getCapsule(this);
output.write(rootBones, "rootBones", null); output.write(rootBones, "rootBones", null);

@ -32,8 +32,7 @@
package com.jme3.animation; package com.jme3.animation;
import com.jme3.export.*; import com.jme3.export.*;
import com.jme3.material.MatParam; import com.jme3.material.MatParamOverride;
import com.jme3.material.Material;
import com.jme3.math.FastMath; import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f; import com.jme3.math.Matrix4f;
import com.jme3.renderer.RenderManager; import com.jme3.renderer.RenderManager;
@ -43,6 +42,7 @@ import com.jme3.scene.*;
import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.control.AbstractControl; import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control; import com.jme3.scene.control.Control;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.shader.VarType; import com.jme3.shader.VarType;
import com.jme3.util.*; import com.jme3.util.*;
import com.jme3.util.clone.Cloner; import com.jme3.util.clone.Cloner;
@ -51,8 +51,6 @@ import java.io.IOException;
import java.nio.Buffer; import java.nio.Buffer;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -107,13 +105,11 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
* Bone offset matrices, recreated each frame * Bone offset matrices, recreated each frame
*/ */
private transient Matrix4f[] offsetMatrices; private transient Matrix4f[] offsetMatrices;
/**
* Material references used for hardware skinning
*/
private Set<Material> materials = new HashSet<Material>();
//temp reader
private BufferUtils.ByteShortIntBufferReader indexReader = new BufferUtils.ByteShortIntBufferReader(); private MatParamOverride numberOfBonesParam;
private MatParamOverride boneMatricesParam;
/** /**
* Serialization only. Do not use. * Serialization only. Do not use.
*/ */
@ -121,11 +117,13 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
} }
private void switchToHardware() { private void switchToHardware() {
numberOfBonesParam.setEnabled(true);
boneMatricesParam.setEnabled(true);
// Next full 10 bones (e.g. 30 on 24 bones) // Next full 10 bones (e.g. 30 on 24 bones)
int numBones = ((skeleton.getBoneCount() / 10) + 1) * 10; int numBones = ((skeleton.getBoneCount() / 10) + 1) * 10;
for (Material m : materials) { numberOfBonesParam.setValue(numBones);
m.setInt("NumberOfBones", numBones);
}
for (Geometry geometry : targets) { for (Geometry geometry : targets) {
Mesh mesh = geometry.getMesh(); Mesh mesh = geometry.getMesh();
if (mesh != null && mesh.isAnimated()) { if (mesh != null && mesh.isAnimated()) {
@ -135,11 +133,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
} }
private void switchToSoftware() { private void switchToSoftware() {
for (Material m : materials) { numberOfBonesParam.setEnabled(false);
if (m.getParam("NumberOfBones") != null) { boneMatricesParam.setEnabled(false);
m.clearParam("NumberOfBones");
}
}
for (Geometry geometry : targets) { for (Geometry geometry : targets) {
Mesh mesh = geometry.getMesh(); Mesh mesh = geometry.getMesh();
if (mesh != null && mesh.isAnimated()) { if (mesh != null && mesh.isAnimated()) {
@ -149,19 +145,6 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
} }
private boolean testHardwareSupported(RenderManager rm) { private boolean testHardwareSupported(RenderManager rm) {
for (Material m : materials) {
// Some of the animated mesh(es) do not support hardware skinning,
// so it is not supported by the model.
if (m.getMaterialDef().getMaterialParam("NumberOfBones") == null) {
Logger.getLogger(SkeletonControl.class.getName()).log(Level.WARNING,
"Not using hardware skinning for {0}, " +
"because material {1} doesn''t support it.",
new Object[]{spatial, m.getMaterialDef().getName()});
return false;
}
}
switchToHardware(); switchToHardware();
try { try {
@ -178,6 +161,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
* supported by GPU, it shall be enabled, if its not preferred, or not * supported by GPU, it shall be enabled, if its not preferred, or not
* supported by GPU, then it shall be disabled. * supported by GPU, then it shall be disabled.
* *
* @param preferred
* @see #isHardwareSkinningUsed() * @see #isHardwareSkinningUsed()
*/ */
public void setHardwareSkinningPreferred(boolean preferred) { public void setHardwareSkinningPreferred(boolean preferred) {
@ -212,6 +196,8 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
throw new IllegalArgumentException("skeleton cannot be null"); throw new IllegalArgumentException("skeleton cannot be null");
} }
this.skeleton = skeleton; this.skeleton = skeleton;
this.numberOfBonesParam = new MatParamOverride(VarType.Int, "NumberOfBones", null);
this.boneMatricesParam = new MatParamOverride(VarType.Matrix4Array, "BoneMatrices", null);
} }
/** /**
@ -222,8 +208,8 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
Mesh mesh = geometry.getMesh(); Mesh mesh = geometry.getMesh();
if (mesh != null && mesh.isAnimated()) { if (mesh != null && mesh.isAnimated()) {
targets.add(geometry); targets.add(geometry);
materials.add(geometry.getMaterial());
} }
} }
private void findTargets(Node node) { private void findTargets(Node node) {
@ -238,8 +224,21 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
@Override @Override
public void setSpatial(Spatial spatial) { public void setSpatial(Spatial spatial) {
Spatial oldSpatial = this.spatial;
super.setSpatial(spatial); super.setSpatial(spatial);
updateTargetsAndMaterials(spatial); updateTargetsAndMaterials(spatial);
if (oldSpatial != null) {
oldSpatial.removeMatParamOverride(numberOfBonesParam);
oldSpatial.removeMatParamOverride(boneMatricesParam);
}
if (spatial != null) {
spatial.removeMatParamOverride(numberOfBonesParam);
spatial.removeMatParamOverride(boneMatricesParam);
spatial.addMatParamOverride(numberOfBonesParam);
spatial.addMatParamOverride(boneMatricesParam);
}
} }
private void controlRenderSoftware() { private void controlRenderSoftware() {
@ -258,27 +257,8 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
private void controlRenderHardware() { private void controlRenderHardware() {
offsetMatrices = skeleton.computeSkinningMatrices(); offsetMatrices = skeleton.computeSkinningMatrices();
for (Material m : materials) { boneMatricesParam.setValue(offsetMatrices);
MatParam currentParam = m.getParam("BoneMatrices");
if (currentParam != null) {
if (currentParam.getValue() != offsetMatrices) {
// Check to see if other SkeletonControl
// is operating on this material, in that case, user
// is sharing materials between models which is NOT allowed
// when hardware skinning used.
Logger.getLogger(SkeletonControl.class.getName()).log(Level.SEVERE,
"Material instances cannot be shared when hardware skinning is used. " +
"Ensure all models use unique material instances."
);
}
}
m.setParam("BoneMatrices", VarType.Matrix4Array, offsetMatrices);
}
} }
@Override @Override
protected void controlRender(RenderManager rm, ViewPort vp) { protected void controlRender(RenderManager rm, ViewPort vp) {
@ -296,7 +276,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
if (hwSkinningSupported) { if (hwSkinningSupported) {
hwSkinningEnabled = true; hwSkinningEnabled = true;
Logger.getLogger(SkeletonControl.class.getName()).log(Level.INFO, "Hardware skinning engaged for " + spatial); Logger.getLogger(SkeletonControl.class.getName()).log(Level.INFO, "Hardware skinning engaged for {0}", spatial);
} else { } else {
switchToSoftware(); switchToSoftware();
} }
@ -420,28 +400,8 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
// were shared then this will share them. // were shared then this will share them.
this.targets = cloner.clone(targets); this.targets = cloner.clone(targets);
// Not automatic set cloning yet this.numberOfBonesParam = cloner.clone(numberOfBonesParam);
Set<Material> newMaterials = new HashSet<Material>(); this.boneMatricesParam = cloner.clone(boneMatricesParam);
for( Material m : this.materials ) {
Material mClone = cloner.clone(m);
newMaterials.add(mClone);
if( mClone != m ) {
// Material was really cloned so clear the bone matrices in case
// this is hardware skinned. This allows a local version to be
// used and will be reset on the material. Really this just avoids
// the 'safety' check in controlRenderHardware(). Right now material
// doesn't clone itself with the cloner (and doesn't clone its parameters)
// else this would be unnecessary.
MatParam boneMatrices = mClone.getParam("BoneMatrices");
// ...because for some strange reason you can't clear a non-existant
// parameter.
if( boneMatrices != null ) {
mClone.clearParam("BoneMatrices");
}
}
}
this.materials = newMaterials;
} }
/** /**
@ -547,10 +507,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
fnb.rewind(); fnb.rewind();
// get boneIndexes and weights for mesh // get boneIndexes and weights for mesh
indexReader.setBuffer(mesh.getBuffer(Type.BoneIndex).getData()); IndexBuffer ib = IndexBuffer.wrapIndexBuffer(mesh.getBuffer(Type.BoneIndex).getData());
FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData(); FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
indexReader.rewind();
wb.rewind(); wb.rewind();
float[] weights = wb.array(); float[] weights = wb.array();
@ -591,7 +550,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
for (int w = maxWeightsPerVert - 1; w >= 0; w--) { for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
float weight = weights[idxWeights]; float weight = weights[idxWeights];
Matrix4f mat = offsetMatrices[indexReader.getUnsigned(idxWeights++)]; Matrix4f mat = offsetMatrices[ib.get(idxWeights++)];
rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight; rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight; ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
@ -664,10 +623,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
// get boneIndexes and weights for mesh // get boneIndexes and weights for mesh
indexReader.setBuffer(mesh.getBuffer(Type.BoneIndex).getData()); IndexBuffer ib = IndexBuffer.wrapIndexBuffer(mesh.getBuffer(Type.BoneIndex).getData());
FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData(); FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
indexReader.rewind();
wb.rewind(); wb.rewind();
float[] weights = wb.array(); float[] weights = wb.array();
@ -723,7 +681,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
for (int w = maxWeightsPerVert - 1; w >= 0; w--) { for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
float weight = weights[idxWeights]; float weight = weights[idxWeights];
Matrix4f mat = offsetMatrices[indexReader.getUnsigned(idxWeights++)]; Matrix4f mat = offsetMatrices[ib.get(idxWeights++)];
rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight; rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight; ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
@ -781,7 +739,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
super.write(ex); super.write(ex);
OutputCapsule oc = ex.getCapsule(this); OutputCapsule oc = ex.getCapsule(this);
oc.write(skeleton, "skeleton", null); oc.write(skeleton, "skeleton", null);
//Targets and materials don't need to be saved, they'll be gathered on each frame
oc.write(numberOfBonesParam, "numberOfBonesParam", null);
oc.write(boneMatricesParam, "boneMatricesParam", null);
} }
@Override @Override
@ -789,6 +749,16 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
super.read(im); super.read(im);
InputCapsule in = im.getCapsule(this); InputCapsule in = im.getCapsule(this);
skeleton = (Skeleton) in.readSavable("skeleton", null); skeleton = (Skeleton) in.readSavable("skeleton", null);
numberOfBonesParam = (MatParamOverride) in.readSavable("numberOfBonesParam", null);
boneMatricesParam = (MatParamOverride) in.readSavable("boneMatricesParam", null);
if (numberOfBonesParam == null) {
numberOfBonesParam = new MatParamOverride(VarType.Int, "NumberOfBones", null);
boneMatricesParam = new MatParamOverride(VarType.Matrix4Array, "BoneMatrices", null);
getSpatial().addMatParamOverride(numberOfBonesParam);
getSpatial().addMatParamOverride(boneMatricesParam);
}
} }
/** /**
@ -798,7 +768,6 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
*/ */
private void updateTargetsAndMaterials(Spatial spatial) { private void updateTargetsAndMaterials(Spatial spatial) {
targets.clear(); targets.clear();
materials.clear();
if (spatial instanceof Node) { if (spatial instanceof Node) {
findTargets((Node) spatial); findTargets((Node) spatial);

Loading…
Cancel
Save