Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use MPOs for skinning #717

Merged
merged 1 commit into from Sep 9, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion jme3-core/src/main/java/com/jme3/animation/Skeleton.java
Expand Up @@ -70,7 +70,7 @@ public final class Skeleton implements Savable, JmeCloneable {
public Skeleton(Bone[] boneList) {
this.boneList = boneList;

List<Bone> rootBoneList = new ArrayList<Bone>();
List<Bone> rootBoneList = new ArrayList<>();
for (int i = boneList.length - 1; i >= 0; i--) {
Bone b = boneList[i];
if (b.getParent() == null) {
Expand Down Expand Up @@ -289,6 +289,7 @@ public String toString() {
return sb.toString();
}

@Override
public void read(JmeImporter im) throws IOException {
InputCapsule input = im.getCapsule(this);

Expand All @@ -308,6 +309,7 @@ public void read(JmeImporter im) throws IOException {
}
}

@Override
public void write(JmeExporter ex) throws IOException {
OutputCapsule output = ex.getCapsule(this);
output.write(rootBones, "rootBones", null);
Expand Down
135 changes: 52 additions & 83 deletions jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java
Expand Up @@ -32,8 +32,7 @@
package com.jme3.animation;

import com.jme3.export.*;
import com.jme3.material.MatParam;
import com.jme3.material.Material;
import com.jme3.material.MatParamOverride;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f;
import com.jme3.renderer.RenderManager;
Expand All @@ -43,6 +42,7 @@
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.shader.VarType;
import com.jme3.util.*;
import com.jme3.util.clone.Cloner;
Expand All @@ -51,8 +51,6 @@
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand Down Expand Up @@ -107,25 +105,25 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
* Bone offset matrices, recreated each frame
*/
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.
*/
public SkeletonControl() {
}

private void switchToHardware() {
numberOfBonesParam.setEnabled(true);
boneMatricesParam.setEnabled(true);

// Next full 10 bones (e.g. 30 on 24 bones)
int numBones = ((skeleton.getBoneCount() / 10) + 1) * 10;
for (Material m : materials) {
m.setInt("NumberOfBones", numBones);
}
numberOfBonesParam.setValue(numBones);

for (Geometry geometry : targets) {
Mesh mesh = geometry.getMesh();
if (mesh != null && mesh.isAnimated()) {
Expand All @@ -135,11 +133,9 @@ private void switchToHardware() {
}

private void switchToSoftware() {
for (Material m : materials) {
if (m.getParam("NumberOfBones") != null) {
m.clearParam("NumberOfBones");
}
}
numberOfBonesParam.setEnabled(false);
boneMatricesParam.setEnabled(false);

for (Geometry geometry : targets) {
Mesh mesh = geometry.getMesh();
if (mesh != null && mesh.isAnimated()) {
Expand All @@ -149,19 +145,6 @@ private void switchToSoftware() {
}

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();

try {
Expand All @@ -178,6 +161,7 @@ private boolean testHardwareSupported(RenderManager rm) {
* supported by GPU, it shall be enabled, if its not preferred, or not
* supported by GPU, then it shall be disabled.
*
* @param preferred
* @see #isHardwareSkinningUsed()
*/
public void setHardwareSkinningPreferred(boolean preferred) {
Expand Down Expand Up @@ -212,6 +196,8 @@ public SkeletonControl(Skeleton skeleton) {
throw new IllegalArgumentException("skeleton cannot be null");
}
this.skeleton = skeleton;
this.numberOfBonesParam = new MatParamOverride(VarType.Int, "NumberOfBones", null);
this.boneMatricesParam = new MatParamOverride(VarType.Matrix4Array, "BoneMatrices", null);
}

/**
Expand All @@ -222,8 +208,8 @@ private void findTargets(Geometry geometry) {
Mesh mesh = geometry.getMesh();
if (mesh != null && mesh.isAnimated()) {
targets.add(geometry);
materials.add(geometry.getMaterial());
}

}

private void findTargets(Node node) {
Expand All @@ -238,8 +224,21 @@ private void findTargets(Node node) {

@Override
public void setSpatial(Spatial spatial) {
Spatial oldSpatial = this.spatial;
super.setSpatial(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() {
Expand All @@ -258,27 +257,8 @@ private void controlRenderSoftware() {

private void controlRenderHardware() {
offsetMatrices = skeleton.computeSkinningMatrices();
for (Material m : materials) {
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);
}
boneMatricesParam.setValue(offsetMatrices);
}


@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
Expand All @@ -296,7 +276,7 @@ protected void controlRender(RenderManager rm, ViewPort vp) {
if (hwSkinningSupported) {
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 {
switchToSoftware();
}
Expand Down Expand Up @@ -420,28 +400,8 @@ public void cloneFields( Cloner cloner, Object original ) {
// were shared then this will share them.
this.targets = cloner.clone(targets);

// Not automatic set cloning yet
Set<Material> newMaterials = new HashSet<Material>();
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;
this.numberOfBonesParam = cloner.clone(numberOfBonesParam);
this.boneMatricesParam = cloner.clone(boneMatricesParam);
}

/**
Expand Down Expand Up @@ -547,10 +507,9 @@ private void applySkinning(Mesh mesh, Matrix4f[] offsetMatrices) {
fnb.rewind();

// get boneIndexes and weights for mesh
indexReader.setBuffer(mesh.getBuffer(Type.BoneIndex).getData());
IndexBuffer ib = IndexBuffer.wrapIndexBuffer(mesh.getBuffer(Type.BoneIndex).getData());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't that create a new instance of the wrapper on each frame ?

FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();

indexReader.rewind();
wb.rewind();

float[] weights = wb.array();
Expand Down Expand Up @@ -591,7 +550,7 @@ private void applySkinning(Mesh mesh, Matrix4f[] offsetMatrices) {

for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
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;
ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
Expand Down Expand Up @@ -664,10 +623,9 @@ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexB


// 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();

indexReader.rewind();
wb.rewind();

float[] weights = wb.array();
Expand Down Expand Up @@ -723,7 +681,7 @@ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexB

for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
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;
ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
Expand Down Expand Up @@ -781,14 +739,26 @@ public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);
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
public void read(JmeImporter im) throws IOException {
super.read(im);
InputCapsule in = im.getCapsule(this);
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);
}
}

/**
Expand All @@ -798,7 +768,6 @@ public void read(JmeImporter im) throws IOException {
*/
private void updateTargetsAndMaterials(Spatial spatial) {
targets.clear();
materials.clear();

if (spatial instanceof Node) {
findTargets((Node) spatial);
Expand Down