diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java b/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java index c290f01073..bc72f02fc5 100644 --- a/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java +++ b/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java @@ -456,6 +456,37 @@ public void setGlobalSpeed(float globalSpeed) { this.globalSpeed = globalSpeed; } + /** + * Access the manager of the named layer. + * + * @param layerName the name of the layer to access + * @return the current manager (typically an AnimEvent) or null for none + */ + public Object getLayerManager(String layerName) { + Layer layer = layers.get(layerName); + if (layer == null) { + throw new IllegalArgumentException("Unknown layer " + layerName); + } + + return layer.manager; + } + + /** + * Assign a manager to the named layer. + * + * @param layerName the name of the layer to modify + * @param manager the desired manager (typically an AnimEvent) or null for + * none + */ + public void setLayerManager(String layerName, Object manager) { + Layer layer = layers.get(layerName); + if (layer == null) { + throw new IllegalArgumentException("Unknown layer " + layerName); + } + + layer.manager = manager; + } + /** * Create a shallow clone for the JME cloner. * @@ -539,6 +570,7 @@ private static class Layer implements JmeCloneable { private Action currentAction; private AnimationMask mask; private double time; + private Object manager; public Layer(AnimComposer ac) { this.ac = ac; diff --git a/jme3-core/src/main/java/com/jme3/anim/Joint.java b/jme3-core/src/main/java/com/jme3/anim/Joint.java index 8a3dcdbe79..22ab81c119 100644 --- a/jme3-core/src/main/java/com/jme3/anim/Joint.java +++ b/jme3-core/src/main/java/com/jme3/anim/Joint.java @@ -167,7 +167,7 @@ private void updateAttachNode() { * have already been computed, otherwise this method will return undefined * results. * - * @param outTransform + * @param outTransform storage for the result (modified) */ void getOffsetTransform(Matrix4f outTransform) { jointModelTransform.getOffsetTransform(outTransform, inverseModelBindMatrix); @@ -358,6 +358,7 @@ public List getChildren() { * @param jointIndex this bone's index in its armature (≥0) * @param targets a list of geometries animated by this bone's skeleton (not * null, unaffected) + * @return the attachments node (not null) */ Node getAttachmentsNode(int jointIndex, SafeArrayList targets) { targetGeometry = null; diff --git a/jme3-core/src/main/java/com/jme3/anim/MorphControl.java b/jme3-core/src/main/java/com/jme3/anim/MorphControl.java index f6bda33e0b..7f87129edb 100644 --- a/jme3-core/src/main/java/com/jme3/anim/MorphControl.java +++ b/jme3-core/src/main/java/com/jme3/anim/MorphControl.java @@ -31,8 +31,10 @@ */ package com.jme3.anim; -import com.jme3.export.Savable; -import com.jme3.material.*; +import com.jme3.export.*; +import com.jme3.material.MatParam; +import com.jme3.material.MatParamOverride; +import com.jme3.material.Material; import com.jme3.renderer.*; import com.jme3.scene.*; import com.jme3.scene.control.AbstractControl; @@ -40,8 +42,10 @@ import com.jme3.shader.VarType; import com.jme3.util.BufferUtils; import com.jme3.util.SafeArrayList; - +import com.jme3.util.clone.Cloner; +import java.io.IOException; import java.nio.FloatBuffer; +import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; @@ -50,6 +54,9 @@ * 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. * + * Note that if morphed children are attached to or detached from the sub graph after the MorphControl is added to + * spatial, you must detach and attach the control again for the changes to get reflected. + * * @author Rémy Bouquet */ public class MorphControl extends AbstractControl implements Savable { @@ -59,11 +66,14 @@ public class MorphControl extends AbstractControl implements Savable { private static final int MAX_MORPH_BUFFERS = 14; private final static float MIN_WEIGHT = 0.005f; - final private SafeArrayList targets = new SafeArrayList<>(Geometry.class); - final private TargetLocator targetLocator = new TargetLocator(); + private static final String TAG_APPROXIMATE = "approximateTangents"; + private static final String TAG_TARGETS = "targets"; + + private SafeArrayList targets = new SafeArrayList<>(Geometry.class); + private TargetLocator targetLocator = new TargetLocator(); private boolean approximateTangents = true; - final private MatParamOverride nullNumberOfBones = new MatParamOverride(VarType.Int, "NumberOfBones", null); + private MatParamOverride nullNumberOfBones = new MatParamOverride(VarType.Int, "NumberOfBones", null); private float[] tmpPosArray; private float[] tmpNormArray; @@ -72,22 +82,31 @@ public class MorphControl extends AbstractControl implements Savable { private static final VertexBuffer.Type bufferTypes[] = VertexBuffer.Type.values(); @Override - protected void controlUpdate(float tpf) { - if (!enabled) { - return; + public void setSpatial(Spatial spatial) { + super.setSpatial(spatial); + + // Remove matparam override from the old targets + for (Geometry target : targets.getArray()) { + target.removeMatParamOverride(nullNumberOfBones); } + // gathering geometries in the sub graph. - // This must be done in the update phase as the gathering might add a matparam override + // This must not be done in the render phase as the gathering might add a matparam override + // which then will throw an IllegalStateException if done in the render phase. targets.clear(); - this.spatial.depthFirstTraversal(targetLocator); + if (spatial != null) { + spatial.depthFirstTraversal(targetLocator); + } + } + + @Override + protected void controlUpdate(float tpf) { + } @Override protected void controlRender(RenderManager rm, ViewPort vp) { - if (!enabled) { - return; - } - for (Geometry geom : targets) { + for (Geometry geom : targets.getArray()) { Mesh mesh = geom.getMesh(); if (!geom.isDirtyMorph()) { continue; @@ -373,6 +392,72 @@ public boolean isApproximateTangents() { return approximateTangents; } + /** + * Callback from {@link com.jme3.util.clone.Cloner} to convert this + * shallow-cloned Control into a deep-cloned one, using the specified Cloner + * and original to resolve copied fields. + * + * @param cloner the Cloner that's cloning this Control (not null, modified) + * @param original the instance from which this Control was shallow-cloned + * (not null, unaffected) + */ + @Override + public void cloneFields(Cloner cloner, Object original) { + super.cloneFields(cloner, original); + + targets = cloner.clone(targets); + targetLocator = new TargetLocator(); + nullNumberOfBones = cloner.clone(nullNumberOfBones); + tmpPosArray = null; + tmpNormArray = null; + tmpTanArray = null; + } + + /** + * Create a shallow clone for the JME cloner. + * + * @return a new instance + */ + @Override + public MorphControl jmeClone() { + try { + MorphControl clone = (MorphControl) super.clone(); + return clone; + } catch (CloneNotSupportedException exception) { + throw new RuntimeException(exception); + } + } + + /** + * De-serialize this Control from the specified importer, for example when + * loading from a J3O file. + * + * @param importer (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); + approximateTangents = capsule.readBoolean(TAG_APPROXIMATE, true); + targets.addAll(capsule.readSavableArrayList(TAG_TARGETS, null)); + } + + /** + * Serialize this Control to the specified exporter, for example when saving + * to a J3O file. + * + * @param exporter (not null) + * @throws IOException from the exporter + */ + @Override + public void write(JmeExporter exporter) throws IOException { + super.write(exporter); + OutputCapsule capsule = exporter.getCapsule(this); + capsule.write(approximateTangents, TAG_APPROXIMATE, true); + capsule.writeSavableArrayList(new ArrayList(targets), TAG_TARGETS, null); + } + private class TargetLocator extends SceneGraphVisitorAdapter { @Override public void visit(Geometry geom) { diff --git a/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java b/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java index 7df1b515a6..5a59820f19 100644 --- a/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java +++ b/jme3-core/src/main/java/com/jme3/anim/TransformTrack.java @@ -83,10 +83,10 @@ public TransformTrack(HasLocalTransform target, float[] times, Vector3f[] transl /** * return the array of rotations of this track * - * @return an array + * @return an array, or null if no rotations */ public Quaternion[] getRotations() { - return rotations.toObjectArray(); + return rotations == null ? null : rotations.toObjectArray(); } /** @@ -110,10 +110,10 @@ public float[] getTimes() { /** * returns the array of translations of this track * - * @return an array + * @return an array, or null if no translations */ public Vector3f[] getTranslations() { - return translations.toObjectArray(); + return translations == null ? null : translations.toObjectArray(); } diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/AnimEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/AnimEvent.java index 3018ec3f96..0a1b3bbac7 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/events/AnimEvent.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/AnimEvent.java @@ -46,7 +46,7 @@ import java.util.logging.Logger; /** - * An CinematicEvent that plays a canned animation action in an + * A CinematicEvent that plays a canned animation action in an * {@link com.jme3.anim.AnimComposer}. * * Inspired by Nehon's {@link AnimationEvent}. @@ -121,10 +121,14 @@ public void initEvent(Application app, Cinematic cinematic) { */ @Override public void onPause() { - logger.log(Level.SEVERE, ""); + logger.log(Level.INFO, "layer={0} action={1}", + new Object[]{layerName, actionName}); - Action eventAction = composer.action(actionName); - eventAction.setSpeed(0f); + Object layerManager = composer.getLayerManager(layerName); + if (layerManager == this) { + Action eventAction = composer.action(actionName); + eventAction.setSpeed(0f); + } } /** @@ -132,7 +136,8 @@ public void onPause() { */ @Override public void onPlay() { - logger.log(Level.INFO, ""); + logger.log(Level.INFO, "layer={0} action={1}", + new Object[]{layerName, actionName}); Action currentAction = composer.getCurrentAction(layerName); Action eventAction = composer.action(actionName); @@ -149,6 +154,7 @@ public void onPlay() { composer.setTime(layerName, 0.0); } eventAction.setSpeed(speed); + composer.setLayerManager(layerName, this); } /** @@ -156,8 +162,14 @@ public void onPlay() { */ @Override public void onStop() { - logger.log(Level.INFO, ""); - composer.removeCurrentAction(layerName); + logger.log(Level.INFO, "layer={0} action={1}", + new Object[]{layerName, actionName}); + + Object layerManager = composer.getLayerManager(layerName); + if (layerManager == this) { + composer.removeCurrentAction(layerName); + composer.setLayerManager(layerName, null); + } } /** @@ -212,7 +224,8 @@ public void setSpeed(float speed) { */ @Override public void setTime(float time) { - logger.log(Level.INFO, "time = {0}", time); + logger.log(Level.INFO, "layer={0} action={1} time={2}", + new Object[]{layerName, actionName, time}); super.setTime(time); Action currentAction = composer.getCurrentAction(layerName); diff --git a/jme3-core/src/main/java/com/jme3/light/SpotLight.java b/jme3-core/src/main/java/com/jme3/light/SpotLight.java index a3a982c60d..4719b62910 100644 --- a/jme3-core/src/main/java/com/jme3/light/SpotLight.java +++ b/jme3-core/src/main/java/com/jme3/light/SpotLight.java @@ -168,7 +168,7 @@ private void computeAngleParameters() { packedAngleCos = (int) (innerCos * 1000); //due to approximations, very close angles can give the same cos - //here we make sure outer cos is bellow inner cos. + //here we make sure outer cos is below inner cos. if (((int) packedAngleCos) == ((int) (outerAngleCos * 1000))) { outerAngleCos -= 0.001f; } diff --git a/jme3-core/src/main/java/com/jme3/math/Matrix4f.java b/jme3-core/src/main/java/com/jme3/math/Matrix4f.java index 1f8a0339f5..711c69a45b 100644 --- a/jme3-core/src/main/java/com/jme3/math/Matrix4f.java +++ b/jme3-core/src/main/java/com/jme3/math/Matrix4f.java @@ -34,27 +34,26 @@ import com.jme3.export.*; import com.jme3.util.BufferUtils; import com.jme3.util.TempVars; - import java.io.IOException; import java.nio.FloatBuffer; import java.util.logging.Logger; /** - * Matrix4f defines and maintains a 4x4 matrix in row major order. - * This matrix is intended for use in a translation and rotational capacity. - * It provides convenience methods for creating the matrix from a multitude - * of sources. + * Matrix4f represents a single-precision 4x4 matrix for use as a + * 3-D coordinate transform or perspective transform. It provides convenience + * methods for loading data from many sources. * - * Matrices are stored assuming column vectors on the right, with the translation - * in the rightmost column. Element numbering is row,column, so m03 is the zeroth - * row, third column, which is the "x" translation part. This means that the implicit - * storage order is column major. However, the get() and set() functions on float - * arrays default to row major order! + * The rightmost column (column 3) stores the translation vector. Element + * numbering is (row,column), so m03 is the row 0, column 3, which is the X + * translation. This means that the implicit storage order is column-major. + * However, the get() and set() functions on float arrays default to row-major + * order! * * @author Mark Powell * @author Joshua Slack */ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable { + static final long serialVersionUID = 1; private static final Logger logger = Logger.getLogger(Matrix4f.class.getName()); @@ -71,7 +70,7 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable */ public float m02; /** - * the element in row 0, column 3 + * the element in row 0, column 3 (the X translation) */ public float m03; /** @@ -87,7 +86,7 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable */ public float m12; /** - * the element in row 1, column 3 + * the element in row 1, column 3 (the Y translation) */ public float m13; /** @@ -103,7 +102,7 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable */ public float m22; /** - * the element in row 2, column 3 + * the element in row 2, column 3 (the Z translation) */ public float m23; /** @@ -115,7 +114,7 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable */ public float m31; /** - * the element in row 0, column 2 + * the element in row 3, column 2 */ public float m32; /** @@ -132,15 +131,15 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable public static final Matrix4f IDENTITY = new Matrix4f(); /** - * Constructor instantiates a new Matrix that is set to the - * identity matrix. + * Create a Matrix4f initialized to identity (diagonals = 1, + * other elements = 0). */ public Matrix4f() { loadIdentity(); } /** - * constructs a matrix with the given values. + * Create a Matrix4f with the specified element values. * * @param m00 the desired value for row 0, column 0 * @param m01 the desired value for row 0, column 1 @@ -183,34 +182,31 @@ public Matrix4f(float m00, float m01, float m02, float m03, } /** - * Create a new Matrix4f, given data in column-major format. + * Create a Matrix4f from the specified array. * - * @param array - * An array of 16 floats in column-major format (translation in elements 12, 13 and 14). + * @param array the source array: 16 floats in column-major order + * (translation in elements 12, 13, and 14) */ public Matrix4f(float[] array) { set(array, false); } /** - * Constructor instantiates a new Matrix that is set to the - * provided matrix. This constructor copies a given Matrix. If the provided - * matrix is null, the constructor sets the matrix to the identity. + * Create a Matrix4f that duplicates the specified matrix. If + * null is specified, the new matrix is initialized to identity (diagonals = + * 1, other elements = 0). * - * @param mat - * the matrix to copy. + * @param mat the source matrix (unaffected, may be null) */ public Matrix4f(Matrix4f mat) { copy(mat); } /** - * copy transfers the contents of a given matrix to this - * matrix. If a null matrix is supplied, this matrix is set to the identity - * matrix. + * Copy all elements of the specified matrix to this matrix. If null is + * specified, load identity (diagonals = 1, other elements = 0). * - * @param matrix - * the matrix to copy. + * @param matrix the source matrix (may be null, unaffected) */ public void copy(Matrix4f matrix) { if (null == matrix) { @@ -267,24 +263,20 @@ public void fromFrame(Vector3f location, Vector3f direction, Vector3f up, Vector } /** - * get retrieves the values of this object into - * a float array in row-major order. + * Copy all elements to a float array, in row-major order. * - * @param matrix - * the matrix to set the values into. + * @param matrix the destination array (not null, length=16) */ public void get(float[] matrix) { get(matrix, true); } /** - * set retrieves the values of this object into - * a float array. + * Copy all elements to a float array. * - * @param matrix - * the matrix to set the values into. - * @param rowMajor - * whether the outgoing data is in row or column major order. + * @param matrix the destination array (not null, length=16) + * @param rowMajor true to store in row-major order, false to store in + * column-major order */ public void get(float[] matrix, boolean rowMajor) { if (matrix.length != 16) { @@ -330,14 +322,12 @@ public void get(float[] matrix, boolean rowMajor) { } /** - * get retrieves a value from the matrix at the given - * position. + * Retrieve the element at the specified position. * - * @param i the row index. - * @param j the column index. - * @return the value at (i, j). - * @throws IllegalArgumentException - * if either index is invalid + * @param i the row index of the element to retrieve (0, 1, 2, or 3) + * @param j the column index of the element to retrieve (0, 1, 2, or 3) + * @return the value at (i, j) + * @throws IllegalArgumentException if either index is invalid. */ @SuppressWarnings("fallthrough") public float get(int i, int j) { @@ -393,25 +383,22 @@ public float get(int i, int j) { } /** - * getColumn returns one of three columns specified by the - * parameter. This column is returned as a float array of length 4. + * Copy the specified column to a new float array. * - * @param i the column to retrieve. Must be between 0 and 3. - * @return the column specified by the index. + * @param i the index of the column to copy (0, 1, 2, or 3) + * @return a new array with length=4 */ public float[] getColumn(int i) { return getColumn(i, null); } /** - * getColumn returns one of three columns specified by the - * parameter. This column is returned as a float[4]. + * Copy the specified column to a float array. * - * @param i the column to retrieve. Must be between 0 and 3. - * @param store - * the float array to store the result in. if null, a new one - * is created. - * @return the column specified by the index. + * @param i the index of the column to copy (0, 1, 2, or 3) + * @param store storage for the result (modified) or null to create a new + * array + * @return either store or a new array with length=4 */ public float[] getColumn(int i, float[] store) { if (store == null) { @@ -450,12 +437,10 @@ public float[] getColumn(int i, float[] store) { } /** - * setColumn sets a particular column of this matrix to that - * represented by the provided vector. + * Load the specified column from the specified array. * - * @param i the column to set. - * @param column - * the data to set. + * @param i the index of the column to fill (0, 1, 2, or 3) + * @param column the source array (unaffected) or null */ public void setColumn(int i, float[] column) { @@ -495,15 +480,12 @@ public void setColumn(int i, float[] column) { } /** - * set places a given value into the matrix at the given - * position. + * Store the specified value at the specified position. * - * @param i the row index. - * @param j the column index. - * @param value - * the value for (i, j). - * @throws IllegalArgumentException - * if either index is invalid + * @param i the row index of the element to set (0, 1, 2, or 3) + * @param j the column index of the element to set (0, 1, 2, or 3) + * @param value the value for element (i, j) + * @throws IllegalArgumentException if either index is invalid. */ @SuppressWarnings("fallthrough") public void set(int i, int j, float value) { @@ -575,13 +557,10 @@ public void set(int i, int j, float value) { } /** - * set sets the values of this matrix from an array of - * values. + * Load all elements from the specified 4x4 array. * - * @param matrix - * the matrix to set the value to. - * @throws IllegalArgumentException - * if the array isn't 4x4 + * @param matrix the source array (not null, unaffected) + * @throws IllegalArgumentException if the source array isn't 4x4. */ public void set(float[][] matrix) { if (matrix.length != 4 || matrix[0].length != 4) { @@ -608,7 +587,7 @@ public void set(float[][] matrix) { } /** - * Sets the values of this matrix + * Load the specified element values. * * @param m00 the desired value for row 0, column 0 * @param m01 the desired value for row 0, column 1 @@ -651,11 +630,10 @@ public void set(float m00, float m01, float m02, float m03, } /** - * set sets the values of this matrix from another matrix. + * Copy all elements of the specified matrix to this matrix. * - * @param matrix - * the matrix to read the value from. - * @return this + * @param matrix the source matrix (not null, unaffected) + * @return this (modified) */ public Matrix4f set(Matrix4f matrix) { m00 = matrix.m00; @@ -678,24 +656,21 @@ public Matrix4f set(Matrix4f matrix) { } /** - * set sets the values of this matrix from an array of - * values assuming that the data is rowMajor order; + * Load all elements from the specified array. * - * @param matrix - * the matrix to set the value to. + * @param matrix the source array, in row-major order (not null, length=16, + * unaffected) */ public void set(float[] matrix) { set(matrix, true); } /** - * set sets the values of this matrix from an array of - * values; + * Load all elements from the specified array. * - * @param matrix - * the matrix to set the value to. - * @param rowMajor - * whether the incoming data is in row or column major order. + * @param matrix the source array (not null, length=16, unaffected) + * @param rowMajor true if the source array is in row-major order, false if + * it's in column-major order */ public void set(float[] matrix, boolean rowMajor) { if (matrix.length != 16) { @@ -741,9 +716,9 @@ public void set(float[] matrix, boolean rowMajor) { } /** - * Generate the transpose of this matrix. + * Generate the transpose. * - * @return a new Matrix4f with its rows and columns transposed + * @return a new Matrix4f with the rows and columns transposed */ public Matrix4f transpose() { float[] tmp = new float[16]; @@ -753,9 +728,9 @@ public Matrix4f transpose() { } /** - * transpose locally transposes this Matrix. + * Transpose in place. * - * @return this object for chaining. + * @return this (transposed) */ public Matrix4f transposeLocal() { float tmp = m01; @@ -786,24 +761,21 @@ public Matrix4f transposeLocal() { } /** - * toFloatBuffer returns a FloatBuffer object that contains - * the matrix data. + * Copy all elements to a new, direct FloatBuffer. * - * @return matrix data as a FloatBuffer. + * @return a rewound buffer containing all 16 element values in row-major + * order */ public FloatBuffer toFloatBuffer() { return toFloatBuffer(false); } /** - * toFloatBuffer returns a FloatBuffer object that contains the - * matrix data. + * Copy all elements to a new, direct FloatBuffer. * - * @param columnMajor - * if true, this buffer should be filled with column major data, - * otherwise it will be filled row major. - * @return matrix data as a FloatBuffer. The position is set to 0 for - * convenience. + * @param columnMajor true to store in column-major order, false to store in + * row-major order + * @return a rewound buffer containing all 16 element values */ public FloatBuffer toFloatBuffer(boolean columnMajor) { FloatBuffer fb = BufferUtils.createFloatBuffer(16); @@ -813,28 +785,26 @@ public FloatBuffer toFloatBuffer(boolean columnMajor) { } /** - * fillFloatBuffer fills a FloatBuffer object with - * the matrix data. + * Copy all elements to an existing FloatBuffer, starting at its current + * position, in row-major order. * - * @param fb the buffer to fill, must be correct size - * @return matrix data as a FloatBuffer. + * @param fb the destination buffer (not null, must have space remaining for + * 16 floats) + * @return the destination buffer, its position advanced by 16 */ public FloatBuffer fillFloatBuffer(FloatBuffer fb) { return fillFloatBuffer(fb, false); } /** - * fillFloatBuffer fills a FloatBuffer object with the matrix - * data. + * Copy all elements to an existing FloatBuffer, starting at its current + * position. * - * @param fb - * the buffer to fill, starting at current position. Must have - * room for 16 more floats. - * @param columnMajor - * if true, this buffer should be filled with column major data, - * otherwise it will be filled row major. - * @return matrix data as a FloatBuffer. (position is advanced by 16 and any - * limit set is not changed). + * @param fb the destination buffer (not null, must have space remaining for + * 16 floats) + * @param columnMajor true to store in column-major order, false to store in + * row-major order + * @return the destination buffer, its position advanced by 16 */ public FloatBuffer fillFloatBuffer(FloatBuffer fb, boolean columnMajor) { // if (columnMajor) { @@ -860,11 +830,11 @@ public FloatBuffer fillFloatBuffer(FloatBuffer fb, boolean columnMajor) { } /** - * Copy the elements of this matrix to a float array. + * Copy all elements to a float array. * - * @param f the array to fill (not null, length≥16) - * @param columnMajor - * true → column-major order, false → row-major order + * @param f the destination array (not null, length≥16, modified) + * @param columnMajor true → column-major order, false → row-major + * order */ public void fillFloatArray(float[] f, boolean columnMajor) { if (columnMajor) { @@ -905,22 +875,22 @@ public void fillFloatArray(float[] f, boolean columnMajor) { } /** - * readFloatBuffer reads value for this matrix from a FloatBuffer. + * Load from the specified FloatBuffer, in row-major order. * - * @param fb the buffer to read from, must be correct size - * @return this data as a FloatBuffer. + * @param fb the source buffer, must have 16 floats remaining to get + * @return this (modified) */ public Matrix4f readFloatBuffer(FloatBuffer fb) { return readFloatBuffer(fb, false); } /** - * readFloatBuffer reads value for this matrix from a FloatBuffer. + * Load from the specified FloatBuffer. * - * @param fb the buffer to read from, must be correct size - * @param columnMajor if true, this buffer should be filled with column - * major data, otherwise it will be filled row major. - * @return this data as a FloatBuffer. + * @param fb the source buffer, must have 16 floats remaining to get + * @param columnMajor if true, the buffer contains column-major data, + * otherwise it contains row-major data. + * @return this (modified) */ public Matrix4f readFloatBuffer(FloatBuffer fb, boolean columnMajor) { @@ -963,8 +933,7 @@ public Matrix4f readFloatBuffer(FloatBuffer fb, boolean columnMajor) { } /** - * loadIdentity sets this matrix to the identity matrix, - * namely all zeros with ones along the diagonal. + * Load identity (diagonals = 1, other elements = 0). */ public void loadIdentity() { m01 = m02 = m03 = 0.0f; @@ -975,8 +944,7 @@ public void loadIdentity() { } /** - * Configure this matrix to represent a perspective-view frustrum or - * rectangular solid with the specified clipping planes. + * Load a perspective-view transform with the specified clipping planes. * * @param near the coordinate of the near plane * @param far the coordinate of the far plane @@ -1023,14 +991,13 @@ public void fromFrustum(float near, float far, float left, float right, } /** - * fromAngleAxis sets this matrix4f to the values specified - * by an angle and an axis of rotation. This method creates an object, so - * use fromAngleNormalAxis if your axis is already normalized. + * Load a 3-D rotation specified by an angle and axis. If the axis is + * already normalized, use + * {@link #fromAngleNormalAxis(float, com.jme3.math.Vector3f)} instead + * because it's more efficient. * - * @param angle - * the angle to rotate (in radians). - * @param axis - * the axis of rotation. + * @param angle the angle to rotate (in radians) + * @param axis the axis of rotation (not null) */ public void fromAngleAxis(float angle, Vector3f axis) { Vector3f normAxis = axis.normalize(); @@ -1038,13 +1005,11 @@ public void fromAngleAxis(float angle, Vector3f axis) { } /** - * fromAngleNormalAxis sets this matrix4f to the values - * specified by an angle and a normalized axis of rotation. + * Load a 3-D rotation specified by an angle and axis. Assumes the axis is + * already normalized. * - * @param angle - * the angle to rotate (in radians). - * @param axis - * the axis of rotation (already normalized). + * @param angle the angle to rotate (in radians) + * @param axis the axis of rotation (not null, already normalized) */ public void fromAngleNormalAxis(float angle, Vector3f axis) { zero(); @@ -1075,10 +1040,9 @@ public void fromAngleNormalAxis(float angle, Vector3f axis) { } /** - * mult multiplies this matrix by a scalar. + * Multiply in place, by the specified scalar. * - * @param scalar - * the scalar to multiply this matrix by. + * @param scalar the scale factor to apply to all elements */ public void multLocal(float scalar) { m00 *= scalar; @@ -1100,9 +1064,9 @@ public void multLocal(float scalar) { } /** - * Multiply this matrix by a scalar. + * Multiply by the specified scalar. * - * @param scalar the scaling factor + * @param scalar the scale factor to apply to all elements * @return a new Matrix4f with every element scaled */ public Matrix4f mult(float scalar) { @@ -1113,11 +1077,12 @@ public Matrix4f mult(float scalar) { } /** - * Multiply this matrix by a scalar. + * Multiply by the specified scalar. * - * @param scalar the scaling factor - * @param store storage for the result (not null, modified) - * @return a scaled matrix (store) + * @param scalar the scale factor to apply to all elements + * @param store storage for the result (modified) or null to create a new + * matrix + * @return a scaled matrix (either store or a new instance) */ public Matrix4f mult(float scalar, Matrix4f store) { store.set(this); @@ -1126,29 +1091,22 @@ public Matrix4f mult(float scalar, Matrix4f store) { } /** - * mult multiplies this matrix with another matrix. The - * result matrix will then be returned. This matrix will be on the left hand - * side, while the parameter matrix will be on the right. + * Right-multiply by the specified matrix. (This matrix is the left factor.) * - * @param in2 - * the matrix to multiply this matrix by. - * @return the resultant matrix + * @param in2 the right factor (not null, unaffected) + * @return the product, this times in2 (a new instance) */ public Matrix4f mult(Matrix4f in2) { return mult(in2, null); } /** - * mult multiplies this matrix with another matrix. The - * result matrix will then be returned. This matrix will be on the left hand - * side, while the parameter matrix will be on the right. + * Right-multiply by the specified matrix. (This matrix is the left factor.) * - * @param in2 - * the matrix to multiply this matrix by. - * @param store - * where to store the result. It is safe for in2 and store to be - * the same object. - * @return the resultant matrix + * @param in2 the right factor (not null) + * @param store storage for the result (modified) or null to create a new + * matrix. It is safe for in2 and store to be the same object. + * @return the product, this times in2 (either store or a new instance) */ public Matrix4f mult(Matrix4f in2, Matrix4f store) { if (store == null) { @@ -1247,40 +1205,33 @@ public Matrix4f mult(Matrix4f in2, Matrix4f store) { } /** - * mult multiplies this matrix with another matrix. The - * results are stored internally and a handle to this matrix will - * then be returned. This matrix will be on the left hand - * side, while the parameter matrix will be on the right. + * Right-multiply in place, by the specified matrix. (This matrix is the + * left factor.) * - * @param in2 - * the matrix to multiply this matrix by. - * @return the resultant matrix + * @param in2 the right factor (not null) + * @return this (modified) */ public Matrix4f multLocal(Matrix4f in2) { return mult(in2, this); } /** - * mult multiplies a vector about a rotation matrix. The - * resulting vector is returned as a new Vector3f. + * Apply this 3-D coordinate transform to the specified Vector3f. * - * @param vec - * vec to multiply against. - * @return the rotated vector. + * @param vec the vector to transform (not null) + * @return a new vector */ public Vector3f mult(Vector3f vec) { return mult(vec, null); } /** - * mult multiplies a vector about a rotation matrix and adds - * translation. The resulting vector is returned. + * Apply this 3-D coordinate transform to the specified Vector3f. * - * @param vec - * vec to multiply against. - * @param store - * a vector to store the result in. Created if null is passed. - * @return the rotated vector. + * @param vec the vector to transform (not null) + * @param store storage for the result (modified) or null to create a new + * vector + * @return the transformed vector (either store or a new vector) */ public Vector3f mult(Vector3f vec, Vector3f store) { if (store == null) { @@ -1296,26 +1247,22 @@ public Vector3f mult(Vector3f vec, Vector3f store) { } /** - * mult multiplies a Vector4f about a rotation - * matrix. The resulting vector is returned as a new Vector4f. + * Multiply the specified Vector4f by this matrix. * - * @param vec - * vec to multiply against. - * @return the rotated vector. + * @param vec the vector to multiply (unaffected) or null + * @return a new vector or null */ public Vector4f mult(Vector4f vec) { return mult(vec, null); } /** - * mult multiplies a Vector4f about a rotation - * matrix. The resulting vector is returned. + * Multiply the specified Vector4f by this matrix. * - * @param vec - * vec to multiply against. - * @param store - * a vector to store the result in. Created if null is passed. - * @return the rotated vector. + * @param vec the vector to multiply (unaffected) or null + * @param store storage for the result (modified) or null to create a new + * vector + * @return the product (either store or a new vector) or null */ public Vector4f mult(Vector4f vec, Vector4f store) { if (null == vec) { @@ -1336,26 +1283,22 @@ public Vector4f mult(Vector4f vec, Vector4f store) { } /** - * mult multiplies a vector about a rotation matrix. The - * resulting vector is returned. + * Multiply the specified Vector4f by the transform of this matrix. * - * @param vec - * vec to multiply against. - * @return the rotated vector. + * @param vec the vector to multiply (unaffected) or null + * @return a new vector or null */ public Vector4f multAcross(Vector4f vec) { return multAcross(vec, null); } /** - * mult multiplies a vector about a rotation matrix. The - * resulting vector is returned. + * Multiply the specified Vector4f by the transform of this matrix. * - * @param vec - * vec to multiply against. - * @param store - * a vector to store the result in. created if null is passed. - * @return the rotated vector. + * @param vec the vector to multiply (unaffected) or null + * @param store storage for the result (modified) or null to create a new + * vector + * @return the product (either store or a new vector) or null */ public Vector4f multAcross(Vector4f vec, Vector4f store) { if (null == vec) { @@ -1376,14 +1319,12 @@ public Vector4f multAcross(Vector4f vec, Vector4f store) { } /** - * multNormal multiplies a vector about a rotation matrix, but - * does not add translation. The resulting vector is returned. + * Rotate and scale the specified vector, but don't translate it. * - * @param vec - * vec to multiply against. - * @param store - * a vector to store the result in. Created if null is passed. - * @return the rotated vector. + * @param vec the vector to transform (not null, unaffected) + * @param store storage for the result (modified) or null to create a new + * vector + * @return the transformed vector (either store or a new vector) */ public Vector3f multNormal(Vector3f vec, Vector3f store) { if (store == null) { @@ -1399,14 +1340,13 @@ public Vector3f multNormal(Vector3f vec, Vector3f store) { } /** - * multNormal multiplies a vector about a rotation matrix, but - * does not add translation. The resulting vector is returned. + * Rotate and scale the specified vector by the transpose, but don't + * translate it. * - * @param vec - * vec to multiply against. - * @param store - * a vector to store the result in. Created if null is passed. - * @return the rotated vector. + * @param vec the vector to transform (not null, unaffected) + * @param store storage for the result (modified) or null to create a new + * vector + * @return the transformed vector (either store or a new vector) */ public Vector3f multNormalAcross(Vector3f vec, Vector3f store) { if (store == null) { @@ -1422,14 +1362,11 @@ public Vector3f multNormalAcross(Vector3f vec, Vector3f store) { } /** - * mult multiplies a vector about a rotation matrix and adds - * translation. The w value is returned as a result of - * multiplying the last column of the matrix by 1.0 + * Apply this perspective transform to the specified Vector3f. Return the W + * value, calculated by dotting the vector with the last row. * - * @param vec - * vec to multiply against. - * @param store - * a vector to store the result in. + * @param vec the vector to transform (not null, unaffected) + * @param store storage for the result (not null, modified) * @return the W value */ public float multProj(Vector3f vec, Vector3f store) { @@ -1441,14 +1378,13 @@ public float multProj(Vector3f vec, Vector3f store) { } /** - * mult multiplies a vector about a rotation matrix. The - * resulting vector is returned. + * Apply the transform of this 3-D coordinate transform to the specified + * Vector3f. * - * @param vec - * vec to multiply against. - * @param store - * a vector to store the result in. created if null is passed. - * @return the rotated vector. + * @param vec the vector to transform (unaffected) or null + * @param store storage for the result (modified) or null to create a new + * vector + * @return the transformed vector (either store or a new vector) or null */ public Vector3f multAcross(Vector3f vec, Vector3f store) { if (null == vec) { @@ -1468,14 +1404,12 @@ public Vector3f multAcross(Vector3f vec, Vector3f store) { } /** - * mult multiplies a quaternion about a matrix. The - * resulting vector is returned. + * Multiply the specified Quaternion by this matrix. * - * @param vec - * vec to multiply against. - * @param store - * a quaternion to store the result in. created if null is passed. - * @return store = this * vec + * @param vec the Quaternion to multiply (unaffected) or null + * @param store storage for the result (modified) or null to create a new + * Quaternion + * @return the product (either store or a new Quaternion) or null */ public Quaternion mult(Quaternion vec, Quaternion store) { @@ -1500,12 +1434,10 @@ public Quaternion mult(Quaternion vec, Quaternion store) { } /** - * mult multiplies an array of 4 floats against this rotation - * matrix. The results are stored directly in the array. (vec4f x mat4f) + * Multiply the specified float array by this matrix. * - * @param vec4f - * float array (size 4) to multiply against the matrix. - * @return the vec4f for chaining. + * @param vec4f the array to multiply or null + * @return vec4f (modified) or null */ public float[] mult(float[] vec4f) { if (null == vec4f || vec4f.length != 4) { @@ -1524,12 +1456,10 @@ public float[] mult(float[] vec4f) { } /** - * mult multiplies an array of 4 floats against this rotation - * matrix. The results are stored directly in the array. (vec4f x mat4f) + * Multiply the specified float array by the transform of this matrix. * - * @param vec4f - * float array (size 4) to multiply against the matrix. - * @return the vec4f for chaining. + * @param vec4f the array to multiply or null + * @return vec4f (modified) or null */ public float[] multAcross(float[] vec4f) { if (null == vec4f || vec4f.length != 4) { @@ -1548,19 +1478,21 @@ public float[] multAcross(float[] vec4f) { } /** - * Inverts this matrix as a new Matrix4f. + * Generate the inverse. * - * @return The new inverse matrix + * @return a new instance */ public Matrix4f invert() { return invert(null); } /** - * Inverts this matrix and stores it in the given store. + * Generate the inverse. * - * @param store storage for the result (modified if not null) - * @return The store + * @param store storage for the result (modified) or null to create a new + * matrix + * @return either store or a new instance + * @throws ArithmeticException if cannot be inverted */ public Matrix4f invert(Matrix4f store) { if (store == null) { @@ -1609,9 +1541,9 @@ public Matrix4f invert(Matrix4f store) { } /** - * Inverts this matrix locally. + * Invert in place. * - * @return this + * @return this matrix (inverted) */ public Matrix4f invertLocal() { @@ -1674,20 +1606,20 @@ public Matrix4f invertLocal() { } /** - * Returns a new matrix representing the adjoint of this matrix. + * Generate the adjoint. * - * @return The adjoint matrix + * @return a new instance */ public Matrix4f adjoint() { return adjoint(null); } /** - * Set this matrix to the specified 3-D coordinate transform. The - * effective sequence of operations is: scale, then rotate, then translate. + * Load with the specified coordinate transform. The effective sequence of + * operations is: scale, then rotate, then translate. * * @param position the desired translation (not null, unaffected) - * @param scale the desired scaling (not null, unaffected) + * @param scale the desired scale factors (not null, unaffected) * @param rotMat the desired rotation (not null, unaffected) */ public void setTransform(Vector3f position, Vector3f scale, Matrix3f rotMat) { @@ -1718,11 +1650,11 @@ public void setTransform(Vector3f position, Vector3f scale, Matrix3f rotMat) { } /** - * Places the adjoint of this matrix in store (creates store if null.) + * Generate the adjoint. * - * @param store - * The matrix to store the result in. If null, a new matrix is created. - * @return store + * @param store storage for the result (modified) or null to create a new + * matrix + * @return either store or a new instance */ public Matrix4f adjoint(Matrix4f store) { if (store == null) { @@ -1763,9 +1695,9 @@ public Matrix4f adjoint(Matrix4f store) { } /** - * determinant generates the determinate of this matrix. + * Calculate the determinant. * - * @return the determinate + * @return the determinant */ public float determinant() { float fA0 = m00 * m11 - m01 * m10; @@ -1785,9 +1717,9 @@ public float determinant() { } /** - * Sets all of the values in this matrix to zero. + * Set all elements to zero. * - * @return this matrix + * @return this (zeroed) */ public Matrix4f zero() { m00 = m01 = m02 = m03 = 0.0f; @@ -1798,10 +1730,10 @@ public Matrix4f zero() { } /** - * Calculate the sum of this matrix and another. + * Add the specified matrix. * - * @param mat the Matrix4f to add (not null, unaffected) - * @return a new Matrix4f + * @param mat the matrix to add (not null) + * @return the sum (a new instance) */ public Matrix4f add(Matrix4f mat) { Matrix4f result = new Matrix4f(); @@ -1825,10 +1757,9 @@ public Matrix4f add(Matrix4f mat) { } /** - * add adds the values of a parameter matrix to this matrix. + * Sum in place, with the specified matrix. * - * @param mat - * the matrix to add to this. + * @param mat the matrix to add (not null) */ public void addLocal(Matrix4f mat) { m00 += mat.m00; @@ -1850,8 +1781,7 @@ public void addLocal(Matrix4f mat) { } /** - * Interpret this matrix as a 3-D coordinate transform and determine its - * translation component. + * Determine the translation component of this 3-D coordinate transform. * * @return a new translation vector */ @@ -1860,19 +1790,17 @@ public Vector3f toTranslationVector() { } /** - * Interpret this matrix as a 3-D coordinate transform and determine its - * translation component. + * Determine the translation component of this 3-D coordinate transform. * * @param vector storage for the result (not null, modified) - * @return the translation vector (vector) + * @return vector */ public Vector3f toTranslationVector(Vector3f vector) { return vector.set(m03, m13, m23); } /** - * Interpret this matrix as a 3-D coordinate transform and determine its - * rotation component. + * Determine the rotation component of this 3-D coordinate transform. * * @return a new rotation Quaternion */ @@ -1883,11 +1811,10 @@ public Quaternion toRotationQuat() { } /** - * Interpret this matrix as a 3-D coordinate transform and determine its - * rotation component. + * Determine the rotation component of this 3-D coordinate transform. * * @param q storage for the result (not null, modified) - * @return the rotation Quaternion (q) + * @return q */ public Quaternion toRotationQuat(Quaternion q) { return q.fromRotationMatrix(m00, m01, m02, m10, @@ -1895,18 +1822,16 @@ public Quaternion toRotationQuat(Quaternion q) { } /** - * Interpret this matrix as a 3-D coordinate transform and determine its - * rotation component. + * Determine the rotation component of this 3-D coordinate transform. * - * @return a new rotation matrix + * @return a new rotation Matrix3f */ public Matrix3f toRotationMatrix() { return new Matrix3f(m00, m01, m02, m10, m11, m12, m20, m21, m22); } /** - * Interpret this matrix as a 3-D coordinate transform and determine its - * rotation component. + * Determine the rotation component of this 3-D coordinate transform. * * @param mat storage for the result (not null, modified) */ @@ -1923,9 +1848,9 @@ public void toRotationMatrix(Matrix3f mat) { } /** - * Retrieves the scale vector from the matrix. + * Determine the scale component of this 3-D coordinate transform. * - * @return the scale vector + * @return a new Vector3f */ public Vector3f toScaleVector() { Vector3f result = new Vector3f(); @@ -1934,11 +1859,10 @@ public Vector3f toScaleVector() { } /** - * Retrieves the scale vector from the matrix and stores it into a given - * vector. + * Determine the scale component of this 3-D coordinate transform. * - * @param store the vector where the scale will be stored - * @return the store vector + * @param store storage for the result (not null, modified) + * @return store */ public Vector3f toScaleVector(Vector3f store) { float scaleX = (float) Math.sqrt(m00 * m00 + m10 * m10 + m20 * m20); @@ -1949,11 +1873,11 @@ public Vector3f toScaleVector(Vector3f store) { } /** - * Sets the scale. + * Alter the scale component of this 3-D coordinate transform. * - * @param x the X scale - * @param y the Y scale - * @param z the Z scale + * @param x the desired scale factor for the X axis + * @param y the desired scale factor for the Y axis + * @param z the desired scale factor for the Z axis */ public void setScale(float x, float y, float z) { @@ -1983,22 +1907,20 @@ public void setScale(float x, float y, float z) { } /** - * Sets the scale. + * Alter the scale component of this 3-D coordinate transform. * - * @param scale - * the scale vector to set + * @param scale the desired scale factors (not null, unaffected) */ public void setScale(Vector3f scale) { this.setScale(scale.x, scale.y, scale.z); } /** - * setTranslation will set the matrix's translation values. + * Alter the translation component of this 3-D coordinate transform. * - * @param translation - * the new values for the translation. - * @throws IllegalArgumentException - * if translation is not size 3. + * @param translation the desired translation (not null, length=3, + * unaffected) + * @throws IllegalArgumentException if translation doesn't have length=3. */ public void setTranslation(float[] translation) { if (translation.length != 3) { @@ -2011,11 +1933,11 @@ public void setTranslation(float[] translation) { } /** - * setTranslation will set the matrix's translation values. + * Alter the translation component of this 3-D coordinate transform. * - * @param x value of the translation on the x axis - * @param y value of the translation on the y axis - * @param z value of the translation on the z axis + * @param x the desired X-axis offset + * @param y the desired Y-axis offset + * @param z the desired Z-axis offset */ public void setTranslation(float x, float y, float z) { m03 = x; @@ -2024,10 +1946,9 @@ public void setTranslation(float x, float y, float z) { } /** - * setTranslation will set the matrix's translation values. + * Alter the translation component of this 3-D coordinate transform. * - * @param translation - * the new values for the translation. + * @param translation the desired translation (not null, unaffected) */ public void setTranslation(Vector3f translation) { m03 = translation.x; @@ -2036,13 +1957,11 @@ public void setTranslation(Vector3f translation) { } /** - * setInverseTranslation will set the matrix's inverse - * translation values. + * Alter the inverse-translation component of this 3-D coordinate transform. * - * @param translation - * the new values for the inverse translation. - * @throws IllegalArgumentException - * if translation is not size 3. + * @param translation the desired inverse translation (not null, length=3, + * unaffected) + * @throws IllegalArgumentException if translation doesn't have length=3. */ public void setInverseTranslation(float[] translation) { if (translation.length != 3) { @@ -2055,13 +1974,11 @@ public void setInverseTranslation(float[] translation) { } /** - * angleRotation sets this matrix to that of a rotation about - * three axes (x, y, z). Where each axis has a specified rotation in - * degrees. These rotations are expressed in a single Vector3f - * object. + * Load a rotation around three axes (x, y, z). Where each axis has a + * specified rotation in degrees. These rotations are expressed in a single + * Vector3f object. * - * @param angles - * the angles to rotate. + * @param angles the desired rotation angles for each axis (in degrees) */ public void angleRotation(Vector3f angles) { float angle; @@ -2093,26 +2010,20 @@ public void angleRotation(Vector3f angles) { } /** - * setRotationQuaternion builds a rotation from a - * Quaternion. + * Load a rotation from a Quaternion. * - * @param quat - * the quaternion to build the rotation from. - * @throws NullPointerException - * if quat is null. + * @param quat the desired rotation (not null, unaffected) + * @throws NullPointerException if quat is null. */ public void setRotationQuaternion(Quaternion quat) { quat.toRotationMatrix(this); } /** - * setInverseRotationRadians builds an inverted rotation from - * Euler angles that are in radians. + * Load an inverted rotation from Euler angles in radians. * - * @param angles - * the Euler angles in radians. - * @throws IllegalArgumentException - * if angles is not size 3. + * @param angles the desired Euler angles (in radians, not null, length=3) + * @throws IllegalArgumentException if angles doesn't have length=3. */ public void setInverseRotationRadians(float[] angles) { if (angles.length != 3) { @@ -2143,13 +2054,10 @@ public void setInverseRotationRadians(float[] angles) { } /** - * setInverseRotationDegrees builds an inverted rotation from - * Euler angles that are in degrees. + * Load an inverted rotation from Euler angles in degrees. * - * @param angles - * the Euler angles in degrees. - * @throws IllegalArgumentException - * if angles is not size 3. + * @param angles the desired Euler angles (in degrees, not null, length=3) + * @throws IllegalArgumentException if angles doesn't have length=3. */ public void setInverseRotationDegrees(float[] angles) { if (angles.length != 3) { @@ -2164,13 +2072,11 @@ public void setInverseRotationDegrees(float[] angles) { } /** - * inverseTranslateVect translates a given Vector3f by the - * translation part of this matrix. + * Inverse translate the specified vector using the translation component of + * this 3-D coordinate transform. * - * @param vec - * the Vector3f data to be translated. - * @throws IllegalArgumentException - * if the size of the Vector3f is not 3. + * @param vec the vector to translate (not null, length=3, modified) + * @throws IllegalArgumentException if vec doesn't have length=3. */ public void inverseTranslateVect(float[] vec) { if (vec.length != 3) { @@ -2184,13 +2090,10 @@ public void inverseTranslateVect(float[] vec) { } /** - * inverseTranslateVect translates a given Vector3f by the - * translation part of this matrix. + * Inverse translate the specified Vector3f using the translation component + * of this 3-D coordinate transform. * - * @param data - * the Vector3f to be translated. - * @throws IllegalArgumentException - * if the size of the Vector3f is not 3. + * @param data the Vector3f to translate (not null, modified) */ public void inverseTranslateVect(Vector3f data) { data.x -= m03; @@ -2199,13 +2102,10 @@ public void inverseTranslateVect(Vector3f data) { } /** - * inverseTranslateVect translates a given Vector3f by the - * translation part of this matrix. + * Translate the specified Vector3f using the translation component of this + * 3-D coordinate transform. * - * @param data - * the Vector3f to be translated. - * @throws IllegalArgumentException - * if the size of the Vector3f is not 3. + * @param data the Vector3f to translate (not null, modified) */ public void translateVect(Vector3f data) { data.x += m03; @@ -2214,11 +2114,10 @@ public void translateVect(Vector3f data) { } /** - * inverseRotateVect rotates a given Vector3f by the rotation - * part of this matrix. + * Inverse rotate the specified Vector3f using the rotation component of + * this 3-D coordinate transform. * - * @param vec - * the Vector3f to be rotated. + * @param vec the Vector3f to inverse rotate (not null, modified) */ public void inverseRotateVect(Vector3f vec) { float vx = vec.x, vy = vec.y, vz = vec.z; @@ -2229,10 +2128,10 @@ public void inverseRotateVect(Vector3f vec) { } /** - * Interpret this matrix as a 3-D coordinate transform and apply its - * rotation component to the specified vector. + * Rotate the specified Vector3f using the rotation component of this 3-D + * coordinate transform. * - * @param vec the vector to rotate (not null, modified) + * @param vec the Vector3f to rotate (not null, modified) */ public void rotateVect(Vector3f vec) { float vx = vec.x, vy = vec.y, vz = vec.z; @@ -2243,8 +2142,7 @@ public void rotateVect(Vector3f vec) { } /** - * toString returns a string representation of this matrix. - * For example, an identity matrix would be represented by: + * Represent as a String. For example, identity would be represented by: *
      * Matrix4f
      * [
@@ -2334,10 +2232,10 @@ public int hashCode() {
     }
 
     /**
-     * are these two matrices the same? they are is they both have the same mXX values.
+     * Test for equality with the specified object.
      *
-     * @param o   the object to compare for equality
-     * @return true if they are equal
+     * @param o the object to compare, or null
+     * @return true if this and o are equal, otherwise false
      */
     @Override
     public boolean equals(Object o) {
@@ -2406,8 +2304,8 @@ public boolean equals(Object o) {
     }
 
     /**
-     * Serialize this matrix to the specified exporter, for example when
-     * saving to a J3O file.
+     * Serialize to the specified exporter, for example when saving to a J3O
+     * file.
      *
      * @param e (not null)
      * @throws IOException from the exporter
@@ -2434,8 +2332,8 @@ public void write(JmeExporter e) throws IOException {
     }
 
     /**
-     * De-serialize this matrix from the specified importer, for example
-     * when loading from a J3O file.
+     * De-serialize from the specified importer, for example when loading from a
+     * J3O file.
      *
      * @param e (not null)
      * @throws IOException from the importer
@@ -2462,7 +2360,9 @@ public void read(JmeImporter e) throws IOException {
     }
 
     /**
-     * @return true if this matrix is identity
+     * Test for exact identity.
+     *
+     * @return true if this is an exact identity, otherwise false
      */
     public boolean isIdentity() {
         return (m00 == 1 && m01 == 0 && m02 == 0 && m03 == 0)
@@ -2472,10 +2372,9 @@ public boolean isIdentity() {
     }
 
     /**
-     * Apply a scale to this matrix.
+     * Scale by the specified Vector3f.
      *
-     * @param scale
-     *            the scale to apply
+     * @param scale the scale factors to apply
      */
     public void scale(Vector3f scale) {
         m00 *= scale.getX();
@@ -2559,9 +2458,9 @@ public void multLocal(Quaternion rotation) {
     }
 
     /**
-     * Create a copy of this matrix.
+     * Create a copy.
      *
-     * @return a new instance, equivalent to this one
+     * @return a new instance with the same element values
      */
     @Override
     public Matrix4f clone() {
diff --git a/jme3-core/src/main/java/com/jme3/profile/AppProfiler.java b/jme3-core/src/main/java/com/jme3/profile/AppProfiler.java
index 35106a4004..e3efc8e3af 100644
--- a/jme3-core/src/main/java/com/jme3/profile/AppProfiler.java
+++ b/jme3-core/src/main/java/com/jme3/profile/AppProfiler.java
@@ -39,7 +39,7 @@
 /**
  *  Can be hooked into the application (and render manager)
  *  to receive callbacks about specific frame steps.  It is up
- *  to the specific implememtation to decide what to do with
+ *  to the specific implementation to decide what to do with
  *  the information.
  *
  *  @author    Paul Speed
diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
index 915629c754..3d2bd1c15a 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
@@ -2852,7 +2852,8 @@ public void clearVertexAttribs() {
         for (int i = 0; i < attribList.oldLen; i++) {
             int idx = attribList.oldList[i];
             gl.glDisableVertexAttribArray(idx);
-            if (context.boundAttribs[idx].get().isInstanced()) {
+            VertexBuffer buffer = context.boundAttribs[idx].get();
+            if (buffer != null && buffer.isInstanced()) {
                 glext.glVertexAttribDivisorARB(idx, 0);
             }
             context.boundAttribs[idx] = null;
diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java
index 7086eb0455..d71c18e38d 100644
--- a/jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java
+++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2019 jMonkeyEngine
+ * Copyright (c) 2009-2021 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -91,12 +91,12 @@ public GLImageFormat getImageFormat(Format fmt, boolean isSrgb) {
     }
 
     public GLImageFormat getImageFormatWithError(Format fmt, boolean isSrgb) {
-        //if the passed format is one kind of depth there isno point in getting the srgb format;
+        //if the passed format is one kind of depth there is no point in getting the srgb format;
         isSrgb = isSrgb && !fmt.isDepthFormat();
         GLImageFormat glFmt = getImageFormat(fmt, isSrgb);
         if (glFmt == null && isSrgb) {
             glFmt = getImageFormat(fmt, false);               
-            logger.log(Level.WARNING, "No sRGB format available for ''{0}''. Failling back to linear.", fmt);
+            logger.log(Level.WARNING, "No sRGB format available for ''{0}''. Falling back to linear.", fmt);
         }
         if (glFmt == null) { 
             throw new RendererException("Image format '" + fmt + "' is unsupported by the video hardware.");
diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugAppState.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugAppState.java
index 51489a2bf5..6b14b49811 100644
--- a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugAppState.java
+++ b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugAppState.java
@@ -40,6 +40,7 @@
 import com.jme3.input.controls.*;
 import com.jme3.light.DirectionalLight;
 import com.jme3.math.*;
+import com.jme3.renderer.Camera;
 import com.jme3.renderer.ViewPort;
 import com.jme3.scene.*;
 
@@ -158,8 +159,11 @@ public void onAction(String name, boolean isPressed, float tpf) {
                 Vector2f click2d = app.getInputManager().getCursorPosition();
                 CollisionResults results = new CollisionResults();
 
-                Vector3f click3d = app.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 0f, tmp);
-                Vector3f dir = app.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 1f, tmp2).subtractLocal(click3d);
+                Camera camera = app.getCamera();
+                Vector3f click3d = camera.getWorldCoordinates(click2d, 0f, tmp);
+                Vector3f dir = camera.getWorldCoordinates(click2d, 1f, tmp2)
+                        .subtractLocal(click3d)
+                        .normalizeLocal();
                 Ray ray = new Ray(click3d, dir);
                 debugNode.collideWith(ray, results);
 
diff --git a/jme3-core/src/main/java/com/jme3/shader/Uniform.java b/jme3-core/src/main/java/com/jme3/shader/Uniform.java
index 30d5d72bf1..5f3fa4148f 100644
--- a/jme3-core/src/main/java/com/jme3/shader/Uniform.java
+++ b/jme3-core/src/main/java/com/jme3/shader/Uniform.java
@@ -251,6 +251,7 @@ public void setValue(VarType type, Object value){
                     this.value = BufferUtils.createIntBuffer(ia);
                 } else {
                     this.value = BufferUtils.ensureLargeEnough((IntBuffer)this.value, ia.length);
+                    ((IntBuffer)this.value).put(ia);
                 }
                 ((IntBuffer)this.value).clear();
                 break;
@@ -260,8 +261,8 @@ public void setValue(VarType type, Object value){
                     multiData = BufferUtils.createFloatBuffer(fa);
                 } else {
                     multiData = BufferUtils.ensureLargeEnough(multiData, fa.length);
+                    multiData.put(fa);
                 }
-                multiData.put(fa);
                 multiData.clear();
                 break;
             case Vector2Array:
@@ -270,9 +271,9 @@ public void setValue(VarType type, Object value){
                     multiData = BufferUtils.createFloatBuffer(v2a);
                 } else {
                     multiData = BufferUtils.ensureLargeEnough(multiData, v2a.length * 2);
-                }
-                for (int i = 0; i < v2a.length; i++) {
-                    BufferUtils.setInBuffer(v2a[i], multiData, i);
+                    for (int i = 0; i < v2a.length; i++) {
+                        BufferUtils.setInBuffer(v2a[i], multiData, i);
+                    }
                 }
                 multiData.clear();
                 break;
@@ -282,9 +283,9 @@ public void setValue(VarType type, Object value){
                     multiData = BufferUtils.createFloatBuffer(v3a);
                 } else {
                     multiData = BufferUtils.ensureLargeEnough(multiData, v3a.length * 3);
-                }
-                for (int i = 0; i < v3a.length; i++) {
-                    BufferUtils.setInBuffer(v3a[i], multiData, i);
+                    for (int i = 0; i < v3a.length; i++) {
+                        BufferUtils.setInBuffer(v3a[i], multiData, i);
+                    }
                 }
                 multiData.clear();
                 break;
@@ -294,9 +295,9 @@ public void setValue(VarType type, Object value){
                     multiData = BufferUtils.createFloatBuffer(v4a);
                 } else {
                     multiData = BufferUtils.ensureLargeEnough(multiData, v4a.length * 4);
-                }
-                for (int i = 0; i < v4a.length; i++) {
-                    BufferUtils.setInBuffer(v4a[i], multiData, i);
+                    for (int i = 0; i < v4a.length; i++) {
+                        BufferUtils.setInBuffer(v4a[i], multiData, i);
+                    }
                 }
                 multiData.clear();
                 break;
diff --git a/jme3-core/src/main/java/com/jme3/util/ListSort.java b/jme3-core/src/main/java/com/jme3/util/ListSort.java
index ba882c2051..9abff4ddae 100644
--- a/jme3-core/src/main/java/com/jme3/util/ListSort.java
+++ b/jme3-core/src/main/java/com/jme3/util/ListSort.java
@@ -200,7 +200,7 @@ public void sort(T[] array, Comparator comparator) {
         int remaining = high - low;
 
         /*
-         * If array's size is bellow min_size we perform a binary insertion sort 
+         * If array's size is below min_size we perform a binary insertion sort 
          * but first we check if some existing ordered pattern exists to reduce 
          * the size of data to be sorted
          */
@@ -219,7 +219,7 @@ public void sort(T[] array, Comparator comparator) {
         while (remaining != 0) {
             int runLength = getRunLength(array, low, high, comparator);
 
-            /* if runlength is bellow the threshold we binary sort the remaining 
+            /* if runlength is below the threshold we binary sort the remaining 
              * elements
              */
             if (runLength < minLength) {
diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/colorToGrey.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/colorToGrey.frag
index d50d75d41f..2456e674c6 100644
--- a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/colorToGrey.frag
+++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/colorToGrey.frag
@@ -1,3 +1,4 @@
 void main(){
-    grey = color.r + color.g + color.b;
+    grey = (color.r == color.g == color.b) ? color.r : (color.r + color.g + color.b)/3;
+    //grey = (color.r + color.g + color.b)/3;
 }
\ No newline at end of file
diff --git a/jme3-core/src/test/java/com/jme3/shader/UniformTest.java b/jme3-core/src/test/java/com/jme3/shader/UniformTest.java
new file mode 100644
index 0000000000..99f09e099f
--- /dev/null
+++ b/jme3-core/src/test/java/com/jme3/shader/UniformTest.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (c) 2009-2021 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.shader;
+
+import com.jme3.math.*;
+import org.hamcrest.MatcherAssert;
+import org.junit.Test;
+
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+
+public class UniformTest {
+
+    @Test
+    public void testSetValue_IntArray() {
+        Uniform uniform = new Uniform();
+
+        // Set value for the first time
+        int[] intArray1 = new int[] {1, 2, 4, 8};
+        uniform.setValue(VarType.IntArray, intArray1);
+
+        assertTrue(uniform.getValue() instanceof IntBuffer);
+        verifyIntBufferContent((IntBuffer) uniform.getValue(), intArray1);
+
+        // Overriding the previous value
+        int[] intArray2 = new int[] {3, 5, 7, 11, 13};
+        uniform.setValue(VarType.IntArray, intArray2);
+
+        assertTrue(uniform.getValue() instanceof IntBuffer);
+        verifyIntBufferContent((IntBuffer) uniform.getValue(), intArray2);
+    }
+
+    private void verifyIntBufferContent(IntBuffer intBuffer, int[] intArray) {
+        assertEquals(0, intBuffer.position());
+        assertEquals(intArray.length, intBuffer.capacity());
+        assertEquals(intArray.length, intBuffer.limit());
+
+        for (int i = 0; i < intArray.length; i++) {
+            assertEquals(intArray[i], intBuffer.get(i));
+        }
+    }
+
+
+    @Test
+    public void testSetValue_FloatArray() {
+        Uniform uniform = new Uniform();
+
+        // Set value for the first time
+        float[] floatArray1 = new float[] {1.1f, 2.2f, 4.4f, 8.8f};
+        uniform.setValue(VarType.FloatArray, floatArray1);
+
+        verifyFloatBufferContent(uniform.getMultiData(), floatArray1);
+
+        // Overriding the previous value
+        float[] floatArray2 = new float[] {3.3f, 5.5f, 7.7f, 11.11f, 13.13f};
+        uniform.setValue(VarType.FloatArray, floatArray2);
+
+        verifyFloatBufferContent(uniform.getMultiData(), floatArray2);
+    }
+
+
+    @Test
+    public void testSetValue_Vector2Array() {
+        Uniform uniform = new Uniform();
+
+        // Set value for the first time
+        float[] expectedData1 = new float[] {
+                1.1f, 2.2f,
+                3.3f, 4.4f
+        };
+        Vector2f[] vector2Array1 = new Vector2f[] {
+                new Vector2f(expectedData1[0], expectedData1[1]),
+                new Vector2f(expectedData1[2], expectedData1[3])
+        };
+        uniform.setValue(VarType.Vector2Array, vector2Array1);
+
+        verifyFloatBufferContent(uniform.getMultiData(), expectedData1);
+
+        // Overriding the previous value
+        float[] expectedData2 = new float[] {
+                1.2f, 2.3f,
+                3.4f, 4.5f,
+                5.6f, 6.7f
+        };
+        Vector2f[] vector2Array2 = new Vector2f[] {
+                new Vector2f(expectedData2[0], expectedData2[1]),
+                new Vector2f(expectedData2[2], expectedData2[3]),
+                new Vector2f(expectedData2[4], expectedData2[5])
+        };
+        uniform.setValue(VarType.Vector2Array, vector2Array2);
+
+        verifyFloatBufferContent(uniform.getMultiData(), expectedData2);
+    }
+
+
+    @Test
+    public void testSetValue_Vector3Array() {
+        Uniform uniform = new Uniform();
+
+        // Set value for the first time
+        float[] expectedData1 = new float[] {
+                1.1f, 2.2f, 3.3f,
+                4.4f, 5.5f, 6.6f
+        };
+        Vector3f[] vector3Array1 = new Vector3f[] {
+                new Vector3f(expectedData1[0], expectedData1[1], expectedData1[2]),
+                new Vector3f(expectedData1[3], expectedData1[4], expectedData1[5])
+        };
+        uniform.setValue(VarType.Vector3Array, vector3Array1);
+
+        verifyFloatBufferContent(uniform.getMultiData(), expectedData1);
+
+        // Overriding the previous value
+        float[] expectedData2 = new float[] {
+                1.2f, 2.3f, 3.4f,
+                4.5f, 5.6f, 6.7f,
+                7.8f, 8.9f, 9.1f
+        };
+        Vector3f[] vector3Array2 = new Vector3f[] {
+                new Vector3f(expectedData2[0], expectedData2[1], expectedData2[2]),
+                new Vector3f(expectedData2[3], expectedData2[4], expectedData2[5]),
+                new Vector3f(expectedData2[6], expectedData2[7], expectedData2[8])
+        };
+        uniform.setValue(VarType.Vector3Array, vector3Array2);
+
+        verifyFloatBufferContent(uniform.getMultiData(), expectedData2);
+    }
+
+
+    @Test
+    public void testSetValue_Vector4Array() {
+        Uniform uniform = new Uniform();
+
+        // Set value for the first time
+        float[] expectedData1 = new float[] {
+                1.1f, 2.2f, 3.3f, 4.4f,
+                5.5f, 6.6f, 7.7f, 8.8f
+        };
+        Vector4f[] vector4Array1 = new Vector4f[] {
+                new Vector4f(expectedData1[0], expectedData1[1], expectedData1[2], expectedData1[3]),
+                new Vector4f(expectedData1[4], expectedData1[5], expectedData1[6], expectedData1[7])
+        };
+        uniform.setValue(VarType.Vector4Array, vector4Array1);
+
+        verifyFloatBufferContent(uniform.getMultiData(), expectedData1);
+
+        // Overriding the previous value
+        float[] expectedData2 = new float[] {
+                1.2f, 2.3f, 3.4f, 4.5f,
+                5.6f, 6.7f, 7.8f, 8.9f,
+                9.10f, 10.11f, 11.12f, 12.13f
+        };
+        Vector4f[] vector4Array2 = new Vector4f[] {
+                new Vector4f(expectedData2[0], expectedData2[1], expectedData2[2], expectedData2[3]),
+                new Vector4f(expectedData2[4], expectedData2[5], expectedData2[6], expectedData2[7]),
+                new Vector4f(expectedData2[8], expectedData2[9], expectedData2[10], expectedData2[11])
+        };
+        uniform.setValue(VarType.Vector4Array, vector4Array2);
+
+        verifyFloatBufferContent(uniform.getMultiData(), expectedData2);
+    }
+
+
+    @Test
+    public void testSetValue_Matrix3Array() {
+        Uniform uniform = new Uniform();
+
+        // Set value for the first time
+        float[] expectedData1 = new float[] {
+                1.1f, 2.2f, 3.3f,
+                4.4f, 5.5f, 6.6f,
+                7.7f, 8.8f, 9.9f,
+
+                10.10f, 11.11f, 12.12f,
+                13.13f, 14.14f, 15.15f,
+                16.16f, 17.17f, 18.18f
+        };
+        Matrix3f[] matrix3Array1 = new Matrix3f[] {
+                new Matrix3f(
+                        expectedData1[0], expectedData1[3], expectedData1[6],
+                        expectedData1[1], expectedData1[4], expectedData1[7],
+                        expectedData1[2], expectedData1[5], expectedData1[8]
+                ),
+                new Matrix3f(
+                        expectedData1[9], expectedData1[12], expectedData1[15],
+                        expectedData1[10], expectedData1[13], expectedData1[16],
+                        expectedData1[11], expectedData1[14], expectedData1[17]
+                )
+        };
+        uniform.setValue(VarType.Matrix3Array, matrix3Array1);
+
+        verifyFloatBufferContent(uniform.getMultiData(), expectedData1);
+
+        // Overriding the previous value
+        float[] expectedData2 = new float[] {
+                1.2f, 2.3f, 3.4f,
+                4.5f, 5.6f, 6.7f,
+                7.8f, 8.9f, 9.1f,
+
+                10.11f, 11.12f, 12.13f,
+                13.14f, 14.15f, 15.16f,
+                16.17f, 17.18f, 18.19f,
+
+                19.20f, 20.21f, 21.22f,
+                22.23f, 23.24f, 24.25f,
+                25.26f, 26.27f, 27.28f
+        };
+        Matrix3f[] matrix3Array2 = new Matrix3f[] {
+                new Matrix3f(
+                        expectedData2[0], expectedData2[3], expectedData2[6],
+                        expectedData2[1], expectedData2[4], expectedData2[7],
+                        expectedData2[2], expectedData2[5], expectedData2[8]
+                ),
+                new Matrix3f(
+                        expectedData2[9], expectedData2[12], expectedData2[15],
+                        expectedData2[10], expectedData2[13], expectedData2[16],
+                        expectedData2[11], expectedData2[14], expectedData2[17]
+                ),
+                new Matrix3f(
+                        expectedData2[18], expectedData2[21], expectedData2[24],
+                        expectedData2[19], expectedData2[22], expectedData2[25],
+                        expectedData2[20], expectedData2[23], expectedData2[26]
+                )
+        };
+        uniform.setValue(VarType.Matrix3Array, matrix3Array2);
+
+        verifyFloatBufferContent(uniform.getMultiData(), expectedData2);
+    }
+
+
+    @Test
+    public void testSetValue_Matrix4Array() {
+        Uniform uniform = new Uniform();
+
+        // Set value for the first time
+        float[] expectedData1 = new float[] {
+                1.1f, 2.2f, 3.3f, 4.4f,
+                5.5f, 6.6f, 7.7f, 8.8f,
+                9.9f, 10.10f, 11.11f, 12.12f,
+                13.13f, 14.14f, 15.15f, 16.16f,
+
+                17.17f, 18.18f, 19.19f, 20.20f,
+                21.21f, 22.22f, 23.23f, 24.24f,
+                25.25f, 26.26f, 27.27f, 28.28f,
+                29.29f, 30.30f, 31.31f, 32.32f
+        };
+        Matrix4f[] matrix4Array1 = new Matrix4f[] {
+                new Matrix4f(Arrays.copyOfRange(expectedData1, 0, 16)),
+                new Matrix4f(Arrays.copyOfRange(expectedData1, 16, 32))
+        };
+        uniform.setValue(VarType.Matrix4Array, matrix4Array1);
+
+        verifyFloatBufferContent(uniform.getMultiData(), expectedData1);
+
+        // Overriding the previous value
+        float[] expectedData2 = new float[] {
+                1.2f, 2.3f, 3.4f, 4.5f,
+                5.6f, 6.7f, 7.8f, 8.9f,
+                9.1f, 10.11f, 11.12f, 12.13f,
+                13.14f, 14.15f, 15.16f, 16.17f,
+
+                17.18f, 18.19f, 19.20f, 20.21f,
+                21.22f, 22.23f, 23.24f, 24.25f,
+                25.26f, 26.27f, 27.28f, 28.29f,
+                29.30f, 30.31f, 31.32f, 32.33f,
+
+                33.34f, 34.35f, 35.36f, 36.37f,
+                37.38f, 38.39f, 39.40f, 40.41f,
+                41.42f, 42.43f, 43.44f, 44.45f,
+                45.46f, 46.47f, 47.48f, 48.49f
+        };
+        Matrix4f[] matrix4Array2 = new Matrix4f[] {
+                new Matrix4f(Arrays.copyOfRange(expectedData2, 0, 16)),
+                new Matrix4f(Arrays.copyOfRange(expectedData2, 16, 32)),
+                new Matrix4f(Arrays.copyOfRange(expectedData2, 32, 48))
+        };
+        uniform.setValue(VarType.Matrix4Array, matrix4Array2);
+
+        verifyFloatBufferContent(uniform.getMultiData(), expectedData2);
+    }
+
+    private void verifyFloatBufferContent(FloatBuffer floatBuffer, float[] floatArray) {
+        assertEquals(0, floatBuffer.position());
+        assertEquals(floatArray.length, floatBuffer.capacity());
+        assertEquals(floatArray.length, floatBuffer.limit());
+
+        for (int i = 0; i < floatArray.length; i++) {
+            assertEquals(floatArray[i], floatBuffer.get(i), 0f);
+        }
+    }
+
+}
diff --git a/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java b/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java
index e06f1541a8..47a7facee9 100644
--- a/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java
+++ b/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java
@@ -70,9 +70,9 @@
 import java.util.logging.Logger;
 
 /**
- * This is an utility class that allows to generated the lod levels for an
- * arbitrary mesh. It computes a collapse cost for each vertex and each edges.
- * The higher the cost the most likely collapsing the edge or the vertex will
+ * This is a utility class that adds the ability to generate the lod levels
+ * for an arbitrary mesh. It computes a collapse cost for each vertex and each edge.
+ * The higher the cost the more likely collapsing the edge or the vertex will
  * produce artifacts on the mesh. 

This class is the java implementation of * the enhanced version of Ogre engine Lod generator, by Péter Szücs, originally * based on Stan Melax "easy mesh simplification". The MIT licenced C++ source @@ -81,8 +81,8 @@ * informations can be found here http://www.melax.com/polychop * http://sajty.elementfx.com/progressivemesh/GSoC2012.pdf

* - *

The algorithm sort vertices according to their collapse cost - * ascending. It collapse from the "cheapest" vertex to the more expensive.
+ *

The algorithm sorts vertices according to their collapse cost in + * ascending order. It collapses from the "cheapest" vertex to the more expensive.
* Usage :
*

  *      LodGenerator lODGenerator = new LodGenerator(geometry);
diff --git a/jme3-examples/src/main/java/jme3test/animation/TestJaime.java b/jme3-examples/src/main/java/jme3test/animation/TestJaime.java
index 0b13cfee51..a49dc89a90 100644
--- a/jme3-examples/src/main/java/jme3test/animation/TestJaime.java
+++ b/jme3-examples/src/main/java/jme3test/animation/TestJaime.java
@@ -152,17 +152,26 @@ public void setupCinematic(final Node jaime) {
         stateManager.attach(cinematic);
         
         jaime.move(0, 0, -3);
-        AnimFactory af = new AnimFactory(0.7f, "JumpForward", 30f);
+        AnimFactory af = new AnimFactory(1f, "JumpForward", 30f);
         af.addTimeTranslation(0, new Vector3f(0, 0, -3));
         af.addTimeTranslation(0.35f, new Vector3f(0, 1, -1.5f));
         af.addTimeTranslation(0.7f, new Vector3f(0, 0, 0));
         AnimClip forwardClip = af.buildAnimation(jaime);
         AnimComposer composer = jaime.getControl(AnimComposer.class);
         composer.addAnimClip(forwardClip);
+        /*
+         * Add a clip that warps the model to its starting position.
+         */
+        AnimFactory af2 = new AnimFactory(0.01f, "StartingPosition", 30f);
+        af2.addTimeTranslation(0f, new Vector3f(0f, 0f, -3f));
+        AnimClip startClip = af2.buildAnimation(jaime);
+        composer.addAnimClip(startClip);
 
         composer.makeLayer("SpatialLayer", null);
         String boneLayer = AnimComposer.DEFAULT_LAYER;
 
+        cinematic.addCinematicEvent(0f,
+                new AnimEvent(composer, "StartingPosition", "SpatialLayer"));
         cinematic.enqueueCinematicEvent(
                 new AnimEvent(composer, "Idle", boneLayer));
         float jumpStart = cinematic.enqueueCinematicEvent(
diff --git a/jme3-examples/src/main/java/jme3test/app/TestBareBonesApp.java b/jme3-examples/src/main/java/jme3test/app/TestBareBonesApp.java
index b90d28310e..811c90ebb1 100644
--- a/jme3-examples/src/main/java/jme3test/app/TestBareBonesApp.java
+++ b/jme3-examples/src/main/java/jme3test/app/TestBareBonesApp.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2021 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -72,7 +72,7 @@ public void update(){
         float tpf = timer.getTimePerFrame();
         boxGeom.rotate(tpf * 2, tpf * 4, tpf * 3);
 
-        // dont forget to update the scenes
+        // don't forget to update the scenes
         boxGeom.updateLogicalState(tpf);
         boxGeom.updateGeometricState();
 
diff --git a/jme3-examples/src/main/java/jme3test/app/state/TestAppStates.java b/jme3-examples/src/main/java/jme3test/app/state/TestAppStates.java
index a19e2add8a..e8d1d49a8c 100644
--- a/jme3-examples/src/main/java/jme3test/app/state/TestAppStates.java
+++ b/jme3-examples/src/main/java/jme3test/app/state/TestAppStates.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2021 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -37,6 +37,7 @@
 import com.jme3.scene.Spatial;
 import com.jme3.system.AppSettings;
 import com.jme3.system.JmeContext;
+import jme3test.niftygui.StartScreenController;
 
 public class TestAppStates extends LegacyApplication {
 
@@ -70,10 +71,12 @@ public void initialize(){
         state.getRootNode().attachChild(model);
 
         NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager,
-                                                           inputManager,
-                                                           audioRenderer,
-                                                           guiViewPort);
-        niftyDisplay.getNifty().fromXml("Interface/Nifty/HelloJme.xml", "start");
+                inputManager,
+                audioRenderer,
+                guiViewPort);
+        StartScreenController startScreen = new StartScreenController(this);
+        niftyDisplay.getNifty().fromXml("Interface/Nifty/HelloJme.xml", "start",
+                startScreen);
         guiViewPort.addProcessor(niftyDisplay);
     }
 
diff --git a/jme3-examples/src/main/java/jme3test/asset/TestManyLocators.java b/jme3-examples/src/main/java/jme3test/asset/TestManyLocators.java
index bf0cc642e1..1a6d6062d4 100644
--- a/jme3-examples/src/main/java/jme3test/asset/TestManyLocators.java
+++ b/jme3-examples/src/main/java/jme3test/asset/TestManyLocators.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2021 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -43,28 +43,29 @@ public class TestManyLocators {
     public static void main(String[] args){
         AssetManager am = JmeSystem.newAssetManager();
 
-        am.registerLocator("http://wiki.jmonkeyengine.org/jme3/beginner",
-                           UrlLocator.class);
+        am.registerLocator(
+                "https://github.com/jMonkeyEngine/wiki/raw/master/docs/modules/tutorials/assets/images/beginner/",
+                UrlLocator.class);
 
         am.registerLocator("town.zip", ZipLocator.class);
         am.registerLocator(
-                    "https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/jmonkeyengine/wildhouse.zip", 
-                    HttpZipLocator.class);       
-        
+                "https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/jmonkeyengine/wildhouse.zip",
+                HttpZipLocator.class);
+
         am.registerLocator("/", ClasspathLocator.class);
         
         
 
-        // Try loading from Core-Data source package
+        // Try loading from jme3-core resources using the ClasspathLocator.
         AssetInfo a = am.locateAsset(new AssetKey("Interface/Fonts/Default.fnt"));
 
-        // Try loading from town scene zip file
+        // Try loading from the "town.zip" file using the ZipLocator.
         AssetInfo b = am.locateAsset(new ModelKey("casaamarela.jpg"));
 
-        // Try loading from wildhouse online scene zip file
+        // Try loading from the Google Code Archive website using the HttpZipLocator.
         AssetInfo c = am.locateAsset(new ModelKey("glasstile2.png"));
 
-        // Try loading directly from HTTP
+        // Try loading from the GitHub website using the UrlLocator.
         AssetInfo d = am.locateAsset(new TextureKey("beginner-physics.png"));
 
         if (a == null)
diff --git a/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.java b/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.java
index 3f4468a320..1f1909292a 100644
--- a/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.java
+++ b/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.java
@@ -206,6 +206,8 @@ public InputStream openStream() {
             }
 
             musicSource = new AudioNode(musicData, key);
+            // A positional AudioNode would prohibit stereo sound!
+            musicSource.setPositional(false);
             musicLength = musicData.getDuration();
             updateTime();
         }
@@ -258,24 +260,13 @@ private void btnStopActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST
     }//GEN-LAST:event_btnStopActionPerformed
 
     private void btnFFActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnFFActionPerformed
-        if (musicSource.getStatus() == Status.Playing){
+        if (musicSource != null && musicSource.getStatus() == Status.Playing) {
             musicSource.setPitch(2);
         }
     }//GEN-LAST:event_btnFFActionPerformed
 
     private void sldBarStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_sldBarStateChanged
-        if (musicSource != null && !sldBar.getValueIsAdjusting()){
-            curTime = sldBar.getValue() / 100f;
-            if (curTime < 0)
-                curTime = 0;
-            
-            musicSource.setTimeOffset(curTime);
-//            if (musicSource.getStatus() == Status.Playing){
-//                musicSource.stop();               
-//                musicSource.play();
-//            }
-            updateTime();
-        }
+        // do nothing: OGG/Vorbis supports seeking, but only for time = 0!
     }//GEN-LAST:event_sldBarStateChanged
 
     /**
diff --git a/jme3-examples/src/main/java/jme3test/batching/TestBatchNode.java b/jme3-examples/src/main/java/jme3test/batching/TestBatchNode.java
index 64b3663817..4b8f0fa75d 100644
--- a/jme3-examples/src/main/java/jme3test/batching/TestBatchNode.java
+++ b/jme3-examples/src/main/java/jme3test/batching/TestBatchNode.java
@@ -79,7 +79,7 @@ public void simpleInitApp() {
 
         /*
          * A cube with a color "bleeding" through transparent texture. Uses
-         * Texture from jme3-test-data library!
+         * Texture from jme3-testdata library!
          */
         Box boxshape4 = new Box(1f, 1f, 1f);
         Geometry cube = new Geometry("cube1", boxshape4);
@@ -90,7 +90,7 @@ public void simpleInitApp() {
         //mat.setBoolean("UseMaterialColors", true);
         /*
          * A cube with a color "bleeding" through transparent texture. Uses
-         * Texture from jme3-test-data library!
+         * Texture from jme3-testdata library!
          */
         Box box = new Box(1f, 1f, 1f);
         cube2 = new Geometry("cube2", box);
diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java b/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java
index 946bde2a0e..2130504dcb 100644
--- a/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java
+++ b/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java
@@ -275,8 +275,11 @@ public void onAction(String binding, boolean value, float tpf) {
             }
         } else if (binding.equals("Space")) {
             if (value) {
-                getPhysicsSpace().remove(slider);
-                slider.destroy();
+                if (slider != null) {
+                    getPhysicsSpace().remove(slider);
+                    slider.destroy();
+                    slider = null;
+                }
                 vehicle.applyImpulse(jumpForce, Vector3f.ZERO);
             }
         } else if (binding.equals("Reset")) {
diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestFancyCar.java b/jme3-examples/src/main/java/jme3test/bullet/TestFancyCar.java
index 051a4e89a2..8a7941c03d 100644
--- a/jme3-examples/src/main/java/jme3test/bullet/TestFancyCar.java
+++ b/jme3-examples/src/main/java/jme3test/bullet/TestFancyCar.java
@@ -59,7 +59,7 @@ public class TestFancyCar extends SimpleApplication implements ActionListener {
     private Node carNode;
 
     public static void main(String[] args) {
-        TestFancyCar app = new TestFancyCar();       
+        TestFancyCar app = new TestFancyCar();
         app.start();
     }
 
@@ -68,13 +68,11 @@ private void setupKeys() {
         inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_K));
         inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_U));
         inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_J));
-        inputManager.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE));
         inputManager.addMapping("Reset", new KeyTrigger(KeyInput.KEY_RETURN));
         inputManager.addListener(this, "Lefts");
         inputManager.addListener(this, "Rights");
         inputManager.addListener(this, "Ups");
         inputManager.addListener(this, "Downs");
-        inputManager.addListener(this, "Space");
         inputManager.addListener(this, "Reset");
     }
 
@@ -86,42 +84,18 @@ public void simpleInitApp() {
         flyCam.setMoveSpeed(10);
 
         setupKeys();
-        PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace());
-//        setupFloor();
+        PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, getPhysicsSpace());
         buildPlayer();
 
         DirectionalLight dl = new DirectionalLight();
         dl.setDirection(new Vector3f(-0.5f, -1f, -0.3f).normalizeLocal());
         rootNode.addLight(dl);
-
-        dl = new DirectionalLight();
-        dl.setDirection(new Vector3f(0.5f, -0.1f, 0.3f).normalizeLocal());
-     //   rootNode.addLight(dl);
     }
 
     private PhysicsSpace getPhysicsSpace() {
         return bulletAppState.getPhysicsSpace();
     }
 
-//    public void setupFloor() {
-//        Material mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m");
-//        mat.getTextureParam("DiffuseMap").getTextureValue().setWrap(WrapMode.Repeat);
-////        mat.getTextureParam("NormalMap").getTextureValue().setWrap(WrapMode.Repeat);
-////        mat.getTextureParam("ParallaxMap").getTextureValue().setWrap(WrapMode.Repeat);
-//
-//        Box floor = new Box(Vector3f.ZERO, 140, 1f, 140);
-//        floor.scaleTextureCoordinates(new Vector2f(112.0f, 112.0f));
-//        Geometry floorGeom = new Geometry("Floor", floor);
-//        floorGeom.setShadowMode(ShadowMode.Receive);
-//        floorGeom.setMaterial(mat);
-//
-//        PhysicsNode tb = new PhysicsNode(floorGeom, new MeshCollisionShape(floorGeom.getMesh()), 0);
-//        tb.setLocalTranslation(new Vector3f(0f, -6, 0f));
-////        tb.attachDebugShape(assetManager);
-//        rootNode.attachChild(tb);
-//        getPhysicsSpace().add(tb);
-//    }
-
     private Geometry findGeom(Spatial spatial, String name) {
         if (spatial instanceof Node) {
             Node node = (Node) spatial;
@@ -147,10 +121,9 @@ private void buildPlayer() {
         final float mass = 400;
 
         //Load model and get chassis Geometry
-        carNode = (Node)assetManager.loadModel("Models/Ferrari/Car.scene");
+        carNode = (Node) assetManager.loadModel("Models/Ferrari/Car.scene");
         carNode.setShadowMode(ShadowMode.Cast);
         Geometry chasis = findGeom(carNode, "Car");
-        BoundingBox box = (BoundingBox) chasis.getModelBound();
 
         //Create a hull collision shape for the chassis
         CollisionShape carHull = CollisionShapeFactory.createDynamicMeshShape(chasis);
@@ -172,7 +145,7 @@ private void buildPlayer() {
 
         Geometry wheel_fr = findGeom(carNode, "WheelFrontRight");
         wheel_fr.center();
-        box = (BoundingBox) wheel_fr.getModelBound();
+        BoundingBox box = (BoundingBox) wheel_fr.getModelBound();
         float wheelRadius = box.getYExtent();
         float back_wheel_h = (wheelRadius * 1.7f) - 1f;
         float front_wheel_h = (wheelRadius * 1.9f) - 1f;
@@ -228,7 +201,6 @@ else if (binding.equals("Ups")) {
                 accelerationValue += 800;
             }
             player.accelerate(accelerationValue);
-            player.setCollisionShape(CollisionShapeFactory.createDynamicMeshShape(findGeom(carNode, "Car")));
         } else if (binding.equals("Downs")) {
             if (value) {
                 player.brake(40f);
@@ -243,7 +215,6 @@ else if (binding.equals("Ups")) {
                 player.setLinearVelocity(Vector3f.ZERO);
                 player.setAngularVelocity(Vector3f.ZERO);
                 player.resetSuspension();
-            } else {
             }
         }
     }
diff --git a/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java b/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java
index 7eb6c497ad..fb421279a5 100644
--- a/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java
+++ b/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java
@@ -60,7 +60,7 @@ public static void main(String[] args) {
     @Override
     public void simpleInitApp() {
         flyCam.setEnabled(false);
-        initMark();       // a red sphere to mark the hit
+        initMark();
 
         /** create four colored boxes and a floor to shoot at: */
         shootables = new Node("Shootables");
@@ -127,21 +127,19 @@ private Geometry makeFloor() {
         return floor;
     }
 
-    /** A red ball that marks the last spot that was "hit" by the "shot". */
-    protected void initMark() {
+    /**
+     * A red arrow to mark the spot being picked.
+     */
+    private void initMark() {
         Arrow arrow = new Arrow(Vector3f.UNIT_Z.mult(2f));
-
-        //Sphere sphere = new Sphere(30, 30, 0.2f);
         mark = new Geometry("BOOM!", arrow);
-        //mark = new Geometry("BOOM!", sphere);
         Material mark_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
-        mark_mat.getAdditionalRenderState().setLineWidth(3);
         mark_mat.setColor("Color", ColorRGBA.Red);
         mark.setMaterial(mark_mat);
     }
 
-    protected Spatial makeCharacter() {
-        // load a character from jme3test-test-data
+    private Spatial makeCharacter() {
+        // load a character from jme3-testdata
         Spatial golem = assetManager.loadModel("Models/Oto/Oto.mesh.xml");
         golem.scale(0.5f);
         golem.setLocalTranslation(-1.0f, -1.5f, -0.6f);
diff --git a/jme3-examples/src/main/java/jme3test/collision/TestTriangleCollision.java b/jme3-examples/src/main/java/jme3test/collision/TestTriangleCollision.java
index e2a61bed1b..6d20ea1fc2 100644
--- a/jme3-examples/src/main/java/jme3test/collision/TestTriangleCollision.java
+++ b/jme3-examples/src/main/java/jme3test/collision/TestTriangleCollision.java
@@ -69,7 +69,7 @@ public void simpleInitApp() {
         geom1.setMaterial(m1);
         rootNode.attachChild(geom1);
 
-        // load a character from jme3test-test-data
+        // load a character from jme3-testdata
         golem = assetManager.loadModel("Models/Oto/Oto.mesh.xml");
         golem.scale(0.5f);
         golem.setLocalTranslation(-1.0f, -1.5f, -0.6f);
diff --git a/jme3-examples/src/main/java/jme3test/gui/TestSoftwareMouse.java b/jme3-examples/src/main/java/jme3test/gui/TestSoftwareMouse.java
index f99ff41146..e1baf0b5c7 100644
--- a/jme3-examples/src/main/java/jme3test/gui/TestSoftwareMouse.java
+++ b/jme3-examples/src/main/java/jme3test/gui/TestSoftwareMouse.java
@@ -36,6 +36,7 @@
 import com.jme3.input.RawInputListener;
 import com.jme3.input.event.*;
 import com.jme3.math.FastMath;
+import com.jme3.math.Vector2f;
 import com.jme3.system.AppSettings;
 import com.jme3.texture.Texture;
 import com.jme3.texture.Texture2D;
@@ -47,8 +48,6 @@ public class TestSoftwareMouse extends SimpleApplication {
 
     final private RawInputListener inputListener = new RawInputListener() {
 
-        private float x = 0, y = 0;
-
         @Override
         public void beginInput() {
         }
@@ -63,8 +62,8 @@ public void onJoyButtonEvent(JoyButtonEvent evt) {
         }
         @Override
         public void onMouseMotionEvent(MouseMotionEvent evt) {
-            x += evt.getDX();
-            y += evt.getDY();
+            float x = evt.getX();
+            float y = evt.getY();
 
             // Prevent mouse from leaving screen
             AppSettings settings = TestSoftwareMouse.this.settings;
@@ -107,6 +106,12 @@ public void simpleInitApp() {
         cursor.setWidth(64);
         cursor.setHeight(64);
         guiNode.attachChild(cursor);
+        /*
+         * Position the software cursor
+         * so that its upper-left corner is at the hotspot.
+         */
+        Vector2f initialPosition = inputManager.getCursorPosition();
+        cursor.setPosition(initialPosition.x, initialPosition.y - 64f);
 
         inputManager.addRawInputListener(inputListener);
 
diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloAssets.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloAssets.java
index 4fb4738ace..6dd8f44a95 100644
--- a/jme3-examples/src/main/java/jme3test/helloworld/HelloAssets.java
+++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloAssets.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2021 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -53,13 +53,13 @@ public static void main(String[] args) {
     @Override
     public void simpleInitApp() {
 
-        /** Load a teapot model (OBJ file from test-data) */
+        /* Load a teapot model (OBJ file from jme3-testdata) */
         Spatial teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
         Material mat_default = new Material( assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
         teapot.setMaterial(mat_default);
         rootNode.attachChild(teapot);
 
-        /** Create a wall (Box with material and texture from test-data) */
+        /* Create a wall (Box with material and texture from jme3-testdata) */
         Box box = new Box(2.5f, 2.5f, 1.0f);
         Spatial wall = new Geometry("Box", box );
         Material mat_brick = new Material( assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
@@ -68,7 +68,7 @@ public void simpleInitApp() {
         wall.setLocalTranslation(2.0f,-2.5f,0.0f);
         rootNode.attachChild(wall);
 
-        /** Display a line of text (default font from test-data) */
+        /* Display a line of text (default font from jme3-testdata) */
         setDisplayStatView(false);
         guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
         BitmapText helloText = new BitmapText(guiFont, false);
diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloPicking.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloPicking.java
index 8cfa67375d..ca7a62e6de 100644
--- a/jme3-examples/src/main/java/jme3test/helloworld/HelloPicking.java
+++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloPicking.java
@@ -168,7 +168,7 @@ private void initCrossHairs() {
   }
 
   private Spatial makeCharacter() {
-    // load a character from jme3test-test-data
+    // load a character from jme3-testdata
     Spatial golem = assetManager.loadModel("Models/Oto/Oto.mesh.xml");
     golem.scale(0.5f);
     golem.setLocalTranslation(-1.0f, -1.5f, -0.6f);
diff --git a/jme3-examples/src/main/java/jme3test/light/TestConeVSFrustum.java b/jme3-examples/src/main/java/jme3test/light/TestConeVSFrustum.java
index f28ac48bea..9a6158a6de 100644
--- a/jme3-examples/src/main/java/jme3test/light/TestConeVSFrustum.java
+++ b/jme3-examples/src/main/java/jme3test/light/TestConeVSFrustum.java
@@ -215,8 +215,8 @@ public void onAction(String name, boolean isPressed, float tpf) {
             }
         }, "click", "middleClick", "shift");
         /**
-         * An unshaded textured cube. // * Uses texture from jme3-test-data
-         * library!
+         * An unshaded textured cube.
+         * Uses texture from jme3-testdata library!
          */
         Box boxMesh = new Box(1f, 1f, 1f);
         Geometry boxGeo = new Geometry("A Textured Box", boxMesh);
diff --git a/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java b/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java
index cf51dd2ed1..6db3356743 100644
--- a/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java
+++ b/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java
@@ -198,9 +198,6 @@ public void simpleInitApp() {
     }
 
     private void initInputs() {
-
-        inputManager.addMapping("ThicknessUp", new KeyTrigger(KeyInput.KEY_Y));
-        inputManager.addMapping("ThicknessDown", new KeyTrigger(KeyInput.KEY_H));
         inputManager.addMapping("lambdaUp", new KeyTrigger(KeyInput.KEY_U));
         inputManager.addMapping("lambdaDown", new KeyTrigger(KeyInput.KEY_J));
         inputManager.addMapping("switchGroundMat", new KeyTrigger(KeyInput.KEY_M));
@@ -219,7 +216,7 @@ private void initInputs() {
         inputManager.addMapping("backShadows", new KeyTrigger(KeyInput.KEY_K));
 
 
-        inputManager.addListener(this, "lambdaUp", "lambdaDown", "ThicknessUp", "ThicknessDown",
+        inputManager.addListener(this, "lambdaUp", "lambdaDown",
                 "switchGroundMat", "debug", "up", "down", "right", "left", "fwd", "back", "pp", "stabilize", "distance", "ShadowUp", "ShadowDown", "backShadows");
 
         ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, dlsr, dlsf, guiNode, inputManager, viewPort);
diff --git a/jme3-examples/src/main/java/jme3test/light/TestGltfUnlit.java b/jme3-examples/src/main/java/jme3test/light/TestGltfUnlit.java
index 5e4ca49bbf..8b2244399f 100644
--- a/jme3-examples/src/main/java/jme3test/light/TestGltfUnlit.java
+++ b/jme3-examples/src/main/java/jme3test/light/TestGltfUnlit.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2019 jMonkeyEngine
+ * Copyright (c) 2009-2021 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -32,7 +32,6 @@
 package jme3test.light;
 
 import com.jme3.app.SimpleApplication;
-import com.jme3.input.controls.ActionListener;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.Vector3f;
 
@@ -43,7 +42,7 @@
  * @author Markil 3
  * @version 3.3.0-SNAPSHOT
  */
-public class TestGltfUnlit extends SimpleApplication implements ActionListener {
+public class TestGltfUnlit extends SimpleApplication {
     public static void main(String[] args) {
         TestGltfUnlit testUnlit = new TestGltfUnlit();
         testUnlit.start();
@@ -51,17 +50,12 @@ public static void main(String[] args) {
 
     @Override
     public void simpleInitApp() {
-
         ColorRGBA skyColor = new ColorRGBA(0.5f, 0.6f, 0.7f, 0.0f);
 
         flyCam.setMoveSpeed(20);
         viewPort.setBackgroundColor(skyColor.mult(0.9f));
 
-        this.cam.setLocation(new Vector3f(0, 10, 20));
-        this.rootNode.attachChild(this.getAssetManager().loadModel("jme3test/scenes/unlit.gltf"));
-    }
-
-    @Override
-    public void onAction(String name, boolean isPressed, float tpf) {
+        cam.setLocation(new Vector3f(0, 10, 20));
+        rootNode.attachChild(getAssetManager().loadModel("jme3test/scenes/unlit.gltf"));
     }
 }
\ No newline at end of file
diff --git a/jme3-examples/src/main/java/jme3test/light/TestTransparentShadow.java b/jme3-examples/src/main/java/jme3test/light/TestTransparentShadow.java
index ce632604ba..a0ffedb5c0 100644
--- a/jme3-examples/src/main/java/jme3test/light/TestTransparentShadow.java
+++ b/jme3-examples/src/main/java/jme3test/light/TestTransparentShadow.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2020 jMonkeyEngine
+ * Copyright (c) 2009-2021 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -94,7 +94,7 @@ public void simpleInitApp() {
 
         rootNode.attachChild(tree);
 
-        // Uses Texture from jme3-test-data library!
+        // Uses Texture from jme3-testdata library!
         ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 30);
         Material mat_red = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
         mat_red.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png"));
diff --git a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java
index 6855cd77b4..4fa18e848f 100644
--- a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java
+++ b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java
@@ -70,7 +70,7 @@ public static void main(String[] args) {
     you can find them here :
     https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0
     https://sketchfab.com/features/gltf
-    You have to copy them in Model/gltf folder in the test-data project.
+    You have to copy them in Model/gltf folder in the jme3-testdata project.
      */
     @Override
     public void simpleInitApp() {
diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestMorph.java b/jme3-examples/src/main/java/jme3test/model/anim/TestMorph.java
index 52dffc1209..5e8d14eb51 100644
--- a/jme3-examples/src/main/java/jme3test/model/anim/TestMorph.java
+++ b/jme3-examples/src/main/java/jme3test/model/anim/TestMorph.java
@@ -3,6 +3,7 @@
 import com.jme3.anim.MorphControl;
 import com.jme3.app.ChaseCameraAppState;
 import com.jme3.app.SimpleApplication;
+import com.jme3.export.binary.BinaryExporter;
 import com.jme3.font.BitmapFont;
 import com.jme3.font.BitmapText;
 import com.jme3.input.KeyInput;
@@ -16,7 +17,7 @@
 import com.jme3.scene.mesh.MorphTarget;
 import com.jme3.scene.shape.Box;
 import com.jme3.util.BufferUtils;
-
+import com.jme3.util.clone.Cloner;
 import java.nio.FloatBuffer;
 
 public class TestMorph extends SimpleApplication {
@@ -90,6 +91,19 @@ public void simpleInitApp() {
 
         g.setMorphState(weights);
         g.addControl(new MorphControl());
+        /*
+         * Attach a clone of the morphing box model, in order to test cloning.
+         */
+        Geometry g2 = Cloner.deepClone(g);
+        g2.move(-4f, 0f, 0f);
+        rootNode.attachChild(g2);
+        /*
+         * Attach a saveAndLoad() copy of the morphing box model,
+         * in order to test serialization.
+         */
+        Geometry g3 = BinaryExporter.saveAndLoad(assetManager, g);
+        g3.move(-4f, 4f, 0f);
+        rootNode.attachChild(g3);
 
         ChaseCameraAppState chase = new ChaseCameraAppState();
         chase.setTarget(rootNode);
diff --git a/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java b/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java
index fc4731615e..25bde0a797 100644
--- a/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java
+++ b/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2021 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -50,32 +50,31 @@ public static void main(String[] args){
         app.start();
     }
 
-    public Geometry putShape(Mesh shape, ColorRGBA color, float lineWidth){
+    private Geometry putShape(Mesh shape, ColorRGBA color) {
         Geometry g = new Geometry("shape", shape);
         Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
         mat.getAdditionalRenderState().setWireframe(true);
-        mat.getAdditionalRenderState().setLineWidth(lineWidth);
         mat.setColor("Color", color);
         g.setMaterial(mat);
         rootNode.attachChild(g);
         return g;
     }
 
-    public void putArrow(Vector3f pos, Vector3f dir, ColorRGBA color){
+    private void putArrow(Vector3f pos, Vector3f dir, ColorRGBA color){
         Arrow arrow = new Arrow(dir);
-        putShape(arrow, color, 4).setLocalTranslation(pos);
+        putShape(arrow, color).setLocalTranslation(pos);
     }
 
-    public void putBox(Vector3f pos, float size, ColorRGBA color){
-        putShape(new WireBox(size, size, size), color, 1).setLocalTranslation(pos);
+    private void putBox(Vector3f pos, float size, ColorRGBA color) {
+        putShape(new WireBox(size, size, size), color).setLocalTranslation(pos);
     }
 
-    public void putGrid(Vector3f pos, ColorRGBA color){
-        putShape(new Grid(6, 6, 0.2f), color, 1).center().move(pos);
+    private void putGrid(Vector3f pos, ColorRGBA color) {
+        putShape(new Grid(6, 6, 0.2f), color).center().move(pos);
     }
 
-    public void putSphere(Vector3f pos, ColorRGBA color){
-        putShape(new WireSphere(1), color, 1).setLocalTranslation(pos);
+    private void putSphere(Vector3f pos, ColorRGBA color) {
+        putShape(new WireSphere(1), color).setLocalTranslation(pos);
     }
 
     @Override
diff --git a/jme3-examples/src/main/java/jme3test/niftygui/StartScreenController.java b/jme3-examples/src/main/java/jme3test/niftygui/StartScreenController.java
new file mode 100644
index 0000000000..3fba0f46a0
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/niftygui/StartScreenController.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2009-2021 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 jme3test.niftygui;
+
+import com.jme3.app.Application;
+import de.lessvoid.nifty.Nifty;
+import de.lessvoid.nifty.screen.Screen;
+import de.lessvoid.nifty.screen.ScreenController;
+
+/**
+ * A ScreenController for the "start" screen defined in
+ * "Interfaces/Nifty/HelloJme.xml", which is used in the TestAppStates and
+ * TestNiftyGui applications.
+ */
+public class StartScreenController implements ScreenController {
+
+    final private Application application;
+
+    /**
+     * Instantiate a ScreenController for the specified Application.
+     *
+     * @param app the Application
+     */
+    public StartScreenController(Application app) {
+        this.application = app;
+    }
+
+    /**
+     * Nifty invokes this method when the screen gets enabled for the first
+     * time.
+     *
+     * @param nifty (not null)
+     * @param screen (not null)
+     */
+    @Override
+    public void bind(Nifty nifty, Screen screen) {
+        System.out.println("bind(" + screen.getScreenId() + ")");
+    }
+
+    /**
+     * Nifty invokes this method each time the screen starts up.
+     */
+    @Override
+    public void onStartScreen() {
+        System.out.println("onStartScreen");
+    }
+
+    /**
+     * Nifty invokes this method each time the screen shuts down.
+     */
+    @Override
+    public void onEndScreen() {
+        System.out.println("onEndScreen");
+    }
+
+    /**
+     * Stop the Application. Nifty invokes this method (via reflection) after
+     * the user clicks on the flashing orange panel.
+     */
+    public void quit() {
+        application.stop();
+    }
+}
diff --git a/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyGui.java b/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyGui.java
index f02c2c2de2..873d90548a 100644
--- a/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyGui.java
+++ b/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyGui.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2021 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -38,10 +38,8 @@
 import com.jme3.scene.Geometry;
 import com.jme3.scene.shape.Box;
 import de.lessvoid.nifty.Nifty;
-import de.lessvoid.nifty.screen.Screen;
-import de.lessvoid.nifty.screen.ScreenController;
 
-public class TestNiftyGui extends SimpleApplication implements ScreenController {
+public class TestNiftyGui extends SimpleApplication {
 
     private Nifty nifty;
 
@@ -66,7 +64,8 @@ public void simpleInitApp() {
                 audioRenderer,
                 guiViewPort);
         nifty = niftyDisplay.getNifty();
-        nifty.fromXml("Interface/Nifty/HelloJme.xml", "start", this);
+        StartScreenController startScreen = new StartScreenController(this);
+        nifty.fromXml("Interface/Nifty/HelloJme.xml", "start", startScreen);
 
         // attach the nifty display to the gui view port as a processor
         guiViewPort.addProcessor(niftyDisplay);
@@ -76,24 +75,4 @@ public void simpleInitApp() {
 //        flyCam.setDragToRotate(true);
         inputManager.setCursorVisible(true);
     }
-
-    @Override
-    public void bind(Nifty nifty, Screen screen) {
-        System.out.println("bind( " + screen.getScreenId() + ")");
-    }
-
-    @Override
-    public void onStartScreen() {
-        System.out.println("onStartScreen");
-    }
-
-    @Override
-    public void onEndScreen() {
-        System.out.println("onEndScreen");
-    }
-
-    public void quit(){
-        nifty.gotoScreen("end");
-    }
-
 }
diff --git a/jme3-examples/src/main/java/jme3test/post/BloomUI.java b/jme3-examples/src/main/java/jme3test/post/BloomUI.java
index a4da8b12f2..f0aef3475c 100644
--- a/jme3-examples/src/main/java/jme3test/post/BloomUI.java
+++ b/jme3-examples/src/main/java/jme3test/post/BloomUI.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2020 jMonkeyEngine
+ * Copyright (c) 2009-2021 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -49,7 +49,7 @@ public BloomUI(InputManager inputManager, final BloomFilter filter) {
         System.out.println("-- blur Scale : press Y to increase, H to decrease");
         System.out.println("-- exposure Power : press U to increase, J to decrease");
         System.out.println("-- exposure CutOff : press I to increase, K to decrease");
-        System.out.println("-- bloom Intensity : press O to increase, P to decrease");
+        System.out.println("-- bloom Intensity : press O to increase, L to decrease");
         System.out.println("-------------------------------------------------------");
 
         inputManager.addMapping("blurScaleUp", new KeyTrigger(KeyInput.KEY_Y));
diff --git a/jme3-examples/src/main/java/jme3test/post/LightScatteringUI.java b/jme3-examples/src/main/java/jme3test/post/LightScatteringUI.java
index 10545be03b..92cd1f36f4 100644
--- a/jme3-examples/src/main/java/jme3test/post/LightScatteringUI.java
+++ b/jme3-examples/src/main/java/jme3test/post/LightScatteringUI.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2020 jMonkeyEngine
+ * Copyright (c) 2009-2021 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -53,7 +53,7 @@ public LightScatteringUI(InputManager inputManager, LightScatteringFilter proc)
         System.out.println("-- Sample number : press Y to increase, H to decrease");
         System.out.println("-- blur start : press U to increase, J to decrease");
         System.out.println("-- blur width : press I to increase, K to decrease");
-        System.out.println("-- Light density : press O to increase, P to decrease");
+        System.out.println("-- Light density : press O to increase, L to decrease");
         System.out.println("-- Toggle LS on/off : press space bar");
         System.out.println("-------------------------------------------------------");
     
diff --git a/jme3-examples/src/main/java/jme3test/post/SSAOUI.java b/jme3-examples/src/main/java/jme3test/post/SSAOUI.java
index 0598d88e00..64372bf971 100644
--- a/jme3-examples/src/main/java/jme3test/post/SSAOUI.java
+++ b/jme3-examples/src/main/java/jme3test/post/SSAOUI.java
@@ -56,7 +56,7 @@ private void init(InputManager inputManager) {
         System.out.println("-- Sample Radius : press Y to increase, H to decrease");
         System.out.println("-- AO Intensity : press U to increase, J to decrease");
         System.out.println("-- AO scale : press I to increase, K to decrease");
-        System.out.println("-- AO bias : press O to increase, P to decrease");
+        System.out.println("-- AO bias : press O to increase, L to decrease");
         System.out.println("-- Toggle AO on/off : press space bar");
         System.out.println("-- Use only AO : press Num pad 0");
         System.out.println("-- Output config declaration : press P");
diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestContextRestart.java b/jme3-examples/src/main/java/jme3test/renderer/TestContextRestart.java
new file mode 100644
index 0000000000..d0ebb73889
--- /dev/null
+++ b/jme3-examples/src/main/java/jme3test/renderer/TestContextRestart.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2009-2021 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 jme3test.renderer;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.control.AbstractControl;
+import com.jme3.scene.shape.Box;
+import com.jme3.system.AppSettings;
+
+/**
+ * Tests whether gamma correction works after a context restart. This test
+ * generates a series of boxes, each one with a slightly different shade from
+ * the other. If the boxes look the same before and after the restart, that
+ * means that gamma correction is working properly.
+ * 

+ * Note that for testing, it may be helpful to bypass the test chooser and run + * this class directly, since it can be easier to define your own settings + * beforehand. Of course, it should still workif all you need to test is the + * gamma correction, as long as you enable it in the settings dialog. + *

+ * + * @author Markil 3 + */ +public class TestContextRestart extends SimpleApplication +{ + public static final String INPUT_RESTART_CONTEXT = "SIMPLEAPP_Restart"; + + public static void main(String[] args) + { + TestContextRestart app = new TestContextRestart(); + AppSettings settings = new AppSettings(true); + settings.setGammaCorrection(true); +// settings.setRenderer(AppSettings.LWJGL_OPENGL32); + app.setSettings(settings); + app.start(); + } + + @Override + public void simpleInitApp() + { + for (int i = 0, l = 256; i < l; i += 8) + { + Geometry box = new Geometry("Box" + i, new Box(10, 200, 10)); + Material mat = new Material(this.assetManager, + "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", new ColorRGBA((float) i / 255F, 0, 0, 1)); + box.setMaterial(mat); + box.setLocalTranslation(-2.5F * (l / 2 - i), 0, -700); + box.addControl(new AbstractControl() + { + @Override + protected void controlUpdate(float tpf) + { + float[] angles = this.getSpatial() + .getLocalRotation() + .toAngles(new float[3]); + angles[0] = angles[0] + (FastMath.PI / 500F); + this.getSpatial() + .setLocalRotation(new Quaternion().fromAngles(angles)); + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) + { + + } + }); + this.rootNode.attachChild(box); + } + + this.viewPort.setBackgroundColor(ColorRGBA.Yellow); + + this.flyCam.setEnabled(false); + this.inputManager.setCursorVisible(true); + + inputManager.addMapping(INPUT_RESTART_CONTEXT, new KeyTrigger( + KeyInput.KEY_TAB)); + this.inputManager.addListener(new ActionListener() + { + @Override + public void onAction(String name, boolean isPressed, float tpf) + { + if (name.equals(INPUT_RESTART_CONTEXT)) + { + restart(); + } + } + }, INPUT_RESTART_CONTEXT); + } +} diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestDepthFuncChange.java b/jme3-examples/src/main/java/jme3test/renderer/TestDepthFuncChange.java index 3c00f40ad8..79eb084d39 100644 --- a/jme3-examples/src/main/java/jme3test/renderer/TestDepthFuncChange.java +++ b/jme3-examples/src/main/java/jme3test/renderer/TestDepthFuncChange.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -73,8 +73,8 @@ public void simpleInitApp() { rootNode.attachChild(cube2); //Bottom of the screen - //here the 2 cubes are clonned and the depthFunc for the red cube's material is set to Less - //You should see the whole bleu cube and a small part of the red cube on the right + //here the 2 cubes are cloned and the depthFunc for the red cube's material is set to Less + //You should see the whole blue cube and a small part of the red cube on the right Geometry cube3 = cube1.clone(); Geometry cube4 = cube2.clone(true); cube4.getMaterial().getAdditionalRenderState().setDepthFunc(RenderState.TestFunction.Less); diff --git a/jme3-examples/src/main/java/jme3test/texture/TestTexture3D.java b/jme3-examples/src/main/java/jme3test/texture/TestTexture3D.java index a513bffb7f..f757249185 100644 --- a/jme3-examples/src/main/java/jme3test/texture/TestTexture3D.java +++ b/jme3-examples/src/main/java/jme3test/texture/TestTexture3D.java @@ -71,7 +71,7 @@ public void simpleInitApp() { BoundingBox bb = (BoundingBox) sphere.getBound(); Vector3f min = bb.getMin(null); float[] ext = new float[]{bb.getXExtent() * 2, bb.getYExtent() * 2, bb.getZExtent() * 2}; - //we need to change the UV coordinates (the sphere is assumet to be inside the 3D image box) + //we need to change the UV coordinates (the sphere is assumed to be inside the 3D image box) sphere.clearBuffer(Type.TexCoord); VertexBuffer vb = sphere.getBuffer(Type.Position); FloatBuffer fb = (FloatBuffer) vb.getData(); @@ -112,7 +112,7 @@ public void simpleInitApp() { } /** - * This method creates a RGB8 texture with the sizes of 10x10x10 pixels. + * This method creates an RGB8 texture with the sizes of 10x10x10 pixels. */ private Texture getTexture() throws IOException { ArrayList data = new ArrayList<>(1); diff --git a/jme3-examples/src/main/java/jme3test/water/WaterUI.java b/jme3-examples/src/main/java/jme3test/water/WaterUI.java index cd2d0dfc51..f2713d885c 100644 --- a/jme3-examples/src/main/java/jme3test/water/WaterUI.java +++ b/jme3-examples/src/main/java/jme3test/water/WaterUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,7 +48,7 @@ public WaterUI(InputManager inputManager, SimpleWaterProcessor proc) { processor=proc; - System.out.println("----------------- SSAO UI Debugger --------------------"); + System.out.println("----------------- Water UI Debugger --------------------"); System.out.println("-- Water transparency : press Y to increase, H to decrease"); System.out.println("-- Water depth : press U to increase, J to decrease"); // System.out.println("-- AO scale : press I to increase, K to decrease"); diff --git a/jme3-jbullet/build.gradle b/jme3-jbullet/build.gradle index 473f03b046..05eb280680 100644 --- a/jme3-jbullet/build.gradle +++ b/jme3-jbullet/build.gradle @@ -12,9 +12,8 @@ sourceSets { } dependencies { - compile 'java3d:vecmath:1.3.1' - compile 'jbullet:jbullet' - compile 'stack-alloc:stack-alloc' + compile 'com.github.stephengold:jbullet:1.0.1' + compile 'javax.vecmath:vecmath:1.5.2' compile project(':jme3-core') compile project(':jme3-terrain') } diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java index f8aabc9497..1a7ed4aae4 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -251,42 +251,65 @@ protected int getNumSamplesToUse() { return samples; } + /** + * Reinitializes the relevant details of the context. For internal use only. + */ + protected void reinitContext() { + initContext(false); + } + + /** + * Initializes the LWJGL renderer and input for the first time. For internal + * use only. + */ protected void initContextFirstTime() { + initContext(true); + } + + /** + * Initializes the LWJGL renderer and input. + * @param first - Whether this is the first time we are initializing and we + * need to create the renderer or not. Otherwise, we'll just reset the + * renderer as needed. + */ + private void initContext(boolean first) { if (!GLContext.getCapabilities().OpenGL20) { throw new RendererException("OpenGL 2.0 or higher is " + "required for jMonkeyEngine"); } - + int vers[] = getGLVersion(settings.getRenderer()); if (vers != null) { - GL gl = new LwjglGL(); - GLExt glext = new LwjglGLExt(); - GLFbo glfbo; - - if (GLContext.getCapabilities().OpenGL30) { - glfbo = new LwjglGLFboGL3(); - } else { - glfbo = new LwjglGLFboEXT(); - } - - if (settings.getBoolean("GraphicsDebug")) { - gl = (GL) GLDebug.createProxy(gl, gl, GL.class, GL2.class, GL3.class, GL4.class); - glext = (GLExt) GLDebug.createProxy(gl, glext, GLExt.class); - glfbo = (GLFbo) GLDebug.createProxy(gl, glfbo, GLFbo.class); - } - if (settings.getBoolean("GraphicsTiming")) { - GLTimingState timingState = new GLTimingState(); - gl = (GL) GLTiming.createGLTiming(gl, timingState, GL.class, GL2.class, GL3.class, GL4.class); - glext = (GLExt) GLTiming.createGLTiming(glext, timingState, GLExt.class); - glfbo = (GLFbo) GLTiming.createGLTiming(glfbo, timingState, GLFbo.class); - } - if (settings.getBoolean("GraphicsTrace")) { - gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class); - glext = (GLExt) GLTracer.createDesktopGlTracer(glext, GLExt.class); - glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLFbo.class); + if (first) { + GL gl = new LwjglGL(); + GLExt glext = new LwjglGLExt(); + GLFbo glfbo; + + if (GLContext.getCapabilities().OpenGL30) { + glfbo = new LwjglGLFboGL3(); + } else { + glfbo = new LwjglGLFboEXT(); + } + + if (settings.getBoolean("GraphicsDebug")) { + gl = (GL) GLDebug.createProxy(gl, gl, GL.class, GL2.class, GL3.class, GL4.class); + glext = (GLExt) GLDebug.createProxy(gl, glext, GLExt.class); + glfbo = (GLFbo) GLDebug.createProxy(gl, glfbo, GLFbo.class); + } + if (settings.getBoolean("GraphicsTiming")) { + GLTimingState timingState = new GLTimingState(); + gl = (GL) GLTiming.createGLTiming(gl, timingState, GL.class, GL2.class, GL3.class, GL4.class); + glext = (GLExt) GLTiming.createGLTiming(glext, timingState, GLExt.class); + glfbo = (GLFbo) GLTiming.createGLTiming(glfbo, timingState, GLFbo.class); + } + if (settings.getBoolean("GraphicsTrace")) { + gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class); + glext = (GLExt) GLTracer.createDesktopGlTracer(glext, GLExt.class); + glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLFbo.class); + } + renderer = new GLRenderer(gl, glext, glfbo); + renderer.initialize(); } - renderer = new GLRenderer(gl, glext, glfbo); - renderer.initialize(); } else { throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer()); } @@ -296,19 +319,20 @@ protected void initContextFirstTime() { renderer.setMainFrameBufferSrgb(settings.isGammaCorrection()); renderer.setLinearizeSrgbImages(settings.isGammaCorrection()); - // Init input - if (keyInput != null) { - keyInput.initialize(); - } + if (first) { + // Init input + if (keyInput != null) { + keyInput.initialize(); + } - if (mouseInput != null) { - mouseInput.initialize(); - } + if (mouseInput != null) { + mouseInput.initialize(); + } - if (joyInput != null) { - joyInput.initialize(); + if (joyInput != null) { + joyInput.initialize(); + } } - } @SuppressWarnings("unchecked") diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java index bbf02adacd..f7c455728c 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java @@ -185,6 +185,11 @@ public void runLoop(){ logger.log(Level.SEVERE, "Failed to set display settings!", ex); } listener.reshape(settings.getWidth(), settings.getHeight()); + if (renderable.get()) { + reinitContext(); + } else { + assert getType() == Type.Canvas; + } logger.fine("Display restarted."); } else if (Display.wasResized()) { int newWidth = Display.getWidth(); diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java index 536c2a18a5..df73583754 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java @@ -174,7 +174,28 @@ protected int getNumSamplesToUse() { return samples; } + /** + * Reinitializes the relevant details of the context. For internal use only. + */ + protected void reinitContext() { + initContext(false); + } + + /** + * Initializes the LWJGL renderer and input for the first time. For internal + * use only. + */ protected void initContextFirstTime() { + initContext(true); + } + + /** + * Initializes the LWJGL renderer and input. + * @param first - Whether this is the first time we are initializing and we + * need to create the renderer or not. Otherwise, we'll just reset the + * renderer as needed. + */ + private void initContext(boolean first) { final String renderer = settings.getRenderer(); final GLCapabilities capabilities = createCapabilities(!renderer.equals(AppSettings.LWJGL_OPENGL2)); @@ -185,36 +206,38 @@ protected void initContextFirstTime() { throw new UnsupportedOperationException("Unsupported renderer: " + renderer); } - GL gl = new LwjglGL(); - GLExt glext = new LwjglGLExt(); - GLFbo glfbo; + if (first) { + GL gl = new LwjglGL(); + GLExt glext = new LwjglGLExt(); + GLFbo glfbo; - if (capabilities.OpenGL30) { - glfbo = new LwjglGLFboGL3(); - } else { - glfbo = new LwjglGLFboEXT(); - } + if (capabilities.OpenGL30) { + glfbo = new LwjglGLFboGL3(); + } else { + glfbo = new LwjglGLFboEXT(); + } - if (settings.getBoolean("GraphicsDebug")) { - gl = (GL) GLDebug.createProxy(gl, gl, GL.class, GL2.class, GL3.class, GL4.class); - glext = (GLExt) GLDebug.createProxy(gl, glext, GLExt.class); - glfbo = (GLFbo) GLDebug.createProxy(gl, glfbo, GLFbo.class); - } + if (settings.getBoolean("GraphicsDebug")) { + gl = (GL) GLDebug.createProxy(gl, gl, GL.class, GL2.class, GL3.class, GL4.class); + glext = (GLExt) GLDebug.createProxy(gl, glext, GLExt.class); + glfbo = (GLFbo) GLDebug.createProxy(gl, glfbo, GLFbo.class); + } - if (settings.getBoolean("GraphicsTiming")) { - GLTimingState timingState = new GLTimingState(); - gl = (GL) GLTiming.createGLTiming(gl, timingState, GL.class, GL2.class, GL3.class, GL4.class); - glext = (GLExt) GLTiming.createGLTiming(glext, timingState, GLExt.class); - glfbo = (GLFbo) GLTiming.createGLTiming(glfbo, timingState, GLFbo.class); - } + if (settings.getBoolean("GraphicsTiming")) { + GLTimingState timingState = new GLTimingState(); + gl = (GL) GLTiming.createGLTiming(gl, timingState, GL.class, GL2.class, GL3.class, GL4.class); + glext = (GLExt) GLTiming.createGLTiming(glext, timingState, GLExt.class); + glfbo = (GLFbo) GLTiming.createGLTiming(glfbo, timingState, GLFbo.class); + } - if (settings.getBoolean("GraphicsTrace")) { - gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class); - glext = (GLExt) GLTracer.createDesktopGlTracer(glext, GLExt.class); - glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLFbo.class); - } + if (settings.getBoolean("GraphicsTrace")) { + gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class); + glext = (GLExt) GLTracer.createDesktopGlTracer(glext, GLExt.class); + glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLFbo.class); + } - this.renderer = new GLRenderer(gl, glext, glfbo); + this.renderer = new GLRenderer(gl, glext, glfbo); + } this.renderer.initialize(); if (capabilities.GL_ARB_debug_output && settings.getBoolean("GraphicsDebug")) { @@ -224,36 +247,38 @@ protected void initContextFirstTime() { this.renderer.setMainFrameBufferSrgb(settings.isGammaCorrection()); this.renderer.setLinearizeSrgbImages(settings.isGammaCorrection()); - // Init input - if (keyInput != null) { - keyInput.initialize(); - } - - if (mouseInput != null) { - mouseInput.initialize(); - } - - if (joyInput != null) { - joyInput.initialize(); - } + if (first) { + // Init input + if (keyInput != null) { + keyInput.initialize(); + } - GLFW.glfwSetJoystickCallback(new GLFWJoystickCallback() { - @Override - public void invoke(int jid, int event) { + if (mouseInput != null) { + mouseInput.initialize(); + } - // Invoke the disconnected event before we reload the joysticks or we lose the reference to it. - // Invoke the connected event after we reload the joysticks to obtain the reference to it. + if (joyInput != null) { + joyInput.initialize(); + } - if ( event == GLFW.GLFW_CONNECTED ) { - joyInput.reloadJoysticks(); - joyInput.fireJoystickConnectedEvent(jid); + GLFW.glfwSetJoystickCallback(new GLFWJoystickCallback() { + @Override + public void invoke(int jid, int event) { + + // Invoke the disconnected event before we reload the joysticks or we lose the reference to it. + // Invoke the connected event after we reload the joysticks to obtain the reference to it. + + if ( event == GLFW.GLFW_CONNECTED ) { + joyInput.reloadJoysticks(); + joyInput.fireJoystickConnectedEvent(jid); + } + else { + joyInput.fireJoystickDisconnectedEvent(jid); + joyInput.reloadJoysticks(); + } } - else { - joyInput.fireJoystickDisconnectedEvent(jid); - joyInput.reloadJoysticks(); - } - } - }); + }); + } renderable.set(true); } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java index 66515e9b78..d04ca57cbc 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java @@ -576,6 +576,8 @@ private void restartContext() { } catch (Exception ex) { LOGGER.log(Level.SEVERE, "Failed to set display settings!", ex); } + // Reinitialize context flags and such + reinitContext(); // We need to reinit the mouse and keyboard input as they are tied to a window handle if (keyInput != null && keyInput.isInitialized()) { diff --git a/jme3-testdata/src/main/resources/Interface/Nifty/HelloJme.xml b/jme3-testdata/src/main/resources/Interface/Nifty/HelloJme.xml index c945de6e2f..fc16771ff9 100644 --- a/jme3-testdata/src/main/resources/Interface/Nifty/HelloJme.xml +++ b/jme3-testdata/src/main/resources/Interface/Nifty/HelloJme.xml @@ -1,6 +1,6 @@ - + diff --git a/lib/jbullet.jar b/lib/jbullet.jar deleted file mode 100644 index 43926d5df8..0000000000 Binary files a/lib/jbullet.jar and /dev/null differ diff --git a/lib/stack-alloc.jar b/lib/stack-alloc.jar deleted file mode 100644 index ab1d988ced..0000000000 Binary files a/lib/stack-alloc.jar and /dev/null differ