Hardware Morph animation implementation and glTF loading

monkanim
Rémy Bouquet 7 years ago committed by Nehon
parent 909716fa03
commit 346173e2be
  1. 10
      jme3-core/src/main/java/com/jme3/anim/AnimClip.java
  2. 12
      jme3-core/src/main/java/com/jme3/anim/AnimTrack.java
  3. 165
      jme3-core/src/main/java/com/jme3/anim/MorphControl.java
  4. 217
      jme3-core/src/main/java/com/jme3/anim/MorphTrack.java
  5. 13
      jme3-core/src/main/java/com/jme3/anim/TransformTrack.java
  6. 95
      jme3-core/src/main/java/com/jme3/anim/Weights.java
  7. 2
      jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolators.java
  8. 15
      jme3-core/src/main/java/com/jme3/anim/interpolator/FrameInterpolator.java
  9. 51
      jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java
  10. 6
      jme3-core/src/main/java/com/jme3/animation/CompactArray.java
  11. 100
      jme3-core/src/main/java/com/jme3/animation/CompactFloatArray.java
  12. 127
      jme3-core/src/main/java/com/jme3/scene/Mesh.java
  13. 66
      jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java
  14. 40
      jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java
  15. 17
      jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md
  16. 10
      jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert
  17. 17
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md
  18. 17
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert
  19. 10
      jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert
  20. 49
      jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md
  21. 6
      jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.vert
  22. 2
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/FixedScale100.frag
  23. 7
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert
  24. 8
      jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert
  25. 212
      jme3-core/src/main/resources/Common/ShaderLib/MorphAnim.glsllib
  26. 44
      jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java
  27. 4
      jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinning.java
  28. 121
      jme3-examples/src/main/java/jme3test/model/anim/TestMorph.java
  29. 112
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfLoader.java
  30. 2
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java
  31. 13
      jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TrackData.java

@ -16,7 +16,7 @@ public class AnimClip implements JmeCloneable, Savable {
private String name;
private double length;
private TransformTrack[] tracks;
private AnimTrack[] tracks;
public AnimClip() {
}
@ -25,9 +25,9 @@ public class AnimClip implements JmeCloneable, Savable {
this.name = name;
}
public void setTracks(TransformTrack[] tracks) {
public void setTracks(AnimTrack[] tracks) {
this.tracks = tracks;
for (TransformTrack track : tracks) {
for (AnimTrack track : tracks) {
if (track.getLength() > length) {
length = track.getLength();
}
@ -44,7 +44,7 @@ public class AnimClip implements JmeCloneable, Savable {
}
public TransformTrack[] getTracks() {
public AnimTrack[] getTracks() {
return tracks;
}
@ -59,7 +59,7 @@ public class AnimClip implements JmeCloneable, Savable {
@Override
public void cloneFields(Cloner cloner, Object original) {
TransformTrack[] newTracks = new TransformTrack[tracks.length];
AnimTrack[] newTracks = new AnimTrack[tracks.length];
for (int i = 0; i < tracks.length; i++) {
newTracks[i] = (cloner.clone(tracks[i]));
}

@ -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);
}
}

@ -48,7 +48,7 @@ import java.io.IOException;
*
* @author Rémy Bouquet
*/
public class TransformTrack implements JmeCloneable, Savable {
public class TransformTrack implements AnimTrack<Transform> {
private double length;
private HasLocalTransform target;
@ -81,15 +81,6 @@ public class TransformTrack implements JmeCloneable, Savable {
this.setKeyframes(times, translations, rotations, scales);
}
/**
* Creates a bone track for the given bone index
*
* @param targetJointIndex the bone's index
*/
public TransformTrack(int targetJointIndex) {
this();
}
/**
* return the array of rotations of this track
*
@ -223,7 +214,7 @@ public class TransformTrack implements JmeCloneable, Savable {
return length;
}
public void getTransformAtTime(double t, Transform transform) {
public void getDataAtTime(double t, Transform transform) {
float time = (float) t;
int lastFrame = times.length - 1;

@ -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);
}
}

@ -58,7 +58,6 @@ public class AnimInterpolators {
};
//Position / Scale interpolators
public static final AnimInterpolator<Vector3f> LinearVec3f = new AnimInterpolator<Vector3f>() {
private Vector3f next = new Vector3f();
@ -70,7 +69,6 @@ public class AnimInterpolators {
return store;
}
};
/**
* CatmullRom interpolation
*/

@ -20,6 +20,7 @@ public class FrameInterpolator {
private TrackDataReader<Vector3f> scaleReader = new TrackDataReader<>();
private TrackTimeReader timesReader = new TrackTimeReader();
private Transform transforms = new Transform();
public Transform interpolate(float t, int currentIndex, CompactVector3Array translations, CompactQuaternionArray rotations, CompactVector3Array scales, float[] times){
@ -42,6 +43,20 @@ public class FrameInterpolator {
return transforms;
}
public void interpolateWeights(float t, int currentIndex, float[] weights, int nbMorphTargets, float[] store) {
int start = currentIndex * nbMorphTargets;
for (int i = 0; i < nbMorphTargets; i++) {
int current = start + i;
int next = current + nbMorphTargets;
if (next >= weights.length) {
next = current;
}
float val = FastMath.interpolateLinear(t, weights[current], weights[next]);
store[i] = val;
}
}
public void setTimeInterpolator(AnimInterpolator<Float> timeInterpolator) {
this.timeInterpolator = timeInterpolator;
}

@ -1,10 +1,10 @@
package com.jme3.anim.tween.action;
import com.jme3.anim.AnimClip;
import com.jme3.anim.TransformTrack;
import com.jme3.anim.*;
import com.jme3.anim.tween.AbstractTween;
import com.jme3.anim.util.HasLocalTransform;
import com.jme3.math.Transform;
import com.jme3.scene.Geometry;
import java.util.ArrayList;
import java.util.Collection;
@ -22,20 +22,41 @@ public class ClipAction extends BlendableAction {
@Override
public void doInterpolate(double t) {
TransformTrack[] tracks = clip.getTracks();
for (TransformTrack track : tracks) {
HasLocalTransform target = track.getTarget();
transform.set(target.getLocalTransform());
track.getTransformAtTime(t, transform);
if (collectTransformDelegate != null) {
collectTransformDelegate.collectTransform(target, transform, getWeight(), this);
} else {
this.collectTransform(target, transform, getTransitionWeight(), this);
AnimTrack[] tracks = clip.getTracks();
for (AnimTrack track : tracks) {
if (track instanceof TransformTrack) {
interpolateTransformTrack(t, (TransformTrack) track);
} else if (track instanceof MorphTrack) {
interpolateMorphTrack(t, (MorphTrack) track);
}
}
}
private void interpolateTransformTrack(double t, TransformTrack track) {
HasLocalTransform target = track.getTarget();
transform.set(target.getLocalTransform());
track.getDataAtTime(t, transform);
if (collectTransformDelegate != null) {
collectTransformDelegate.collectTransform(target, transform, getWeight(), this);
} else {
this.collectTransform(target, transform, getTransitionWeight(), this);
}
}
private void interpolateMorphTrack(double t, MorphTrack track) {
Geometry target = track.getTarget();
float[] weights = new float[target.getMesh().getMorphTargets().length];
track.getDataAtTime(t, weights);
target.getMesh().setMorphState(weights);
// if (collectTransformDelegate != null) {
// collectTransformDelegate.collectTransform(target, transform, getWeight(), this);
// } else {
// this.collectTransform(target, transform, getTransitionWeight(), this);
// }
}
public void reset() {
}
@ -43,8 +64,10 @@ public class ClipAction extends BlendableAction {
@Override
public Collection<HasLocalTransform> getTargets() {
List<HasLocalTransform> targets = new ArrayList<>(clip.getTracks().length);
for (TransformTrack track : clip.getTracks()) {
targets.add(track.getTarget());
for (AnimTrack track : clip.getTracks()) {
if (track instanceof TransformTrack) {
targets.add(((TransformTrack) track).getTarget());
}
}
return targets;
}

@ -44,7 +44,7 @@ import java.util.Map;
*/
public abstract class CompactArray<T> implements JmeCloneable {
private Map<T, Integer> indexPool = new HashMap<T, Integer>();
protected Map<T, Integer> indexPool = new HashMap<T, Integer>();
protected int[] index;
protected float[] array;
private boolean invalid;
@ -114,6 +114,10 @@ public abstract class CompactArray<T> implements JmeCloneable {
indexPool.clear();
}
protected void setInvalid(boolean invalid) {
this.invalid = invalid;
}
/**
* @param index
* @param value

@ -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];
}
}

@ -50,6 +50,7 @@ import com.jme3.util.clone.JmeCloneable;
import java.io.IOException;
import java.nio.*;
import java.util.ArrayList;
import java.util.Arrays;
/**
* <code>Mesh</code> is used to store rendering data.
@ -164,8 +165,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
private CollisionData collisionTree = null;
private SafeArrayList<VertexBuffer> buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class);
private IntMap<VertexBuffer> buffers = new IntMap<VertexBuffer>();
private SafeArrayList<VertexBuffer> buffersList = new SafeArrayList<>(VertexBuffer.class);
private IntMap<VertexBuffer> buffers = new IntMap<>();
private VertexBuffer[] lodLevels;
private float pointSize = 1;
private float lineWidth = 1;
@ -183,6 +184,11 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
private Mode mode = Mode.Triangles;
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}.
*/
@ -203,7 +209,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
clone.meshBound = meshBound.clone();
clone.collisionTree = collisionTree != null ? collisionTree : null;
clone.buffers = buffers.clone();
clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class,buffersList);
clone.buffersList = new SafeArrayList<>(VertexBuffer.class, buffersList);
clone.vertexArrayID = -1;
if (elementLengths != null) {
clone.elementLengths = elementLengths.clone();
@ -233,8 +239,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
//clone.collisionTree = collisionTree != null ? collisionTree : null;
clone.collisionTree = null; // it will get re-generated in any case
clone.buffers = new IntMap<VertexBuffer>();
clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class);
clone.buffers = new IntMap<>();
clone.buffersList = new SafeArrayList<>(VertexBuffer.class);
for (VertexBuffer vb : buffersList.getArray()){
VertexBuffer bufClone = vb.clone();
clone.buffers.put(vb.getBufferType().ordinal(), bufClone);
@ -697,7 +703,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
*/
@Deprecated
public void setInterleaved(){
ArrayList<VertexBuffer> vbs = new ArrayList<VertexBuffer>();
ArrayList<VertexBuffer> vbs = new ArrayList<>();
vbs.addAll(buffersList);
// ArrayList<VertexBuffer> vbs = new ArrayList<VertexBuffer>(buffers.values());
@ -820,8 +826,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
* {@link #setInterleaved() interleaved} format.
*/
public void updateCounts(){
if (getBuffer(Type.InterleavedData) != null)
if (getBuffer(Type.InterleavedData) != null) {
throw new IllegalStateException("Should update counts before interleave");
}
VertexBuffer pb = getBuffer(Type.Position);
VertexBuffer ib = getBuffer(Type.Index);
@ -844,11 +851,13 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
*/
public int getTriangleCount(int lod){
if (lodLevels != null){
if (lod < 0)
if (lod < 0) {
throw new IllegalArgumentException("LOD level cannot be < 0");
}
if (lod >= lodLevels.length)
throw new IllegalArgumentException("LOD level "+lod+" does not exist!");
if (lod >= lodLevels.length) {
throw new IllegalArgumentException("LOD level " + lod + " does not exist!");
}
return computeNumElements(lodLevels[lod].getData().limit());
}else if (lod == 0){
@ -968,8 +977,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
* Sets the mesh's VAO ID. Internal use only.
*/
public void setId(int id){
if (vertexArrayID != -1)
if (vertexArrayID != -1) {
throw new IllegalStateException("ID has already been set.");
}
vertexArrayID = id;
}
@ -1037,8 +1047,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
* @throws IllegalArgumentException If the buffer type is already set
*/
public void setBuffer(VertexBuffer vb){
if (buffers.containsKey(vb.getBufferType().ordinal()))
throw new IllegalArgumentException("Buffer type already set: "+vb.getBufferType());
if (buffers.containsKey(vb.getBufferType().ordinal())) {
throw new IllegalArgumentException("Buffer type already set: " + vb.getBufferType());
}
buffers.put(vb.getBufferType().ordinal(), vb);
buffersList.add(vb);
@ -1151,8 +1162,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
*/
public FloatBuffer getFloatBuffer(Type type) {
VertexBuffer vb = getBuffer(type);
if (vb == null)
if (vb == null) {
return null;
}
return (FloatBuffer) vb.getData();
}
@ -1166,8 +1178,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
*/
public ShortBuffer getShortBuffer(Type type) {
VertexBuffer vb = getBuffer(type);
if (vb == null)
if (vb == null) {
return null;
}
return (ShortBuffer) vb.getData();
}
@ -1179,8 +1192,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
* @return A virtual or wrapped index buffer to read the data as a list
*/
public IndexBuffer getIndicesAsList(){
if (mode == Mode.Hybrid)
if (mode == Mode.Hybrid) {
throw new UnsupportedOperationException("Hybrid mode not supported");
}
IndexBuffer ib = getIndexBuffer();
if (ib != null){
@ -1209,8 +1223,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
*/
public IndexBuffer getIndexBuffer() {
VertexBuffer vb = getBuffer(Type.Index);
if (vb == null)
if (vb == null) {
return null;
}
return IndexBuffer.wrapIndexBuffer(vb.getData());
}
@ -1233,8 +1248,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
IndexBuffer indexBuf = getIndexBuffer();
int numIndices = indexBuf.size();
IntMap<Integer> oldIndicesToNewIndices = new IntMap<Integer>(numIndices);
ArrayList<Integer> newIndicesToOldIndices = new ArrayList<Integer>();
IntMap<Integer> oldIndicesToNewIndices = new IntMap<>(numIndices);
ArrayList<Integer> newIndicesToOldIndices = new ArrayList<>();
int newIndex = 0;
for (int i = 0; i < numIndices; i++) {
@ -1345,14 +1360,17 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
*/
public void scaleTextureCoordinates(Vector2f scaleFactor){
VertexBuffer tc = getBuffer(Type.TexCoord);
if (tc == null)
if (tc == null) {
throw new IllegalStateException("The mesh has no texture coordinates");
}
if (tc.getFormat() != VertexBuffer.Format.Float)
if (tc.getFormat() != VertexBuffer.Format.Float) {
throw new UnsupportedOperationException("Only float texture coord format is supported");
}
if (tc.getNumComponents() != 2)
if (tc.getNumComponents() != 2) {
throw new UnsupportedOperationException("Only 2D texture coords are supported");
}
FloatBuffer fb = (FloatBuffer) tc.getData();
fb.clear();
@ -1504,6 +1522,70 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
return patchVertexCount;
}
public void addMorphTarget(MorphTarget target) {
if (morphTargets == null) {
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);
}
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() {
return morphTargets.getArray();
}
public boolean hasMorphTargets() {
return morphTargets != null && !morphTargets.isEmpty();
}
public boolean isDirtyMorph() {
return dirtyMorph;
}
@Override
public void write(JmeExporter ex) throws IOException {
OutputCapsule out = ex.getCapsule(this);
@ -1550,6 +1632,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
out.write(lodLevels, "lodLevels", null);
}
@Override
public void read(JmeImporter im) throws IOException {
InputCapsule in = im.getCapsule(this);
meshBound = (BoundingVolume) in.readSavable("modelBound", null);

@ -212,7 +212,41 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
* Format should be {@link Format#Float} and number of components
* should be 16.
*/
InstanceData
InstanceData,
/**
* Morph animations targets.
* Supports up tp 8 morph target buffers at the same time
* Limited due to the limited number of attributes you can bind to a vertex shader usually 16
* <p>
* MorphTarget buffers are either POSITION, NORMAL or TANGENT buffers.
* So we can support up to
* 10 simultaneous POSITION targets
* 5 simultaneous POSITION and NORMAL targets
* 3 simultaneous POSTION, NORMAL and TANGENT targets.
* <p>
* Note that all buffers have 3 components (Vector3f) even the Tangent buffer that
* does not contain the w (handedness) component that will not be interpolated for morph animation.
* <p>
* Note that those buffers contain the difference between the base buffer (POSITION, NORMAL or TANGENT) and the target value
* So that you can interpolate with a MADD operation in the vertex shader
* position = weight * diffPosition + basePosition;
*/
MorphTarget0,
MorphTarget1,
MorphTarget2,
MorphTarget3,
MorphTarget4,
MorphTarget5,
MorphTarget6,
MorphTarget7,
MorphTarget8,
MorphTarget9,
MorphTarget10,
MorphTarget11,
MorphTarget12,
MorphTarget13,
}
/**
@ -241,7 +275,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
* Mesh data is <em>not</em> sent to GPU at all. It is only
* used by the CPU.
*/
CpuOnly;
CpuOnly
}
/**
@ -610,8 +644,9 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
return 0;
}
int elements = data.limit() / components;
if (format == Format.Half)
if (format == Format.Half) {
elements /= 2;
}
return elements;
}
@ -642,14 +677,17 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
* argument.
*/
public void setupData(Usage usage, int components, Format format, Buffer data){
if (id != -1)
if (id != -1) {
throw new UnsupportedOperationException("Data has already been sent. Cannot setupData again.");
}
if (usage == null || format == null || data == null)
if (usage == null || format == null || data == null) {
throw new IllegalArgumentException("None of the arguments can be null");
if (data.isReadOnly())
throw new IllegalArgumentException( "VertexBuffer data cannot be read-only." );
}
if (data.isReadOnly()) {
throw new IllegalArgumentException("VertexBuffer data cannot be read-only.");
}
if (bufType != Type.InstanceData) {
if (components < 1 || components > 4) {
@ -720,11 +758,13 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
* Converts single floating-point data to {@link Format#Half half} floating-point data.
*/
public void convertToHalf(){
if (id != -1)
if (id != -1) {
throw new UnsupportedOperationException("Data has already been sent.");
}
if (format != Format.Float)
if (format != Format.Float) {
throw new IllegalStateException("Format must be float!");
}
int numElements = data.limit() / components;
format = Format.Half;
@ -913,8 +953,9 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
* match.
*/
public void copyElements(int inIndex, VertexBuffer outVb, int outIndex, int len){
if (outVb.format != format || outVb.components != components)
if (outVb.format != format || outVb.components != components) {
throw new IllegalArgumentException("Buffer format mismatch. Cannot copy");
}
int inPos = inIndex * components;
int outPos = outIndex * components;
@ -981,8 +1022,9 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
* of elements with the given number of components in each element.
*/
public static Buffer createBuffer(Format format, int components, int numElements){
if (components < 1 || components > 4)
if (components < 1 || components > 4) {
throw new IllegalArgumentException("Num components must be between 1 and 4");
}
int total = numElements * components;

@ -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 {
}
}

@ -110,6 +110,11 @@ MaterialDef Phong Lighting {
// For hardware skinning
Int NumberOfBones
Matrix4Array BoneMatrices
// For Morph animation
FloatArray MorphWeights
Int NumberOfMorphTargets
Int NumberOfTargetsBuffers : 1
//For instancing
Boolean UseInstancing
@ -152,6 +157,8 @@ MaterialDef Phong Lighting {
SPHERE_MAP : EnvMapAsSphereMap
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
}
@ -191,6 +198,8 @@ MaterialDef Phong Lighting {
SPHERE_MAP : EnvMapAsSphereMap
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
}
@ -210,6 +219,8 @@ MaterialDef Phong Lighting {
DISCARD_ALPHA : AlphaDiscardThreshold
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
ForcedRenderState {
@ -247,6 +258,8 @@ MaterialDef Phong Lighting {
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
BACKFACE_SHADOWS: BackfaceShadows
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
ForcedRenderState {
@ -274,6 +287,8 @@ MaterialDef Phong Lighting {
DIFFUSEMAP_ALPHA : DiffuseMap
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
}
@ -296,6 +311,8 @@ MaterialDef Phong Lighting {
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
}

@ -2,6 +2,8 @@
#import "Common/ShaderLib/Instancing.glsllib"
#import "Common/ShaderLib/Skinning.glsllib"
#import "Common/ShaderLib/Lighting.glsllib"
#import "Common/ShaderLib/MorphAnim.glsllib"
#ifdef VERTEX_LIGHTING
#import "Common/ShaderLib/BlinnPhongLighting.glsllib"
#endif
@ -90,6 +92,14 @@ void main(){
vec3 modelSpaceTan = inTangent.xyz;
#endif
#ifdef NUM_MORPH_TARGETS
#if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
Morph_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);
#else
Morph_Compute(modelSpacePos, modelSpaceNorm);
#endif
#endif
#ifdef NUM_BONES
#ifndef VERTEX_LIGHTING
Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);

@ -110,6 +110,11 @@ MaterialDef PBR Lighting {
// For hardware skinning
Int NumberOfBones
Matrix4Array BoneMatrices
// For Morph animation
FloatArray MorphWeights
Int NumberOfMorphTargets
Int NumberOfTargetsBuffers : 1
//For instancing
Boolean UseInstancing
@ -158,6 +163,8 @@ MaterialDef PBR Lighting {
NORMAL_TYPE: NormalType
VERTEX_COLOR : UseVertexColor
AO_MAP: LightMapAsAOMap
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
}
@ -178,6 +185,8 @@ MaterialDef PBR Lighting {
DISCARD_ALPHA : AlphaDiscardThreshold
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
ForcedRenderState {
@ -215,6 +224,8 @@ MaterialDef PBR Lighting {
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
BACKFACE_SHADOWS: BackfaceShadows
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
ForcedRenderState {
@ -247,6 +258,8 @@ MaterialDef PBR Lighting {
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
BACKFACE_SHADOWS: BackfaceShadows
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
ForcedRenderState {
@ -272,6 +285,8 @@ MaterialDef PBR Lighting {
Defines {
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
}
@ -291,6 +306,8 @@ MaterialDef PBR Lighting {
NEED_TEXCOORD1
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
}

@ -1,10 +1,9 @@
#import "Common/ShaderLib/GLSLCompat.glsllib"
#import "Common/ShaderLib/Instancing.glsllib"
#import "Common/ShaderLib/Skinning.glsllib"
#import "Common/ShaderLib/MorphAnim.glsllib"
uniform vec4 m_BaseColor;
uniform vec4 g_AmbientLightColor;
varying vec2 texCoord;
@ -38,11 +37,19 @@ void main(){
vec3 modelSpaceTan = inTangent.xyz;
#endif
#ifdef NUM_MORPH_TARGETS
#if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
Morph_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);
#else
Morph_Compute(modelSpacePos, modelSpaceNorm);
#endif
#endif
#ifdef NUM_BONES
#if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);
Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);
#else
Skinning_Compute(modelSpacePos, modelSpaceNorm);
Skinning_Compute(modelSpacePos, modelSpaceNorm);
#endif
#endif
@ -64,4 +71,4 @@ void main(){
#ifdef VERTEX_COLOR
Color *= inColor;
#endif
}
}

@ -2,6 +2,8 @@
#import "Common/ShaderLib/Instancing.glsllib"
#import "Common/ShaderLib/Skinning.glsllib"
#import "Common/ShaderLib/Lighting.glsllib"
#import "Common/ShaderLib/MorphAnim.glsllib"
#ifdef VERTEX_LIGHTING
#import "Common/ShaderLib/BlinnPhongLighting.glsllib"
#endif
@ -84,6 +86,14 @@ void main(){
vec3 modelSpaceTan = inTangent.xyz;
#endif
#ifdef NUM_MORPH_TARGETS
#if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
Morph_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);
#else
Morph_Compute(modelSpacePos, modelSpaceNorm);
#endif
#endif
#ifdef NUM_BONES
#if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);

@ -20,6 +20,11 @@ MaterialDef Unshaded {
Int NumberOfBones
Matrix4Array BoneMatrices
// For Morph animation
FloatArray MorphWeights
Int NumberOfMorphTargets
Int NumberOfTargetsBuffers : 1
// Alpha threshold for fragment discarding
Float AlphaDiscardThreshold (AlphaTestFallOff)
@ -76,26 +81,30 @@ MaterialDef Unshaded {
HAS_COLOR : Color
NUM_BONES : NumberOfBones
DISCARD_ALPHA : AlphaDiscardThreshold
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
}
Technique PreNormalPass {
VertexShader GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.vert
FragmentShader GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.frag
WorldParameters {
WorldViewProjectionMatrix
WorldViewMatrix
NormalMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
}
VertexShader GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.vert
FragmentShader GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.frag
WorldParameters {
WorldViewProjectionMatrix
WorldViewMatrix
NormalMatrix
ViewProjectionMatrix
ViewMatrix
}
Defines {
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
}
Technique PreShadow {
@ -115,6 +124,8 @@ MaterialDef Unshaded {
DISCARD_ALPHA : AlphaDiscardThreshold
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
ForcedRenderState {
@ -150,8 +161,10 @@ MaterialDef Unshaded {
PSSM : Splits
POINTLIGHT : LightViewProjectionMatrix5
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
INSTANCING : UseInstancing
BACKFACE_SHADOWS: BackfaceShadows
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
ForcedRenderState {
@ -177,8 +190,10 @@ MaterialDef Unshaded {
HAS_GLOWMAP : GlowMap
HAS_GLOWCOLOR : GlowColor
NUM_BONES : NumberOfBones
INSTANCING : UseInstancing
INSTANCING : UseInstancing
HAS_POINTSIZE : PointSize
NUM_MORPH_TARGETS: NumberOfMorphTargets
NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
}
}
}

@ -1,6 +1,7 @@
#import "Common/ShaderLib/GLSLCompat.glsllib"
#import "Common/ShaderLib/Skinning.glsllib"
#import "Common/ShaderLib/Instancing.glsllib"
#import "Common/ShaderLib/MorphAnim.glsllib"
attribute vec3 inPosition;
@ -38,6 +39,11 @@ void main(){
#endif
vec4 modelSpacePos = vec4(inPosition, 1.0);
#ifdef NUM_MORPH_TARGETS
Morph_Compute(modelSpacePos);
#endif
#ifdef NUM_BONES
Skinning_Compute(modelSpacePos);
#endif

@ -1,5 +1,5 @@
void main(){
vec4 worldPos = worldMatrix * vec4(modelPosition, 1.0);
vec4 worldPos = worldMatrix * vec4(0.0, 0.0, 0.0, 1.0);
vec3 dir = worldPos.xyz - cameraPos;
float distance = dot(cameraDir, dir);
float m11 = projectionMatrix[1][1];

@ -49,10 +49,13 @@ const mat4 biasMat = mat4(0.5, 0.0, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.0);
void main(){
vec4 modelSpacePos = vec4(inPosition, 1.0);
#ifdef NUM_MORPH_TARGETS
Morph_Compute(modelSpacePos);
#endif
#ifdef NUM_BONES
Skinning_Compute(modelSpacePos);
#endif

@ -1,6 +1,8 @@
#import "Common/ShaderLib/GLSLCompat.glsllib"
#import "Common/ShaderLib/Instancing.glsllib"
#import "Common/ShaderLib/Skinning.glsllib"
#import "Common/ShaderLib/MorphAnim.glsllib"
attribute vec3 inPosition;
attribute vec2 inTexCoord;
@ -8,7 +10,11 @@ varying vec2 texCoord;
void main(){
vec4 modelSpacePos = vec4(inPosition, 1.0);
#ifdef NUM_MORPH_TARGETS
Morph_Compute(modelSpacePos, modelSpaceNorm);
#endif
#ifdef NUM_BONES
Skinning_Compute(modelSpacePos);
#endif

@ -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

@ -41,11 +41,12 @@ import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.math.*;
import com.jme3.renderer.Limits;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.*;
import com.jme3.scene.control.Control;
import com.jme3.scene.debug.custom.ArmatureDebugAppState;
import com.jme3.scene.plugins.gltf.GltfModelKey;
import com.jme3.shader.VarType;
import jme3test.model.anim.EraseTimer;
import java.util.*;
@ -58,9 +59,13 @@ public class TestGltfLoading extends SimpleApplication {
int assetIndex = 0;
boolean useAutoRotate = false;
private final static String indentString = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
int duration = 2;
int duration = 1;
boolean playAnim = true;
Geometry g;
int morphIndex = 0;
public static void main(String[] args) {
TestGltfLoading app = new TestGltfLoading();
app.start();
@ -73,10 +78,12 @@ public class TestGltfLoading extends SimpleApplication {
https://sketchfab.com/features/gltf
You have to copy them in Model/gltf folder in the test-data project.
*/
@Override
public void simpleInitApp() {
ArmatureDebugAppState armatureDebugappState = new ArmatureDebugAppState();
getStateManager().attach(armatureDebugappState);
setTimer(new EraseTimer());
String folder = System.getProperty("user.home");
assetManager.registerLocator(folder, FileLocator.class);
@ -110,7 +117,14 @@ public class TestGltfLoading extends SimpleApplication {
// PointLight pl1 = new PointLight(new Vector3f(-5.0f, -5.0f, -5.0f), ColorRGBA.White.mult(0.5f), 50);
// 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/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/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/morphCube/AnimatedMorphCube.gltf", new Vector3f(0, 0, 0), 1f);
// loadModel("Models/gltf/morph/SimpleMorph.gltf", new Vector3f(0, 0, 0), 0.1f);
//loadModel("Models/gltf/nier/scene.gltf", new Vector3f(0, -1.5f, 0), 0.01f);
//loadModel("Models/gltf/izzy/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
//loadModel("Models/gltf/darth/scene.gltf", new Vector3f(0, -1, 0), 0.01f);
@ -149,6 +163,8 @@ public class TestGltfLoading extends SimpleApplication {
probeNode.attachChild(assets.get(0));
// setMorphTarget(morphIndex);
ChaseCameraAppState chaseCam = new ChaseCameraAppState();
chaseCam.setTarget(probeNode);
getStateManager().attach(chaseCam);
@ -200,6 +216,15 @@ public class TestGltfLoading extends SimpleApplication {
dumpScene(rootNode, 0);
}
public void setMorphTarget(int index) {
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) {
T ctrl = s.getControl(controlClass);
if (ctrl != null) {
@ -291,18 +316,19 @@ public class TestGltfLoading extends SimpleApplication {
@Override
public void simpleUpdate(float tpf) {
if (!useAutoRotate) {
return;
}
time += tpf;
autoRotate.rotate(0, tpf * 0.5f, 0);
// autoRotate.rotate(0, tpf * 0.5f, 0);
if (time > duration) {
// morphIndex++;
// setMorphTarget(morphIndex);
assets.get(assetIndex).removeFromParent();
assetIndex = (assetIndex + 1) % assets.size();
if (assetIndex == 0) {
duration = 10;
}
// if (assetIndex == 0) {
// duration = 10;
// }
probeNode.attachChild(assets.get(assetIndex));
time = 0;
}

@ -52,9 +52,9 @@ public class TestHWSkinning extends SimpleApplication implements ActionListener{
// private AnimComposer composer;
private String[] animNames = {"Dodge", "Walk", "pull", "push"};
private final static int SIZE = 60;
private final static int SIZE = 40;
private boolean hwSkinningEnable = true;
private List<SkinningControl> skControls = new ArrayList<SkinningControl>();
private List<SkinningControl> skControls = new ArrayList<>();
private BitmapText hwsText;
public static void main(String[] args) {

@ -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");
}
}

@ -11,6 +11,7 @@ import com.jme3.renderer.Camera;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.*;
import com.jme3.scene.control.CameraControl;
import com.jme3.scene.mesh.MorphTarget;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.util.IntMap;
@ -19,6 +20,8 @@ import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
import javax.xml.bind.DatatypeConverter;
import java.io.*;
import java.nio.Buffer;
import java.nio.FloatBuffer;
import java.rmi.ServerError;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -128,8 +131,6 @@ public class GltfLoader implements AssetLoader {
rootNode = customContentManager.readExtensionAndExtras("root", docRoot, rootNode);
setupControls();
//Loading animations
if (animations != null) {
for (int i = 0; i < animations.size(); i++) {
@ -137,6 +138,8 @@ public class GltfLoader implements AssetLoader {
}
}
setupControls();
//only one scene let's not return the root.
if (rootNode.getChildren().size() == 1) {
rootNode = (Node) rootNode.getChild(0);
@ -389,7 +392,7 @@ public class GltfLoader implements AssetLoader {
//the buffers will be setup if ever used.
VertexBuffer weightsHW = new VertexBuffer(VertexBuffer.Type.HWBoneWeight);
VertexBuffer indicesHW = new VertexBuffer(VertexBuffer.Type.HWBoneIndex);
//setting usage to cpuOnly so that the buffer is not send empty to the GPU
//setting usage to cpuOnly so that the buffer is not sent empty to the GPU
indicesHW.setUsage(VertexBuffer.Usage.CpuOnly);
weightsHW.setUsage(VertexBuffer.Usage.CpuOnly);
mesh.setBuffer(weightsHW);
@ -397,6 +400,22 @@ public class GltfLoader implements AssetLoader {
mesh.generateBindPose();
}
JsonArray targets = meshObject.getAsJsonArray("targets");
if(targets != null){
for (JsonElement target : targets) {
MorphTarget morphTarget = new MorphTarget();
for (Map.Entry<String, JsonElement> entry : target.getAsJsonObject().entrySet()) {
String bufferType = entry.getKey();
VertexBuffer.Type type = getVertexBufferType(bufferType);
VertexBuffer vb = readAccessorData(entry.getValue().getAsInt(), new VertexBufferPopulator(type));
if (vb != null) {
morphTarget.setBuffer(type, (FloatBuffer)vb.getData());
}
}
mesh.addMorphTarget(morphTarget);
}
}
mesh = customContentManager.readExtensionAndExtras("primitive", meshObject, mesh);
Geometry geom = new Geometry(null, mesh);
@ -425,7 +444,6 @@ public class GltfLoader implements AssetLoader {
geomArray[index] = geom;
index++;
//TODO targets(morph anim...)
}
geomArray = customContentManager.readExtensionAndExtras("mesh", meshData, geomArray);
@ -721,6 +739,7 @@ public class GltfLoader implements AssetLoader {
//temp data storage of track data
TrackData[] tracks = new TrackData[nodes.size()];
boolean hasMorphTrack = false;
for (JsonElement channel : channels) {
@ -733,12 +752,12 @@ public class GltfLoader implements AssetLoader {
continue;
}
assertNotNull(targetPath, "No target path for channel");
if (targetPath.equals("weight")) {
//Morph animation, not implemented in JME, let's warn the user and skip the channel
logger.log(Level.WARNING, "Morph animation is not supported by JME yet, skipping animation");
continue;
}
//
// if (targetPath.equals("weights")) {
// //Morph animation, not implemented in JME, let's warn the user and skip the channel
// logger.log(Level.WARNING, "Morph animation is not supported by JME yet, skipping animation track");
// continue;
// }
TrackData trackData = tracks[targetNode];
if (trackData == null) {
@ -782,9 +801,15 @@ public class GltfLoader implements AssetLoader {
Quaternion[] rotations = readAccessorData(dataIndex, quaternionArrayPopulator);
trackData.rotations = rotations;
} else {
//TODO support weights
logger.log(Level.WARNING, "Morph animation is not supported");
continue;
trackData.timeArrays.add(new TrackData.TimeData(times, TrackData.Type.Morph));
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;
hasMorphTrack = true;
}
tracks[targetNode] = customContentManager.readExtensionAndExtras("channel", channel, trackData);
}
@ -795,7 +820,7 @@ public class GltfLoader implements AssetLoader {
List<Spatial> spatials = new ArrayList<>();
AnimClip anim = new AnimClip(name);
List<TransformTrack> ttracks = new ArrayList<>();
List<AnimTrack> aTracks = new ArrayList<>();
int skinIndex = -1;
List<Joint> usedJoints = new ArrayList<>();
@ -809,8 +834,22 @@ public class GltfLoader implements AssetLoader {
if (node instanceof Spatial) {
Spatial s = (Spatial) node;
spatials.add(s);
TransformTrack track = new TransformTrack(s, trackData.times, trackData.translations, trackData.rotations, trackData.scales);
ttracks.add(track);
if (trackData.rotations != null || trackData.translations != null || trackData.scales != null) {
TransformTrack track = new TransformTrack(s, trackData.times, trackData.translations, trackData.rotations, trackData.scales);
aTracks.add(track);
}
if( trackData.weights != null && s instanceof Geometry){
Geometry g = (Geometry)s;
int nbMorph = g.getMesh().getMorphTargets().length;
// for (int k = 0; k < trackData.weights.length; k++) {
// System.err.print(trackData.weights[k] + ",");
// if(k % nbMorph == 0 && k!=0){
// System.err.println(" ");
// }
// }
MorphTrack track = new MorphTrack(g, trackData.times, trackData.weights, nbMorph);
aTracks.add(track);
}
} else if (node instanceof JointWrapper) {
JointWrapper jw = (JointWrapper) node;
usedJoints.add(jw.joint);
@ -826,8 +865,7 @@ public class GltfLoader implements AssetLoader {
}
TransformTrack track = new TransformTrack(jw.joint, trackData.times, trackData.translations, trackData.rotations, trackData.scales);
ttracks.add(track);
aTracks.add(track);
}
}
@ -845,12 +883,12 @@ public class GltfLoader implements AssetLoader {
Quaternion[] rotations = new Quaternion[]{joint.getLocalRotation()};
Vector3f[] scales = new Vector3f[]{joint.getLocalScale()};
TransformTrack track = new TransformTrack(joint, times, translations, rotations, scales);
ttracks.add(track);
aTracks.add(track);
}
}
}
anim.setTracks(ttracks.toArray(new TransformTrack[ttracks.size()]));
anim.setTracks(aTracks.toArray(new AnimTrack[aTracks.size()]));
anim = customContentManager.readExtensionAndExtras("animations", animation, anim);
@ -862,11 +900,14 @@ public class GltfLoader implements AssetLoader {
if (!spatials.isEmpty()) {
if (skinIndex != -1) {
//there are some spatial tracks in this bone animation... or the other way around. Let's add the spatials in the skinnedSpatials.
//there are some spatial or moph tracks in this bone animation... or the other way around. Let's add the spatials in the skinnedSpatials.
SkinData skin = fetchFromCache("skins", skinIndex, SkinData.class);
List<Spatial> spat = skinnedSpatials.get(skin);
spat.addAll(spatials);
//the animControl will be added in the setupControls();
if (hasMorphTrack && skin.morphControl == null) {
skin.morphControl = new MorphControl();
}
} else {
//Spatial animation
Spatial spatial = null;
@ -882,6 +923,9 @@ public class GltfLoader implements AssetLoader {
spatial.addControl(composer);
}
composer.addAnimClip(anim);
if (hasMorphTrack && spatial.getControl(MorphControl.class) == null) {
spatial.addControl(new MorphControl());
}
}
}
}
@ -1049,6 +1093,9 @@ public class GltfLoader implements AssetLoader {
spatial.addControl(skinData.animComposer);
}
spatial.addControl(skinData.skinningControl);
if (skinData.morphControl != null) {
spatial.addControl(skinData.morphControl);
}
}
for (int i = 0; i < nodes.size(); i++) {
@ -1131,7 +1178,9 @@ public class GltfLoader implements AssetLoader {
private class SkinData {
SkinningControl skinningControl;
MorphControl morphControl;
AnimComposer animComposer;
Spatial spatial;
Spatial parent;
Transform rootBoneTransformOffset;
Joint[] joints;
@ -1221,6 +1270,27 @@ public class GltfLoader implements AssetLoader {
}
}
//
// private class FloaGridPopulator implements Populator<float[]> {
//
// @Override
// public float[][] populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset, boolean normalized) throws IOException {
//
// int numComponents = getNumberOfComponents(type);
// int dataSize = numComponents * count;
// float[] data = new float[dataSize];
//
// if (bufferViewIndex == null) {
// //no referenced buffer, specs says to pad the data with zeros.
// padBuffer(data, dataSize);
// } else {
// readBuffer(bufferViewIndex, byteOffset, count, data, numComponents, getVertexBufferFormat(componentType));
// }
//
// return data;
// }
//
// }
private class Vector3fArrayPopulator implements Populator<Vector3f[]> {

@ -486,6 +486,8 @@ public class GltfUtils {
mesh.setBuffer(VertexBuffer.Type.BoneIndex, 4, BufferUtils.createShortBuffer(jointsArray));
}
mesh.setBuffer(VertexBuffer.Type.BoneWeight, 4, BufferUtils.createFloatBuffer(weightsArray));
mesh.getBuffer(VertexBuffer.Type.BoneIndex).setUsage(VertexBuffer.Usage.CpuOnly);
mesh.getBuffer(VertexBuffer.Type.BoneWeight).setUsage(VertexBuffer.Usage.CpuOnly);
}
private static void populateFloatArray(float[] array, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException {

@ -10,7 +10,8 @@ public class TrackData {
public enum Type {
Translation,
Rotation,
Scale
Scale,
Morph
}
Float length;
@ -21,7 +22,6 @@ public class TrackData {
Vector3f[] translations;
Quaternion[] rotations;
Vector3f[] scales;
//not used for now
float[] weights;
public void update() {
@ -125,6 +125,13 @@ public class TrackData {
System.arraycopy(scales, 0, newScales, 1, scales.length);
scales = newScales;
}
if (weights != null) {
int nbMorph = weights.length / (times.length - 1);
float[] newWeights = new float[weights.length + nbMorph];
System.arraycopy(weights, 0, newWeights, 0, nbMorph);
System.arraycopy(weights, 0, newWeights, nbMorph, weights.length);
weights = newWeights;
}
}
checkTimesConsistantcy();
@ -336,4 +343,4 @@ public class TrackData {
Vector3f scale;
}
}
}

Loading…
Cancel
Save