parent
909716fa03
commit
346173e2be
@ -0,0 +1,12 @@ |
||||
package com.jme3.anim; |
||||
|
||||
import com.jme3.export.Savable; |
||||
import com.jme3.util.clone.JmeCloneable; |
||||
|
||||
public interface AnimTrack<T> extends Savable, JmeCloneable { |
||||
|
||||
public void getDataAtTime(double time, T store); |
||||
public double getLength(); |
||||
|
||||
|
||||
} |
@ -0,0 +1,165 @@ |
||||
package com.jme3.anim; |
||||
|
||||
import com.jme3.material.*; |
||||
import com.jme3.renderer.*; |
||||
import com.jme3.scene.Geometry; |
||||
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.mesh.MorphTarget; |
||||
import com.jme3.shader.VarType; |
||||
import com.jme3.util.SafeArrayList; |
||||
|
||||
import java.nio.FloatBuffer; |
||||
|
||||
/** |
||||
* 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. |
||||
* If you want to use other types of buffers you will need a custom MorphControl and a custom shader. |
||||
* @author Rémy Bouquet |
||||
*/ |
||||
public class MorphControl extends AbstractControl { |
||||
|
||||
private static final int MAX_MORPH_BUFFERS = 14; |
||||
private final static float MIN_WEIGHT = 0.005f; |
||||
|
||||
private SafeArrayList<Geometry> targets = new SafeArrayList<>(Geometry.class); |
||||
private TargetLocator targetLocator = new TargetLocator(); |
||||
|
||||
private boolean approximateTangents = true; |
||||
private MatParamOverride nullNumberOfBones = new MatParamOverride(VarType.Int, "NumberOfBones", null); |
||||
|
||||
@Override |
||||
protected void controlUpdate(float tpf) { |
||||
// gathering geometries in the sub graph.
|
||||
// This must be done in the update phase as the gathering might add a matparam override
|
||||
targets.clear(); |
||||
this.spatial.depthFirstTraversal(targetLocator); |
||||
} |
||||
|
||||
@Override |
||||
protected void controlRender(RenderManager rm, ViewPort vp) { |
||||
for (Geometry target : targets) { |
||||
Mesh mesh = target.getMesh(); |
||||
if (!mesh.isDirtyMorph()) { |
||||
continue; |
||||
} |
||||
int nbMaxBuffers = getRemainingBuffers(mesh, rm.getRenderer()); |
||||
Material m = target.getMaterial(); |
||||
|
||||
float weights[] = mesh.getMorphState(); |
||||
MorphTarget morphTargets[] = mesh.getMorphTargets(); |
||||
float matWeights[]; |
||||
MatParam param = m.getParam("MorphWeights"); |
||||
|
||||
//Number of buffer to handle for each morph target
|
||||
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.
|
||||
m.setInt("NumberOfMorphTargets", maxGPUTargets); |
||||
m.setInt("NumberOfTargetsBuffers", targetNumBuffers); |
||||
|
||||
int nbGPUTargets = 0; |
||||
int nbCPUBuffers = 0; |
||||
int boundBufferIdx = 0; |
||||
for (int i = 0; i < morphTargets.length; i++) { |
||||
if (weights[i] < MIN_WEIGHT) { |
||||
continue; |
||||
} |
||||
if (nbGPUTargets >= maxGPUTargets) { |
||||
//TODO we should fallback to CPU there.
|
||||
nbCPUBuffers++; |
||||
continue; |
||||
} |
||||
int start = VertexBuffer.Type.MorphTarget0.ordinal(); |
||||
MorphTarget t = morphTargets[i]; |
||||
if (targetNumBuffers >= 1) { |
||||
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Position)); |
||||
boundBufferIdx++; |
||||
} |
||||
if (targetNumBuffers >= 2) { |
||||
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Normal)); |
||||
boundBufferIdx++; |
||||
} |
||||
if (!approximateTangents && targetNumBuffers == 3) { |
||||
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Tangent)); |
||||
boundBufferIdx++; |
||||
} |
||||
matWeights[nbGPUTargets] = weights[i]; |
||||
nbGPUTargets++; |
||||
|
||||
} |
||||
if (nbGPUTargets < matWeights.length) { |
||||
for (int i = nbGPUTargets; i < matWeights.length; i++) { |
||||
matWeights[i] = 0; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void activateBuffer(Mesh mesh, int idx, int start, FloatBuffer b) { |
||||
mesh.setBuffer(VertexBuffer.Type.values()[start + idx], 3, b); |
||||
} |
||||
|
||||
private int getTargetNumBuffers(MorphTarget morphTarget) { |
||||
int num = 0; |
||||
if (morphTarget.getBuffer(VertexBuffer.Type.Position) != null) num++; |
||||
if (morphTarget.getBuffer(VertexBuffer.Type.Normal) != null) num++; |
||||
|
||||
// if tangents are not needed we don't count the tangent buffer
|
||||
if (!approximateTangents && morphTarget.getBuffer(VertexBuffer.Type.Tangent) != null) { |
||||
num++; |
||||
} |
||||
return num; |
||||
} |
||||
|
||||
private int getRemainingBuffers(Mesh mesh, Renderer renderer) { |
||||
int nbUsedBuffers = 0; |
||||
for (VertexBuffer vb : mesh.getBufferList().getArray()) { |
||||
boolean isMorphBuffer = vb.getBufferType().ordinal() >= VertexBuffer.Type.MorphTarget0.ordinal() && vb.getBufferType().ordinal() <= VertexBuffer.Type.MorphTarget9.ordinal(); |
||||
if (vb.getBufferType() == VertexBuffer.Type.Index || isMorphBuffer) continue; |
||||
if (vb.getUsage() != VertexBuffer.Usage.CpuOnly) { |
||||
nbUsedBuffers++; |
||||
} |
||||
} |
||||
return renderer.getLimits().get(Limits.VertexAttributes) - nbUsedBuffers; |
||||
} |
||||
|
||||
public void setApproximateTangents(boolean approximateTangents) { |
||||
this.approximateTangents = approximateTangents; |
||||
} |
||||
|
||||
public boolean isApproximateTangents() { |
||||
return approximateTangents; |
||||
} |
||||
|
||||
private class TargetLocator extends SceneGraphVisitorAdapter { |
||||
@Override |
||||
public void visit(Geometry geom) { |
||||
MatParam p = geom.getMaterial().getMaterialDef().getMaterialParam("MorphWeights"); |
||||
if (p == null) { |
||||
return; |
||||
} |
||||
Mesh mesh = geom.getMesh(); |
||||
if (mesh != null && mesh.hasMorphTargets()) { |
||||
targets.add(geom); |
||||
// If the mesh is in a subgraph of a node with a SkinningControl it might have hardware skinning activated through mat param override even if it's not skinned.
|
||||
// this code makes sure that if the mesh has no hardware skinning buffers hardware skinning won't be activated.
|
||||
// this is important, because if HW skinning is activated the shader will declare 2 additional useless attributes,
|
||||
// and we desperately need all the attributes we can find for Morph animation.
|
||||
if (mesh.getBuffer(VertexBuffer.Type.HWBoneIndex) == null && !geom.getLocalMatParamOverrides().contains(nullNumberOfBones)) { |
||||
geom.addMatParamOverride(nullNumberOfBones); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,217 @@ |
||||
/* |
||||
* Copyright (c) 2009-2012 jMonkeyEngine |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are |
||||
* met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* |
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||
* may be used to endorse or promote products derived from this software |
||||
* without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
package com.jme3.anim; |
||||
|
||||
import com.jme3.anim.interpolator.FrameInterpolator; |
||||
import com.jme3.animation.*; |
||||
import com.jme3.export.*; |
||||
import com.jme3.scene.Geometry; |
||||
import com.jme3.util.clone.Cloner; |
||||
import com.jme3.util.clone.JmeCloneable; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
/** |
||||
* Contains a list of weights and times for each keyframe. |
||||
* |
||||
* @author Rémy Bouquet |
||||
*/ |
||||
public class MorphTrack implements AnimTrack<float[]> { |
||||
|
||||
private double length; |
||||
private Geometry target; |
||||
|
||||
/** |
||||
* Weights and times for track. |
||||
*/ |
||||
private float[] weights; |
||||
private FrameInterpolator interpolator = FrameInterpolator.DEFAULT; |
||||
private float[] times; |
||||
private int nbMorphTargets; |
||||
|
||||
/** |
||||
* Serialization-only. Do not use. |
||||
*/ |
||||
public MorphTrack() { |
||||
} |
||||
|
||||
/** |
||||
* Creates a morph track with the given Geometry as a target |
||||
* |
||||
* @param times a float array with the time of each frame |
||||
* @param weights the morphs for each frames |
||||
*/ |
||||
public MorphTrack(Geometry target, float[] times, float[] weights, int nbMorphTargets) { |
||||
this.target = target; |
||||
this.nbMorphTargets = nbMorphTargets; |
||||
this.setKeyframes(times, weights); |
||||
} |
||||
|
||||
/** |
||||
* return the array of weights of this track |
||||
* |
||||
* @return |
||||
*/ |
||||
public float[] getWeights() { |
||||
return weights; |
||||
} |
||||
|
||||
/** |
||||
* returns the arrays of time for this track |
||||
* |
||||
* @return |
||||
*/ |
||||
public float[] getTimes() { |
||||
return times; |
||||
} |
||||
|
||||
/** |
||||
* Sets the keyframes times for this Joint track |
||||
* |
||||
* @param times the keyframes times |
||||
*/ |
||||
public void setTimes(float[] times) { |
||||
if (times.length == 0) { |
||||
throw new RuntimeException("TransformTrack with no keyframes!"); |
||||
} |
||||
this.times = times; |
||||
length = times[times.length - 1] - times[0]; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set the weight for this morph track |
||||
* |
||||
* @param times a float array with the time of each frame |
||||
* @param weights the weights of the morphs for each frame |
||||
|
||||
*/ |
||||
public void setKeyframes(float[] times, float[] weights) { |
||||
setTimes(times); |
||||
if (weights != null) { |
||||
if (times == null) { |
||||
throw new RuntimeException("MorphTrack doesn't have any time for key frames, please call setTimes first"); |
||||
} |
||||
|
||||
this.weights = weights; |
||||
|
||||
assert times != null && times.length == weights.length; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public double getLength() { |
||||
return length; |
||||
} |
||||
|
||||
@Override |
||||
public void getDataAtTime(double t, float[] store) { |
||||
float time = (float) t; |
||||
|
||||
int lastFrame = times.length - 1; |
||||
if (time < 0 || lastFrame == 0) { |
||||
if (weights != null) { |
||||
System.arraycopy(weights,0,store,0, nbMorphTargets); |
||||
} |
||||
return; |
||||
} |
||||
|
||||
int startFrame = 0; |
||||
int endFrame = 1; |
||||
float blend = 0; |
||||
if (time >= times[lastFrame]) { |
||||
startFrame = lastFrame; |
||||
|
||||
time = time - times[startFrame] + times[startFrame - 1]; |
||||
blend = (time - times[startFrame - 1]) |
||||
/ (times[startFrame] - times[startFrame - 1]); |
||||
|
||||
} else { |
||||
// use lastFrame so we never overflow the array
|
||||
int i; |
||||
for (i = 0; i < lastFrame && times[i] < time; i++) { |
||||
startFrame = i; |
||||
endFrame = i + 1; |
||||
} |
||||
blend = (time - times[startFrame]) |
||||
/ (times[endFrame] - times[startFrame]); |
||||
} |
||||
|
||||
interpolator.interpolateWeights(blend, startFrame, weights, nbMorphTargets, store); |
||||
} |
||||
|
||||
public void setFrameInterpolator(FrameInterpolator interpolator) { |
||||
this.interpolator = interpolator; |
||||
} |
||||
|
||||
public Geometry getTarget() { |
||||
return target; |
||||
} |
||||
|
||||
public void setTarget(Geometry target) { |
||||
this.target = target; |
||||
} |
||||
|
||||
@Override |
||||
public void write(JmeExporter ex) throws IOException { |
||||
OutputCapsule oc = ex.getCapsule(this); |
||||
oc.write(weights, "weights", null); |
||||
oc.write(times, "times", null); |
||||
oc.write(target, "target", null); |
||||
} |
||||
|
||||
@Override |
||||
public void read(JmeImporter im) throws IOException { |
||||
InputCapsule ic = im.getCapsule(this); |
||||
weights = ic.readFloatArray("weights", null); |
||||
times = ic.readFloatArray("times", null); |
||||
target = (Geometry) ic.readSavable("target", null); |
||||
setTimes(times); |
||||
} |
||||
|
||||
@Override |
||||
public Object jmeClone() { |
||||
try { |
||||
MorphTrack clone = (MorphTrack) super.clone(); |
||||
return clone; |
||||
} catch (CloneNotSupportedException ex) { |
||||
throw new AssertionError(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void cloneFields(Cloner cloner, Object original) { |
||||
this.target = cloner.clone(target); |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,95 @@ |
||||
package com.jme3.anim; |
||||
|
||||
import java.util.ArrayList; |
||||
|
||||
public class Weights {//} extends Savable, JmeCloneable{
|
||||
|
||||
|
||||
private final static float MIN_WEIGHT = 0.005f; |
||||
|
||||
private int[] indices; |
||||
private float[] data; |
||||
private int size; |
||||
|
||||
public Weights(float[] array, int start, int length) { |
||||
ArrayList<Float> list = new ArrayList<>(); |
||||
ArrayList<Integer> idx = new ArrayList<>(); |
||||
|
||||
for (int i = start; i < length; i++) { |
||||
float val = array[i]; |
||||
if (val > MIN_WEIGHT) { |
||||
list.add(val); |
||||
idx.add(i); |
||||
} |
||||
} |
||||
size = list.size(); |
||||
data = new float[size]; |
||||
indices = new int[size]; |
||||
for (int i = 0; i < size; i++) { |
||||
data[i] = list.get(i); |
||||
indices[i] = idx.get(i); |
||||
} |
||||
} |
||||
|
||||
public int getSize() { |
||||
return size; |
||||
} |
||||
|
||||
// public Weights(float[] array, int start, int length) {
|
||||
// LinkedList<Float> list = new LinkedList<>();
|
||||
// LinkedList<Integer> idx = new LinkedList<>();
|
||||
// for (int i = start; i < length; i++) {
|
||||
// float val = array[i];
|
||||
// if (val > MIN_WEIGHT) {
|
||||
// int index = insert(list, val);
|
||||
// if (idx.size() < index) {
|
||||
// idx.add(i);
|
||||
// } else {
|
||||
// idx.add(index, i);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// data = new float[list.size()];
|
||||
// for (int i = 0; i < data.length; i++) {
|
||||
// data[i] = list.get(i);
|
||||
// }
|
||||
//
|
||||
// indices = new int[idx.size()];
|
||||
// for (int i = 0; i < indices.length; i++) {
|
||||
// indices[i] = idx.get(i);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private int insert(LinkedList<Float> list, float value) {
|
||||
// for (int i = 0; i < list.size(); i++) {
|
||||
// float w = list.get(i);
|
||||
// if (value > w) {
|
||||
// list.add(i, value);
|
||||
// return i;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// list.add(value);
|
||||
// return list.size();
|
||||
// }
|
||||
|
||||
@Override |
||||
public String toString() { |
||||
StringBuilder b = new StringBuilder(); |
||||
for (int i = 0; i < indices.length; i++) { |
||||
b.append(indices[i]).append(","); |
||||
} |
||||
b.append("\n"); |
||||
for (int i = 0; i < data.length; i++) { |
||||
b.append(data[i]).append(","); |
||||
} |
||||
return b.toString(); |
||||
} |
||||
|
||||
public static void main(String... args) { |
||||
// 6 7 4 8
|
||||
float values[] = {0, 0, 0, 0, 0.5f, 0.001f, 0.7f, 0.6f, 0.2f, 0, 0, 0}; |
||||
Weights w = new Weights(values, 0, values.length); |
||||
System.err.println(w); |
||||
} |
||||
} |
@ -0,0 +1,100 @@ |
||||
/* |
||||
* Copyright (c) 2009-2012 jMonkeyEngine |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are |
||||
* met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* |
||||
* * Redistributions in binary form must reproduce the above copyright |
||||
* notice, this list of conditions and the following disclaimer in the |
||||
* documentation and/or other materials provided with the distribution. |
||||
* |
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors |
||||
* may be used to endorse or promote products derived from this software |
||||
* without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
package com.jme3.animation; |
||||
|
||||
import com.jme3.export.*; |
||||
import com.jme3.math.Vector3f; |
||||
|
||||
import java.io.IOException; |
||||
|
||||
/** |
||||
* Serialize and compress Float by indexing similar values |
||||
* @author Lim, YongHoon |
||||
*/ |
||||
public class CompactFloatArray extends CompactArray<Float> implements Savable { |
||||
|
||||
/** |
||||
* Creates a compact vector array |
||||
*/ |
||||
public CompactFloatArray() { |
||||
} |
||||
|
||||
/** |
||||
* creates a compact vector array |
||||
* @param dataArray the data array |
||||
* @param index the indices |
||||
*/ |
||||
public CompactFloatArray(float[] dataArray, int[] index) { |
||||
super(dataArray, index); |
||||
} |
||||
|
||||
@Override |
||||
protected final int getTupleSize() { |
||||
return 1; |
||||
} |
||||
|
||||
@Override |
||||
protected final Class<Float> getElementClass() { |
||||
return Float.class; |
||||
} |
||||
|
||||
@Override |
||||
public void write(JmeExporter ex) throws IOException { |
||||
serialize(); |
||||
OutputCapsule out = ex.getCapsule(this); |
||||
out.write(array, "array", null); |
||||
out.write(index, "index", null); |
||||
} |
||||
|
||||
@Override |
||||
public void read(JmeImporter im) throws IOException { |
||||
InputCapsule in = im.getCapsule(this); |
||||
array = in.readFloatArray("array", null); |
||||
index = in.readIntArray("index", null); |
||||
} |
||||
|
||||
public void fill(int startIndex, float[] store ){ |
||||
for (int i = 0; i < store.length; i++) { |
||||
store[i] = get(startIndex + i, null); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
protected void serialize(int i, Float data) { |
||||
array[i] = data; |
||||
} |
||||
|
||||
@Override |
||||
protected Float deserialize(int i, Float store) { |
||||
return array[i]; |
||||
} |
||||
} |
@ -0,0 +1,40 @@ |
||||
package com.jme3.scene.mesh; |
||||
|
||||
import com.jme3.export.JmeExporter; |
||||
import com.jme3.export.JmeImporter; |
||||
import com.jme3.export.Savable; |
||||
import com.jme3.scene.VertexBuffer; |
||||
|
||||
import java.io.IOException; |
||||
import java.nio.FloatBuffer; |
||||
import java.util.EnumMap; |
||||
|
||||
public class MorphTarget implements Savable { |
||||
private EnumMap<VertexBuffer.Type, FloatBuffer> buffers = new EnumMap<>(VertexBuffer.Type.class); |
||||
|
||||
public void setBuffer(VertexBuffer.Type type, FloatBuffer buffer) { |
||||
buffers.put(type, buffer); |
||||
} |
||||
|
||||
public FloatBuffer getBuffer(VertexBuffer.Type type) { |
||||
return buffers.get(type); |
||||
} |
||||
|
||||
public EnumMap<VertexBuffer.Type, FloatBuffer> getBuffers() { |
||||
return buffers; |
||||
} |
||||
|
||||
public int getNumBuffers() { |
||||
return buffers.size(); |
||||
} |
||||
|
||||
@Override |
||||
public void write(JmeExporter ex) throws IOException { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void read(JmeImporter im) throws IOException { |
||||
|
||||
} |
||||
} |
@ -0,0 +1,212 @@ |
||||
/** |
||||
A glsllib that perform morph animation. |
||||
Note that it only handles morphing position, normals and tangents. |
||||
*/ |
||||
#ifdef NUM_MORPH_TARGETS |
||||
vec3 dummy_norm = vec3(0.0); |
||||
vec3 dummy_tan = vec3(0.0); |
||||
#define NUM_BUFFERS NUM_MORPH_TARGETS * NUM_TARGETS_BUFFERS |
||||
#if (NUM_BUFFERS > 0) |
||||
uniform float m_MorphWeights[NUM_MORPH_TARGETS]; |
||||
attribute vec3 inMorphTarget0; |
||||
#endif |
||||
#if (NUM_BUFFERS > 1) |
||||
attribute vec3 inMorphTarget1; |
||||
#endif |
||||
#if (NUM_BUFFERS > 2) |
||||
attribute vec3 inMorphTarget2; |
||||
#endif |
||||
#if (NUM_BUFFERS > 3) |
||||
attribute vec3 inMorphTarget3; |
||||
#endif |
||||
#if (NUM_BUFFERS > 4) |
||||
attribute vec3 inMorphTarget4; |
||||
#endif |
||||
#if (NUM_BUFFERS > 5) |
||||
attribute vec3 inMorphTarget5; |
||||
#endif |
||||
#if (NUM_BUFFERS > 6) |
||||
attribute vec3 inMorphTarget6; |
||||
#endif |
||||
#if (NUM_BUFFERS > 7) |
||||
attribute vec3 inMorphTarget7; |
||||
#endif |
||||
#if (NUM_BUFFERS > 8) |
||||
attribute vec3 inMorphTarget8; |
||||
#endif |
||||
#if (NUM_BUFFERS > 9) |
||||
attribute vec3 inMorphTarget9; |
||||
#endif |
||||
#if (NUM_BUFFERS > 10) |
||||
attribute vec3 inMorphTarget10; |
||||
#endif |
||||
#if (NUM_BUFFERS > 11) |
||||
attribute vec3 inMorphTarget11; |
||||
#endif |
||||
#if (NUM_BUFFERS > 12) |
||||
attribute vec3 inMorphTarget12; |
||||
#endif |
||||
#if (NUM_BUFFERS > 13) |
||||
attribute vec3 inMorphTarget13; |
||||
#endif |
||||
|
||||
void Morph_Compute_Pos(inout vec4 pos){ |
||||
#if (NUM_TARGETS_BUFFERS == 1) |
||||
#if (NUM_MORPH_TARGETS > 0) |
||||
pos.xyz += m_MorphWeights[0] * inMorphTarget0; |
||||
#endif |
||||
#if (NUM_MORPH_TARGETS > 1) |
||||
pos.xyz += m_MorphWeights[1] * inMorphTarget1; |
||||
#endif |
||||
#if (NUM_MORPH_TARGETS > 2) |
||||
pos.xyz += m_MorphWeights[2] * inMorphTarget2; |
||||
#endif |
||||
#if (NUM_MORPH_TARGETS > 3) |
||||
pos.xyz += m_MorphWeights[3] * inMorphTarget3; |
||||
#endif |
||||
#if (NUM_MORPH_TARGETS > 4) |
||||
pos.xyz += m_MorphWeights[4] * inMorphTarget4; |
||||
#endif |
||||
#if (NUM_MORPH_TARGETS > 5) |
||||
pos.xyz += m_MorphWeights[5] * inMorphTarget5; |
||||
#endif |
||||
#if (NUM_MORPH_TARGETS > 6) |
||||
pos.xyz += m_MorphWeights[6] * inMorphTarget6; |
||||
#endif |
||||
#if (NUM_MORPH_TARGETS > 7) |
||||
pos.xyz += m_MorphWeights[7] * inMorphTarget7; |
||||
#endif |
||||
#if (NUM_MORPH_TARGETS > 8) |
||||
pos.xyz += m_MorphWeights[8] * inMorphTarget8; |
||||
#endif |
||||
#if (NUM_MORPH_TARGETS > 9) |
||||
pos.xyz += m_MorphWeights[9] * inMorphTarget9; |
||||
#endif |
||||
#if (NUM_MORPH_TARGETS > 10) |
||||
pos.xyz += m_MorphWeights[10] * inMorphTarget10; |
||||
#endif |
||||
#if (NUM_MORPH_TARGETS > 11) |
||||
pos.xyz += m_MorphWeights[11] * inMorphTarget11; |
||||
#endif |
||||
#if (NUM_MORPH_TARGETS > 12) |
||||
pos.xyz += m_MorphWeights[12] * inMorphTarget12; |
||||
#endif |
||||
#if (NUM_MORPH_TARGETS > 13) |
||||
pos.xyz += m_MorphWeights[13] * inMorphTarget13; |
||||
#endif |
||||
#endif |
||||
} |
||||
|
||||
float Get_Inverse_Weights_Sum(){ |
||||
float sum = 0; |
||||
for( int i = 0;i < NUM_MORPH_TARGETS; i++){ |
||||
sum += m_MorphWeights[i]; |
||||
} |
||||
return 1.0 / max(1.0, sum); |
||||
} |
||||
|
||||
void Morph_Compute_Pos_Norm(inout vec4 pos, inout vec3 norm){ |
||||
#if (NUM_TARGETS_BUFFERS == 2) |
||||
// weight sum may be over 1.0. It's totallyvalid for position |
||||
// but for normals. the weights needs to be normalized. |
||||
float invWeightsSum = Get_Inverse_Weights_Sum(); |
||||
#if (NUM_BUFFERS > 1) |
||||
pos.xyz += m_MorphWeights[0] * inMorphTarget0; |
||||
norm += m_MorphWeights[0] * invWeightsSum * inMorphTarget1; |
||||
#endif |
||||
#if (NUM_BUFFERS > 3) |
||||
pos.xyz += m_MorphWeights[1] * inMorphTarget2; |
||||
norm.xyz += m_MorphWeights[1] * invWeightsSum * inMorphTarget3; |
||||
#endif |
||||
#if (NUM_BUFFERS > 5) |
||||
pos.xyz += m_MorphWeights[2] * inMorphTarget4; |
||||
norm += m_MorphWeights[2] * invWeightsSum * inMorphTarget5; |
||||
#endif |
||||
#if (NUM_BUFFERS > 7) |
||||
pos.xyz += m_MorphWeights[3] * inMorphTarget6; |
||||
norm += m_MorphWeights[3] * invWeightsSum * inMorphTarget7; |
||||
#endif |
||||
#if (NUM_BUFFERS > 9) |
||||
pos.xyz += m_MorphWeights[4] * inMorphTarget8; |
||||
norm += m_MorphWeights[4] * invWeightsSum * inMorphTarget9; |
||||
#endif |
||||
#if (NUM_BUFFERS > 11) |
||||
pos.xyz += m_MorphWeights[5] * inMorphTarget10; |
||||
norm += m_MorphWeights[5] * invWeightsSum * inMorphTarget11; |
||||
#endif |
||||
#if (NUM_BUFFERS > 13) |
||||
pos.xyz += m_MorphWeights[6] * inMorphTarget12; |
||||
norm += m_MorphWeights[6] * invWeightsSum * inMorphTarget13; |
||||
#endif |
||||
#endif |
||||
} |
||||
|
||||
void Morph_Compute_Pos_Norm_Tan(inout vec4 pos, inout vec3 norm, inout vec3 tan){ |
||||
#if (NUM_TARGETS_BUFFERS == 3) |
||||
// weight sum may be over 1.0. It's totallyvalid for position |
||||
// but for normals. the weights needs to be normalized. |
||||
float invWeightsSum = Get_Inverse_Weights_Sum(); |
||||
#if (NUM_BUFFERS > 2) |
||||
float normWeight = m_MorphWeights[0] * invWeightsSum; |
||||
pos.xyz += m_MorphWeights[0] * inMorphTarget0; |
||||
norm += normWeight * inMorphTarget1; |
||||
tan += normWeight * inMorphTarget2; |
||||
#endif |
||||
#if (NUM_BUFFERS > 5) |
||||
float normWeight = m_MorphWeights[1] * invWeightsSum; |
||||
pos.xyz += m_MorphWeights[1] * inMorphTarget3; |
||||
norm += normWeight * inMorphTarget4; |
||||
tan += normWeight * inMorphTarget5; |
||||
#endif |
||||
#if (NUM_BUFFERS > 8) |
||||
float normWeight = m_MorphWeights[2] * invWeightsSum; |
||||
pos.xyz += m_MorphWeights[2] * inMorphTarget6; |
||||
norm += normWeight * inMorphTarget7; |
||||
tan += normWeight * inMorphTarget8; |
||||
#endif |
||||
#if (NUM_BUFFERS > 11) |
||||
float normWeight = m_MorphWeights[3] * invWeightsSum; |
||||
pos.xyz += m_MorphWeights[3] * inMorphTarget9; |
||||
norm += normWeight * inMorphTarget10; |
||||
tan += normWeight * inMorphTarget11; |
||||
#endif |
||||
#endif |
||||
} |
||||
|
||||
void Morph_Compute(inout vec4 pos){ |
||||
#if (NUM_TARGETS_BUFFERS == 2) |
||||
Morph_Compute_Pos_Norm(pos,dummy_norm); |
||||
return; |
||||
#elif (NUM_TARGETS_BUFFERS == 3) |
||||
Morph_Compute_Pos_Norm_Tan(pos, dummy_norm, dummy_tan); |
||||
return; |
||||
#endif |
||||
Morph_Compute_Pos(pos); |
||||
} |
||||
|
||||
void Morph_Compute(inout vec4 pos, inout vec3 norm){ |
||||
#if (NUM_TARGETS_BUFFERS == 1) |
||||
Morph_Compute_Pos(pos); |
||||
return; |
||||
#elif (NUM_TARGETS_BUFFERS == 3) |
||||
Morph_Compute_Pos_Norm_Tan(pos, dummy_norm, dummy_tan); |
||||
return; |
||||
#elif (NUM_TARGETS_BUFFERS == 2) |
||||
Morph_Compute_Pos_Norm(pos, norm); |
||||
#endif |
||||
} |
||||
|
||||
void Morph_Compute(inout vec4 pos, inout vec3 norm, inout vec3 tan){ |
||||
#if (NUM_TARGETS_BUFFERS == 1) |
||||
Morph_Compute_Pos(pos); |
||||
return; |
||||
#elif (NUM_TARGETS_BUFFERS == 2) |
||||
Morph_Compute_Pos_Norm(pos, norm); |
||||
tan = normalize(tan - dot(tan, norm) * norm); |
||||
return; |
||||
#elif (NUM_TARGETS_BUFFERS == 3) |
||||
Morph_Compute_Pos_Norm_Tan(pos, norm, tan); |
||||
#endif |
||||
} |
||||
|
||||
#endif |
@ -0,0 +1,121 @@ |
||||
package jme3test.model.anim; |
||||
|
||||
import com.jme3.app.ChaseCameraAppState; |
||||
import com.jme3.app.SimpleApplication; |
||||
import com.jme3.input.KeyInput; |
||||
import com.jme3.input.controls.ActionListener; |
||||
import com.jme3.input.controls.AnalogListener; |
||||
import com.jme3.input.controls.KeyTrigger; |
||||
import com.jme3.material.Material; |
||||
import com.jme3.math.ColorRGBA; |
||||
import com.jme3.scene.Geometry; |
||||
import com.jme3.scene.Node; |
||||
import com.jme3.scene.VertexBuffer; |
||||
import com.jme3.scene.mesh.MorphTarget; |
||||
import com.jme3.scene.shape.Box; |
||||
import com.jme3.shader.VarType; |
||||
import com.jme3.util.BufferUtils; |
||||
|
||||
import java.nio.FloatBuffer; |
||||
|
||||
public class TestMorph extends SimpleApplication { |
||||
|
||||
float[] weights = new float[2]; |
||||
|
||||
public static void main(String... args) { |
||||
TestMorph app = new TestMorph(); |
||||
app.start(); |
||||
} |
||||
|
||||
@Override |
||||
public void simpleInitApp() { |
||||
final Box box = new Box(1, 1, 1); |
||||
FloatBuffer buffer = BufferUtils.createVector3Buffer(box.getVertexCount()); |
||||
|
||||
float[] d = new float[box.getVertexCount() * 3]; |
||||
for (int i = 0; i < d.length; i++) { |
||||
d[i] = 0; |
||||
} |
||||
|
||||
d[12] = 1f; |
||||
d[15] = 1f; |
||||
d[18] = 1f; |
||||
d[21] = 1f; |
||||
|
||||
buffer.put(d); |
||||
buffer.rewind(); |
||||
|
||||
MorphTarget target = new MorphTarget(); |
||||
target.setBuffer(VertexBuffer.Type.Position, buffer); |
||||
box.addMorphTarget(target); |
||||
|
||||
|
||||
buffer = BufferUtils.createVector3Buffer(box.getVertexCount()); |
||||
|
||||
for (int i = 0; i < d.length; i++) { |
||||
d[i] = 0; |
||||
} |
||||
|
||||
d[13] = 1f; |
||||
d[16] = 1f; |
||||
d[19] = 1f; |
||||
d[22] = 1f; |
||||
|
||||
buffer.put(d); |
||||
buffer.rewind(); |
||||
|
||||
final MorphTarget target2 = new MorphTarget(); |
||||
target2.setBuffer(VertexBuffer.Type.Position, buffer); |
||||
box.addMorphTarget(target2); |
||||
|
||||
Geometry g = new Geometry("box", box); |
||||
final Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); |
||||
g.setMaterial(m); |
||||
m.setColor("Color", ColorRGBA.Red); |
||||
m.setInt("NumberOfMorphTargets", 2); |
||||
|
||||
rootNode.attachChild(g); |
||||
|
||||
box.setActiveMorphTargets(0,1); |
||||
m.setParam("MorphWeights", VarType.FloatArray, weights); |
||||
|
||||
ChaseCameraAppState chase = new ChaseCameraAppState(); |
||||
chase.setTarget(rootNode); |
||||
getStateManager().attach(chase); |
||||
flyCam.setEnabled(false); |
||||
|
||||
inputManager.addMapping("morphright", new KeyTrigger(KeyInput.KEY_I)); |
||||
inputManager.addMapping("morphleft", new KeyTrigger(KeyInput.KEY_Y)); |
||||
inputManager.addMapping("morphup", new KeyTrigger(KeyInput.KEY_U)); |
||||
inputManager.addMapping("morphdown", new KeyTrigger(KeyInput.KEY_J)); |
||||
inputManager.addMapping("change", new KeyTrigger(KeyInput.KEY_SPACE)); |
||||
inputManager.addListener(new AnalogListener() { |
||||
@Override |
||||
public void onAnalog(String name, float value, float tpf) { |
||||
if (name.equals("morphleft")) { |
||||
weights[0] -= tpf; |
||||
} |
||||
if (name.equals("morphright")) { |
||||
weights[0] += tpf; |
||||
} |
||||
if (name.equals("morphup")) { |
||||
weights[1] += tpf; |
||||
} |
||||
if (name.equals("morphdown")) { |
||||
weights[1] -= tpf; |
||||
} |
||||
m.setParam("MorphWeights", VarType.FloatArray, weights); |
||||
|
||||
} |
||||
}, "morphup", "morphdown", "morphleft", "morphright"); |
||||
|
||||
inputManager.addListener(new ActionListener() { |
||||
@Override |
||||
public void onAction(String name, boolean isPressed, float tpf) { |
||||
if (name.equals("change") && isPressed) { |
||||
box.setBuffer(VertexBuffer.Type.MorphTarget0, 3, target2.getBuffer(VertexBuffer.Type.Position)); |
||||
} |
||||
} |
||||
}, "change"); |
||||
} |
||||
} |
Loading…
Reference in new issue