From 98315dcdcf7819b716992f90fe2351b30ceb8245 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Mon, 4 Sep 2017 12:39:56 -0400 Subject: [PATCH] Use MPOs for skinning --- .../java/com/jme3/animation/Skeleton.java | 4 +- .../com/jme3/animation/SkeletonControl.java | 135 +++++++----------- 2 files changed, 55 insertions(+), 84 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/animation/Skeleton.java b/jme3-core/src/main/java/com/jme3/animation/Skeleton.java index 47efc629d2..904d7a298f 100644 --- a/jme3-core/src/main/java/com/jme3/animation/Skeleton.java +++ b/jme3-core/src/main/java/com/jme3/animation/Skeleton.java @@ -70,7 +70,7 @@ public final class Skeleton implements Savable, JmeCloneable { public Skeleton(Bone[] boneList) { this.boneList = boneList; - List rootBoneList = new ArrayList(); + List rootBoneList = new ArrayList<>(); for (int i = boneList.length - 1; i >= 0; i--) { Bone b = boneList[i]; if (b.getParent() == null) { @@ -289,6 +289,7 @@ public String toString() { return sb.toString(); } + @Override public void read(JmeImporter im) throws IOException { InputCapsule input = im.getCapsule(this); @@ -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); diff --git a/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java index 63e4d09e30..4cefd721c0 100644 --- a/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java +++ b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java @@ -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; @@ -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; @@ -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; @@ -107,13 +105,11 @@ 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 materials = new HashSet(); - //temp reader - private BufferUtils.ByteShortIntBufferReader indexReader = new BufferUtils.ByteShortIntBufferReader(); + + private MatParamOverride numberOfBonesParam; + private MatParamOverride boneMatricesParam; + /** * Serialization only. Do not use. */ @@ -121,11 +117,13 @@ 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()) { @@ -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()) { @@ -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 { @@ -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) { @@ -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); } /** @@ -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) { @@ -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() { @@ -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) { @@ -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(); } @@ -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 newMaterials = new HashSet(); - 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); } /** @@ -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()); FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData(); - indexReader.rewind(); wb.rewind(); float[] weights = wb.array(); @@ -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; @@ -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(); @@ -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; @@ -781,7 +739,9 @@ 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 @@ -789,6 +749,16 @@ 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); + } } /** @@ -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);