jMonkeyEngine / jMonkeyEngine/jmonkeyengine
NullPointer on Geometry.setMorphState(String, float)
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 4.3k
- Forks
- 1.2k
- Avg merge
- 4d 7h
- Merged PRs (30d)
- 14
Description
(Created with help from AI)
Geometry.setMorphState(String, float) throws NPE on a geometry whose morph state was never set
Summary
Geometry allocates its morphState array lazily. setMorphState(float[]) and
getMorphState() both allocate it on demand, but the two name-based overloads
setMorphState(String, float) and getMorphState(String) index the field
directly. Calling either of them before one of the array-based methods throws:
java.lang.NullPointerException: Cannot store to float array because "this.morphState" is null
at com.jme3.scene.Geometry.setMorphState(Geometry.java:632)
Version
Reproduced on 3.8.1-stable, 3.9.0-stable and 3.10.0-beta1 (the newest
release on Maven Central), and the code is unchanged on current master
(65e413232).
Reproducer
No Application or render loop needed this is pure scene-graph state:
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.mesh.MorphTarget;
public class Repro {
public static void main(String[] args) {
Mesh mesh = new Mesh();
mesh.addMorphTarget(new MorphTarget("morph"));
Geometry geometry = new Geometry("body", mesh);
System.out.println("morph index = " + mesh.getMorphIndex("morph")); // 0
geometry.setMorphState("morph", 1f); // NPE
}
}
getMorphState(String) fails the same way, at return morphState[index];.
Cause
In jme3-core/src/main/java/com/jme3/scene/Geometry.java, the array-based setter
allocates:
public void setMorphState(float[] state) {
if (mesh == null || mesh.getMorphTargets().length == 0) {
return;
}
int nbMorphTargets = mesh.getMorphTargets().length;
if (morphState == null) {
morphState = new float[nbMorphTargets]; // <-- allocated here
}
System.arraycopy(state, 0, morphState, 0, morphState.length);
this.dirtyMorph = true;
}
…while the name-based one immediately below it does not:
public void setMorphState(String morphTarget, float state) {
int index = mesh.getMorphIndex(morphTarget);
if (index >= 0) {
morphState[index] = state; // <-- NPE if nothing allocated it yet
this.dirtyMorph = true;
}
}
getMorphState(String) has the same omission, even though the no-arg
getMorphState() right beside it allocates correctly.
Why it is easy to hit
Nothing else populates the array. MorphControl only ever reads it, and
GltfLoader attaches a MorphControl at all only when the asset carries an
animation channel targeting weights (its hasMorphTrack flag).
So an application that drives morph targets from its own code — expression
blending, a UI slider, any procedural deformation — has an unallocated array
exactly when it reaches for the name-based setter. And the name-based setter is
the natural choice there, because it is the only API that resolves a morph by
name; the float[] overload requires the caller to know target indices.
Working around it means calling setMorphState(new float[mesh.getMorphTargets().length])
once purely to force the allocation, which is not discoverable from the signature
or the javadoc.
Suggested fix
Route both name-based overloads through getMorphState(), which already
allocates on demand. The lookup guard is untouched, so an unknown name still
writes nothing and reads -1:
public void setMorphState(String morphTarget, float state) {
int index = mesh.getMorphIndex(morphTarget);
if (index >= 0) {
getMorphState()[index] = state;
this.dirtyMorph = true;
}
}
public float getMorphState(String morphTarget) {
int index = mesh.getMorphIndex(morphTarget);
if (index < 0) {
return -1;
} else {
return getMorphState()[index];
}
}
A PR follows with this change plus GeometryMorphStateTest, covering the setter,
the getter, and the unknown-name path. The tests fail on master and pass with
the fix.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in jme3-core/src/main/java/com/jme3/scene/Geometry.java and inspect the name-based morph-state accessors alongside the array-based methods. Run GeometryMorphStateTest, which covers the setter, getter, and unknown-name path. Done means name-based access works before array initialization, unknown names retain their existing behavior, and the tests pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- computer-graphics, game-dev
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 82/100