Explorar o código

Merge remote-tracking branch 'official/master' into #2379_remove_OSVR

# Conflicts:
#	jme3-vr/src/main/java/com/jme3/input/vr/osvr/OSVR.java
Richard Tingle hai 6 meses
pai
achega
bcd0cabeeb
Modificáronse 20 ficheiros con 728 adicións e 326 borrados
  1. 1 1
      LICENSE.md
  2. 1 1
      gradle/libs.versions.toml
  3. 11 2
      jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java
  4. 38 23
      jme3-android/src/main/java/com/jme3/view/surfaceview/JmeSurfaceView.java
  5. 6 9
      jme3-core/src/main/java/com/jme3/anim/AnimComposer.java
  6. 73 135
      jme3-core/src/main/java/com/jme3/anim/SingleLayerInfluenceMask.java
  7. 85 0
      jme3-core/src/main/java/com/jme3/anim/tween/action/BlendSpace.java
  8. 91 0
      jme3-core/src/main/java/com/jme3/input/RawInputListenerAdapter.java
  9. 25 4
      jme3-core/src/main/java/com/jme3/math/Spline.java
  10. 4 30
      jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag
  11. 2 2
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert
  12. 3 4
      jme3-core/src/main/resources/Common/ShaderLib/MaterialFog.glsllib
  13. 7 15
      jme3-core/src/main/resources/Common/ShaderLib/TangentUtils.glsllib
  14. 41 7
      jme3-core/src/main/resources/Common/ShaderLib/TriPlanarUtils.glsllib
  15. 29 11
      jme3-core/src/main/resources/Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib
  16. 175 0
      jme3-core/src/test/java/com/jme3/math/SplineTest.java
  17. 24 18
      jme3-desktop/src/main/java/com/jme3/input/awt/AwtMouseInput.java
  18. 108 57
      jme3-examples/src/main/java/jme3test/model/anim/TestSingleLayerInfluenceMask.java
  19. 2 2
      jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java
  20. 2 5
      jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.vert

+ 1 - 1
LICENSE.md

@@ -1,4 +1,4 @@
-Copyright (c) 2009-2024 jMonkeyEngine.
+Copyright (c) 2009-2025 jMonkeyEngine.
 
 
 Redistribution and use in source and binary forms, with or without
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 modification, are permitted provided that the following conditions

+ 1 - 1
gradle/libs.versions.toml

@@ -3,7 +3,7 @@
 [versions]
 [versions]
 
 
 checkstyle = "9.3"
 checkstyle = "9.3"
-lwjgl3 = "3.3.4"
+lwjgl3 = "3.3.6"
 nifty = "1.4.3"
 nifty = "1.4.3"
 
 
 [libraries]
 [libraries]

+ 11 - 2
jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2009-2023 jMonkeyEngine
+ * Copyright (c) 2009-2025 jMonkeyEngine
  * All rights reserved.
  * All rights reserved.
  *
  *
  * Redistribution and use in source and binary forms, with or without
  * Redistribution and use in source and binary forms, with or without
@@ -247,10 +247,19 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
             }
             }
 
 
             listener.destroy();
             listener.destroy();
-
+            // releases the view holder from the Android Input Resources
+            // releasing the view enables the context instance to be
+            // reclaimed by the GC.
+            // if not released; it leads to a weak reference leak
+            // disabling the destruction of the Context View Holder.
+            androidInput.setView(null);
+
+            // nullifying the references
+            // signals their memory to be reclaimed
             listener = null;
             listener = null;
             renderer = null;
             renderer = null;
             timer = null;
             timer = null;
+            androidInput = null;
 
 
             // do android specific cleaning here
             // do android specific cleaning here
             logger.fine("Display destroyed.");
             logger.fine("Display destroyed.");

+ 38 - 23
jme3-android/src/main/java/com/jme3/view/surfaceview/JmeSurfaceView.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2009-2022 jMonkeyEngine
+ * Copyright (c) 2009-2025 jMonkeyEngine
  * All rights reserved.
  * All rights reserved.
  *
  *
  * Redistribution and use in source and binary forms, with or without
  * Redistribution and use in source and binary forms, with or without
@@ -239,11 +239,7 @@ public class JmeSurfaceView extends RelativeLayout implements SystemListener, Di
     }
     }
 
 
     private void removeGLSurfaceView() {
     private void removeGLSurfaceView() {
-        ((Activity) getContext()).runOnUiThread(() -> {
-            if (glSurfaceView != null) {
-                JmeSurfaceView.this.removeView(glSurfaceView);
-            }
-        });
+        ((Activity) getContext()).runOnUiThread(() -> JmeSurfaceView.this.removeView(glSurfaceView));
     }
     }
 
 
     @Override
     @Override
@@ -265,19 +261,34 @@ public class JmeSurfaceView extends RelativeLayout implements SystemListener, Di
     public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
     public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
         switch (event) {
         switch (event) {
             case ON_DESTROY:
             case ON_DESTROY:
-                /*destroy only if the policy flag is enabled*/
-                if (destructionPolicy == DestructionPolicy.DESTROY_WHEN_FINISH) {
-                    legacyApplication.stop(!isGLThreadPaused());
-                }
+                // activity is off the foreground stack
+                // activity is being destructed completely as a result of Activity#finish()
+                // this is a killable automata state!
+                jmeSurfaceViewLogger.log(Level.INFO, "Hosting Activity has been destructed.");
                 break;
                 break;
             case ON_PAUSE:
             case ON_PAUSE:
-                loseFocus();
+                // activity is still on the foreground stack but not
+                // on the topmost level or before transition to stopped/hidden or destroyed state
+                // as a result of dispatch to Activity#finish()
+                // activity is no longer visible and is out of foreground
+                if (((Activity) getContext()).isFinishing()) {
+                    if (destructionPolicy == DestructionPolicy.DESTROY_WHEN_FINISH) {
+                        legacyApplication.stop(!isGLThreadPaused());
+                    } else if (destructionPolicy == DestructionPolicy.KEEP_WHEN_FINISH) {
+                        jmeSurfaceViewLogger.log(Level.INFO, "Context stops, but game is still running.");
+                    }
+                } else {
+                    loseFocus();
+                }
                 break;
                 break;
             case ON_RESUME:
             case ON_RESUME:
+                // activity is back to the topmost of the
+                // foreground stack
                 gainFocus();
                 gainFocus();
                 break;
                 break;
             case ON_STOP:
             case ON_STOP:
-                jmeSurfaceViewLogger.log(Level.INFO, "Context stops, but game is still running");
+                // activity is out off the foreground stack or being destructed by a finishing dispatch
+                // this is a killable automata state!
                 break;
                 break;
         }
         }
     }
     }
@@ -404,13 +415,13 @@ public class JmeSurfaceView extends RelativeLayout implements SystemListener, Di
 
 
     @Override
     @Override
     public void destroy() {
     public void destroy() {
-        /*skip the destroy block if the invoking instance is null*/
-        if (legacyApplication == null) {
-            return;
+        if (glSurfaceView != null) {
+            removeGLSurfaceView();
+        }
+        if (legacyApplication != null) {
+            legacyApplication.destroy();
         }
         }
-        removeGLSurfaceView();
-        legacyApplication.destroy();
-        /*help the Dalvik Garbage collector to destruct the pointers, by making them nullptr*/
+        /*help the Dalvik Garbage collector to destruct the objects, by releasing their references*/
         /*context instances*/
         /*context instances*/
         legacyApplication = null;
         legacyApplication = null;
         appSettings = null;
         appSettings = null;
@@ -430,10 +441,10 @@ public class JmeSurfaceView extends RelativeLayout implements SystemListener, Di
         onRendererCompleted = null;
         onRendererCompleted = null;
         onExceptionThrown = null;
         onExceptionThrown = null;
         onLayoutDrawn = null;
         onLayoutDrawn = null;
-        /*nullifying the static memory (pushing zero to registers to prepare for a clean use)*/
         GameState.setLegacyApplication(null);
         GameState.setLegacyApplication(null);
         GameState.setFirstUpdatePassed(false);
         GameState.setFirstUpdatePassed(false);
-        jmeSurfaceViewLogger.log(Level.INFO, "Context and Game have been destructed");
+        JmeAndroidSystem.setView(null);
+        jmeSurfaceViewLogger.log(Level.INFO, "Context and Game have been destructed.");
     }
     }
 
 
     @Override
     @Override
@@ -516,11 +527,13 @@ public class JmeSurfaceView extends RelativeLayout implements SystemListener, Di
             /*register this Ui Component as an observer to the context of jmeSurfaceView only if this context is a LifeCycleOwner*/
             /*register this Ui Component as an observer to the context of jmeSurfaceView only if this context is a LifeCycleOwner*/
             if (getContext() instanceof LifecycleOwner) {
             if (getContext() instanceof LifecycleOwner) {
                 ((LifecycleOwner) getContext()).getLifecycle().addObserver(JmeSurfaceView.this);
                 ((LifecycleOwner) getContext()).getLifecycle().addObserver(JmeSurfaceView.this);
+                jmeSurfaceViewLogger.log(Level.INFO, "Command binding SurfaceView to the Activity Lifecycle.");
             }
             }
         } else {
         } else {
             /*un-register this Ui Component as an observer to the context of jmeSurfaceView only if this context is a LifeCycleOwner*/
             /*un-register this Ui Component as an observer to the context of jmeSurfaceView only if this context is a LifeCycleOwner*/
             if (getContext() instanceof LifecycleOwner) {
             if (getContext() instanceof LifecycleOwner) {
                 ((LifecycleOwner) getContext()).getLifecycle().removeObserver(JmeSurfaceView.this);
                 ((LifecycleOwner) getContext()).getLifecycle().removeObserver(JmeSurfaceView.this);
+                jmeSurfaceViewLogger.log(Level.INFO, "Command removing SurfaceView from the Activity Lifecycle.");
             }
             }
         }
         }
     }
     }
@@ -917,7 +930,7 @@ public class JmeSurfaceView extends RelativeLayout implements SystemListener, Di
     }
     }
 
 
     /**
     /**
-     * Determines whether the app context would be destructed
+     * Determines whether the app context would be destructed as a result of dispatching {@link Activity#finish()}
      * with the holder activity context in case of {@link DestructionPolicy#DESTROY_WHEN_FINISH} or be
      * with the holder activity context in case of {@link DestructionPolicy#DESTROY_WHEN_FINISH} or be
      * spared for a second use in case of {@link DestructionPolicy#KEEP_WHEN_FINISH}.
      * spared for a second use in case of {@link DestructionPolicy#KEEP_WHEN_FINISH}.
      * Default value is : {@link DestructionPolicy#DESTROY_WHEN_FINISH}.
      * Default value is : {@link DestructionPolicy#DESTROY_WHEN_FINISH}.
@@ -926,12 +939,14 @@ public class JmeSurfaceView extends RelativeLayout implements SystemListener, Di
      */
      */
     public enum DestructionPolicy {
     public enum DestructionPolicy {
         /**
         /**
-         * Finishes the game context with the activity context (ignores the static memory {@link GameState#legacyApplication}).
+         * Finishes the game context with the activity context (ignores the static memory {@link GameState#legacyApplication})
+         * as a result of dispatching {@link Activity#finish()}.
          */
          */
         DESTROY_WHEN_FINISH,
         DESTROY_WHEN_FINISH,
         /**
         /**
          * Spares the game context inside a static memory {@link GameState#legacyApplication}
          * Spares the game context inside a static memory {@link GameState#legacyApplication}
-         * when the activity context is destroyed, but the app stills in the background.
+         * when the activity context is destroyed dispatching {@link Activity#finish()}, but the {@link android.app.Application}
+         * stills in the background.
          */
          */
         KEEP_WHEN_FINISH
         KEEP_WHEN_FINISH
     }
     }

+ 6 - 9
jme3-core/src/main/java/com/jme3/anim/AnimComposer.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2009-2024 jMonkeyEngine
+ * Copyright (c) 2009-2025 jMonkeyEngine
  * All rights reserved.
  * All rights reserved.
  *
  *
  * Redistribution and use in source and binary forms, with or without
  * Redistribution and use in source and binary forms, with or without
@@ -313,24 +313,21 @@ public class AnimComposer extends AbstractControl {
     /**
     /**
      * Add a layer to this composer.
      * Add a layer to this composer.
      *
      *
-     * @param name The desired name for the new layer
-     * @param mask The desired mask for the new layer (alias created)
-     * @return a new layer
+     * @param name the desired name for the new layer
+     * @param mask the desired mask for the new layer (alias created)
      */
      */
-    public AnimLayer makeLayer(String name, AnimationMask mask) {
+    public void makeLayer(String name, AnimationMask mask) {
         AnimLayer l = new AnimLayer(name, mask);
         AnimLayer l = new AnimLayer(name, mask);
         layers.put(name, l);
         layers.put(name, l);
-        return l;
     }
     }
 
 
     /**
     /**
      * Remove specified layer. This will stop the current action on this layer.
      * Remove specified layer. This will stop the current action on this layer.
      *
      *
      * @param name The name of the layer to remove.
      * @param name The name of the layer to remove.
-     * @return The removed layer.
      */
      */
-    public AnimLayer removeLayer(String name) {
-        return layers.remove(name);
+    public void removeLayer(String name) {
+        layers.remove(name);
     }
     }
 
 
     /**
     /**

+ 73 - 135
jme3-core/src/main/java/com/jme3/anim/SingleLayerInfluenceMask.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2024 jMonkeyEngine
+ * Copyright (c) 2025 jMonkeyEngine
  * All rights reserved.
  * All rights reserved.
  *
  *
  * Redistribution and use in source and binary forms, with or without
  * Redistribution and use in source and binary forms, with or without
@@ -31,188 +31,126 @@
  */
  */
 package com.jme3.anim;
 package com.jme3.anim;
 
 
-import com.jme3.scene.Spatial;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
 
 
 /**
 /**
- * Mask that excludes joints from participating in the layer
- * if a higher layer is using those joints in an animation.
- * 
+ * Mask that excludes joints from participating in the layer if a higher layer
+ * is using those joints in an animation.
+ *
  * @author codex
  * @author codex
  */
  */
 public class SingleLayerInfluenceMask extends ArmatureMask {
 public class SingleLayerInfluenceMask extends ArmatureMask {
-    
-    private final String layer;
-    private final AnimComposer anim;
-    private final SkinningControl skin;
-    private boolean checkUpperLayers = true;
-    
-    /**
-     * @param layer The layer this mask is targeted for. It is important
-     * that this match the name of the layer this mask is (or will be) part of. You
-     * can use {@link makeLayer} to ensure this.
-     * @param spatial Spatial containing necessary controls ({@link AnimComposer} and {@link SkinningControl})
-     */
-    public SingleLayerInfluenceMask(String layer, Spatial spatial) {
-        super();
-        this.layer = layer;
-        anim = spatial.getControl(AnimComposer.class);
-        skin = spatial.getControl(SkinningControl.class);
-    }
-    /**
-     * @param layer The layer this mask is targeted for. It is important
-     * that this match the name of the layer this mask is (or will be) part of. You
-     * can use {@link makeLayer} to ensure this.
-     * @param anim anim composer this mask is assigned to
-     * @param skin skinning control complimenting the anim composer.
-     */
-    public SingleLayerInfluenceMask(String layer, AnimComposer anim, SkinningControl skin) {
-        super();
-        this.layer = layer;
-        this.anim = anim;
-        this.skin = skin;
-    }
-    
-    /**
-     * Makes a layer from this mask.
-     */
-    public void makeLayer() {
-        anim.makeLayer(layer, this);
-    }
-    
-    /**
-     * Adds all joints to this mask.
-     * @return this.instance
-     */
-    public SingleLayerInfluenceMask addAll() {
-        for (Joint j : skin.getArmature().getJointList()) {
-            super.addBones(skin.getArmature(), j.getName());
-        }
-        return this;
-    }
+
+    private String targetLayer;
+    private AnimComposer animComposer;
     
     
     /**
     /**
-     * Adds the given joint and all its children to this mask.
-     * @param joint
-     * @return this instance
+     * For serialization only. Do not use
      */
      */
-    public SingleLayerInfluenceMask addFromJoint(String joint) {
-        super.addFromJoint(skin.getArmature(), joint);
-        return this;
+    protected SingleLayerInfluenceMask() {
     }
     }
     
     
     /**
     /**
-     * Adds the given joints to this mask.
-     * @param joints
-     * @return this instance
+     * Instantiate a mask that affects all joints in the specified Armature.
+     *
+     * @param targetLayer  The layer this mask is targeted for.
+     * @param animComposer The animation composer associated with this mask.
+     * @param armature     The Armature containing the joints.
      */
      */
-    public SingleLayerInfluenceMask addJoints(String... joints) {
-        super.addBones(skin.getArmature(), joints);
-        return this;
+    public SingleLayerInfluenceMask(String targetLayer, AnimComposer animComposer, Armature armature) {
+        super(armature);
+        this.targetLayer = targetLayer;
+        this.animComposer = animComposer;
     }
     }
     
     
     /**
     /**
-     * Makes this mask check if each joint is being used by a higher layer
-     * before it uses them.
-     * <p>Not checking is more efficient, but checking can avoid some
-     * interpolation issues between layers. Default=true
-     * @param check 
-     * @return this instance
+     * Instantiate a mask that affects no joints.
+     * 
+     * @param targetLayer  The layer this mask is targeted for.
+     * @param animComposer The animation composer associated with this mask.
      */
      */
-    public SingleLayerInfluenceMask setCheckUpperLayers(boolean check) {
-        checkUpperLayers = check;
-        return this;
+    public SingleLayerInfluenceMask(String targetLayer, AnimComposer animComposer) {
+        this.targetLayer = targetLayer;
+        this.animComposer = animComposer;
     }
     }
-    
+
     /**
     /**
      * Get the layer this mask is targeted for.
      * Get the layer this mask is targeted for.
-     * <p>It is extremely important that this value match the actual layer
-     * this is included in, because checking upper layers may not work if
-     * they are different.
-     * @return target layer
+     *
+     * @return The target layer
      */
      */
     public String getTargetLayer() {
     public String getTargetLayer() {
-        return layer;
+        return targetLayer;
     }
     }
-    
-    /**
-     * Get the {@link AnimComposer} this mask is for.
-     * @return anim composer
-     */
-    public AnimComposer getAnimComposer() {
-        return anim;
-    }
-    
+
     /**
     /**
-     * Get the {@link SkinningControl} this mask is for.
-     * @return skinning control
+     * Sets the animation composer for this mask.
+     *
+     * @param animComposer The new animation composer.
      */
      */
-    public SkinningControl getSkinningControl() {
-        return skin;
+    public void setAnimComposer(AnimComposer animComposer) {
+        this.animComposer = animComposer;
     }
     }
-    
+
     /**
     /**
-     * Returns true if this mask is checking upper layers for joint use.
-     * @return 
+     * Checks if the specified target is contained within this mask.
+     *
+     * @param target The target to check.
+     * @return True if the target is contained within this mask, false otherwise.
      */
      */
-    public boolean isCheckUpperLayers() {
-        return checkUpperLayers;
-    }
-    
     @Override
     @Override
     public boolean contains(Object target) {
     public boolean contains(Object target) {
-        return simpleContains(target) && (!checkUpperLayers || !isAffectedByUpperLayers(target));
+        return simpleContains(target) && (animComposer == null || !isAffectedByUpperLayers(target));
     }
     }
-    
+
     private boolean simpleContains(Object target) {
     private boolean simpleContains(Object target) {
         return super.contains(target);
         return super.contains(target);
     }
     }
-    
+
     private boolean isAffectedByUpperLayers(Object target) {
     private boolean isAffectedByUpperLayers(Object target) {
         boolean higher = false;
         boolean higher = false;
-        for (String name : anim.getLayerNames()) {
-            if (name.equals(layer)) {
+        for (String layerName : animComposer.getLayerNames()) {
+            if (layerName.equals(targetLayer)) {
                 higher = true;
                 higher = true;
                 continue;
                 continue;
             }
             }
             if (!higher) {
             if (!higher) {
                 continue;
                 continue;
             }
             }
-            AnimLayer lyr = anim.getLayer(name);  
-            // if there is no action playing, no joints are used, so we can skip
-            if (lyr.getCurrentAction() == null) continue;
-            if (lyr.getMask() instanceof SingleLayerInfluenceMask) {
-                // dodge some needless recursion by calling a simpler method
-                if (((SingleLayerInfluenceMask)lyr.getMask()).simpleContains(target)) {
+            
+            AnimLayer animLayer = animComposer.getLayer(layerName);
+            if (animLayer.getCurrentAction() != null) {
+                AnimationMask mask = animLayer.getMask();
+                
+                if (mask instanceof SingleLayerInfluenceMask) {
+                    // dodge some needless recursion by calling a simpler method
+                    if (((SingleLayerInfluenceMask) mask).simpleContains(target)) {
+                        return true;
+                    }
+                } else if (mask != null && mask.contains(target)) {
                     return true;
                     return true;
                 }
                 }
             }
             }
-            else if (lyr.getMask().contains(target)) {
-                return true;
-            }
         }
         }
         return false;
         return false;
     }
     }
     
     
-    /**
-     * Creates an {@code SingleLayerInfluenceMask} for all joints.
-     * @param layer layer the returned mask is, or will be, be assigned to
-     * @param spatial spatial containing anim composer and skinning control
-     * @return new mask
-     */
-    public static SingleLayerInfluenceMask all(String layer, Spatial spatial) {
-        return new SingleLayerInfluenceMask(layer, spatial).addAll();
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(targetLayer, "targetLayer", null);
     }
     }
-    
-    /**
-     * Creates an {@code SingleLayerInfluenceMask} for all joints.
-     * @param layer layer the returned mask is, or will be, assigned to
-     * @param anim anim composer
-     * @param skin skinning control
-     * @return new mask
-     */
-    public static SingleLayerInfluenceMask all(String layer, AnimComposer anim, SkinningControl skin) {
-        return new SingleLayerInfluenceMask(layer, anim, skin).addAll();
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        targetLayer = ic.readString("targetLayer", null);
     }
     }
-    
-}
 
 
+}

+ 85 - 0
jme3-core/src/main/java/com/jme3/anim/tween/action/BlendSpace.java

@@ -1,10 +1,95 @@
+/*
+ * Copyright (c) 2009-2025 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
 package com.jme3.anim.tween.action;
 package com.jme3.anim.tween.action;
 
 
+/**
+ * A provider interface which provides a value {@link BlendSpace#getWeight()} to control the blending between 2 successive actions in a {@link BlendAction}.
+ * The blending weight is a read-only value, and it can be manipulated using the arbitrary value {@link BlendSpace#setValue(float)} during the application runtime.
+ * 
+ * <p>
+ * Notes about the blending action and its relations with the blending weight:
+ * <ul>
+ * <li> Blending is the action of mixing between 2 successive animation {@link BlendableAction}s by interpolating their transforms and 
+ * then applying the result on the assigned {@link HasLocalTransform} object, the {@link BlendSpace} provides this blending action with a blend weight value. </li>
+ * <li> The blend weight is the value for the interpolation for the target transforms. </li>
+ * <li> The blend weight value must be in this interval [0, 1]. </li>
+ * </ul>
+ * </p>
+ * 
+ * <p>
+ * Different blending weight case scenarios managed by {@link BlendAction} internally:
+ * <ul>
+ * <li> In case of (0 < Blending weight < 1), the blending is executed each update among 2 actions, the first action will use 
+ * a blend value of 1 and the second action will use the blend space weight as a value for the interpolation. </li>
+ * <li> In case of (Blending weight = 0), the blending hasn't started yet, only the first action will be interpolated at (weight = 1). </li>
+ * <li> In case of (Blending weight = 1), the blending is finished and only the second action will continue to run at (weight = 1). </li>
+ * </ul>
+ * </p>
+ * 
+ * <p>
+ * Notes about the blending weight value:
+ * <ul>
+ * <li> Negative values and values greater than 1 aren't allowed (i.e., extrapolations aren't allowed). </li>
+ * <li> For more details, see {@link BlendAction#doInterpolate(double)} and {@link BlendAction#collectTransform(HasLocalTransform, Transform, float, BlendableAction)}. </li>
+ * </ul>
+ * </p> 
+ *
+ * Created by Nehon.
+ * @see LinearBlendSpace an example of blendspace implementation
+ */
 public interface BlendSpace {
 public interface BlendSpace {
 
 
+    /**
+     * Adjusts the target blend action instance that will utilize the blend weight value provided by this blend-space implementation.
+     * 
+     * @param action the blend action instance that will utilize this blend-space (not null).
+     */
     public void setBlendAction(BlendAction action);
     public void setBlendAction(BlendAction action);
 
 
+    /**
+     * Provides the blend weight value to the assigned {@link BlendAction} instance,
+     * this value will be used for interpolating a collection of actions' transformations (i.e., keyframes).
+     * 
+     * @return the blending weight value in the range from 0 to 1, 
+     *         negative values and values above 1 aren't allowed.
+     * @see LinearBlendSpace#getWeight()
+     */
     public float getWeight();
     public float getWeight();
 
 
+    /**
+     * An arbitrary value used for adjusting the blending weight value.
+     * 
+     * @param value the value in floats.
+     * @see LinearBlendSpace#setValue(float)
+     */
     public void setValue(float value);
     public void setValue(float value);
 }
 }

+ 91 - 0
jme3-core/src/main/java/com/jme3/input/RawInputListenerAdapter.java

@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2009-2024 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.input;
+
+import com.jme3.input.event.JoyAxisEvent;
+import com.jme3.input.event.JoyButtonEvent;
+import com.jme3.input.event.KeyInputEvent;
+import com.jme3.input.event.MouseButtonEvent;
+import com.jme3.input.event.MouseMotionEvent;
+import com.jme3.input.event.TouchEvent;
+
+/**
+ * An abstract adapter class for {@link RawInputListener}.
+ *
+ * This class provides empty implementations for all methods in the
+ * {@link RawInputListener} interface, making it easier to create custom
+ * listeners by extending this class and overriding only the methods of
+ * interest.
+ */
+public abstract class RawInputListenerAdapter implements RawInputListener {
+
+    @Override
+    public void beginInput() {
+        // No-op implementation
+    }
+
+    @Override
+    public void endInput() {
+        // No-op implementation
+    }
+
+    @Override
+    public void onJoyAxisEvent(JoyAxisEvent evt) {
+        // No-op implementation
+    }
+
+    @Override
+    public void onJoyButtonEvent(JoyButtonEvent evt) {
+        // No-op implementation
+    }
+
+    @Override
+    public void onMouseMotionEvent(MouseMotionEvent evt) {
+        // No-op implementation
+    }
+
+    @Override
+    public void onMouseButtonEvent(MouseButtonEvent evt) {
+        // No-op implementation
+    }
+
+    @Override
+    public void onKeyEvent(KeyInputEvent evt) {
+        // No-op implementation
+    }
+
+    @Override
+    public void onTouchEvent(TouchEvent evt) {
+        // No-op implementation
+    }
+
+}

+ 25 - 4
jme3-core/src/main/java/com/jme3/math/Spline.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2025 jMonkeyEngine
  * All rights reserved.
  * All rights reserved.
  *
  *
  * Redistribution and use in source and binary forms, with or without
  * Redistribution and use in source and binary forms, with or without
@@ -480,7 +480,18 @@ public class Spline implements Savable {
         oc.writeSavableArrayList((ArrayList) CRcontrolPoints, "CRControlPoints", null);
         oc.writeSavableArrayList((ArrayList) CRcontrolPoints, "CRControlPoints", null);
         oc.write(curveTension, "curveTension", 0.5f);
         oc.write(curveTension, "curveTension", 0.5f);
         oc.write(cycle, "cycle", false);
         oc.write(cycle, "cycle", false);
-        oc.writeSavableArrayList((ArrayList<Float>) knots, "knots", null);
+
+        float[] knotArray;
+        if (knots == null) {
+            knotArray = null;
+        } else {
+            knotArray = new float[knots.size()];
+            for (int i = 0; i < knotArray.length; ++i) {
+                knotArray[i] = knots.get(i);
+            }
+        }
+        oc.write(knotArray, "knots", null);
+
         oc.write(weights, "weights", null);
         oc.write(weights, "weights", null);
         oc.write(basisFunctionDegree, "basisFunctionDegree", 0);
         oc.write(basisFunctionDegree, "basisFunctionDegree", 0);
     }
     }
@@ -506,12 +517,22 @@ public class Spline implements Savable {
                 segmentsLength.add(list[i]);
                 segmentsLength.add(list[i]);
             }
             }
         }
         }
-        type = in.readEnum("pathSplineType", SplineType.class, SplineType.CatmullRom);
+        type = in.readEnum("type", SplineType.class, SplineType.CatmullRom);
         totalLength = in.readFloat("totalLength", 0);
         totalLength = in.readFloat("totalLength", 0);
         CRcontrolPoints = in.readSavableArrayList("CRControlPoints", null);
         CRcontrolPoints = in.readSavableArrayList("CRControlPoints", null);
         curveTension = in.readFloat("curveTension", 0.5f);
         curveTension = in.readFloat("curveTension", 0.5f);
         cycle = in.readBoolean("cycle", false);
         cycle = in.readBoolean("cycle", false);
-        knots = in.readSavableArrayList("knots", null);
+
+        float[] knotArray = in.readFloatArray("knots", null);
+        if (knotArray == null) {
+            this.knots = null;
+        } else {
+            this.knots = new ArrayList<>(knotArray.length);
+            for (float knot : knotArray) {
+                knots.add(knot);
+            }
+        }
+
         weights = in.readFloatArray("weights", null);
         weights = in.readFloatArray("weights", null);
         basisFunctionDegree = in.readInt("basisFunctionDegree", 0);
         basisFunctionDegree = in.readInt("basisFunctionDegree", 0);
     }
     }

+ 4 - 30
jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag

@@ -6,25 +6,9 @@
     #import "Common/ShaderLib/Lighting.glsllib"
     #import "Common/ShaderLib/Lighting.glsllib"
 #endif
 #endif
 
 
-// fog - jayfella
 #ifdef USE_FOG
 #ifdef USE_FOG
-#import "Common/ShaderLib/MaterialFog.glsllib"
-varying float fog_distance;
-uniform vec4 m_FogColor;
-
-#ifdef FOG_LINEAR
-uniform vec2 m_LinearFog;
-#endif
-
-#ifdef FOG_EXP
-uniform float m_ExpFog;
-#endif
-
-#ifdef FOG_EXPSQ
-uniform float m_ExpSqFog;
-#endif
-
-#endif // end fog
+    #import "Common/ShaderLib/MaterialFog.glsllib"
+#endif 
 
 
 varying vec2 texCoord;
 varying vec2 texCoord;
 #ifdef SEPARATE_TEXCOORD
 #ifdef SEPARATE_TEXCOORD
@@ -231,21 +215,11 @@ void main(){
                            SpecularSum2.rgb * specularColor.rgb * vec3(light.y);
                            SpecularSum2.rgb * specularColor.rgb * vec3(light.y);
     #endif
     #endif
 
 
-
     // add fog after the lighting because shadows will cause the fog to darken
     // add fog after the lighting because shadows will cause the fog to darken
     // which just results in the geometry looking like it's changed color
     // which just results in the geometry looking like it's changed color
     #ifdef USE_FOG
     #ifdef USE_FOG
-        #ifdef FOG_LINEAR
-            gl_FragColor = getFogLinear(gl_FragColor, m_FogColor, m_LinearFog.x, m_LinearFog.y, fog_distance);
-        #endif
-        #ifdef FOG_EXP
-            gl_FragColor = getFogExp(gl_FragColor, m_FogColor, m_ExpFog, fog_distance);
-        #endif
-        #ifdef FOG_EXPSQ
-            gl_FragColor = getFogExpSquare(gl_FragColor, m_FogColor, m_ExpSqFog, fog_distance);
-        #endif
-    #endif // end fog
-
+        gl_FragColor = MaterialFog_calculateFogColor(vec4(gl_FragColor));
+    #endif
 
 
     gl_FragColor.a = alpha;
     gl_FragColor.a = alpha;
 }
 }

+ 2 - 2
jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert

@@ -30,7 +30,7 @@ attribute vec3 inNormal;
 varying vec3 wNormal;
 varying vec3 wNormal;
 varying vec3 wPosition;
 varying vec3 wPosition;
 
 
-varying vec4 lPosition;
+varying vec3 lPosition;
 
 
 attribute vec4 inTangent;
 attribute vec4 inTangent;
 varying vec4 wTangent;
 varying vec4 wTangent;
@@ -46,7 +46,7 @@ void main(){
     vec3 modelSpaceNorm = inNormal;
     vec3 modelSpaceNorm = inNormal;
     vec3 modelSpaceTan  = inTangent.xyz;
     vec3 modelSpaceTan  = inTangent.xyz;
     
     
-    lPosition = modelSpacePos;
+    lPosition = modelSpacePos.xyz;
     
     
     
     
     #ifdef USE_VERTEX_COLORS_AS_SUN_INTENSITY
     #ifdef USE_VERTEX_COLORS_AS_SUN_INTENSITY

+ 3 - 4
jme3-core/src/main/resources/Common/ShaderLib/MaterialFog.glsllib

@@ -1,7 +1,7 @@
+//author @jayfella
 #ifndef __MATERIAL_FOG_UTIL__
 #ifndef __MATERIAL_FOG_UTIL__
     #define __MATERIAL_FOG_UTIL__
     #define __MATERIAL_FOG_UTIL__
 
 
-
     vec4 getFogLinear(in vec4 diffuseColor, in vec4 fogColor, in float start, in float end, in float distance) {
     vec4 getFogLinear(in vec4 diffuseColor, in vec4 fogColor, in float start, in float end, in float distance) {
 
 
         float fogFactor = (end - distance) / (end - start);
         float fogFactor = (end - distance) / (end - start);
@@ -39,8 +39,7 @@
 
 
         #ifdef FOG_EXPSQ
         #ifdef FOG_EXPSQ
             uniform float m_ExpSqFog;        
             uniform float m_ExpSqFog;        
-        #endif
-        
+        #endif        
 
 
         vec4 MaterialFog_calculateFogColor(in vec4 fragColor){
         vec4 MaterialFog_calculateFogColor(in vec4 fragColor){
             #ifdef FOG_LINEAR
             #ifdef FOG_LINEAR
@@ -54,7 +53,7 @@
             #endif
             #endif
 
 
             return fragColor;
             return fragColor;
-        }           
+        }          
 
 
     #endif   
     #endif   
 #endif
 #endif

+ 7 - 15
jme3-core/src/main/resources/Common/ShaderLib/TangentUtils.glsllib

@@ -1,21 +1,13 @@
 #ifndef __TANGENT_UTILS_MODULE__
 #ifndef __TANGENT_UTILS_MODULE__
     #define __TANGENT_UTILS_MODULE__
     #define __TANGENT_UTILS_MODULE__
 
 
-    //used for calculating tangents in-shader
-   
-
-    //primarily used for terrains, since jme terrains do not store pre-calculated tangents by default (thus the tbnMat cannot be used for PBR light calculation like it is in jme's stock PBR shader)
-    vec3 calculateTangentsAndApplyToNormals(in vec3 normalIn, in vec3 worldNorm){
-        vec3 returnNorm = normalize((normalIn.xyz * vec3(2.0) - vec3(1.0)));
-
+    //used for calculating tangents in-shader for axis-aligned quads/planes/terrains   
+    //primarily used for PBR terrains, since jme terrains do not store pre-calculated tangents by default (thus the tbnMat cannot be used for PBR light calculation like it is in jme's stock PBR shader)
+    vec3 calculateTangentsAndApplyToNormals(in vec3 normalIn, in vec3 worldNorm){        
         vec3 baseNorm = worldNorm.rgb + vec3(0, 0, 1);
         vec3 baseNorm = worldNorm.rgb + vec3(0, 0, 1);
-        returnNorm *= vec3(-1, -1, 1);
-        returnNorm = baseNorm.rgb*dot(baseNorm.rgb, returnNorm.rgb)/baseNorm.z - returnNorm.rgb;
-
-        returnNorm = normalize(returnNorm);
+        normalIn = baseNorm.rgb*dot(baseNorm.rgb, normalIn.rgb)/baseNorm.z - normalIn.rgb;
+        normalIn = normalize(normalIn);
 
 
-
-        return returnNorm;
+        return normalIn;
     }
     }
-
-#endif
+#endif

+ 41 - 7
jme3-core/src/main/resources/Common/ShaderLib/TriPlanarUtils.glsllib

@@ -1,8 +1,8 @@
 #ifndef __TRIPLANAR_UTILS_MODULE__
 #ifndef __TRIPLANAR_UTILS_MODULE__
     #define __TRIPLANAR_UTILS_MODULE__
     #define __TRIPLANAR_UTILS_MODULE__
     
     
-    vec3 triBlending;
-    
+    vec3 triBlending;    
+
     void TriPlanarUtils_calculateBlending(vec3 geometryNormal){
     void TriPlanarUtils_calculateBlending(vec3 geometryNormal){
         triBlending = abs( geometryNormal );
         triBlending = abs( geometryNormal );
         triBlending = (triBlending -0.2) * 0.7;
         triBlending = (triBlending -0.2) * 0.7;
@@ -11,7 +11,8 @@
         triBlending /= vec3(b, b, b);
         triBlending /= vec3(b, b, b);
     }    
     }    
 
 
-    vec4 getTriPlanarBlend(in vec4 coords, in sampler2D map, in float scale) {
+    //  basic triplanar blend:
+    vec4 getTriPlanarBlend(in vec3 coords, in sampler2D map, in float scale) {
         vec4 col1 = texture2D( map, coords.yz * scale);
         vec4 col1 = texture2D( map, coords.yz * scale);
         vec4 col2 = texture2D( map, coords.xz * scale);
         vec4 col2 = texture2D( map, coords.xz * scale);
         vec4 col3 = texture2D( map, coords.xy * scale); 
         vec4 col3 = texture2D( map, coords.xy * scale); 
@@ -21,7 +22,8 @@
         return tex;
         return tex;
     }
     }
 
 
-    vec4 getTriPlanarBlendFromTexArray(in vec4 coords, in int idInTexArray, in float scale, in sampler2DArray texArray) {
+    //  basic triplanar blend for TextureArrays:
+    vec4 getTriPlanarBlendFromTexArray(in vec3 coords, in int idInTexArray, in float scale, in sampler2DArray texArray) {
         vec4 col1 = texture2DArray( texArray, vec3((coords.yz * scale), idInTexArray ) );
         vec4 col1 = texture2DArray( texArray, vec3((coords.yz * scale), idInTexArray ) );
         vec4 col2 = texture2DArray( texArray, vec3((coords.xz * scale), idInTexArray ) );
         vec4 col2 = texture2DArray( texArray, vec3((coords.xz * scale), idInTexArray ) );
         vec4 col3 = texture2DArray( texArray, vec3((coords.xy * scale), idInTexArray ) );
         vec4 col3 = texture2DArray( texArray, vec3((coords.xy * scale), idInTexArray ) );
@@ -29,6 +31,38 @@
         vec4 tex = col1 * triBlending.x + col2 * triBlending.y + col3 * triBlending.z;
         vec4 tex = col1 * triBlending.x + col2 * triBlending.y + col3 * triBlending.z;
 
 
         return tex;
         return tex;
-    }
-
-#endif
+    }    
+     
+    //  triplanar blend for Normal maps:
+    vec4 getTriPlanarNormalBlend(in vec3 coords, in sampler2D map, in float scale) {
+        vec4 col1 = texture2D( map, coords.yz * scale);
+        vec4 col2 = texture2D( map, coords.xz * scale);
+        vec4 col3 = texture2D( map, coords.xy * scale); 
+      
+        col1.xyz = col1.xyz * vec3(2.0) - vec3(1.0);
+        col2.xyz = col2.xyz * vec3(2.0) - vec3(1.0);
+        col3.xyz = col3.xyz * vec3(2.0) - vec3(1.0);
+        
+        // blend the results of the 3 planar projections.
+        vec4 tex = normalize(col1 * triBlending.x + col2 * triBlending.y + col3 * triBlending.z);
+      
+        return tex;
+    }    
+ 
+    //  triplanar blend for Normal maps in a TextureArray:
+    vec4 getTriPlanarNormalBlendFromTexArray(in vec3 coords, in int idInTexArray, in float scale, in sampler2DArray texArray) {
+        vec4 col1 = texture2DArray( texArray, vec3((coords.yz * scale), idInTexArray ) );
+        vec4 col2 = texture2DArray( texArray, vec3((coords.xz * scale), idInTexArray ) );
+        vec4 col3 = texture2DArray( texArray, vec3((coords.xy * scale), idInTexArray ) );
+      
+        col1.xyz = col1.xyz * vec3(2.0) - vec3(1.0);
+        col2.xyz = col2.xyz * vec3(2.0) - vec3(1.0);
+        col3.xyz = col3.xyz * vec3(2.0) - vec3(1.0);
+        
+        // blend the results of the 3 planar projections.
+        vec4 tex = normalize(col1 * triBlending.x + col2 * triBlending.y + col3 * triBlending.z);
+      
+        return tex;
+    }   
+    
+#endif

+ 29 - 11
jme3-core/src/main/resources/Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib

@@ -41,7 +41,7 @@
     #endif
     #endif
     
     
     #if defined(ENABLE_PBRLightingUtils_getLocalPosition)
     #if defined(ENABLE_PBRLightingUtils_getLocalPosition)
-        varying vec4 lPosition;
+        varying vec3 lPosition;
     #endif 
     #endif 
 
 
 
 
@@ -236,7 +236,7 @@
             Math_lengthAndNormalize(light.vector,dist,L);
             Math_lengthAndNormalize(light.vector,dist,L);
 
 
             float invRange=light.invRadius; // position.w
             float invRange=light.invRadius; // position.w
-            const float light_threshold=0.01;
+            const float light_threshold = 0.01;
 
 
             #ifdef SRGB
             #ifdef SRGB
                 light.fallOff = (1.0 - invRange * dist) / (1.0 + invRange * dist * dist); // lightDir.w
                 light.fallOff = (1.0 - invRange * dist) / (1.0 + invRange * dist * dist); // lightDir.w
@@ -272,11 +272,26 @@
 
 
     PBRSurface PBRLightingUtils_createPBRSurface(in vec3 wViewDir){
     PBRSurface PBRLightingUtils_createPBRSurface(in vec3 wViewDir){
 
 
-        PBRSurface surface;
+        PBRSurface surface; //creates a new PBRSurface
+        
         surface.position = wPosition;
         surface.position = wPosition;
         surface.viewDir = wViewDir;
         surface.viewDir = wViewDir;
         surface.geometryNormal = normalize(wNormal);
         surface.geometryNormal = normalize(wNormal);
 
 
+        //set default values
+        surface.hasTangents = false;
+        surface.hasBasicLightMap = false;        
+        surface.albedo = vec3(1.0);
+        surface.normal = surface.geometryNormal;
+        surface.emission = vec3(0.0);        
+        surface.ao = vec3(1.0);
+        surface.lightMapColor = vec3(0.0);
+        surface.alpha = 1.0;
+        surface.roughness = 1.0;
+        surface.metallic = 0.0;
+        surface.alpha = 1.0;
+        surface.exposure = 1.0;        
+        
         return surface;
         return surface;
     }
     }
 
 
@@ -285,9 +300,9 @@
         
         
         void PBRLightingUtils_readPBRSurface(inout PBRSurface surface){
         void PBRLightingUtils_readPBRSurface(inout PBRSurface surface){
 
 
-            surface.bakedLightContribution = vec3(0);
-            surface.directLightContribution = vec3(0);
-            surface.envLightContribution = vec3(0);
+            surface.bakedLightContribution = vec3(0.0);
+            surface.directLightContribution = vec3(0.0);
+            surface.envLightContribution = vec3(0.0);
 
 
             #ifdef ENABLE_PBRLightingUtils_getWorldTangent
             #ifdef ENABLE_PBRLightingUtils_getWorldTangent
                 vec3 tan = normalize(wTangent.xyz); 
                 vec3 tan = normalize(wTangent.xyz); 
@@ -297,7 +312,7 @@
 
 
             
             
             #if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP)))
             #if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP)))
-                vec3 vViewDir =  wViewDir * surface.tbnMat;  
+                vec3 vViewDir =  surface.viewDir * surface.tbnMat;  
                 #ifdef STEEP_PARALLAX
                 #ifdef STEEP_PARALLAX
                     #ifdef NORMALMAP_PARALLAX
                     #ifdef NORMALMAP_PARALLAX
                         //parallax map is stored in the alpha channel of the normal map         
                         //parallax map is stored in the alpha channel of the normal map         
@@ -429,7 +444,7 @@
                 #endif
                 #endif
                 surface.emission = emissive.rgb * pow(emissive.a, m_EmissivePower) * m_EmissiveIntensity;
                 surface.emission = emissive.rgb * pow(emissive.a, m_EmissivePower) * m_EmissiveIntensity;
             #else 
             #else 
-                surface.emission = vec3(0);
+                surface.emission = vec3(0.0);
             #endif
             #endif
 
 
             PBRLightingUtils_readSunLightExposureParams(surface);
             PBRLightingUtils_readSunLightExposureParams(surface);
@@ -573,8 +588,8 @@
 
 
 
 
             #if NB_PROBES > 0
             #if NB_PROBES > 0
-                float probeNdfSum=0;
-                float invProbeNdfSum=0;    
+                float probeNdfSum = 0.0;
+                float invProbeNdfSum = 0.0;    
 
 
                 #for i=1..4 ( #if NB_PROBES >= $i $0 #endif )
                 #for i=1..4 ( #if NB_PROBES >= $i $0 #endif )
                     vec3 probeColor$i;
                     vec3 probeColor$i;
@@ -607,7 +622,7 @@
                 #endfor
                 #endfor
 
 
                 #if NB_PROBES > 1
                 #if NB_PROBES > 1
-                    float probeWeightSum=0;
+                    float probeWeightSum = 0.0;
                     #for i=1..4 ( #if NB_PROBES >= $i $0 #endif )
                     #for i=1..4 ( #if NB_PROBES >= $i $0 #endif )
                         float probeWeight$i = ((1.0 - (probeNdf$i / probeNdfSum)) / (NB_PROBES - 1)) *  ( probeInvNdf$i / invProbeNdfSum);
                         float probeWeight$i = ((1.0 - (probeNdf$i / probeNdfSum)) / (NB_PROBES - 1)) *  ( probeInvNdf$i / invProbeNdfSum);
                         probeWeightSum += probeWeight$i;    
                         probeWeightSum += probeWeight$i;    
@@ -650,6 +665,9 @@
         else if(debugValuesMode == 7){
         else if(debugValuesMode == 7){
             outputColorForLayer.rgb = vec3(surface.alpha);          
             outputColorForLayer.rgb = vec3(surface.alpha);          
         } 
         } 
+        else if(debugValuesMode == 8){
+            outputColorForLayer.rgb = vec3(surface.geometryNormal);          
+        }
 
 
         if(debugValuesMode >= 0){
         if(debugValuesMode >= 0){
             gl_FragColor.a = 1.0;
             gl_FragColor.a = 1.0;

+ 175 - 0
jme3-core/src/test/java/com/jme3/math/SplineTest.java

@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2025 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.math;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.DesktopAssetManager;
+import com.jme3.export.binary.BinaryExporter;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Verifies that the {@link Spline} class works correctly.
+ *
+ * @author Stephen Gold
+ */
+public class SplineTest {
+    // *************************************************************************
+    // fields
+
+    private static final AssetManager assetManager = new DesktopAssetManager();
+    // *************************************************************************
+    // tests
+
+    /**
+     * Verifies that spline serialization/deserialization works correctly.
+     */
+    @Test
+    public void saveAndLoadSplines() {
+        // Serialize and deserialize a Bezier spline:
+        {
+            Vector3f[] controlPoints1 = {
+                new Vector3f(0f, 1f, 0f), new Vector3f(1f, 2f, 1f),
+                new Vector3f(1.5f, 1.5f, 1.5f), new Vector3f(2f, 0f, 1f)
+            };
+
+            Spline test1 = new Spline(
+                    Spline.SplineType.Bezier, controlPoints1, 0.1f, true);
+            Spline copy1 = BinaryExporter.saveAndLoad(assetManager, test1);
+            assertSplineEquals(test1, copy1);
+        }
+
+        // Serialize and deserialize a NURB spline:
+        {
+            List<Vector4f> controlPoints2 = new ArrayList<>(5);
+            controlPoints2.add(new Vector4f(0f, 1f, 2f, 3f));
+            controlPoints2.add(new Vector4f(3f, 1f, 4f, 0f));
+            controlPoints2.add(new Vector4f(2f, 5f, 3f, 0f));
+            controlPoints2.add(new Vector4f(3f, 2f, 3f, 1f));
+            controlPoints2.add(new Vector4f(0.5f, 1f, 0.6f, 5f));
+            List<Float> nurbKnots = new ArrayList<>(6);
+            nurbKnots.add(0.2f);
+            nurbKnots.add(0.3f);
+            nurbKnots.add(0.4f);
+            nurbKnots.add(0.43f);
+            nurbKnots.add(0.51f);
+            nurbKnots.add(0.52f);
+
+            Spline test2 = new Spline(controlPoints2, nurbKnots);
+            Spline copy2 = BinaryExporter.saveAndLoad(assetManager, test2);
+            assertSplineEquals(test2, copy2);
+        }
+
+        // Serialize and deserialize a Catmull-Rom spline:
+        {
+            List<Vector3f> controlPoints3 = new ArrayList<>(6);
+            controlPoints3.add(new Vector3f(0f, 1f, 2f));
+            controlPoints3.add(new Vector3f(3f, -1f, 4f));
+            controlPoints3.add(new Vector3f(2f, 5f, 3f));
+            controlPoints3.add(new Vector3f(3f, -2f, 3f));
+            controlPoints3.add(new Vector3f(0.5f, 1f, 0.6f));
+            controlPoints3.add(new Vector3f(-0.5f, 4f, 0.2f));
+
+            Spline test3 = new Spline(
+                    Spline.SplineType.CatmullRom, controlPoints3, 0.01f, false);
+            Spline copy3 = BinaryExporter.saveAndLoad(assetManager, test3);
+            assertSplineEquals(test3, copy3);
+        }
+
+        // Serialize and deserialize a linear spline:
+        {
+            List<Vector3f> controlPoints4 = new ArrayList<>(3);
+            controlPoints4.add(new Vector3f(3f, -1f, 4f));
+            controlPoints4.add(new Vector3f(2f, 0f, 3f));
+            controlPoints4.add(new Vector3f(3f, -2f, 3f));
+
+            Spline test4 = new Spline(
+                    Spline.SplineType.Linear, controlPoints4, 0f, true);
+            Spline copy4 = BinaryExporter.saveAndLoad(assetManager, test4);
+            assertSplineEquals(test4, copy4);
+        }
+
+        // Serialize and deserialize a default spline:
+        {
+            Spline test5 = new Spline();
+            Spline copy5 = BinaryExporter.saveAndLoad(assetManager, test5);
+            assertSplineEquals(test5, copy5);
+        }
+    }
+    // *************************************************************************
+    // private helper methods
+
+    /**
+     * Verify that the specified lists are equivalent.
+     *
+     * @param s1 the first list to compare (may be null, unaffected)
+     * @param s2 the 2nd list to compare (may be null, unaffected)
+     */
+    private static void assertListEquals(List<?> a1, List<?> a2) {
+        if (a1 != a2) {
+            Assert.assertEquals(a1.size(), a2.size());
+            for (int i = 0; i < a1.size(); ++i) {
+                Assert.assertEquals(a1.get(i), a2.get(i));
+            }
+        }
+    }
+
+    /**
+     * Verify that the specified splines are equivalent.
+     *
+     * @param s1 the first spline to compare (not null, unaffected)
+     * @param s2 the 2nd split to compare (not null, unaffected)
+     */
+    private static void assertSplineEquals(Spline s1, Spline s2) {
+        Assert.assertEquals(s1.getType(), s2.getType());
+        Assert.assertEquals(s1.isCycle(), s2.isCycle());
+
+        Assert.assertEquals(
+                s1.getBasisFunctionDegree(), s2.getBasisFunctionDegree());
+        assertListEquals(s1.getControlPoints(), s2.getControlPoints());
+        Assert.assertEquals(s1.getCurveTension(), s2.getCurveTension(), 0f);
+        assertListEquals(s1.getKnots(), s2.getKnots());
+
+        if (s1.getType() == Spline.SplineType.Nurb) {
+            // These methods throw NPEs on non-NURB splines.
+            Assert.assertEquals(s1.getMaxNurbKnot(), s2.getMaxNurbKnot(), 0f);
+            Assert.assertEquals(s1.getMinNurbKnot(), s2.getMinNurbKnot(), 0f);
+        }
+
+        assertListEquals(s1.getSegmentsLength(), s2.getSegmentsLength());
+        Assert.assertEquals(
+                s1.getTotalLength(), s2.getTotalLength(), 0f);
+        Assert.assertArrayEquals(s1.getWeights(), s2.getWeights(), 0f);
+    }
+}

+ 24 - 18
jme3-desktop/src/main/java/com/jme3/input/awt/AwtMouseInput.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2024 jMonkeyEngine
  * All rights reserved.
  * All rights reserved.
  *
  *
  * Redistribution and use in source and binary forms, with or without
  * Redistribution and use in source and binary forms, with or without
@@ -36,10 +36,16 @@ import com.jme3.input.MouseInput;
 import com.jme3.input.RawInputListener;
 import com.jme3.input.RawInputListener;
 import com.jme3.input.event.MouseButtonEvent;
 import com.jme3.input.event.MouseButtonEvent;
 import com.jme3.input.event.MouseMotionEvent;
 import com.jme3.input.event.MouseMotionEvent;
-import java.awt.*;
+
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.Point;
+import java.awt.Robot;
+import java.awt.Toolkit;
 import java.awt.event.*;
 import java.awt.event.*;
 import java.awt.image.BufferedImage;
 import java.awt.image.BufferedImage;
 import java.util.ArrayList;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.logging.Logger;
 import javax.swing.SwingUtilities;
 import javax.swing.SwingUtilities;
@@ -64,8 +70,8 @@ public class AwtMouseInput implements MouseInput, MouseListener, MouseWheelListe
 
 
     private Component component;
     private Component component;
 
 
-    private final java.util.List<MouseButtonEvent> eventQueue = new ArrayList<>();
-    private final java.util.List<MouseButtonEvent> eventQueueCopy = new ArrayList<>();
+    private final List<MouseButtonEvent> eventQueue = new ArrayList<>();
+    private final List<MouseButtonEvent> eventQueueCopy = new ArrayList<>();
 
 
     private int lastEventX;
     private int lastEventX;
     private int lastEventY;
     private int lastEventY;
@@ -79,6 +85,7 @@ public class AwtMouseInput implements MouseInput, MouseListener, MouseWheelListe
     private Point centerLocation;
     private Point centerLocation;
     private Point centerLocationOnScreen;
     private Point centerLocationOnScreen;
     private Point lastKnownLocation;
     private Point lastKnownLocation;
+    private Point grabLocation;
     private boolean isRecentering;
     private boolean isRecentering;
     private boolean cursorMoved;
     private boolean cursorMoved;
     private int eventsSinceRecenter;
     private int eventsSinceRecenter;
@@ -88,6 +95,7 @@ public class AwtMouseInput implements MouseInput, MouseListener, MouseWheelListe
         centerLocation = new Point();
         centerLocation = new Point();
         centerLocationOnScreen = new Point();
         centerLocationOnScreen = new Point();
         lastKnownLocation = new Point();
         lastKnownLocation = new Point();
+        grabLocation = new Point();
 
 
         try {
         try {
             robot = new Robot();
             robot = new Robot();
@@ -111,6 +119,7 @@ public class AwtMouseInput implements MouseInput, MouseListener, MouseWheelListe
             lastEventY = 0;
             lastEventY = 0;
             lastEventWheel = 0;
             lastEventWheel = 0;
             location = new Point();
             location = new Point();
+            grabLocation = new Point();
             centerLocation = new Point();
             centerLocation = new Point();
             centerLocationOnScreen = new Point();
             centerLocationOnScreen = new Point();
             lastKnownLocation = new Point();
             lastKnownLocation = new Point();
@@ -144,28 +153,21 @@ public class AwtMouseInput implements MouseInput, MouseListener, MouseWheelListe
     public long getInputTimeNanos() {
     public long getInputTimeNanos() {
         return System.nanoTime();
         return System.nanoTime();
     }
     }
-    
+
     @Override
     @Override
     public void setCursorVisible(boolean visible) {
     public void setCursorVisible(boolean visible) {
-//        if(JmeSystem.getPlatform() != Platform.MacOSX32 &&
-//                JmeSystem.getPlatform() != Platform.MacOSX64 &&
-//                JmeSystem.getPlatform() != Platform.MacOSX_PPC32 &&
-//                JmeSystem.getPlatform() != Platform.MacOSX_PPC64){
         if (this.visible != visible) {
         if (this.visible != visible) {
-            lastKnownLocation.x = lastKnownLocation.y = 0;
+            grabLocation.x = lastKnownLocation.x;
+            grabLocation.y = lastKnownLocation.y;
 
 
             this.visible = visible;
             this.visible = visible;
             final boolean newVisible = visible;
             final boolean newVisible = visible;
-            SwingUtilities.invokeLater(new Runnable() {
-                @Override
-                public void run() {
-                    component.setCursor(newVisible ? null : getTransparentCursor());
-                    if (!newVisible) {
+            SwingUtilities.invokeLater(() -> {
+                component.setCursor(newVisible ? null : getTransparentCursor());
+                if (!newVisible) {
                         recenterMouse(component);
                         recenterMouse(component);
-                    }
                 }
                 }
             });
             });
-//        }
         }
         }
     }
     }
 
 
@@ -314,7 +316,11 @@ public class AwtMouseInput implements MouseInput, MouseListener, MouseWheelListe
         if (robot != null) {
         if (robot != null) {
             eventsSinceRecenter = 0;
             eventsSinceRecenter = 0;
             isRecentering = true;
             isRecentering = true;
-            centerLocation.setLocation(component.getWidth() / 2, component.getHeight() / 2);
+            if (grabLocation.x == 0 && grabLocation.y == 0) {
+                centerLocation.setLocation(component.getWidth() / 2, component.getHeight() / 2);
+            } else {
+                centerLocation.setLocation(grabLocation.x, grabLocation.y);
+            }
             centerLocationOnScreen.setLocation(centerLocation);
             centerLocationOnScreen.setLocation(centerLocation);
             SwingUtilities.convertPointToScreen(centerLocationOnScreen, component);
             SwingUtilities.convertPointToScreen(centerLocationOnScreen, component);
             robot.mouseMove(centerLocationOnScreen.x, centerLocationOnScreen.y);
             robot.mouseMove(centerLocationOnScreen.x, centerLocationOnScreen.y);

+ 108 - 57
jme3-examples/src/main/java/jme3test/model/anim/TestSingleLayerInfluenceMask.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2024 jMonkeyEngine
+ * Copyright (c) 2025 jMonkeyEngine
  * All rights reserved.
  * All rights reserved.
  *
  *
  * Redistribution and use in source and binary forms, with or without
  * Redistribution and use in source and binary forms, with or without
@@ -32,42 +32,47 @@
 package jme3test.model.anim;
 package jme3test.model.anim;
 
 
 import com.jme3.anim.AnimComposer;
 import com.jme3.anim.AnimComposer;
+import com.jme3.anim.AnimLayer;
+import com.jme3.anim.Armature;
 import com.jme3.anim.ArmatureMask;
 import com.jme3.anim.ArmatureMask;
 import com.jme3.anim.SingleLayerInfluenceMask;
 import com.jme3.anim.SingleLayerInfluenceMask;
 import com.jme3.anim.SkinningControl;
 import com.jme3.anim.SkinningControl;
-import com.jme3.anim.tween.action.ClipAction;
+import com.jme3.anim.tween.action.BlendableAction;
 import com.jme3.anim.util.AnimMigrationUtils;
 import com.jme3.anim.util.AnimMigrationUtils;
 import com.jme3.app.SimpleApplication;
 import com.jme3.app.SimpleApplication;
+import com.jme3.export.binary.BinaryExporter;
 import com.jme3.font.BitmapFont;
 import com.jme3.font.BitmapFont;
 import com.jme3.font.BitmapText;
 import com.jme3.font.BitmapText;
 import com.jme3.input.KeyInput;
 import com.jme3.input.KeyInput;
 import com.jme3.input.controls.ActionListener;
 import com.jme3.input.controls.ActionListener;
 import com.jme3.input.controls.KeyTrigger;
 import com.jme3.input.controls.KeyTrigger;
 import com.jme3.light.DirectionalLight;
 import com.jme3.light.DirectionalLight;
-import com.jme3.math.ColorRGBA;
 import com.jme3.math.Vector3f;
 import com.jme3.math.Vector3f;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.Spatial;
 
 
 /**
 /**
  * Tests {@link SingleLayerInfluenceMask}.
  * Tests {@link SingleLayerInfluenceMask}.
- * 
- * The test runs two simultaneous looping actions on seperate layers.
+ *
+ * The test runs two simultaneous looping actions on separate layers.
  * <p>
  * <p>
  * The test <strong>fails</strong> if the visible dancing action does <em>not</em>
  * The test <strong>fails</strong> if the visible dancing action does <em>not</em>
- * loop seamlessly when using SingleLayerInfluenceMasks. Note that the action is not
- * expected to loop seamlessly when <em>not</em> using SingleLayerArmatureMasks.
+ * loop seamlessly when using SingleLayerInfluenceMasks. Note that the action is
+ * not expected to loop seamlessly when <em>not</em> using
+ * SingleLayerArmatureMasks.
  * <p>
  * <p>
- * Press the spacebar to switch between using SingleLayerInfluenceMasks and masks
- * provided by {@link ArmatureMask}.
- * 
+ * Press the spacebar to switch between using SingleLayerInfluenceMasks and
+ * masks provided by {@link ArmatureMask}.
+ *
  * @author codex
  * @author codex
  */
  */
 public class TestSingleLayerInfluenceMask extends SimpleApplication implements ActionListener {
 public class TestSingleLayerInfluenceMask extends SimpleApplication implements ActionListener {
-    
+
     private Spatial model;
     private Spatial model;
-    private AnimComposer anim;
-    private SkinningControl skin;
-    private boolean useSLIMask = true;
+    private AnimComposer animComposer;
+    private SkinningControl skinningControl;
+    private final String idleLayer = "idleLayer";
+    private final String danceLayer = "danceLayer";
+    private boolean useSingleLayerInfMask = true;
     private BitmapText display;
     private BitmapText display;
 
 
     public static void main(String[] args) {
     public static void main(String[] args) {
@@ -77,74 +82,120 @@ public class TestSingleLayerInfluenceMask extends SimpleApplication implements A
 
 
     @Override
     @Override
     public void simpleInitApp() {
     public void simpleInitApp() {
-        
-        flyCam.setMoveSpeed(30f);
 
 
+        flyCam.setMoveSpeed(30f);
+        
         DirectionalLight dl = new DirectionalLight();
         DirectionalLight dl = new DirectionalLight();
         dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal());
         dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal());
-        dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));
         rootNode.addLight(dl);
         rootNode.addLight(dl);
-        
+
         BitmapFont font = assetManager.loadFont("Interface/Fonts/Default.fnt");
         BitmapFont font = assetManager.loadFont("Interface/Fonts/Default.fnt");
         display = new BitmapText(font);
         display = new BitmapText(font);
         display.setSize(font.getCharSet().getRenderedSize());
         display.setSize(font.getCharSet().getRenderedSize());
         display.setText("");
         display.setText("");
-        display.setLocalTranslation(5, context.getSettings().getHeight()-5, 0);
+        display.setLocalTranslation(5, context.getSettings().getHeight() - 5, 0);
         guiNode.attachChild(display);
         guiNode.attachChild(display);
- 
-        inputManager.addMapping("reset", new KeyTrigger(KeyInput.KEY_SPACE));
-        inputManager.addListener(this, "reset");
-        
+
+        inputManager.addMapping("SWITCH_MASKS", new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addListener(this, "SWITCH_MASKS");
+
         setupModel();
         setupModel();
-        
+        createAnimMasks();
+        testSerialization();
+        playAnimations();
+        updateUI();
     }
     }
+
     @Override
     @Override
     public void simpleUpdate(float tpf) {
     public void simpleUpdate(float tpf) {
         cam.lookAt(model.getWorldTranslation(), Vector3f.UNIT_Y);
         cam.lookAt(model.getWorldTranslation(), Vector3f.UNIT_Y);
     }
     }
+
     @Override
     @Override
     public void onAction(String name, boolean isPressed, float tpf) {
     public void onAction(String name, boolean isPressed, float tpf) {
-        if (name.equals("reset") && isPressed) {
-            model.removeFromParent();
-            setupModel();
+        if (name.equals("SWITCH_MASKS") && isPressed) {
+            useSingleLayerInfMask = !useSingleLayerInfMask;
+            animComposer.removeLayer(idleLayer);
+            animComposer.removeLayer(danceLayer);
+            
+            createAnimMasks();
+            playAnimations();
+            updateUI();
         }
         }
     }
     }
-    
+
+    /**
+     * Sets up the model by loading it, migrating animations, and attaching it to
+     * the root node.
+     */
     private void setupModel() {
     private void setupModel() {
-        
         model = assetManager.loadModel("Models/Sinbad/SinbadOldAnim.j3o");
         model = assetManager.loadModel("Models/Sinbad/SinbadOldAnim.j3o");
+        // Migrate the model's animations to the new system
         AnimMigrationUtils.migrate(model);
         AnimMigrationUtils.migrate(model);
-        anim = model.getControl(AnimComposer.class);
-        skin = model.getControl(SkinningControl.class);
-        
-        if (useSLIMask) {
-            SingleLayerInfluenceMask walkLayer = new SingleLayerInfluenceMask("idleLayer", anim, skin);
-            walkLayer.addAll();
-            walkLayer.makeLayer();
-            SingleLayerInfluenceMask danceLayer = new SingleLayerInfluenceMask("danceLayer", anim, skin);
-            danceLayer.addAll();
-            danceLayer.makeLayer();
+        rootNode.attachChild(model);
+
+        animComposer = model.getControl(AnimComposer.class);
+        skinningControl = model.getControl(SkinningControl.class);
+
+        ((BlendableAction) animComposer.action("Dance")).setMaxTransitionWeight(.9f);
+        ((BlendableAction) animComposer.action("IdleTop")).setMaxTransitionWeight(.8f);
+    }
+
+    /**
+     * Creates animation masks for the idle and dance layers.
+     */
+    private void createAnimMasks() {
+        Armature armature = skinningControl.getArmature();
+        ArmatureMask idleMask;
+        ArmatureMask danceMask;
+
+        if (useSingleLayerInfMask) {
+            // Create single layer influence masks for idle and dance layers
+            idleMask = new SingleLayerInfluenceMask(idleLayer, animComposer, armature);
+            danceMask = new SingleLayerInfluenceMask(danceLayer, animComposer, armature);
+
         } else {
         } else {
-            anim.makeLayer("idleLayer", ArmatureMask.createMask(skin.getArmature(), "Root"));
-            anim.makeLayer("danceLayer", ArmatureMask.createMask(skin.getArmature(), "Root"));
+            // Create default armature masks for idle and dance layers
+            idleMask = new ArmatureMask(armature);
+            danceMask = new ArmatureMask(armature);
         }
         }
-        
-        setSLIMaskInfo();
-        useSLIMask = !useSLIMask;
-        
-        ClipAction clip = (ClipAction)anim.action("Dance");
-        clip.setMaxTransitionWeight(.9f);
-        ClipAction clip2 = (ClipAction)anim.action("IdleTop");
-        clip2.setMaxTransitionWeight(.8f);
-        
-        anim.setCurrentAction("Dance", "danceLayer");
-        anim.setCurrentAction("IdleTop", "idleLayer");
 
 
-        rootNode.attachChild(model);
-        
+        // Assign the masks to the respective animation layers
+        animComposer.makeLayer(idleLayer, idleMask);
+        animComposer.makeLayer(danceLayer, danceMask);
     }
     }
-    private void setSLIMaskInfo() {
-        display.setText("Using SingleLayerInfluenceMasks: "+useSLIMask+"\nPress Spacebar to switch masks");
+
+    /**
+     * Plays the "Dance" and "IdleTop" animations on their respective layers.
+     */
+    private void playAnimations() {
+        animComposer.setCurrentAction("Dance", danceLayer);
+        animComposer.setCurrentAction("IdleTop", idleLayer);
     }
     }
-    
+
+    /**
+     * Tests the serialization of animation masks.
+     */
+    private void testSerialization() {
+        AnimComposer aComposer = model.getControl(AnimComposer.class);
+        for (String layerName : aComposer.getLayerNames()) {
+
+            System.out.println("layerName: " + layerName);
+            AnimLayer layer = aComposer.getLayer(layerName);
+
+            if (layer.getMask() instanceof SingleLayerInfluenceMask) {
+                SingleLayerInfluenceMask mask = (SingleLayerInfluenceMask) layer.getMask();
+                // Serialize and deserialize the mask
+                mask = BinaryExporter.saveAndLoad(assetManager, mask);
+                // Reassign the AnimComposer to the mask and remake the layer
+                mask.setAnimComposer(aComposer);
+                aComposer.makeLayer(layerName, mask);
+            }
+        }
+    }
+
+    private void updateUI() {
+        display.setText("Using SingleLayerInfluenceMasks: " + useSingleLayerInfMask + "\nPress Spacebar to switch masks");
+    }
+
 }
 }

+ 2 - 2
jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java

@@ -357,8 +357,8 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
         if (!settings.isFullscreen()) {
         if (!settings.isFullscreen()) {
             if (settings.getCenterWindow()) {
             if (settings.getCenterWindow()) {
                 // Center the window
                 // Center the window
-                requestX = videoMode.width() - requestWidth;
-                requestY = videoMode.height() - requestWidth;
+                requestX = (videoMode.width() - requestWidth) / 2;
+                requestY = (videoMode.height() - requestHeight) / 2;
             } else {
             } else {
                 requestX = settings.getWindowXPosition();
                 requestX = settings.getWindowXPosition();
                 requestY = settings.getWindowYPosition();
                 requestY = settings.getWindowYPosition();

+ 2 - 5
jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.vert

@@ -8,7 +8,7 @@ attribute vec2 inTexCoord;
 varying vec2 texCoord;
 varying vec2 texCoord;
 varying vec3 wPosition;
 varying vec3 wPosition;
 varying vec3 wNormal;
 varying vec3 wNormal;
-
+varying vec3 lPosition;
 
 
 uniform vec4 g_AmbientLightColor;
 uniform vec4 g_AmbientLightColor;
 
 
@@ -17,9 +17,6 @@ uniform vec4 g_AmbientLightColor;
     uniform vec3 g_CameraPosition;
     uniform vec3 g_CameraPosition;
 #endif
 #endif
 
 
-
-varying vec4 lPosition;
-
 void main(){
 void main(){
     vec4 modelSpacePos = vec4(inPosition, 1.0);
     vec4 modelSpacePos = vec4(inPosition, 1.0);
 
 
@@ -31,7 +28,7 @@ void main(){
     
     
     wNormal  = normalize(TransformWorldNormal(inNormal));
     wNormal  = normalize(TransformWorldNormal(inNormal));
     
     
-    lPosition = vec4(inPosition, 0.0);       
+    lPosition = modelSpacePos.xyz;     
     
     
     #ifdef USE_FOG
     #ifdef USE_FOG
         fogDistance = distance(g_CameraPosition, (g_WorldMatrix * modelSpacePos).xyz);
         fogDistance = distance(g_CameraPosition, (g_WorldMatrix * modelSpacePos).xyz);