Adds a cpu fallback for morph animation when all morph targets cannot be handled on the gpu
This commit is contained in:
parent
42215f4890
commit
4048f8ba26
@ -9,7 +9,9 @@ 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;
|
||||||
|
import com.jme3.util.BufferUtils;
|
||||||
import com.jme3.util.SafeArrayList;
|
import com.jme3.util.SafeArrayList;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
|
||||||
import java.nio.FloatBuffer;
|
import java.nio.FloatBuffer;
|
||||||
|
|
||||||
@ -17,6 +19,7 @@ import java.nio.FloatBuffer;
|
|||||||
* A control that handle morph animation for Position, Normal and Tangent buffers.
|
* A control that handle morph animation for Position, Normal and Tangent buffers.
|
||||||
* All stock shaders only support morphing these 3 buffers, but note that MorphTargets can have any type of buffers.
|
* All stock shaders only support morphing these 3 buffers, but note that MorphTargets can have any type of buffers.
|
||||||
* If you want to use other types of buffers you will need a custom MorphControl and a custom shader.
|
* If you want to use other types of buffers you will need a custom MorphControl and a custom shader.
|
||||||
|
*
|
||||||
* @author Rémy Bouquet
|
* @author Rémy Bouquet
|
||||||
*/
|
*/
|
||||||
public class MorphControl extends AbstractControl {
|
public class MorphControl extends AbstractControl {
|
||||||
@ -30,8 +33,17 @@ public class MorphControl extends AbstractControl {
|
|||||||
private boolean approximateTangents = true;
|
private boolean approximateTangents = true;
|
||||||
private MatParamOverride nullNumberOfBones = new MatParamOverride(VarType.Int, "NumberOfBones", null);
|
private MatParamOverride nullNumberOfBones = new MatParamOverride(VarType.Int, "NumberOfBones", null);
|
||||||
|
|
||||||
|
private float[] tmpPosArray;
|
||||||
|
private float[] tmpNormArray;
|
||||||
|
private float[] tmpTanArray;
|
||||||
|
|
||||||
|
private static final VertexBuffer.Type bufferTypes[] = VertexBuffer.Type.values();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void controlUpdate(float tpf) {
|
protected void controlUpdate(float tpf) {
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// gathering geometries in the sub graph.
|
// gathering geometries in the sub graph.
|
||||||
// This must be done in the update phase as the gathering might add a matparam override
|
// This must be done in the update phase as the gathering might add a matparam override
|
||||||
targets.clear();
|
targets.clear();
|
||||||
@ -40,15 +52,18 @@ public class MorphControl extends AbstractControl {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void controlRender(RenderManager rm, ViewPort vp) {
|
protected void controlRender(RenderManager rm, ViewPort vp) {
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (Geometry target : targets) {
|
for (Geometry target : targets) {
|
||||||
Mesh mesh = target.getMesh();
|
Mesh mesh = target.getMesh();
|
||||||
if (!mesh.isDirtyMorph()) {
|
if (!target.isDirtyMorph()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int nbMaxBuffers = getRemainingBuffers(mesh, rm.getRenderer());
|
int nbMaxBuffers = getRemainingBuffers(mesh, rm.getRenderer());
|
||||||
Material m = target.getMaterial();
|
Material m = target.getMaterial();
|
||||||
|
|
||||||
float weights[] = mesh.getMorphState();
|
float weights[] = target.getMorphState();
|
||||||
MorphTarget morphTargets[] = mesh.getMorphTargets();
|
MorphTarget morphTargets[] = mesh.getMorphTargets();
|
||||||
float matWeights[];
|
float matWeights[];
|
||||||
MatParam param = m.getParam("MorphWeights");
|
MatParam param = m.getParam("MorphWeights");
|
||||||
@ -69,19 +84,70 @@ public class MorphControl extends AbstractControl {
|
|||||||
m.setInt("NumberOfTargetsBuffers", targetNumBuffers);
|
m.setInt("NumberOfTargetsBuffers", targetNumBuffers);
|
||||||
|
|
||||||
int nbGPUTargets = 0;
|
int nbGPUTargets = 0;
|
||||||
int nbCPUBuffers = 0;
|
int lastGpuTargetIndex = 0;
|
||||||
int boundBufferIdx = 0;
|
int boundBufferIdx = 0;
|
||||||
|
float cpuWeightSum = 0;
|
||||||
|
// binding the morphTargets buffer to the mesh morph buffers
|
||||||
for (int i = 0; i < morphTargets.length; i++) {
|
for (int i = 0; i < morphTargets.length; i++) {
|
||||||
|
// discard weights below the threshold
|
||||||
if (weights[i] < MIN_WEIGHT) {
|
if (weights[i] < MIN_WEIGHT) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (nbGPUTargets >= maxGPUTargets) {
|
if (nbGPUTargets >= maxGPUTargets) {
|
||||||
//TODO we should fallback to CPU there.
|
// we already bound all the available gpu slots we need to merge the remaining morph targets.
|
||||||
nbCPUBuffers++;
|
cpuWeightSum += weights[i];
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int start = VertexBuffer.Type.MorphTarget0.ordinal();
|
lastGpuTargetIndex = i;
|
||||||
|
// binding the morph target's buffers to the mesh morph buffers.
|
||||||
MorphTarget t = morphTargets[i];
|
MorphTarget t = morphTargets[i];
|
||||||
|
boundBufferIdx = bindMorphtargetBuffer(mesh, targetNumBuffers, boundBufferIdx, t);
|
||||||
|
// setting the weight in the mat param array
|
||||||
|
matWeights[nbGPUTargets] = weights[i];
|
||||||
|
nbGPUTargets++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nbGPUTargets < matWeights.length) {
|
||||||
|
// if we have less simultaneous GPU targets than the length of the weight array, the array is padded with 0
|
||||||
|
for (int i = nbGPUTargets; i < matWeights.length; i++) {
|
||||||
|
matWeights[i] = 0;
|
||||||
|
}
|
||||||
|
} else if (cpuWeightSum > 0) {
|
||||||
|
// we have more simultaneous morph targets than available gpu slots,
|
||||||
|
// we merge the additional morph targets and bind them to the last gpu slot
|
||||||
|
MorphTarget mt = target.getFallbackMorphTarget();
|
||||||
|
if (mt == null) {
|
||||||
|
mt = initCpuMorphTarget(target);
|
||||||
|
target.setFallbackMorphTarget(mt);
|
||||||
|
}
|
||||||
|
// adding the last Gpu target weight
|
||||||
|
cpuWeightSum += matWeights[nbGPUTargets - 1];
|
||||||
|
ensureTmpArraysCapacity(target.getVertexCount() * 3, targetNumBuffers);
|
||||||
|
|
||||||
|
// merging all remaining targets in tmp arrays
|
||||||
|
for (int i = lastGpuTargetIndex; i < morphTargets.length; i++) {
|
||||||
|
if (weights[i] < MIN_WEIGHT) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
float weight = weights[i] / cpuWeightSum;
|
||||||
|
MorphTarget t = target.getMesh().getMorphTargets()[i];
|
||||||
|
mergeMorphTargets(targetNumBuffers, weight, t, i == lastGpuTargetIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// writing the tmp arrays to the float buffer
|
||||||
|
writeCpuBuffer(targetNumBuffers, mt);
|
||||||
|
|
||||||
|
// binding the merged morph target
|
||||||
|
bindMorphtargetBuffer(mesh, targetNumBuffers, (nbGPUTargets - 1) * targetNumBuffers, mt);
|
||||||
|
|
||||||
|
// setting the eight of the merged targets
|
||||||
|
matWeights[nbGPUTargets - 1] = cpuWeightSum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int bindMorphtargetBuffer(Mesh mesh, int targetNumBuffers, int boundBufferIdx, MorphTarget t) {
|
||||||
|
int start = VertexBuffer.Type.MorphTarget0.ordinal();
|
||||||
if (targetNumBuffers >= 1) {
|
if (targetNumBuffers >= 1) {
|
||||||
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Position));
|
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Position));
|
||||||
boundBufferIdx++;
|
boundBufferIdx++;
|
||||||
@ -94,20 +160,95 @@ public class MorphControl extends AbstractControl {
|
|||||||
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Tangent));
|
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Tangent));
|
||||||
boundBufferIdx++;
|
boundBufferIdx++;
|
||||||
}
|
}
|
||||||
matWeights[nbGPUTargets] = weights[i];
|
return boundBufferIdx;
|
||||||
nbGPUTargets++;
|
}
|
||||||
|
|
||||||
|
private void writeCpuBuffer(int targetNumBuffers, MorphTarget mt) {
|
||||||
|
if (targetNumBuffers >= 1) {
|
||||||
|
FloatBuffer dest = mt.getBuffer(VertexBuffer.Type.Position);
|
||||||
|
dest.rewind();
|
||||||
|
dest.put(tmpPosArray, 0, dest.capacity());
|
||||||
}
|
}
|
||||||
if (nbGPUTargets < matWeights.length) {
|
if (targetNumBuffers >= 2) {
|
||||||
for (int i = nbGPUTargets; i < matWeights.length; i++) {
|
FloatBuffer dest = mt.getBuffer(VertexBuffer.Type.Normal);
|
||||||
matWeights[i] = 0;
|
dest.rewind();
|
||||||
|
dest.put(tmpNormArray, 0, dest.capacity());
|
||||||
|
}
|
||||||
|
if (!approximateTangents && targetNumBuffers == 3) {
|
||||||
|
FloatBuffer dest = mt.getBuffer(VertexBuffer.Type.Tangent);
|
||||||
|
dest.rewind();
|
||||||
|
dest.put(tmpTanArray, 0, dest.capacity());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void mergeMorphTargets(int targetNumBuffers, float weight, MorphTarget t, boolean init) {
|
||||||
|
if (targetNumBuffers >= 1) {
|
||||||
|
mergeTargetBuffer(tmpPosArray, weight, t.getBuffer(VertexBuffer.Type.Position), init);
|
||||||
|
}
|
||||||
|
if (targetNumBuffers >= 2) {
|
||||||
|
mergeTargetBuffer(tmpNormArray, weight, t.getBuffer(VertexBuffer.Type.Normal), init);
|
||||||
|
}
|
||||||
|
if (!approximateTangents && targetNumBuffers == 3) {
|
||||||
|
mergeTargetBuffer(tmpTanArray, weight, t.getBuffer(VertexBuffer.Type.Tangent), init);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureTmpArraysCapacity(int capacity, int targetNumBuffers) {
|
||||||
|
if (targetNumBuffers >= 1) {
|
||||||
|
tmpPosArray = ensureCapacity(tmpPosArray, capacity);
|
||||||
|
}
|
||||||
|
if (targetNumBuffers >= 2) {
|
||||||
|
tmpNormArray = ensureCapacity(tmpNormArray, capacity);
|
||||||
|
}
|
||||||
|
if (!approximateTangents && targetNumBuffers == 3) {
|
||||||
|
tmpTanArray = ensureCapacity(tmpTanArray, capacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mergeTargetBuffer(float[] array, float weight, FloatBuffer src, boolean init) {
|
||||||
|
src.rewind();
|
||||||
|
for (int j = 0; j < src.capacity(); j++) {
|
||||||
|
if (init) {
|
||||||
|
array[j] = 0;
|
||||||
|
}
|
||||||
|
array[j] += weight * src.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void activateBuffer(Mesh mesh, int idx, int start, FloatBuffer b) {
|
private void activateBuffer(Mesh mesh, int idx, int start, FloatBuffer b) {
|
||||||
mesh.setBuffer(VertexBuffer.Type.values()[start + idx], 3, b);
|
VertexBuffer.Type t = bufferTypes[start + idx];
|
||||||
|
VertexBuffer vb = mesh.getBuffer(t);
|
||||||
|
// only set the buffer if it's different
|
||||||
|
if (vb == null || vb.getData() != b) {
|
||||||
|
mesh.setBuffer(t, 3, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float[] ensureCapacity(float[] tmpArray, int size) {
|
||||||
|
if (tmpArray == null || tmpArray.length < size) {
|
||||||
|
return new float[size];
|
||||||
|
}
|
||||||
|
return tmpArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MorphTarget initCpuMorphTarget(Geometry geom) {
|
||||||
|
MorphTarget res = new MorphTarget();
|
||||||
|
MorphTarget mt = geom.getMesh().getMorphTargets()[0];
|
||||||
|
FloatBuffer b = mt.getBuffer(VertexBuffer.Type.Position);
|
||||||
|
if (b != null) {
|
||||||
|
res.setBuffer(VertexBuffer.Type.Position, BufferUtils.createFloatBuffer(b.capacity()));
|
||||||
|
}
|
||||||
|
b = mt.getBuffer(VertexBuffer.Type.Normal);
|
||||||
|
if (b != null) {
|
||||||
|
res.setBuffer(VertexBuffer.Type.Normal, BufferUtils.createFloatBuffer(b.capacity()));
|
||||||
|
}
|
||||||
|
if (!approximateTangents) {
|
||||||
|
b = mt.getBuffer(VertexBuffer.Type.Tangent);
|
||||||
|
if (b != null) {
|
||||||
|
res.setBuffer(VertexBuffer.Type.Tangent, BufferUtils.createFloatBuffer(b.capacity()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getTargetNumBuffers(MorphTarget morphTarget) {
|
private int getTargetNumBuffers(MorphTarget morphTarget) {
|
||||||
|
@ -45,10 +45,9 @@ public class ClipAction extends BlendableAction {
|
|||||||
}
|
}
|
||||||
private void interpolateMorphTrack(double t, MorphTrack track) {
|
private void interpolateMorphTrack(double t, MorphTrack track) {
|
||||||
Geometry target = track.getTarget();
|
Geometry target = track.getTarget();
|
||||||
float[] weights = new float[target.getMesh().getMorphTargets().length];
|
float[] weights = target.getMorphState();
|
||||||
track.getDataAtTime(t, weights);
|
track.getDataAtTime(t, weights);
|
||||||
target.getMesh().setMorphState(weights);
|
target.setMorphState(weights);
|
||||||
|
|
||||||
|
|
||||||
// if (collectTransformDelegate != null) {
|
// if (collectTransformDelegate != null) {
|
||||||
// collectTransformDelegate.collectTransform(target, transform, getWeight(), this);
|
// collectTransformDelegate.collectTransform(target, transform, getWeight(), this);
|
||||||
|
@ -43,6 +43,7 @@ import com.jme3.material.Material;
|
|||||||
import com.jme3.math.Matrix4f;
|
import com.jme3.math.Matrix4f;
|
||||||
import com.jme3.renderer.Camera;
|
import com.jme3.renderer.Camera;
|
||||||
import com.jme3.scene.VertexBuffer.Type;
|
import com.jme3.scene.VertexBuffer.Type;
|
||||||
|
import com.jme3.scene.mesh.MorphTarget;
|
||||||
import com.jme3.util.TempVars;
|
import com.jme3.util.TempVars;
|
||||||
import com.jme3.util.clone.Cloner;
|
import com.jme3.util.clone.Cloner;
|
||||||
import com.jme3.util.clone.IdentityCloneFunction;
|
import com.jme3.util.clone.IdentityCloneFunction;
|
||||||
@ -86,6 +87,15 @@ public class Geometry extends Spatial {
|
|||||||
*/
|
*/
|
||||||
protected int startIndex = -1;
|
protected int startIndex = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Morph state variable for morph animation
|
||||||
|
*/
|
||||||
|
private float[] morphState;
|
||||||
|
private boolean dirtyMorph = true;
|
||||||
|
// a Morph target that will be used to merge all targets that
|
||||||
|
// can't be handled on the cpu on each frame.
|
||||||
|
private MorphTarget fallbackMorphTarget;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialization only. Do not use.
|
* Serialization only. Do not use.
|
||||||
*/
|
*/
|
||||||
@ -576,6 +586,39 @@ public class Geometry extends Spatial {
|
|||||||
this.material = cloner.clone(material);
|
this.material = cloner.clone(material);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setMorphState(float[] state) {
|
||||||
|
if (mesh == null || mesh.getMorphTargets().length == 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nbMorphTargets = mesh.getMorphTargets().length;
|
||||||
|
|
||||||
|
if (morphState == null) {
|
||||||
|
morphState = new float[nbMorphTargets];
|
||||||
|
}
|
||||||
|
System.arraycopy(state, 0, morphState, 0, morphState.length);
|
||||||
|
this.dirtyMorph = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDirtyMorph() {
|
||||||
|
return dirtyMorph;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float[] getMorphState() {
|
||||||
|
if (morphState == null) {
|
||||||
|
morphState = new float[mesh.getMorphTargets().length];
|
||||||
|
}
|
||||||
|
return morphState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MorphTarget getFallbackMorphTarget() {
|
||||||
|
return fallbackMorphTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFallbackMorphTarget(MorphTarget fallbackMorphTarget) {
|
||||||
|
this.fallbackMorphTarget = fallbackMorphTarget;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(JmeExporter ex) throws IOException {
|
public void write(JmeExporter ex) throws IOException {
|
||||||
super.write(ex);
|
super.write(ex);
|
||||||
|
@ -185,9 +185,6 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
|||||||
private Mode mode = Mode.Triangles;
|
private Mode mode = Mode.Triangles;
|
||||||
|
|
||||||
private SafeArrayList<MorphTarget> morphTargets;
|
private SafeArrayList<MorphTarget> morphTargets;
|
||||||
private int numMorphBuffers = 0;
|
|
||||||
private float[] morphState;
|
|
||||||
private boolean dirtyMorph = true;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new mesh with no {@link VertexBuffer vertex buffers}.
|
* Creates a new mesh with no {@link VertexBuffer vertex buffers}.
|
||||||
@ -1527,52 +1524,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
|||||||
if (morphTargets == null) {
|
if (morphTargets == null) {
|
||||||
morphTargets = new SafeArrayList<>(MorphTarget.class);
|
morphTargets = new SafeArrayList<>(MorphTarget.class);
|
||||||
}
|
}
|
||||||
// if (numMorphBuffers == 0) {
|
|
||||||
// numMorphBuffers = target.getNumBuffers();
|
|
||||||
// int start = Type.MorphTarget0.ordinal();
|
|
||||||
// int end = start + numMorphBuffers;
|
|
||||||
// for (int i = start; i < end; i++) {
|
|
||||||
// VertexBuffer vb = new VertexBuffer(Type.values()[i]);
|
|
||||||
// setBuffer(vb);
|
|
||||||
// }
|
|
||||||
// } else if (target.getNumBuffers() != numMorphBuffers) {
|
|
||||||
// throw new IllegalArgumentException("Morph target has different number of buffers");
|
|
||||||
// }
|
|
||||||
|
|
||||||
morphTargets.add(target);
|
morphTargets.add(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMorphState(float[] state) {
|
|
||||||
if (morphTargets.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (morphState == null) {
|
|
||||||
morphState = new float[morphTargets.size()];
|
|
||||||
}
|
|
||||||
System.arraycopy(state, 0, morphState, 0, morphState.length);
|
|
||||||
this.dirtyMorph = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float[] getMorphState() {
|
|
||||||
if (morphState == null) {
|
|
||||||
morphState = new float[morphTargets.size()];
|
|
||||||
}
|
|
||||||
return morphState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setActiveMorphTargets(int... targetsIndex) {
|
|
||||||
int start = Type.MorphTarget0.ordinal();
|
|
||||||
for (int i = 0; i < targetsIndex.length; i++) {
|
|
||||||
MorphTarget t = morphTargets.get(targetsIndex[i]);
|
|
||||||
int idx = 0;
|
|
||||||
for (Type type : t.getBuffers().keySet()) {
|
|
||||||
FloatBuffer b = t.getBuffer(type);
|
|
||||||
setBuffer(Type.values()[start + i + idx], 3, b);
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public MorphTarget[] getMorphTargets() {
|
public MorphTarget[] getMorphTargets() {
|
||||||
return morphTargets.getArray();
|
return morphTargets.getArray();
|
||||||
}
|
}
|
||||||
@ -1581,21 +1535,10 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
|||||||
return morphTargets != null && !morphTargets.isEmpty();
|
return morphTargets != null && !morphTargets.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDirtyMorph() {
|
|
||||||
return dirtyMorph;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(JmeExporter ex) throws IOException {
|
public void write(JmeExporter ex) throws IOException {
|
||||||
OutputCapsule out = ex.getCapsule(this);
|
OutputCapsule out = ex.getCapsule(this);
|
||||||
|
|
||||||
// HashMap<String, VertexBuffer> map = new HashMap<String, VertexBuffer>();
|
|
||||||
// for (Entry<VertexBuffer> buf : buffers){
|
|
||||||
// if (buf.getValue() != null)
|
|
||||||
// map.put(buf.getKey()+"a", buf.getValue());
|
|
||||||
// }
|
|
||||||
// out.writeStringSavableMap(map, "buffers", null);
|
|
||||||
|
|
||||||
out.write(meshBound, "modelBound", null);
|
out.write(meshBound, "modelBound", null);
|
||||||
out.write(vertCount, "vertCount", -1);
|
out.write(vertCount, "vertCount", -1);
|
||||||
out.write(elementCount, "elementCount", -1);
|
out.write(elementCount, "elementCount", -1);
|
||||||
@ -1630,6 +1573,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
out.write(lodLevels, "lodLevels", null);
|
out.write(lodLevels, "lodLevels", null);
|
||||||
|
out.writeSavableArrayList(new ArrayList(morphTargets), "morphTargets", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1669,6 +1613,11 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
|||||||
lodLevels = new VertexBuffer[lodLevelsSavable.length];
|
lodLevels = new VertexBuffer[lodLevelsSavable.length];
|
||||||
System.arraycopy( lodLevelsSavable, 0, lodLevels, 0, lodLevels.length);
|
System.arraycopy( lodLevelsSavable, 0, lodLevels, 0, lodLevels.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArrayList<Savable> l = in.readSavableArrayList("morphTargets", null);
|
||||||
|
if (l != null) {
|
||||||
|
morphTargets = new SafeArrayList(MorphTarget.class, l);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package com.jme3.scene.mesh;
|
package com.jme3.scene.mesh;
|
||||||
|
|
||||||
import com.jme3.export.JmeExporter;
|
import com.jme3.export.*;
|
||||||
import com.jme3.export.JmeImporter;
|
|
||||||
import com.jme3.export.Savable;
|
|
||||||
import com.jme3.scene.VertexBuffer;
|
import com.jme3.scene.VertexBuffer;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.Buffer;
|
||||||
import java.nio.FloatBuffer;
|
import java.nio.FloatBuffer;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class MorphTarget implements Savable {
|
public class MorphTarget implements Savable {
|
||||||
private EnumMap<VertexBuffer.Type, FloatBuffer> buffers = new EnumMap<>(VertexBuffer.Type.class);
|
private EnumMap<VertexBuffer.Type, FloatBuffer> buffers = new EnumMap<>(VertexBuffer.Type.class);
|
||||||
@ -30,11 +30,22 @@ public class MorphTarget implements Savable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(JmeExporter ex) throws IOException {
|
public void write(JmeExporter ex) throws IOException {
|
||||||
|
OutputCapsule oc = ex.getCapsule(this);
|
||||||
|
for (Map.Entry<VertexBuffer.Type, FloatBuffer> entry : buffers.entrySet()) {
|
||||||
|
Buffer roData = entry.getValue().asReadOnlyBuffer();
|
||||||
|
oc.write((FloatBuffer) roData, entry.getKey().name(),null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void read(JmeImporter im) throws IOException {
|
public void read(JmeImporter im) throws IOException {
|
||||||
|
InputCapsule ic = im.getCapsule(this);
|
||||||
|
for (VertexBuffer.Type type : VertexBuffer.Type.values()) {
|
||||||
|
FloatBuffer b = ic.readFloatBuffer(type.name(), null);
|
||||||
|
if(b!= null){
|
||||||
|
setBuffer(type, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,8 +33,7 @@ package jme3test.model;
|
|||||||
|
|
||||||
import com.jme3.anim.AnimComposer;
|
import com.jme3.anim.AnimComposer;
|
||||||
import com.jme3.anim.SkinningControl;
|
import com.jme3.anim.SkinningControl;
|
||||||
import com.jme3.app.ChaseCameraAppState;
|
import com.jme3.app.*;
|
||||||
import com.jme3.app.SimpleApplication;
|
|
||||||
import com.jme3.asset.plugins.FileLocator;
|
import com.jme3.asset.plugins.FileLocator;
|
||||||
import com.jme3.input.KeyInput;
|
import com.jme3.input.KeyInput;
|
||||||
import com.jme3.input.controls.ActionListener;
|
import com.jme3.input.controls.ActionListener;
|
||||||
@ -45,7 +44,6 @@ import com.jme3.scene.*;
|
|||||||
import com.jme3.scene.control.Control;
|
import com.jme3.scene.control.Control;
|
||||||
import com.jme3.scene.debug.custom.ArmatureDebugAppState;
|
import com.jme3.scene.debug.custom.ArmatureDebugAppState;
|
||||||
import com.jme3.scene.plugins.gltf.GltfModelKey;
|
import com.jme3.scene.plugins.gltf.GltfModelKey;
|
||||||
import com.jme3.shader.VarType;
|
|
||||||
import jme3test.model.anim.EraseTimer;
|
import jme3test.model.anim.EraseTimer;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@ -118,8 +116,8 @@ public class TestGltfLoading extends SimpleApplication {
|
|||||||
// rootNode.addLight(pl1);
|
// rootNode.addLight(pl1);
|
||||||
|
|
||||||
//loadModel("Models/gltf/polly/project_polly.gltf", new Vector3f(0, 0, 0), 0.5f);
|
//loadModel("Models/gltf/polly/project_polly.gltf", new Vector3f(0, 0, 0), 0.5f);
|
||||||
//loadModel("Models/gltf/zophrac/scene.gltf", new Vector3f(0, 0, 0), 0.1f);
|
loadModel("Models/gltf/zophrac/scene.gltf", new Vector3f(0, 0, 0), 0.1f);
|
||||||
loadModel("Models/gltf/scifigirl/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
|
// loadModel("Models/gltf/scifigirl/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
|
||||||
//loadModel("Models/gltf/man/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
|
//loadModel("Models/gltf/man/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
|
||||||
//loadModel("Models/gltf/torus/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
|
//loadModel("Models/gltf/torus/scene.gltf", new Vector3f(0, -1, 0), 0.1f);
|
||||||
//loadModel("Models/gltf/morph/scene.gltf", new Vector3f(0, 0, 0), 0.2f);
|
//loadModel("Models/gltf/morph/scene.gltf", new Vector3f(0, 0, 0), 0.2f);
|
||||||
@ -214,15 +212,8 @@ public class TestGltfLoading extends SimpleApplication {
|
|||||||
}, "nextAnim");
|
}, "nextAnim");
|
||||||
|
|
||||||
dumpScene(rootNode, 0);
|
dumpScene(rootNode, 0);
|
||||||
}
|
|
||||||
|
|
||||||
public void setMorphTarget(int index) {
|
stateManager.attach(new DetailedProfilerState());
|
||||||
g = (Geometry) probeNode.getChild("0");
|
|
||||||
g.getMesh().setActiveMorphTargets(index);
|
|
||||||
g.getMaterial().setInt("NumberOfMorphTargets", 1);
|
|
||||||
g.getMaterial().setInt("NumberOfTargetsBuffers", 3);
|
|
||||||
float[] weights = {1.0f};
|
|
||||||
g.getMaterial().setParam("MorphWeights", VarType.FloatArray, weights);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T extends Control> T findControl(Spatial s, Class<T> controlClass) {
|
private <T extends Control> T findControl(Spatial s, Class<T> controlClass) {
|
||||||
|
@ -0,0 +1,168 @@
|
|||||||
|
package jme3test.model.anim;
|
||||||
|
|
||||||
|
import com.jme3.anim.AnimComposer;
|
||||||
|
import com.jme3.anim.SkinningControl;
|
||||||
|
import com.jme3.anim.util.AnimMigrationUtils;
|
||||||
|
import com.jme3.app.ChaseCameraAppState;
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.asset.plugins.FileLocator;
|
||||||
|
import com.jme3.export.binary.BinaryExporter;
|
||||||
|
import com.jme3.input.KeyInput;
|
||||||
|
import com.jme3.input.controls.ActionListener;
|
||||||
|
import com.jme3.input.controls.KeyTrigger;
|
||||||
|
import com.jme3.light.AmbientLight;
|
||||||
|
import com.jme3.light.DirectionalLight;
|
||||||
|
import com.jme3.math.*;
|
||||||
|
import com.jme3.scene.Node;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.debug.custom.ArmatureDebugAppState;
|
||||||
|
import com.jme3.system.JmeSystem;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Nehon on 18/12/2017.
|
||||||
|
*/
|
||||||
|
public class TestAnimSerialization extends SimpleApplication {
|
||||||
|
|
||||||
|
ArmatureDebugAppState debugAppState;
|
||||||
|
AnimComposer composer;
|
||||||
|
Queue<String> anims = new LinkedList<>();
|
||||||
|
boolean playAnim = true;
|
||||||
|
File file;
|
||||||
|
|
||||||
|
public static void main(String... argv) {
|
||||||
|
TestAnimSerialization app = new TestAnimSerialization();
|
||||||
|
app.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void simpleInitApp() {
|
||||||
|
setTimer(new EraseTimer());
|
||||||
|
//cam.setFrustumPerspective(90f, (float) cam.getWidth() / cam.getHeight(), 0.01f, 10f);
|
||||||
|
viewPort.setBackgroundColor(ColorRGBA.DarkGray);
|
||||||
|
rootNode.addLight(new DirectionalLight(new Vector3f(-1, -1, -1).normalizeLocal()));
|
||||||
|
rootNode.addLight(new AmbientLight(ColorRGBA.DarkGray));
|
||||||
|
|
||||||
|
Spatial model = assetManager.loadModel("Models/Jaime/Jaime.j3o");
|
||||||
|
|
||||||
|
AnimMigrationUtils.migrate(model);
|
||||||
|
|
||||||
|
File storageFolder = JmeSystem.getStorageFolder();
|
||||||
|
file = new File(storageFolder.getPath() + File.separator + "newJaime.j3o");
|
||||||
|
BinaryExporter be = new BinaryExporter();
|
||||||
|
try {
|
||||||
|
be.save(model, file);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
assetManager.registerLocator(storageFolder.getPath(), FileLocator.class);
|
||||||
|
model = assetManager.loadModel("newJaime.j3o");
|
||||||
|
|
||||||
|
rootNode.attachChild(model);
|
||||||
|
|
||||||
|
debugAppState = new ArmatureDebugAppState();
|
||||||
|
stateManager.attach(debugAppState);
|
||||||
|
|
||||||
|
setupModel(model);
|
||||||
|
|
||||||
|
flyCam.setEnabled(false);
|
||||||
|
|
||||||
|
Node target = new Node("CamTarget");
|
||||||
|
//target.setLocalTransform(model.getLocalTransform());
|
||||||
|
target.move(0, 1, 0);
|
||||||
|
ChaseCameraAppState chaseCam = new ChaseCameraAppState();
|
||||||
|
chaseCam.setTarget(target);
|
||||||
|
getStateManager().attach(chaseCam);
|
||||||
|
chaseCam.setInvertHorizontalAxis(true);
|
||||||
|
chaseCam.setInvertVerticalAxis(true);
|
||||||
|
chaseCam.setZoomSpeed(0.5f);
|
||||||
|
chaseCam.setMinVerticalRotation(-FastMath.HALF_PI);
|
||||||
|
chaseCam.setRotationSpeed(3);
|
||||||
|
chaseCam.setDefaultDistance(3);
|
||||||
|
chaseCam.setMinDistance(0.01f);
|
||||||
|
chaseCam.setZoomSpeed(0.01f);
|
||||||
|
chaseCam.setDefaultVerticalRotation(0.3f);
|
||||||
|
|
||||||
|
initInputs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initInputs() {
|
||||||
|
inputManager.addMapping("toggleAnim", new KeyTrigger(KeyInput.KEY_RETURN));
|
||||||
|
|
||||||
|
inputManager.addListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void onAction(String name, boolean isPressed, float tpf) {
|
||||||
|
if (isPressed) {
|
||||||
|
playAnim = !playAnim;
|
||||||
|
if (playAnim) {
|
||||||
|
String anim = anims.poll();
|
||||||
|
anims.add(anim);
|
||||||
|
composer.setCurrentAction(anim);
|
||||||
|
System.err.println(anim);
|
||||||
|
} else {
|
||||||
|
composer.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, "toggleAnim");
|
||||||
|
inputManager.addMapping("nextAnim", new KeyTrigger(KeyInput.KEY_RIGHT));
|
||||||
|
inputManager.addListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void onAction(String name, boolean isPressed, float tpf) {
|
||||||
|
if (isPressed && composer != null) {
|
||||||
|
String anim = anims.poll();
|
||||||
|
anims.add(anim);
|
||||||
|
composer.setCurrentAction(anim);
|
||||||
|
System.err.println(anim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, "nextAnim");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupModel(Spatial model) {
|
||||||
|
if (composer != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
composer = model.getControl(AnimComposer.class);
|
||||||
|
if (composer != null) {
|
||||||
|
|
||||||
|
SkinningControl sc = model.getControl(SkinningControl.class);
|
||||||
|
|
||||||
|
debugAppState.addArmatureFrom(sc);
|
||||||
|
anims.clear();
|
||||||
|
for (String name : composer.getAnimClipsNames()) {
|
||||||
|
anims.add(name);
|
||||||
|
}
|
||||||
|
if (anims.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (playAnim) {
|
||||||
|
String anim = anims.poll();
|
||||||
|
anims.add(anim);
|
||||||
|
composer.setCurrentAction(anim);
|
||||||
|
System.err.println(anim);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (model instanceof Node) {
|
||||||
|
Node n = (Node) model;
|
||||||
|
for (Spatial child : n.getChildren()) {
|
||||||
|
setupModel(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
super.destroy();
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package jme3test.model.anim;
|
package jme3test.model.anim;
|
||||||
|
|
||||||
|
import com.jme3.anim.MorphControl;
|
||||||
import com.jme3.app.ChaseCameraAppState;
|
import com.jme3.app.ChaseCameraAppState;
|
||||||
import com.jme3.app.SimpleApplication;
|
import com.jme3.app.SimpleApplication;
|
||||||
import com.jme3.input.KeyInput;
|
import com.jme3.input.KeyInput;
|
||||||
@ -68,16 +69,16 @@ public class TestMorph extends SimpleApplication {
|
|||||||
target2.setBuffer(VertexBuffer.Type.Position, buffer);
|
target2.setBuffer(VertexBuffer.Type.Position, buffer);
|
||||||
box.addMorphTarget(target2);
|
box.addMorphTarget(target2);
|
||||||
|
|
||||||
Geometry g = new Geometry("box", box);
|
final Geometry g = new Geometry("box", box);
|
||||||
final Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
g.setMaterial(m);
|
g.setMaterial(m);
|
||||||
m.setColor("Color", ColorRGBA.Red);
|
m.setColor("Color", ColorRGBA.Red);
|
||||||
m.setInt("NumberOfMorphTargets", 2);
|
m.setInt("NumberOfMorphTargets", 2);
|
||||||
|
|
||||||
rootNode.attachChild(g);
|
rootNode.attachChild(g);
|
||||||
|
|
||||||
box.setActiveMorphTargets(0,1);
|
g.setMorphState(weights);
|
||||||
m.setParam("MorphWeights", VarType.FloatArray, weights);
|
g.addControl(new MorphControl());
|
||||||
|
|
||||||
ChaseCameraAppState chase = new ChaseCameraAppState();
|
ChaseCameraAppState chase = new ChaseCameraAppState();
|
||||||
chase.setTarget(rootNode);
|
chase.setTarget(rootNode);
|
||||||
@ -104,7 +105,7 @@ public class TestMorph extends SimpleApplication {
|
|||||||
if (name.equals("morphdown")) {
|
if (name.equals("morphdown")) {
|
||||||
weights[1] -= tpf;
|
weights[1] -= tpf;
|
||||||
}
|
}
|
||||||
m.setParam("MorphWeights", VarType.FloatArray, weights);
|
g.setMorphState(weights);
|
||||||
|
|
||||||
}
|
}
|
||||||
}, "morphup", "morphdown", "morphleft", "morphright");
|
}, "morphup", "morphdown", "morphleft", "morphright");
|
||||||
|
@ -803,11 +803,6 @@ public class GltfLoader implements AssetLoader {
|
|||||||
} else {
|
} else {
|
||||||
trackData.timeArrays.add(new TrackData.TimeData(times, TrackData.Type.Morph));
|
trackData.timeArrays.add(new TrackData.TimeData(times, TrackData.Type.Morph));
|
||||||
float[] weights = readAccessorData(dataIndex, floatArrayPopulator);
|
float[] weights = readAccessorData(dataIndex, floatArrayPopulator);
|
||||||
Geometry g = fetchFromCache("nodes", targetNode, Geometry.class);
|
|
||||||
int expectedSize = g.getMesh().getMorphTargets().length * times.length;
|
|
||||||
if( expectedSize != weights.length ){
|
|
||||||
throw new AssetLoadException("Morph animation should contain " + expectedSize + " entries, got" + weights.length);
|
|
||||||
}
|
|
||||||
trackData.weights = weights;
|
trackData.weights = weights;
|
||||||
hasMorphTrack = true;
|
hasMorphTrack = true;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user