Skip to content

Commit 42215f4

Browse files
committed
Hardware Morph animation implementation and glTF loading
1 parent d9a8666 commit 42215f4

31 files changed

Lines changed: 1442 additions & 128 deletions

jme3-core/src/main/java/com/jme3/anim/AnimClip.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class AnimClip implements JmeCloneable, Savable {
1616
private String name;
1717
private double length;
1818

19-
private TransformTrack[] tracks;
19+
private AnimTrack[] tracks;
2020

2121
public AnimClip() {
2222
}
@@ -25,9 +25,9 @@ public AnimClip(String name) {
2525
this.name = name;
2626
}
2727

28-
public void setTracks(TransformTrack[] tracks) {
28+
public void setTracks(AnimTrack[] tracks) {
2929
this.tracks = tracks;
30-
for (TransformTrack track : tracks) {
30+
for (AnimTrack track : tracks) {
3131
if (track.getLength() > length) {
3232
length = track.getLength();
3333
}
@@ -44,7 +44,7 @@ public double getLength() {
4444
}
4545

4646

47-
public TransformTrack[] getTracks() {
47+
public AnimTrack[] getTracks() {
4848
return tracks;
4949
}
5050

@@ -59,7 +59,7 @@ public Object jmeClone() {
5959

6060
@Override
6161
public void cloneFields(Cloner cloner, Object original) {
62-
TransformTrack[] newTracks = new TransformTrack[tracks.length];
62+
AnimTrack[] newTracks = new AnimTrack[tracks.length];
6363
for (int i = 0; i < tracks.length; i++) {
6464
newTracks[i] = (cloner.clone(tracks[i]));
6565
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.jme3.anim;
2+
3+
import com.jme3.export.Savable;
4+
import com.jme3.util.clone.JmeCloneable;
5+
6+
public interface AnimTrack<T> extends Savable, JmeCloneable {
7+
8+
public void getDataAtTime(double time, T store);
9+
public double getLength();
10+
11+
12+
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package com.jme3.anim;
2+
3+
import com.jme3.material.*;
4+
import com.jme3.renderer.*;
5+
import com.jme3.scene.Geometry;
6+
import com.jme3.scene.Mesh;
7+
import com.jme3.scene.SceneGraphVisitorAdapter;
8+
import com.jme3.scene.VertexBuffer;
9+
import com.jme3.scene.control.AbstractControl;
10+
import com.jme3.scene.mesh.MorphTarget;
11+
import com.jme3.shader.VarType;
12+
import com.jme3.util.SafeArrayList;
13+
14+
import java.nio.FloatBuffer;
15+
16+
/**
17+
* A control that handle morph animation for Position, Normal and Tangent buffers.
18+
* All stock shaders only support morphing these 3 buffers, but note that MorphTargets can have any type of buffers.
19+
* If you want to use other types of buffers you will need a custom MorphControl and a custom shader.
20+
* @author Rémy Bouquet
21+
*/
22+
public class MorphControl extends AbstractControl {
23+
24+
private static final int MAX_MORPH_BUFFERS = 14;
25+
private final static float MIN_WEIGHT = 0.005f;
26+
27+
private SafeArrayList<Geometry> targets = new SafeArrayList<>(Geometry.class);
28+
private TargetLocator targetLocator = new TargetLocator();
29+
30+
private boolean approximateTangents = true;
31+
private MatParamOverride nullNumberOfBones = new MatParamOverride(VarType.Int, "NumberOfBones", null);
32+
33+
@Override
34+
protected void controlUpdate(float tpf) {
35+
// gathering geometries in the sub graph.
36+
// This must be done in the update phase as the gathering might add a matparam override
37+
targets.clear();
38+
this.spatial.depthFirstTraversal(targetLocator);
39+
}
40+
41+
@Override
42+
protected void controlRender(RenderManager rm, ViewPort vp) {
43+
for (Geometry target : targets) {
44+
Mesh mesh = target.getMesh();
45+
if (!mesh.isDirtyMorph()) {
46+
continue;
47+
}
48+
int nbMaxBuffers = getRemainingBuffers(mesh, rm.getRenderer());
49+
Material m = target.getMaterial();
50+
51+
float weights[] = mesh.getMorphState();
52+
MorphTarget morphTargets[] = mesh.getMorphTargets();
53+
float matWeights[];
54+
MatParam param = m.getParam("MorphWeights");
55+
56+
//Number of buffer to handle for each morph target
57+
int targetNumBuffers = getTargetNumBuffers(morphTargets[0]);
58+
// compute the max number of targets to send to the GPU
59+
int maxGPUTargets = Math.min(nbMaxBuffers, MAX_MORPH_BUFFERS) / targetNumBuffers;
60+
if (param == null) {
61+
matWeights = new float[maxGPUTargets];
62+
m.setParam("MorphWeights", VarType.FloatArray, matWeights);
63+
} else {
64+
matWeights = (float[]) param.getValue();
65+
}
66+
67+
// setting the maximum number as the real number may change every frame and trigger a shader recompilation since it's bound to a define.
68+
m.setInt("NumberOfMorphTargets", maxGPUTargets);
69+
m.setInt("NumberOfTargetsBuffers", targetNumBuffers);
70+
71+
int nbGPUTargets = 0;
72+
int nbCPUBuffers = 0;
73+
int boundBufferIdx = 0;
74+
for (int i = 0; i < morphTargets.length; i++) {
75+
if (weights[i] < MIN_WEIGHT) {
76+
continue;
77+
}
78+
if (nbGPUTargets >= maxGPUTargets) {
79+
//TODO we should fallback to CPU there.
80+
nbCPUBuffers++;
81+
continue;
82+
}
83+
int start = VertexBuffer.Type.MorphTarget0.ordinal();
84+
MorphTarget t = morphTargets[i];
85+
if (targetNumBuffers >= 1) {
86+
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Position));
87+
boundBufferIdx++;
88+
}
89+
if (targetNumBuffers >= 2) {
90+
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Normal));
91+
boundBufferIdx++;
92+
}
93+
if (!approximateTangents && targetNumBuffers == 3) {
94+
activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Tangent));
95+
boundBufferIdx++;
96+
}
97+
matWeights[nbGPUTargets] = weights[i];
98+
nbGPUTargets++;
99+
100+
}
101+
if (nbGPUTargets < matWeights.length) {
102+
for (int i = nbGPUTargets; i < matWeights.length; i++) {
103+
matWeights[i] = 0;
104+
}
105+
}
106+
}
107+
}
108+
109+
private void activateBuffer(Mesh mesh, int idx, int start, FloatBuffer b) {
110+
mesh.setBuffer(VertexBuffer.Type.values()[start + idx], 3, b);
111+
}
112+
113+
private int getTargetNumBuffers(MorphTarget morphTarget) {
114+
int num = 0;
115+
if (morphTarget.getBuffer(VertexBuffer.Type.Position) != null) num++;
116+
if (morphTarget.getBuffer(VertexBuffer.Type.Normal) != null) num++;
117+
118+
// if tangents are not needed we don't count the tangent buffer
119+
if (!approximateTangents && morphTarget.getBuffer(VertexBuffer.Type.Tangent) != null) {
120+
num++;
121+
}
122+
return num;
123+
}
124+
125+
private int getRemainingBuffers(Mesh mesh, Renderer renderer) {
126+
int nbUsedBuffers = 0;
127+
for (VertexBuffer vb : mesh.getBufferList().getArray()) {
128+
boolean isMorphBuffer = vb.getBufferType().ordinal() >= VertexBuffer.Type.MorphTarget0.ordinal() && vb.getBufferType().ordinal() <= VertexBuffer.Type.MorphTarget9.ordinal();
129+
if (vb.getBufferType() == VertexBuffer.Type.Index || isMorphBuffer) continue;
130+
if (vb.getUsage() != VertexBuffer.Usage.CpuOnly) {
131+
nbUsedBuffers++;
132+
}
133+
}
134+
return renderer.getLimits().get(Limits.VertexAttributes) - nbUsedBuffers;
135+
}
136+
137+
public void setApproximateTangents(boolean approximateTangents) {
138+
this.approximateTangents = approximateTangents;
139+
}
140+
141+
public boolean isApproximateTangents() {
142+
return approximateTangents;
143+
}
144+
145+
private class TargetLocator extends SceneGraphVisitorAdapter {
146+
@Override
147+
public void visit(Geometry geom) {
148+
MatParam p = geom.getMaterial().getMaterialDef().getMaterialParam("MorphWeights");
149+
if (p == null) {
150+
return;
151+
}
152+
Mesh mesh = geom.getMesh();
153+
if (mesh != null && mesh.hasMorphTargets()) {
154+
targets.add(geom);
155+
// 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.
156+
// this code makes sure that if the mesh has no hardware skinning buffers hardware skinning won't be activated.
157+
// this is important, because if HW skinning is activated the shader will declare 2 additional useless attributes,
158+
// and we desperately need all the attributes we can find for Morph animation.
159+
if (mesh.getBuffer(VertexBuffer.Type.HWBoneIndex) == null && !geom.getLocalMatParamOverrides().contains(nullNumberOfBones)) {
160+
geom.addMatParamOverride(nullNumberOfBones);
161+
}
162+
}
163+
}
164+
}
165+
}

0 commit comments

Comments
 (0)