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