|
|
@ -1,11 +1,9 @@ |
|
|
|
package com.jme3.anim; |
|
|
|
package com.jme3.anim; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import com.jme3.export.*; |
|
|
|
import com.jme3.material.*; |
|
|
|
import com.jme3.material.*; |
|
|
|
import com.jme3.renderer.*; |
|
|
|
import com.jme3.renderer.*; |
|
|
|
import com.jme3.scene.Geometry; |
|
|
|
import com.jme3.scene.*; |
|
|
|
import com.jme3.scene.Mesh; |
|
|
|
|
|
|
|
import com.jme3.scene.SceneGraphVisitorAdapter; |
|
|
|
|
|
|
|
import com.jme3.scene.VertexBuffer; |
|
|
|
|
|
|
|
import com.jme3.scene.control.AbstractControl; |
|
|
|
import com.jme3.scene.control.AbstractControl; |
|
|
|
import com.jme3.scene.mesh.MorphTarget; |
|
|
|
import com.jme3.scene.mesh.MorphTarget; |
|
|
|
import com.jme3.shader.VarType; |
|
|
|
import com.jme3.shader.VarType; |
|
|
@ -13,7 +11,10 @@ import com.jme3.util.BufferUtils; |
|
|
|
import com.jme3.util.SafeArrayList; |
|
|
|
import com.jme3.util.SafeArrayList; |
|
|
|
import javafx.geometry.Pos; |
|
|
|
import javafx.geometry.Pos; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import java.io.IOException; |
|
|
|
import java.nio.FloatBuffer; |
|
|
|
import java.nio.FloatBuffer; |
|
|
|
|
|
|
|
import java.util.logging.Level; |
|
|
|
|
|
|
|
import java.util.logging.Logger; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* A control that handle morph animation for Position, Normal and Tangent buffers. |
|
|
|
* A control that handle morph animation for Position, Normal and Tangent buffers. |
|
|
@ -22,7 +23,9 @@ import java.nio.FloatBuffer; |
|
|
|
* |
|
|
|
* |
|
|
|
* @author Rémy Bouquet |
|
|
|
* @author Rémy Bouquet |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public class MorphControl extends AbstractControl { |
|
|
|
public class MorphControl extends AbstractControl implements Savable { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static final Logger logger = Logger.getLogger(MorphControl.class.getName()); |
|
|
|
|
|
|
|
|
|
|
|
private static final int MAX_MORPH_BUFFERS = 14; |
|
|
|
private static final int MAX_MORPH_BUFFERS = 14; |
|
|
|
private final static float MIN_WEIGHT = 0.005f; |
|
|
|
private final static float MIN_WEIGHT = 0.005f; |
|
|
@ -55,33 +58,23 @@ public class MorphControl extends AbstractControl { |
|
|
|
if (!enabled) { |
|
|
|
if (!enabled) { |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
for (Geometry target : targets) { |
|
|
|
for (Geometry geom : targets) { |
|
|
|
Mesh mesh = target.getMesh(); |
|
|
|
Mesh mesh = geom.getMesh(); |
|
|
|
if (!target.isDirtyMorph()) { |
|
|
|
if (!geom.isDirtyMorph()) { |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
int nbMaxBuffers = getRemainingBuffers(mesh, rm.getRenderer()); |
|
|
|
|
|
|
|
Material m = target.getMaterial(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float weights[] = target.getMorphState(); |
|
|
|
Material m = geom.getMaterial(); |
|
|
|
|
|
|
|
float weights[] = geom.getMorphState(); |
|
|
|
MorphTarget morphTargets[] = mesh.getMorphTargets(); |
|
|
|
MorphTarget morphTargets[] = mesh.getMorphTargets(); |
|
|
|
float matWeights[]; |
|
|
|
float matWeights[]; |
|
|
|
MatParam param = m.getParam("MorphWeights"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//Number of buffer to handle for each morph target
|
|
|
|
//Number of buffer to handle for each morph target
|
|
|
|
int targetNumBuffers = getTargetNumBuffers(morphTargets[0]); |
|
|
|
int targetNumBuffers = getTargetNumBuffers(morphTargets[0]); |
|
|
|
// compute the max number of targets to send to the GPU
|
|
|
|
|
|
|
|
int maxGPUTargets = Math.min(nbMaxBuffers, MAX_MORPH_BUFFERS) / targetNumBuffers; |
|
|
|
|
|
|
|
if (param == null) { |
|
|
|
|
|
|
|
matWeights = new float[maxGPUTargets]; |
|
|
|
|
|
|
|
m.setParam("MorphWeights", VarType.FloatArray, matWeights); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
matWeights = (float[]) param.getValue(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// setting the maximum number as the real number may change every frame and trigger a shader recompilation since it's bound to a define.
|
|
|
|
int maxGPUTargets = getMaxGPUTargets(rm, geom, m, targetNumBuffers); |
|
|
|
m.setInt("NumberOfMorphTargets", maxGPUTargets); |
|
|
|
|
|
|
|
m.setInt("NumberOfTargetsBuffers", targetNumBuffers); |
|
|
|
MatParam param2 = m.getParam("MorphWeights"); |
|
|
|
|
|
|
|
matWeights = (float[]) param2.getValue(); |
|
|
|
|
|
|
|
|
|
|
|
int nbGPUTargets = 0; |
|
|
|
int nbGPUTargets = 0; |
|
|
|
int lastGpuTargetIndex = 0; |
|
|
|
int lastGpuTargetIndex = 0; |
|
|
@ -115,14 +108,14 @@ public class MorphControl extends AbstractControl { |
|
|
|
} else if (cpuWeightSum > 0) { |
|
|
|
} else if (cpuWeightSum > 0) { |
|
|
|
// we have more simultaneous morph targets than available gpu slots,
|
|
|
|
// we have more simultaneous morph targets than available gpu slots,
|
|
|
|
// we merge the additional morph targets and bind them to the last gpu slot
|
|
|
|
// we merge the additional morph targets and bind them to the last gpu slot
|
|
|
|
MorphTarget mt = target.getFallbackMorphTarget(); |
|
|
|
MorphTarget mt = geom.getFallbackMorphTarget(); |
|
|
|
if (mt == null) { |
|
|
|
if (mt == null) { |
|
|
|
mt = initCpuMorphTarget(target); |
|
|
|
mt = initCpuMorphTarget(geom); |
|
|
|
target.setFallbackMorphTarget(mt); |
|
|
|
geom.setFallbackMorphTarget(mt); |
|
|
|
} |
|
|
|
} |
|
|
|
// adding the last Gpu target weight
|
|
|
|
// adding the last Gpu target weight
|
|
|
|
cpuWeightSum += matWeights[nbGPUTargets - 1]; |
|
|
|
cpuWeightSum += matWeights[nbGPUTargets - 1]; |
|
|
|
ensureTmpArraysCapacity(target.getVertexCount() * 3, targetNumBuffers); |
|
|
|
ensureTmpArraysCapacity(geom.getVertexCount() * 3, targetNumBuffers); |
|
|
|
|
|
|
|
|
|
|
|
// merging all remaining targets in tmp arrays
|
|
|
|
// merging all remaining targets in tmp arrays
|
|
|
|
for (int i = lastGpuTargetIndex; i < morphTargets.length; i++) { |
|
|
|
for (int i = lastGpuTargetIndex; i < morphTargets.length; i++) { |
|
|
@ -130,7 +123,7 @@ public class MorphControl extends AbstractControl { |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
float weight = weights[i] / cpuWeightSum; |
|
|
|
float weight = weights[i] / cpuWeightSum; |
|
|
|
MorphTarget t = target.getMesh().getMorphTargets()[i]; |
|
|
|
MorphTarget t = geom.getMesh().getMorphTargets()[i]; |
|
|
|
mergeMorphTargets(targetNumBuffers, weight, t, i == lastGpuTargetIndex); |
|
|
|
mergeMorphTargets(targetNumBuffers, weight, t, i == lastGpuTargetIndex); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -143,7 +136,52 @@ public class MorphControl extends AbstractControl { |
|
|
|
// setting the eight of the merged targets
|
|
|
|
// setting the eight of the merged targets
|
|
|
|
matWeights[nbGPUTargets - 1] = cpuWeightSum; |
|
|
|
matWeights[nbGPUTargets - 1] = cpuWeightSum; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
geom.setDirtyMorph(false); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private int getMaxGPUTargets(RenderManager rm, Geometry geom, Material mat, int targetNumBuffers) { |
|
|
|
|
|
|
|
if (geom.getNbSimultaneousGPUMorph() > -1) { |
|
|
|
|
|
|
|
return geom.getNbSimultaneousGPUMorph(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Evaluate the number of CPU slots remaining for morph buffers.
|
|
|
|
|
|
|
|
int nbMaxBuffers = getRemainingBuffers(geom.getMesh(), rm.getRenderer()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int realNumTargetsBuffers = geom.getMesh().getMorphTargets().length * targetNumBuffers; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// compute the max number of targets to send to the GPU
|
|
|
|
|
|
|
|
int maxGPUTargets = Math.min(realNumTargetsBuffers, Math.min(nbMaxBuffers, MAX_MORPH_BUFFERS)) / targetNumBuffers; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MatParam param = mat.getParam("MorphWeights"); |
|
|
|
|
|
|
|
if (param == null) { |
|
|
|
|
|
|
|
// init the mat param if it doesn't exists.
|
|
|
|
|
|
|
|
float[] wts = new float[maxGPUTargets]; |
|
|
|
|
|
|
|
mat.setParam("MorphWeights", VarType.FloatArray, wts); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mat.setInt("NumberOfTargetsBuffers", targetNumBuffers); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// test compile the shader to find the accurate number of remaining attributes slots
|
|
|
|
|
|
|
|
boolean compilationOk = false; |
|
|
|
|
|
|
|
// Note that if ever the shader has an unrelated issue we want to break at some point, hence the maxGPUTargets > 0
|
|
|
|
|
|
|
|
while (!compilationOk && maxGPUTargets > 0) { |
|
|
|
|
|
|
|
// setting the maximum number as the real number may change every frame and trigger a shader recompilation since it's bound to a define.
|
|
|
|
|
|
|
|
mat.setInt("NumberOfMorphTargets", maxGPUTargets); |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
// preload the spatial. this will trigger a shader compilation that will fail if the number of attributes is over the limit.
|
|
|
|
|
|
|
|
rm.preloadScene(spatial); |
|
|
|
|
|
|
|
compilationOk = true; |
|
|
|
|
|
|
|
} catch (RendererException e) { |
|
|
|
|
|
|
|
logger.log(Level.FINE, geom.getName() + ": failed at " + maxGPUTargets); |
|
|
|
|
|
|
|
// the compilation failed let's decrement the number of targets an try again.
|
|
|
|
|
|
|
|
maxGPUTargets--; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
logger.log(Level.FINE, geom.getName() + ": " + maxGPUTargets); |
|
|
|
|
|
|
|
// set the number of GPU morph on the geom to not have to recompute it next frame.
|
|
|
|
|
|
|
|
geom.setNbSimultaneousGPUMorph(maxGPUTargets); |
|
|
|
|
|
|
|
return maxGPUTargets; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private int bindMorphtargetBuffer(Mesh mesh, int targetNumBuffers, int boundBufferIdx, MorphTarget t) { |
|
|
|
private int bindMorphtargetBuffer(Mesh mesh, int targetNumBuffers, int boundBufferIdx, MorphTarget t) { |
|
|
@ -263,6 +301,17 @@ public class MorphControl extends AbstractControl { |
|
|
|
return num; |
|
|
|
return num; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Computes the number of remaining buffers on this mesh. |
|
|
|
|
|
|
|
* This is supposed to give a hint on how many attributes will be used in the material and computes the remaining available slots for the morph attributes. |
|
|
|
|
|
|
|
* However, the shader can declare attributes that are not used and not bound to a real buffer. |
|
|
|
|
|
|
|
* That's why we attempt to compile the shader later on to avoid any compilation crash. |
|
|
|
|
|
|
|
* This method is here to avoid too much render test iteration. |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param mesh |
|
|
|
|
|
|
|
* @param renderer |
|
|
|
|
|
|
|
* @return |
|
|
|
|
|
|
|
*/ |
|
|
|
private int getRemainingBuffers(Mesh mesh, Renderer renderer) { |
|
|
|
private int getRemainingBuffers(Mesh mesh, Renderer renderer) { |
|
|
|
int nbUsedBuffers = 0; |
|
|
|
int nbUsedBuffers = 0; |
|
|
|
for (VertexBuffer vb : mesh.getBufferList().getArray()) { |
|
|
|
for (VertexBuffer vb : mesh.getBufferList().getArray()) { |
|
|
|