2
0
Эх сурвалжийг харах

Merge branch 'master' into master

Ryan McDonough 8 сар өмнө
parent
commit
b2bf1b4470
100 өөрчлөгдсөн 3590 нэмэгдсэн , 534 устгасан
  1. 62 21
      .github/workflows/main.yml
  2. 1 1
      LICENSE.md
  3. 64 14
      README.md
  4. 13 52
      build.gradle
  5. 12 15
      common.gradle
  6. 1 1
      gradle.properties
  7. 50 0
      gradle/libs.versions.toml
  8. BIN
      gradle/wrapper/gradle-wrapper.jar
  9. 2 1
      gradle/wrapper/gradle-wrapper.properties
  10. 18 13
      gradlew
  11. 10 10
      gradlew.bat
  12. 2 3
      jme3-android-examples/build.gradle
  13. 3 0
      jme3-android-native/bufferallocator.gradle
  14. 0 3
      jme3-android-native/build.gradle
  15. 3 0
      jme3-android-native/decode.gradle
  16. 3 0
      jme3-android-native/openalsoft.gradle
  17. 2 2
      jme3-android/build.gradle
  18. 14 9
      jme3-core/src/main/java/com/jme3/anim/AnimComposer.java
  19. 37 16
      jme3-core/src/main/java/com/jme3/anim/AnimLayer.java
  20. 2 2
      jme3-core/src/main/java/com/jme3/anim/Armature.java
  21. 20 2
      jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java
  22. 9 0
      jme3-core/src/main/java/com/jme3/anim/MorphTrack.java
  23. 218 0
      jme3-core/src/main/java/com/jme3/anim/SingleLayerInfluenceMask.java
  24. 25 15
      jme3-core/src/main/java/com/jme3/anim/SkinningControl.java
  25. 9 0
      jme3-core/src/main/java/com/jme3/anim/TransformTrack.java
  26. 133 21
      jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java
  27. 60 15
      jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java
  28. 50 8
      jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java
  29. 34 2
      jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java
  30. 31 0
      jme3-core/src/main/java/com/jme3/anim/util/HasLocalTransform.java
  31. 31 0
      jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java
  32. 31 0
      jme3-core/src/main/java/com/jme3/anim/util/Primitives.java
  33. 31 0
      jme3-core/src/main/java/com/jme3/anim/util/Weighted.java
  34. 25 13
      jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java
  35. 236 0
      jme3-core/src/main/java/com/jme3/app/state/CompositeAppState.java
  36. 1 4
      jme3-core/src/main/java/com/jme3/asset/ImplHandler.java
  37. 1 2
      jme3-core/src/main/java/com/jme3/asset/cache/WeakRefCloneAssetCache.java
  38. 1 1
      jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java
  39. 72 1
      jme3-core/src/main/java/com/jme3/bounding/BoundingBox.java
  40. 64 1
      jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java
  41. 44 1
      jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.java
  42. 1 1
      jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java
  43. 1 1
      jme3-core/src/main/java/com/jme3/cinematic/events/AnimEvent.java
  44. 14 9
      jme3-core/src/main/java/com/jme3/collision/bih/BIHNode.java
  45. 351 0
      jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java
  46. 122 0
      jme3-core/src/main/java/com/jme3/environment/FastLightProbeFactory.java
  47. 89 0
      jme3-core/src/main/java/com/jme3/environment/baker/EnvBaker.java
  48. 293 0
      jme3-core/src/main/java/com/jme3/environment/baker/GenericEnvBaker.java
  49. 74 0
      jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBaker.java
  50. 52 0
      jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBakerLight.java
  51. 296 0
      jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBaker.java
  52. 176 0
      jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBakerLight.java
  53. 209 0
      jme3-core/src/main/java/com/jme3/environment/baker/IBLHybridEnvBakerLight.java
  54. 5 17
      jme3-core/src/main/java/com/jme3/environment/util/BoundingSphereDebug.java
  55. 1 1
      jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java
  56. 1 1
      jme3-core/src/main/java/com/jme3/font/BitmapCharacterSet.java
  57. 1 1
      jme3-core/src/main/java/com/jme3/font/ColorTags.java
  58. 1 1
      jme3-core/src/main/java/com/jme3/font/LetterQuad.java
  59. 2 2
      jme3-core/src/main/java/com/jme3/font/Letters.java
  60. 6 6
      jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java
  61. 21 21
      jme3-core/src/main/java/com/jme3/input/CameraInput.java
  62. 7 7
      jme3-core/src/main/java/com/jme3/input/ChaseCamera.java
  63. 7 7
      jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java
  64. 5 5
      jme3-core/src/main/java/com/jme3/input/DefaultJoystickButton.java
  65. 1 1
      jme3-core/src/main/java/com/jme3/input/FlyByCamera.java
  66. 2 2
      jme3-core/src/main/java/com/jme3/input/JoystickCompatibilityMappings.java
  67. 2 2
      jme3-core/src/main/java/com/jme3/input/event/JoyButtonEvent.java
  68. 4 4
      jme3-core/src/main/java/com/jme3/input/event/KeyInputEvent.java
  69. 4 4
      jme3-core/src/main/java/com/jme3/input/event/MouseButtonEvent.java
  70. 1 1
      jme3-core/src/main/java/com/jme3/input/event/MouseMotionEvent.java
  71. 1 1
      jme3-core/src/main/java/com/jme3/light/Light.java
  72. 1 1
      jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java
  73. 1 1
      jme3-core/src/main/java/com/jme3/light/WeightedProbeBlendingStrategy.java
  74. 41 30
      jme3-core/src/main/java/com/jme3/material/MatParam.java
  75. 40 24
      jme3-core/src/main/java/com/jme3/material/MatParamTexture.java
  76. 93 76
      jme3-core/src/main/java/com/jme3/material/Material.java
  77. 20 3
      jme3-core/src/main/java/com/jme3/material/RenderState.java
  78. 4 3
      jme3-core/src/main/java/com/jme3/material/Technique.java
  79. 1 1
      jme3-core/src/main/java/com/jme3/material/TechniqueDef.java
  80. 3 2
      jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java
  81. 3 2
      jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java
  82. 6 6
      jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java
  83. 3 2
      jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java
  84. 3 2
      jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java
  85. 3 2
      jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java
  86. 17 1
      jme3-core/src/main/java/com/jme3/math/AbstractTriangle.java
  87. 10 1
      jme3-core/src/main/java/com/jme3/math/FastMath.java
  88. 17 1
      jme3-core/src/main/java/com/jme3/math/Line.java
  89. 18 1
      jme3-core/src/main/java/com/jme3/math/LineSegment.java
  90. 19 1
      jme3-core/src/main/java/com/jme3/math/Matrix4f.java
  91. 35 5
      jme3-core/src/main/java/com/jme3/math/Quaternion.java
  92. 16 1
      jme3-core/src/main/java/com/jme3/math/Rectangle.java
  93. 1 1
      jme3-core/src/main/java/com/jme3/math/Ring.java
  94. 14 1
      jme3-core/src/main/java/com/jme3/math/Transform.java
  95. 24 0
      jme3-core/src/main/java/com/jme3/math/Vector2f.java
  96. 8 8
      jme3-core/src/main/java/com/jme3/math/Vector3f.java
  97. 9 9
      jme3-core/src/main/java/com/jme3/math/Vector4f.java
  98. 2 2
      jme3-core/src/main/java/com/jme3/opencl/OpenCLObjectManager.java
  99. 1 1
      jme3-core/src/main/java/com/jme3/post/HDRRenderer.java
  100. 2 2
      jme3-core/src/main/java/com/jme3/post/PreDepthProcessor.java

+ 62 - 21
.github/workflows/main.yml

@@ -46,6 +46,7 @@ on:
   push:
   push:
     branches:
     branches:
       - master
       - master
+      - v3.7
       - v3.6
       - v3.6
       - v3.5
       - v3.5
       - v3.4
       - v3.4
@@ -55,7 +56,49 @@ on:
     types: [published]
     types: [published]
 
 
 jobs:
 jobs:
-
+  ScreenshotTests:
+    name: Run Screenshot Tests
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+    steps:
+    - uses: actions/checkout@v4
+    - name: Set up JDK 17
+      uses: actions/setup-java@v4
+      with:
+        java-version: '17'
+        distribution: 'temurin'
+    - name: Install Mesa3D
+      run: |
+        sudo apt-get update
+        sudo apt-get install -y mesa-utils libgl1-mesa-dri libgl1 libglx-mesa0 xvfb
+    - name: Set environment variables for Mesa3D
+      run: |
+        echo "LIBGL_ALWAYS_SOFTWARE=1" >> $GITHUB_ENV
+        echo "MESA_LOADER_DRIVER_OVERRIDE=llvmpipe" >> $GITHUB_ENV
+    - name: Start xvfb
+      run: |
+        sudo Xvfb :99 -ac -screen 0 1024x768x16 &
+        export DISPLAY=:99
+        echo "DISPLAY=:99" >> $GITHUB_ENV
+    - name: Verify Mesa3D Installation
+      run: |
+        glxinfo | grep "OpenGL"
+    - name: Validate the Gradle wrapper
+      uses: gradle/actions/wrapper-validation@v3
+    - name: Test with Gradle Wrapper
+      run: |
+        ./gradlew :jme3-screenshot-test:screenshotTest
+    - name: Upload Test Reports
+      uses: actions/upload-artifact@master
+      if: always()
+      with:
+        name: screenshot-test-report
+        retention-days: 30
+        path: |
+          **/build/reports/**
+          **/build/changed-images/**
+          **/build/test-results/**
   # Build the natives on android
   # Build the natives on android
   BuildAndroidNatives:
   BuildAndroidNatives:
     name: Build natives for android
     name: Build natives for android
@@ -69,7 +112,7 @@ jobs:
         with:
         with:
           fetch-depth: 1
           fetch-depth: 1
       - name: Validate the Gradle wrapper
       - name: Validate the Gradle wrapper
-        uses: gradle/wrapper-validation-action@v1
+        uses: gradle/actions/wrapper-validation@v3
       - name: Build
       - name: Build
         run: |
         run: |
           ./gradlew -PuseCommitHashAsVersionName=true --no-daemon -PbuildNativeProjects=true \
           ./gradlew -PuseCommitHashAsVersionName=true --no-daemon -PbuildNativeProjects=true \
@@ -81,7 +124,7 @@ jobs:
           name: android-natives
           name: android-natives
           path: build/native
           path: build/native
 
 
-  # Build the engine, we only deploy from ubuntu-latest jdk17
+  # Build the engine, we only deploy from ubuntu-latest jdk21
   BuildJMonkey:
   BuildJMonkey:
     needs: [BuildAndroidNatives]
     needs: [BuildAndroidNatives]
     name: Build on ${{ matrix.osName }} jdk${{ matrix.jdk }}
     name: Build on ${{ matrix.osName }} jdk${{ matrix.jdk }}
@@ -89,22 +132,22 @@ jobs:
     strategy:
     strategy:
       fail-fast: false
       fail-fast: false
       matrix:
       matrix:
-        os: [ubuntu-latest,windows-2019,macOS-latest]
-        jdk: [8, 11, 17]
+        os: [ubuntu-latest,windows-latest,macOS-latest]
+        jdk: [11, 17, 21]
         include:
         include:
           - os: ubuntu-latest
           - os: ubuntu-latest
             osName: linux
             osName: linux
             deploy: true
             deploy: true
-          - os: windows-2019
+          - os: windows-latest
             osName: windows
             osName: windows
             deploy: false
             deploy: false
           - os: macOS-latest
           - os: macOS-latest
             osName: mac
             osName: mac
             deploy: false
             deploy: false
-          - jdk: 8
-            deploy: false
           - jdk: 11
           - jdk: 11
             deploy: false
             deploy: false
+          - jdk: 17
+            deploy: false
 
 
     steps:
     steps:
       - name: Clone the repo
       - name: Clone the repo
@@ -113,7 +156,7 @@ jobs:
           fetch-depth: 1
           fetch-depth: 1
 
 
       - name: Setup the java environment
       - name: Setup the java environment
-        uses: actions/setup-java@v3
+        uses: actions/setup-java@v4
         with:
         with:
           distribution: 'temurin'
           distribution: 'temurin'
           java-version: ${{ matrix.jdk }}
           java-version: ${{ matrix.jdk }}
@@ -125,12 +168,13 @@ jobs:
           path: build/native
           path: build/native
 
 
       - name: Validate the Gradle wrapper
       - name: Validate the Gradle wrapper
-        uses: gradle/wrapper-validation-action@v1
+        uses: gradle/actions/wrapper-validation@v3
       - name: Build Engine
       - name: Build Engine
         shell: bash
         shell: bash
         run: |
         run: |
-          # Build
-          ./gradlew -PuseCommitHashAsVersionName=true -PskipPrebuildLibraries=true build
+          # Normal build plus ZIP distribution and merged javadoc
+          ./gradlew -PuseCommitHashAsVersionName=true -PskipPrebuildLibraries=true \
+          build createZipDistribution mergedJavadoc
 
 
           if [ "${{ matrix.deploy }}" = "true" ];
           if [ "${{ matrix.deploy }}" = "true" ];
           then
           then
@@ -138,9 +182,6 @@ jobs:
             sudo apt-get update
             sudo apt-get update
             sudo apt-get install -y zip
             sudo apt-get install -y zip
 
 
-            # Create the zip release and the javadoc
-            ./gradlew -PuseCommitHashAsVersionName=true -PskipPrebuildLibraries=true mergedJavadoc createZipDistribution
-
             # We prepare the release for deploy
             # We prepare the release for deploy
             mkdir -p ./dist/release/
             mkdir -p ./dist/release/
             mv build/distributions/*.zip dist/release/
             mv build/distributions/*.zip dist/release/
@@ -305,12 +346,12 @@ jobs:
         with:
         with:
           fetch-depth: 1
           fetch-depth: 1
 
 
-      # Setup jdk 17 used for building Maven-style artifacts
+      # Setup jdk 21 used for building Maven-style artifacts
       - name: Setup the java environment
       - name: Setup the java environment
-        uses: actions/setup-java@v3
+        uses: actions/setup-java@v4
         with:
         with:
           distribution: 'temurin'
           distribution: 'temurin'
-          java-version: '17'
+          java-version: '21'
 
 
       - name: Download natives for android
       - name: Download natives for android
         uses: actions/download-artifact@master
         uses: actions/download-artifact@master
@@ -349,12 +390,12 @@ jobs:
         with:
         with:
           fetch-depth: 1
           fetch-depth: 1
 
 
-      # Setup jdk 17 used for building Sonatype OSSRH artifacts
+      # Setup jdk 21 used for building Sonatype OSSRH artifacts
       - name: Setup the java environment
       - name: Setup the java environment
-        uses: actions/setup-java@v3
+        uses: actions/setup-java@v4
         with:
         with:
           distribution: 'temurin'
           distribution: 'temurin'
-          java-version: '17'
+          java-version: '21'
 
 
       # Download all the stuff...
       # Download all the stuff...
       - name: Download maven artifacts
       - name: Download maven artifacts

+ 1 - 1
LICENSE.md

@@ -1,4 +1,4 @@
-Copyright (c) 2009-2023 jMonkeyEngine.
+Copyright (c) 2009-2024 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

+ 64 - 14
README.md

@@ -4,19 +4,19 @@ jMonkeyEngine
 [![Build Status](https://github.com/jMonkeyEngine/jmonkeyengine/workflows/Build%20jMonkeyEngine/badge.svg)](https://github.com/jMonkeyEngine/jmonkeyengine/actions)
 [![Build Status](https://github.com/jMonkeyEngine/jmonkeyengine/workflows/Build%20jMonkeyEngine/badge.svg)](https://github.com/jMonkeyEngine/jmonkeyengine/actions)
 
 
 jMonkeyEngine is a 3-D game engine for adventurous Java developers. It’s open-source, cross-platform, and cutting-edge.
 jMonkeyEngine is a 3-D game engine for adventurous Java developers. It’s open-source, cross-platform, and cutting-edge.
-v3.6.1 is the latest stable version of the engine.
+v3.7.0 is the latest stable version of the engine.
 
 
 The engine is used by several commercial game studios and computer-science courses. Here's a taste:
 The engine is used by several commercial game studios and computer-science courses. Here's a taste:
 
 
 ![jME3 Games Mashup](https://i.imgur.com/nF8WOW6.jpg)
 ![jME3 Games Mashup](https://i.imgur.com/nF8WOW6.jpg)
 
 
- - [jME powered games on IndieDB](http://www.indiedb.com/engines/jmonkeyengine/games)
+ - [jME powered games on IndieDB](https://www.indiedb.com/engines/jmonkeyengine/games)
  - [Boardtastic 2](https://boardtastic-2.fileplanet.com/apk)
  - [Boardtastic 2](https://boardtastic-2.fileplanet.com/apk)
  - [Attack of the Gelatinous Blob](https://attack-gelatinous-blob.softwareandgames.com/)
  - [Attack of the Gelatinous Blob](https://attack-gelatinous-blob.softwareandgames.com/)
- - [Mythruna](http://mythruna.com/)
+ - [Mythruna](https://mythruna.com/)
  - [PirateHell (on Steam)](https://store.steampowered.com/app/321080/Pirate_Hell/)
  - [PirateHell (on Steam)](https://store.steampowered.com/app/321080/Pirate_Hell/)
- - [3089 (on Steam)](http://store.steampowered.com/app/263360/)
- - [3079 (on Steam)](http://store.steampowered.com/app/259620/)
+ - [3089 (on Steam)](https://store.steampowered.com/app/263360/3089__Futuristic_Action_RPG/)
+ - [3079 (on Steam)](https://store.steampowered.com/app/259620/3079__Block_Action_RPG/)
  - [Lightspeed Frontier (on Steam)](https://store.steampowered.com/app/548650/Lightspeed_Frontier/)
  - [Lightspeed Frontier (on Steam)](https://store.steampowered.com/app/548650/Lightspeed_Frontier/)
  - [Skullstone](http://www.skullstonegame.com/)
  - [Skullstone](http://www.skullstonegame.com/)
  - [Spoxel (on Steam)](https://store.steampowered.com/app/746880/Spoxel/)
  - [Spoxel (on Steam)](https://store.steampowered.com/app/746880/Spoxel/)
@@ -24,28 +24,34 @@ The engine is used by several commercial game studios and computer-science cours
  - [Leap](https://gamejolt.com/games/leap/313308)
  - [Leap](https://gamejolt.com/games/leap/313308)
  - [Jumping Jack Flag](http://timealias.bplaced.net/jack/)
  - [Jumping Jack Flag](http://timealias.bplaced.net/jack/)
  - [PapaSpace Flight Simulation](https://www.papaspace.at/)
  - [PapaSpace Flight Simulation](https://www.papaspace.at/)
- - [Cubic Nightmare](https://jaredbgreat.itch.io/cubic-nightmare)
+ - [Cubic Nightmare (on Itch)](https://jaredbgreat.itch.io/cubic-nightmare)
  - [Chatter Games](https://chatter-games.com)
  - [Chatter Games](https://chatter-games.com)
  - [Exotic Matter](https://exoticmatter.io)
  - [Exotic Matter](https://exoticmatter.io)
  - [Demon Lord (on Google Play)](https://play.google.com/store/apps/details?id=com.dreiInitiative.demonLord&pli=1)
  - [Demon Lord (on Google Play)](https://play.google.com/store/apps/details?id=com.dreiInitiative.demonLord&pli=1)
  - [Marvelous Marbles (on Steam)](https://store.steampowered.com/app/2244540/Marvelous_Marbles/)
  - [Marvelous Marbles (on Steam)](https://store.steampowered.com/app/2244540/Marvelous_Marbles/)
  - [Boxer (on Google Play)](https://play.google.com/store/apps/details?id=com.tharg.boxer)
  - [Boxer (on Google Play)](https://play.google.com/store/apps/details?id=com.tharg.boxer)
  - [Depthris (on Itch)](https://codewalker.itch.io/depthris)
  - [Depthris (on Itch)](https://codewalker.itch.io/depthris)
+ - [Stranded (on Itch)](https://tgiant.itch.io/stranded)
+ - [The Afflicted Forests (Coming Soon to Steam)](https://www.indiedb.com/games/the-afflicted-forests)
+ - [Star Colony: Beyond Horizons (on Google Play)](https://play.google.com/store/apps/details?id=game.colony.ColonyBuilder)
+ - [High Impact (on Steam)](https://store.steampowered.com/app/3059050/High_Impact/)
 
 
-## Getting started
+## Getting Started
 
 
 Go to https://github.com/jMonkeyEngine/sdk/releases to download the jMonkeyEngine SDK.
 Go to https://github.com/jMonkeyEngine/sdk/releases to download the jMonkeyEngine SDK.
-[Read the wiki](https://jmonkeyengine.github.io/wiki) for a complete install guide. Power up with some SDK Plugins and AssetPacks and you are off to the races. At this point you're gonna want to [join the forum](http://hub.jmonkeyengine.org/) so our tribe can grow stronger.
+Read [the wiki](https://jmonkeyengine.github.io/wiki) for the installation guide and tutorials.
+Join [the discussion forum](https://hub.jmonkeyengine.org/) to participate in our community,
+get your questions answered, and share your projects.
 
 
-Note: The master branch on GitHub is a development version of the engine and is NOT MEANT TO BE USED IN PRODUCTION, it will break constantly during development of the stable jME versions!
+Note: The master branch on GitHub is a development version of the engine and is NOT MEANT TO BE USED IN PRODUCTION.
 
 
 ### Technology Stack
 ### Technology Stack
 
 
- - Java
- - NetBeans Platform
- - Gradle
-
-Plus a bunch of awesome libraries & tight integrations like Bullet, NiftyGUI and other goodies.
+ - windowed, multi-platform IDE derived from NetBeans
+ - libraries for GUI, networking, physics, SFX, terrain, importing assets, etc.
+ - platform-neutral core library for scene graph, animation, rendering, math, etc.
+ - LWJGL v2/v3 (to access GLFW, OpenAL, OpenGL, and OpenVR) or Android or iOS
+ - Java Virtual Machine (v8 or higher)
 
 
 ### Documentation
 ### Documentation
 
 
@@ -59,3 +65,47 @@ Read our [contribution guide](https://github.com/jMonkeyEngine/jmonkeyengine/blo
 
 
 [New BSD (3-clause) License](https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/LICENSE.md)
 [New BSD (3-clause) License](https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/LICENSE.md)
 
 
+### How to Build the Engine from Source
+
+1. Install a Java Development Kit (JDK),
+   if you don't already have one.
+2. Point the `JAVA_HOME` environment variable to your JDK installation:
+   (In other words, set it to the path of a directory/folder
+   containing a "bin" that contains a Java executable.
+   That path might look something like
+   "C:\Program Files\Eclipse Adoptium\jdk-17.0.3.7-hotspot"
+   or "/usr/lib/jvm/java-17-openjdk-amd64/" or
+   "/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home" .)
+  + using Bash or Zsh: `export JAVA_HOME="` *path to installation* `"`
+  + using Fish: `set -g JAVA_HOME "` *path to installation* `"`
+  + using Windows Command Prompt: `set JAVA_HOME="` *path to installation* `"`
+  + using PowerShell: `$env:JAVA_HOME = '` *path to installation* `'`
+3. Download and extract the engine source code from GitHub:
+  + using Git:
+    + `git clone https://github.com/jMonkeyEngine/jmonkeyengine.git`
+    + `cd jmonkeyengine`
+    + `git checkout -b latest v3.7.0-stable` (unless you plan to do development)
+  + using a web browser:
+    + browse to [the latest release](https://github.com/jMonkeyEngine/jmonkeyengine/releases/latest)
+    + follow the "Source code (zip)" link at the bottom of the page
+    + save the ZIP file
+    + extract the contents of the saved ZIP file
+    + `cd` to the extracted directory/folder
+4. Run the Gradle wrapper:
+  + using Bash or Fish or PowerShell or Zsh: `./gradlew build`
+  + using Windows Command Prompt: `.\gradlew build`
+
+After a successful build,
+fresh JARs will be found in "*/build/libs".
+
+You can install the JARs to your local Maven repository:
++ using Bash or Fish or PowerShell or Zsh: `./gradlew install`
++ using Windows Command Prompt: `.\gradlew install`
+
+You can run the "jme3-examples" app:
++ using Bash or Fish or PowerShell or Zsh: `./gradlew run`
++ using Windows Command Prompt: `.\gradlew run`
+
+You can restore the project to a pristine state:
++ using Bash or Fish or PowerShell or Zsh: `./gradlew clean`
++ using Windows Command Prompt: `.\gradlew clean`

+ 13 - 52
build.gradle

@@ -10,9 +10,9 @@ buildscript {
         }
         }
     }
     }
     dependencies {
     dependencies {
-        classpath 'com.android.tools.build:gradle:4.2.0'
-        classpath 'me.tatarka:gradle-retrolambda:3.7.1'
-        classpath "gradle.plugin.com.github.spotbugs.snom:spotbugs-gradle-plugin:4.5.1"
+        classpath libs.android.build.gradle
+        classpath libs.gradle.retrolambda
+        classpath libs.spotbugs.gradle.plugin
     }
     }
 }
 }
 
 
@@ -49,6 +49,7 @@ subprojects {
         // Currently we only warn about issues and try to fix them as we go, but those aren't mission critical.
         // Currently we only warn about issues and try to fix them as we go, but those aren't mission critical.
         spotbugs {
         spotbugs {
             ignoreFailures = true
             ignoreFailures = true
+            toolVersion = '4.8.6'
         }
         }
 
 
         tasks.withType(com.github.spotbugs.snom.SpotBugsTask ) {
         tasks.withType(com.github.spotbugs.snom.SpotBugsTask ) {
@@ -74,23 +75,25 @@ task libDist(dependsOn: subprojects.build, description: 'Builds and copies the e
         subprojects.each {project ->
         subprojects.each {project ->
             if(!project.hasProperty('mainClassName')){
             if(!project.hasProperty('mainClassName')){
                 project.tasks.withType(Jar).each {archiveTask ->
                 project.tasks.withType(Jar).each {archiveTask ->
-                    if(archiveTask.classifier == "sources"){
+                    String classifier = archiveTask.archiveClassifier.get()
+                    String ext = archiveTask.archiveExtension.get()
+                    if (classifier == "sources") {
                         copy {
                         copy {
                             from archiveTask.archivePath
                             from archiveTask.archivePath
                                 into sourceFolder
                                 into sourceFolder
-                                rename {project.name + '-' + archiveTask.classifier +'.'+ archiveTask.extension}
+                                rename {project.name + '-' + classifier + '.' + ext}
                         }
                         }
-                    } else if(archiveTask.classifier == "javadoc"){
+                    } else if (classifier == "javadoc") {
                         copy {
                         copy {
                             from archiveTask.archivePath
                             from archiveTask.archivePath
                                 into javadocFolder
                                 into javadocFolder
-                                rename {project.name + '-' + archiveTask.classifier +'.'+ archiveTask.extension}
+                                rename {project.name + '-' + classifier + '.' + ext}
                         }
                         }
                     } else{
                     } else{
                         copy {
                         copy {
                             from archiveTask.archivePath
                             from archiveTask.archivePath
                                 into libFolder
                                 into libFolder
-                                rename {project.name + '.' + archiveTask.extension}
+                                rename {project.name + '.' + ext}
                         }
                         }
                     }
                     }
                 }
                 }
@@ -114,7 +117,7 @@ task createZipDistribution(type:Zip,dependsOn:["dist","libDist"], description:"P
 task copyLibs(type: Copy){
 task copyLibs(type: Copy){
 //    description 'Copies the engine dependencies to build/libDist'
 //    description 'Copies the engine dependencies to build/libDist'
     from {
     from {
-        subprojects*.configurations*.compile*.copyRecursive({ !(it instanceof ProjectDependency); })*.resolve()
+        subprojects*.configurations*.implementation*.copyRecursive({ !(it instanceof ProjectDependency); })*.resolve()
     }
     }
 
 
     into "$buildDir/libDist/lib-ext" //buildDir.path + '/' + libsDirName + '/lib'
     into "$buildDir/libDist/lib-ext" //buildDir.path + '/' + libsDirName + '/lib'
@@ -245,50 +248,8 @@ if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") {
     }
     }
 }
 }
 
 
-
-//class IncrementalReverseTask extends DefaultTask {
-//    @InputDirectory
-//    def File inputDir
-//
-//    @OutputDirectory
-//    def File outputDir
-//
-//    @Input
-//    def inputProperty
-//
-//    @TaskAction
-//    void execute(IncrementalTaskInputs inputs) {
-//        println inputs.incremental ? "CHANGED inputs considered out of date" : "ALL inputs considered out of date"
-//        inputs.outOfDate { change ->
-//            println "out of date: ${change.file.name}"
-//            def targetFile = new File(outputDir, change.file.name)
-//            targetFile.text = change.file.text.reverse()
-//        }
-//
-//        inputs.removed { change ->
-//            println "removed: ${change.file.name}"
-//            def targetFile = new File(outputDir, change.file.name)
-//            targetFile.delete()
-//        }
-//    }
-//}
-
-//allprojects {
-//    tasks.withType(JavaExec) {
-//        enableAssertions = true // false by default
-//    }
-//    tasks.withType(Test) {
-//        enableAssertions = true // true by default
-//    }
-//}
-
-wrapper {
-    gradleVersion = '7.6.2'
-}
-
-
 retrolambda {
 retrolambda {
   javaVersion JavaVersion.VERSION_1_7
   javaVersion JavaVersion.VERSION_1_7
   incremental true
   incremental true
     jvmArgs '-noverify'
     jvmArgs '-noverify'
-}
+}

+ 12 - 15
common.gradle

@@ -15,8 +15,10 @@ eclipse.jdt.file.withProperties { props ->
 group = 'org.jmonkeyengine'
 group = 'org.jmonkeyengine'
 version = jmeFullVersion
 version = jmeFullVersion
 
 
-sourceCompatibility = JavaVersion.VERSION_1_8
-targetCompatibility = JavaVersion.VERSION_1_8
+java {
+    sourceCompatibility = JavaVersion.VERSION_1_8
+    targetCompatibility = JavaVersion.VERSION_1_8
+}
 
 
 tasks.withType(JavaCompile) { // compile-time options:
 tasks.withType(JavaCompile) { // compile-time options:
     //options.compilerArgs << '-Xlint:deprecation' // to show deprecation warnings
     //options.compilerArgs << '-Xlint:deprecation' // to show deprecation warnings
@@ -27,11 +29,6 @@ tasks.withType(JavaCompile) { // compile-time options:
     }
     }
 }
 }
 
 
-ext {
-    lwjgl3Version = '3.3.3' // used in both the jme3-lwjgl3 and jme3-vr build scripts
-    niftyVersion = '1.4.3' // used in both the jme3-niftygui and jme3-examples build scripts
-}
-
 repositories {
 repositories {
     mavenCentral()
     mavenCentral()
     flatDir {
     flatDir {
@@ -41,9 +38,9 @@ repositories {
 
 
 dependencies {
 dependencies {
     // Adding dependencies here will add the dependencies to each subproject.
     // Adding dependencies here will add the dependencies to each subproject.
-    testImplementation 'junit:junit:4.13.2'
-    testImplementation 'org.mockito:mockito-core:3.12.4'
-    testImplementation 'org.codehaus.groovy:groovy-test:3.0.19'
+    testImplementation libs.junit4
+    testImplementation libs.mokito.core
+    testImplementation libs.groovy.test
 }
 }
 
 
 // Uncomment if you want to see the status of every test that is run and
 // Uncomment if you want to see the status of every test that is run and
@@ -85,12 +82,12 @@ test {
 }
 }
 
 
 task sourcesJar(type: Jar, dependsOn: classes, description: 'Creates a jar from the source files.') {
 task sourcesJar(type: Jar, dependsOn: classes, description: 'Creates a jar from the source files.') {
-    classifier = 'sources'
+    archiveClassifier = 'sources'
     from sourceSets*.allSource
     from sourceSets*.allSource
 }
 }
 
 
 task javadocJar(type: Jar, dependsOn: javadoc, description: 'Creates a jar from the javadoc files.') {
 task javadocJar(type: Jar, dependsOn: javadoc, description: 'Creates a jar from the javadoc files.') {
-    classifier = 'javadoc'
+    archiveClassifier = 'javadoc'
     from javadoc.destinationDir
     from javadoc.destinationDir
 }
 }
 
 
@@ -204,7 +201,7 @@ tasks.withType(Sign) {
 }
 }
 
 
 checkstyle {
 checkstyle {
-    toolVersion '9.3'
+    toolVersion libs.versions.checkstyle.get()
     configFile file("${gradle.rootProject.rootDir}/config/checkstyle/checkstyle.xml")
     configFile file("${gradle.rootProject.rootDir}/config/checkstyle/checkstyle.xml")
 }
 }
 
 
@@ -218,8 +215,8 @@ checkstyleTest {
 
 
 tasks.withType(Checkstyle) {
 tasks.withType(Checkstyle) {
     reports {
     reports {
-        xml.enabled false
-        html.enabled true
+        xml.required.set(false)
+        html.required.set(true)
     }
     }
     include("**/com/jme3/renderer/**/*.java")
     include("**/com/jme3/renderer/**/*.java")
 }
 }

+ 1 - 1
gradle.properties

@@ -1,5 +1,5 @@
 # Version number: Major.Minor.SubMinor (e.g. 3.3.0)
 # Version number: Major.Minor.SubMinor (e.g. 3.3.0)
-jmeVersion = 3.7.0
+jmeVersion = 3.8.0
 
 
 # Leave empty to autogenerate
 # Leave empty to autogenerate
 # (use -PjmeVersionName="myVersion" from commandline to specify a custom version name )
 # (use -PjmeVersionName="myVersion" from commandline to specify a custom version name )

+ 50 - 0
gradle/libs.versions.toml

@@ -0,0 +1,50 @@
+## catalog of libraries and plugins used to build the jmonkeyengine project
+
+[versions]
+
+checkstyle = "9.3"
+lwjgl3 = "3.3.3"
+nifty = "1.4.3"
+
+[libraries]
+
+android-build-gradle = "com.android.tools.build:gradle:4.2.0"
+android-support-appcompat = "com.android.support:appcompat-v7:28.0.0"
+androidx-annotation = "androidx.annotation:annotation:1.3.0"
+androidx-lifecycle-common = "androidx.lifecycle:lifecycle-common:2.4.0"
+gradle-git = "org.ajoberstar:gradle-git:1.2.0"
+gradle-retrolambda = "me.tatarka:gradle-retrolambda:3.7.1"
+groovy-test = "org.codehaus.groovy:groovy-test:3.0.22"
+gson = "com.google.code.gson:gson:2.9.1"
+j-ogg-vorbis = "com.github.stephengold:j-ogg-vorbis:1.0.6"
+jbullet = "com.github.stephengold:jbullet:1.0.3"
+jinput = "net.java.jinput:jinput:2.0.9"
+jna = "net.java.dev.jna:jna:5.10.0"
+jnaerator-runtime = "com.nativelibs4java:jnaerator-runtime:0.12"
+junit4 = "junit:junit:4.13.2"
+lwjgl2 = "org.jmonkeyengine:lwjgl:2.9.5"
+lwjgl3-awt = "org.lwjglx:lwjgl3-awt:0.1.8"
+
+lwjgl3-base     = { module = "org.lwjgl:lwjgl",          version.ref = "lwjgl3" }
+lwjgl3-glfw     = { module = "org.lwjgl:lwjgl-glfw",     version.ref = "lwjgl3" }
+lwjgl3-jawt     = { module = "org.lwjgl:lwjgl-jawt",     version.ref = "lwjgl3" }
+lwjgl3-jemalloc = { module = "org.lwjgl:lwjgl-jemalloc", version.ref = "lwjgl3" }
+lwjgl3-openal   = { module = "org.lwjgl:lwjgl-openal",   version.ref = "lwjgl3" }
+lwjgl3-opencl   = { module = "org.lwjgl:lwjgl-opencl",   version.ref = "lwjgl3" }
+lwjgl3-opengl   = { module = "org.lwjgl:lwjgl-opengl",   version.ref = "lwjgl3" }
+lwjgl3-openvr   = { module = "org.lwjgl:lwjgl-openvr",   version.ref = "lwjgl3" }
+lwjgl3-ovr      = { module = "org.lwjgl:lwjgl-ovr",      version.ref = "lwjgl3" }
+
+mokito-core = "org.mockito:mockito-core:3.12.4"
+
+nifty                  = { module = "com.github.nifty-gui:nifty",                  version.ref = "nifty" }
+nifty-default-controls = { module = "com.github.nifty-gui:nifty-default-controls", version.ref = "nifty" }
+nifty-examples         = { module = "com.github.nifty-gui:nifty-examples",         version.ref = "nifty" }
+nifty-style-black      = { module = "com.github.nifty-gui:nifty-style-black",      version.ref = "nifty" }
+
+spotbugs-gradle-plugin = "com.github.spotbugs.snom:spotbugs-gradle-plugin:6.0.18"
+vecmath = "javax.vecmath:vecmath:1.5.2"
+
+[bundles]
+
+[plugins]

BIN
gradle/wrapper/gradle-wrapper.jar


+ 2 - 1
gradle/wrapper/gradle-wrapper.properties

@@ -1,6 +1,7 @@
 distributionBase=GRADLE_USER_HOME
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
 networkTimeout=10000
 networkTimeout=10000
+validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
 zipStorePath=wrapper/dists

+ 18 - 13
gradlew

@@ -55,7 +55,7 @@
 #       Darwin, MinGW, and NonStop.
 #       Darwin, MinGW, and NonStop.
 #
 #
 #   (3) This script is generated from the Groovy template
 #   (3) This script is generated from the Groovy template
-#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
 #       within the Gradle project.
 #       within the Gradle project.
 #
 #
 #       You can find Gradle at https://github.com/gradle/gradle/.
 #       You can find Gradle at https://github.com/gradle/gradle/.
@@ -83,10 +83,8 @@ done
 # This is normally unused
 # This is normally unused
 # shellcheck disable=SC2034
 # shellcheck disable=SC2034
 APP_BASE_NAME=${0##*/}
 APP_BASE_NAME=${0##*/}
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
 
 
 # Use the maximum available, or set MAX_FD != -1 to use that value.
 # Use the maximum available, or set MAX_FD != -1 to use that value.
 MAX_FD=maximum
 MAX_FD=maximum
@@ -133,10 +131,13 @@ location of your Java installation."
     fi
     fi
 else
 else
     JAVACMD=java
     JAVACMD=java
-    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+    if ! command -v java >/dev/null 2>&1
+    then
+        die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
 
 
 Please set the JAVA_HOME variable in your environment to match the
 Please set the JAVA_HOME variable in your environment to match the
 location of your Java installation."
 location of your Java installation."
+    fi
 fi
 fi
 
 
 # Increase the maximum file descriptors if we can.
 # Increase the maximum file descriptors if we can.
@@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
     case $MAX_FD in #(
     case $MAX_FD in #(
       max*)
       max*)
         # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
         # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
-        # shellcheck disable=SC3045 
+        # shellcheck disable=SC2039,SC3045
         MAX_FD=$( ulimit -H -n ) ||
         MAX_FD=$( ulimit -H -n ) ||
             warn "Could not query maximum file descriptor limit"
             warn "Could not query maximum file descriptor limit"
     esac
     esac
@@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
       '' | soft) :;; #(
       '' | soft) :;; #(
       *)
       *)
         # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
         # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
-        # shellcheck disable=SC3045 
+        # shellcheck disable=SC2039,SC3045
         ulimit -n "$MAX_FD" ||
         ulimit -n "$MAX_FD" ||
             warn "Could not set maximum file descriptor limit to $MAX_FD"
             warn "Could not set maximum file descriptor limit to $MAX_FD"
     esac
     esac
@@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then
     done
     done
 fi
 fi
 
 
-# Collect all arguments for the java command;
-#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
-#     shell script including quotes and variable substitutions, so put them in
-#     double quotes to make sure that they get re-expanded; and
-#   * put everything else in single quotes, so that it's not re-expanded.
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+#     and any embedded shellness will be escaped.
+#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+#     treated as '${Hostname}' itself on the command line.
 
 
 set -- \
 set -- \
         "-Dorg.gradle.appname=$APP_BASE_NAME" \
         "-Dorg.gradle.appname=$APP_BASE_NAME" \

+ 10 - 10
gradlew.bat

@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
 %JAVA_EXE% -version >NUL 2>&1
 %JAVA_EXE% -version >NUL 2>&1
 if %ERRORLEVEL% equ 0 goto execute
 if %ERRORLEVEL% equ 0 goto execute
 
 
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
 
 
 goto fail
 goto fail
 
 
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
 
 
 if exist "%JAVA_EXE%" goto execute
 if exist "%JAVA_EXE%" goto execute
 
 
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
 
 
 goto fail
 goto fail
 
 

+ 2 - 3
jme3-android-examples/build.gradle

@@ -41,8 +41,8 @@ android {
 
 
 dependencies {
 dependencies {
     implementation fileTree(dir: 'libs', include: ['*.jar'])
     implementation fileTree(dir: 'libs', include: ['*.jar'])
-    testImplementation 'junit:junit:4.13.2'
-    implementation 'com.android.support:appcompat-v7:28.0.0'
+    testImplementation libs.junit4
+    implementation libs.android.support.appcompat
 
 
     implementation project(':jme3-core')
     implementation project(':jme3-core')
     implementation project(':jme3-android')
     implementation project(':jme3-android')
@@ -54,5 +54,4 @@ dependencies {
     implementation project(':jme3-plugins')
     implementation project(':jme3-plugins')
     implementation project(':jme3-terrain')
     implementation project(':jme3-terrain')
     implementation fileTree(dir: '../jme3-examples/build/libs', include: ['*.jar'], exclude: ['*sources*.*'])
     implementation fileTree(dir: '../jme3-examples/build/libs', include: ['*.jar'], exclude: ['*sources*.*'])
-//    compile project(':jme3-examples')
 }
 }

+ 3 - 0
jme3-android-native/bufferallocator.gradle

@@ -44,6 +44,9 @@ task copyPreCompiledLibsBufferAllocator(type: Copy) {
     from file(bufferAllocatorPreCompiledLibsDir)
     from file(bufferAllocatorPreCompiledLibsDir)
     into file(bufferAllocatorBuildLibsDir)
     into file(bufferAllocatorBuildLibsDir)
 }
 }
+if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") {
+    copyPreCompiledLibsBufferAllocator.dependsOn(rootProject.extractPrebuiltNatives)
+}
 
 
 // ndkExists is a boolean from the build.gradle in the root project
 // ndkExists is a boolean from the build.gradle in the root project
 // buildNativeProjects is a string set to "true"
 // buildNativeProjects is a string set to "true"

+ 0 - 3
jme3-android-native/build.gradle

@@ -28,11 +28,8 @@ ext {
         exclude ".gradle"
         exclude ".gradle"
     }.asPath
     }.asPath
 }
 }
-//println "projectClassPath = " + projectClassPath
 
 
 // add each native lib build file
 // add each native lib build file
 apply from: file('openalsoft.gradle')
 apply from: file('openalsoft.gradle')
-// apply from: file('stb_image.gradle')
-// apply from: file('tremor.gradle')
 apply from: file('decode.gradle')
 apply from: file('decode.gradle')
 apply from: file('bufferallocator.gradle')
 apply from: file('bufferallocator.gradle')

+ 3 - 0
jme3-android-native/decode.gradle

@@ -83,6 +83,9 @@ task copyPreCompiledLibs(type: Copy) {
     from sourceDir
     from sourceDir
     into outputDir
     into outputDir
 }
 }
+if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") {
+    copyPreCompiledLibs.dependsOn(rootProject.extractPrebuiltNatives)
+}
 
 
 // ndkExists is a boolean from the build.gradle in the root project
 // ndkExists is a boolean from the build.gradle in the root project
 // buildNativeProjects is a string set to "true"
 // buildNativeProjects is a string set to "true"

+ 3 - 0
jme3-android-native/openalsoft.gradle

@@ -111,6 +111,9 @@ task copyPreCompiledOpenAlSoftLibs(type: Copy) {
     from sourceDir
     from sourceDir
     into outputDir
     into outputDir
 }
 }
+if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") {
+    copyPreCompiledOpenAlSoftLibs.dependsOn(rootProject.extractPrebuiltNatives)
+}
 
 
 // ndkExists is a boolean from the build.gradle in the root project
 // ndkExists is a boolean from the build.gradle in the root project
 // buildNativeProjects is a string set to "true"
 // buildNativeProjects is a string set to "true"

+ 2 - 2
jme3-android/build.gradle

@@ -2,8 +2,8 @@ apply plugin: 'java'
 
 
 dependencies {
 dependencies {
     //added annotations used by JmeSurfaceView.
     //added annotations used by JmeSurfaceView.
-    compileOnly 'androidx.annotation:annotation:1.3.0'
-    compileOnly 'androidx.lifecycle:lifecycle-common:2.4.0'
+    compileOnly libs.androidx.annotation
+    compileOnly libs.androidx.lifecycle.common
     api project(':jme3-core')
     api project(':jme3-core')
     compileOnly 'android:android'
     compileOnly 'android:android'
 }
 }

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

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2009-2022 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
@@ -66,7 +66,7 @@ public class AnimComposer extends AbstractControl {
      * Instantiate a composer with a single layer, no actions, and no clips.
      * Instantiate a composer with a single layer, no actions, and no clips.
      */
      */
     public AnimComposer() {
     public AnimComposer() {
-        layers.put(DEFAULT_LAYER, new AnimLayer(this, DEFAULT_LAYER, null));
+        layers.put(DEFAULT_LAYER, new AnimLayer(DEFAULT_LAYER, null));
     }
     }
 
 
     /**
     /**
@@ -310,21 +310,24 @@ 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)
+     * @param name The desired name for the new layer
+     * @param mask The desired mask for the new layer (alias created)
+     * @return a new layer
      */
      */
-    public void makeLayer(String name, AnimationMask mask) {
-        AnimLayer l = new AnimLayer(this, name, mask);
+    public AnimLayer makeLayer(String name, AnimationMask 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 void removeLayer(String name) {
-        layers.remove(name);
+    public AnimLayer removeLayer(String name) {
+        return layers.remove(name);
     }
     }
 
 
     /**
     /**
@@ -399,7 +402,7 @@ public class AnimComposer extends AbstractControl {
     @Override
     @Override
     protected void controlUpdate(float tpf) {
     protected void controlUpdate(float tpf) {
         for (AnimLayer layer : layers.values()) {
         for (AnimLayer layer : layers.values()) {
-            layer.update(tpf);
+            layer.update(tpf, globalSpeed);
         }
         }
     }
     }
 
 
@@ -542,6 +545,7 @@ public class AnimComposer extends AbstractControl {
         InputCapsule ic = im.getCapsule(this);
         InputCapsule ic = im.getCapsule(this);
         animClipMap = (Map<String, AnimClip>) ic.readStringSavableMap("animClipMap", new HashMap<String, AnimClip>());
         animClipMap = (Map<String, AnimClip>) ic.readStringSavableMap("animClipMap", new HashMap<String, AnimClip>());
         globalSpeed = ic.readFloat("globalSpeed", 1f);
         globalSpeed = ic.readFloat("globalSpeed", 1f);
+        layers = (Map<String, AnimLayer>) ic.readStringSavableMap("layers", new HashMap<String, AnimLayer>());
     }
     }
 
 
     /**
     /**
@@ -557,5 +561,6 @@ public class AnimComposer extends AbstractControl {
         OutputCapsule oc = ex.getCapsule(this);
         OutputCapsule oc = ex.getCapsule(this);
         oc.writeStringSavableMap(animClipMap, "animClipMap", new HashMap<String, AnimClip>());
         oc.writeStringSavableMap(animClipMap, "animClipMap", new HashMap<String, AnimClip>());
         oc.write(globalSpeed, "globalSpeed", 1f);
         oc.write(globalSpeed, "globalSpeed", 1f);
+        oc.writeStringSavableMap(layers, "layers", new HashMap<String, AnimLayer>());
     }
     }
 }
 }

+ 37 - 16
jme3-core/src/main/java/com/jme3/anim/AnimLayer.java

@@ -32,8 +32,14 @@
 package com.jme3.anim;
 package com.jme3.anim;
 
 
 import com.jme3.anim.tween.action.Action;
 import com.jme3.anim.tween.action.Action;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
 import com.jme3.util.clone.Cloner;
 import com.jme3.util.clone.Cloner;
 import com.jme3.util.clone.JmeCloneable;
 import com.jme3.util.clone.JmeCloneable;
+import java.io.IOException;
 
 
 /**
 /**
  * A named portion of an AnimComposer that can run (at most) one Action at a
  * A named portion of an AnimComposer that can run (at most) one Action at a
@@ -48,7 +54,7 @@ import com.jme3.util.clone.JmeCloneable;
  * <p>Animation time may advance at a different rate from application time,
  * <p>Animation time may advance at a different rate from application time,
  * based on speedup factors in the composer and the current Action.
  * based on speedup factors in the composer and the current Action.
  */
  */
-public class AnimLayer implements JmeCloneable {
+public class AnimLayer implements JmeCloneable, Savable {
     /**
     /**
      * The Action currently running on this layer, or null if none.
      * The Action currently running on this layer, or null if none.
      */
      */
@@ -57,16 +63,12 @@ public class AnimLayer implements JmeCloneable {
      * The name of Action currently running on this layer, or null if none.
      * The name of Action currently running on this layer, or null if none.
      */
      */
     private String currentActionName;
     private String currentActionName;
-    /**
-     * The composer that owns this layer. Were it not for cloning, this field
-     * would be final.
-     */
-    private AnimComposer composer;
+    
     /**
     /**
      * Limits the portion of the model animated by this layer. If null, this
      * Limits the portion of the model animated by this layer. If null, this
      * layer can animate the entire model.
      * layer can animate the entire model.
      */
      */
-    private final AnimationMask mask;
+    private AnimationMask mask;
     /**
     /**
      * The current animation time, in scaled seconds. Always non-negative.
      * The current animation time, in scaled seconds. Always non-negative.
      */
      */
@@ -79,23 +81,26 @@ public class AnimLayer implements JmeCloneable {
     /**
     /**
      * The name of this layer.
      * The name of this layer.
      */
      */
-    final private String name;
+    private String name;
 
 
     private boolean loop = true;
     private boolean loop = true;
+    
+    /**
+    * For serialization only. Do not use.
+    */
+    protected AnimLayer() {
+        
+    }
 
 
     /**
     /**
      * Instantiates a layer without a manager or a current Action, owned by the
      * Instantiates a layer without a manager or a current Action, owned by the
      * specified composer.
      * specified composer.
      *
      *
-     * @param composer the owner (not null, alias created)
      * @param name the layer name (not null)
      * @param name the layer name (not null)
      * @param mask the AnimationMask (alias created) or null to allow this layer
      * @param mask the AnimationMask (alias created) or null to allow this layer
      *     to animate the entire model
      *     to animate the entire model
      */
      */
-    AnimLayer(AnimComposer composer, String name, AnimationMask mask) {
-        assert composer != null;
-        this.composer = composer;
-
+    AnimLayer(String name, AnimationMask mask) {
         assert name != null;
         assert name != null;
         this.name = name;
         this.name = name;
 
 
@@ -248,14 +253,15 @@ public class AnimLayer implements JmeCloneable {
      *
      *
      * @param appDeltaTimeInSeconds the amount application time to advance the
      * @param appDeltaTimeInSeconds the amount application time to advance the
      *     current Action, in seconds
      *     current Action, in seconds
+     * @param globalSpeed the global speed applied to all layers.
      */
      */
-    void update(float appDeltaTimeInSeconds) {
+    void update(float appDeltaTimeInSeconds, float globalSpeed) {
         Action action = currentAction;
         Action action = currentAction;
         if (action == null) {
         if (action == null) {
             return;
             return;
         }
         }
 
 
-        double speedup = action.getSpeed() * composer.getGlobalSpeed();
+        double speedup = action.getSpeed() * globalSpeed;
         double scaledDeltaTime = speedup * appDeltaTimeInSeconds;
         double scaledDeltaTime = speedup * appDeltaTimeInSeconds;
         time += scaledDeltaTime;
         time += scaledDeltaTime;
 
 
@@ -292,7 +298,6 @@ public class AnimLayer implements JmeCloneable {
      */
      */
     @Override
     @Override
     public void cloneFields(Cloner cloner, Object original) {
     public void cloneFields(Cloner cloner, Object original) {
-        composer = cloner.clone(composer);
         currentAction = null;
         currentAction = null;
         currentActionName = null;
         currentActionName = null;
     }
     }
@@ -306,4 +311,20 @@ public class AnimLayer implements JmeCloneable {
             throw new AssertionError();
             throw new AssertionError();
         }
         }
     }
     }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(name, "name", null);
+        if (mask instanceof Savable) {
+            oc.write((Savable) mask, "mask", null);
+        }
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        name = ic.readString("name", null);
+        mask = (AnimationMask) ic.readSavable("mask", null);
+    }
 }
 }

+ 2 - 2
jme3-core/src/main/java/com/jme3/anim/Armature.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
@@ -111,7 +111,7 @@ public class Armature implements JmeCloneable, Savable {
 
 
     /**
     /**
      * Sets the JointModelTransform implementation
      * Sets the JointModelTransform implementation
-     * Default is {@link MatrixJointModelTransform}
+     * Default is {@link SeparateJointModelTransform}
      *
      *
      * @param modelTransformClass which implementation to use
      * @param modelTransformClass which implementation to use
      * @see JointModelTransform
      * @see JointModelTransform

+ 20 - 2
jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java

@@ -31,14 +31,20 @@
  */
  */
 package com.jme3.anim;
 package com.jme3.anim;
 
 
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import java.io.IOException;
 import java.util.BitSet;
 import java.util.BitSet;
 
 
 /**
 /**
  * An AnimationMask to select joints from a single Armature.
  * An AnimationMask to select joints from a single Armature.
  */
  */
-public class ArmatureMask implements AnimationMask {
+public class ArmatureMask implements AnimationMask, Savable {
 
 
-    final private BitSet affectedJoints = new BitSet();
+    private BitSet affectedJoints = new BitSet();
 
 
     /**
     /**
      * Instantiate a mask that affects no joints.
      * Instantiate a mask that affects no joints.
@@ -206,4 +212,16 @@ public class ArmatureMask implements AnimationMask {
 
 
         return this;
         return this;
     }
     }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(affectedJoints, "affectedJoints", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        InputCapsule ic = im.getCapsule(this);
+        affectedJoints = ic.readBitSet("affectedJoints", null);
+    }
 }
 }

+ 9 - 0
jme3-core/src/main/java/com/jme3/anim/MorphTrack.java

@@ -228,6 +228,15 @@ public class MorphTrack implements AnimTrack<float[]> {
         fi.interpolateWeights(blend, startFrame, weights, nbMorphTargets, store);
         fi.interpolateWeights(blend, startFrame, weights, nbMorphTargets, store);
     }
     }
 
 
+    /**
+     * Access the FrameInterpolator.
+     *
+     * @return the pre-existing instance or null
+     */
+    public FrameInterpolator getFrameInterpolator() {
+        return interpolator;
+    }
+
     /**
     /**
      * Replace the FrameInterpolator.
      * Replace the FrameInterpolator.
      *
      *

+ 218 - 0
jme3-core/src/main/java/com/jme3/anim/SingleLayerInfluenceMask.java

@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 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.anim;
+
+import com.jme3.scene.Spatial;
+
+/**
+ * Mask that excludes joints from participating in the layer
+ * if a higher layer is using those joints in an animation.
+ * 
+ * @author codex
+ */
+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;
+    }
+    
+    /**
+     * Adds the given joint and all its children to this mask.
+     * @param joint
+     * @return this instance
+     */
+    public SingleLayerInfluenceMask addFromJoint(String joint) {
+        super.addFromJoint(skin.getArmature(), joint);
+        return this;
+    }
+    
+    /**
+     * Adds the given joints to this mask.
+     * @param joints
+     * @return this instance
+     */
+    public SingleLayerInfluenceMask addJoints(String... joints) {
+        super.addBones(skin.getArmature(), joints);
+        return this;
+    }
+    
+    /**
+     * 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
+     */
+    public SingleLayerInfluenceMask setCheckUpperLayers(boolean check) {
+        checkUpperLayers = check;
+        return this;
+    }
+    
+    /**
+     * 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
+     */
+    public String getTargetLayer() {
+        return layer;
+    }
+    
+    /**
+     * 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
+     */
+    public SkinningControl getSkinningControl() {
+        return skin;
+    }
+    
+    /**
+     * Returns true if this mask is checking upper layers for joint use.
+     * @return 
+     */
+    public boolean isCheckUpperLayers() {
+        return checkUpperLayers;
+    }
+    
+    @Override
+    public boolean contains(Object target) {
+        return simpleContains(target) && (!checkUpperLayers || !isAffectedByUpperLayers(target));
+    }
+    
+    private boolean simpleContains(Object target) {
+        return super.contains(target);
+    }
+    
+    private boolean isAffectedByUpperLayers(Object target) {
+        boolean higher = false;
+        for (String name : anim.getLayerNames()) {
+            if (name.equals(layer)) {
+                higher = true;
+                continue;
+            }
+            if (!higher) {
+                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)) {
+                    return true;
+                }
+            }
+            else if (lyr.getMask().contains(target)) {
+                return true;
+            }
+        }
+        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();
+    }
+    
+    /**
+     * 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();
+    }
+    
+}
+

+ 25 - 15
jme3-core/src/main/java/com/jme3/anim/SkinningControl.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2023 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
@@ -327,15 +327,20 @@ public class SkinningControl extends AbstractControl implements Cloneable, JmeCl
                 VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition);
                 VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition);
                 VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal);
                 VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal);
                 VertexBuffer pos = mesh.getBuffer(Type.Position);
                 VertexBuffer pos = mesh.getBuffer(Type.Position);
-                VertexBuffer norm = mesh.getBuffer(Type.Normal);
                 FloatBuffer pb = (FloatBuffer) pos.getData();
                 FloatBuffer pb = (FloatBuffer) pos.getData();
-                FloatBuffer nb = (FloatBuffer) norm.getData();
                 FloatBuffer bpb = (FloatBuffer) bindPos.getData();
                 FloatBuffer bpb = (FloatBuffer) bindPos.getData();
-                FloatBuffer bnb = (FloatBuffer) bindNorm.getData();
                 pb.clear();
                 pb.clear();
-                nb.clear();
                 bpb.clear();
                 bpb.clear();
-                bnb.clear();
+
+                // reset bind normals if there is a BindPoseNormal buffer
+                if (bindNorm != null) {
+                    VertexBuffer norm = mesh.getBuffer(Type.Normal);
+                    FloatBuffer nb = (FloatBuffer) norm.getData();
+                    FloatBuffer bnb = (FloatBuffer) bindNorm.getData();
+                    nb.clear();
+                    bnb.clear();
+                    nb.put(bnb).clear();
+                }
 
 
                 //resetting bind tangents if there is a bind tangent buffer
                 //resetting bind tangents if there is a bind tangent buffer
                 VertexBuffer bindTangents = mesh.getBuffer(Type.BindPoseTangent);
                 VertexBuffer bindTangents = mesh.getBuffer(Type.BindPoseTangent);
@@ -348,9 +353,7 @@ public class SkinningControl extends AbstractControl implements Cloneable, JmeCl
                     tb.put(btb).clear();
                     tb.put(btb).clear();
                 }
                 }
 
 
-
                 pb.put(bpb).clear();
                 pb.put(bpb).clear();
-                nb.put(bnb).clear();
             }
             }
         }
         }
     }
     }
@@ -583,9 +586,10 @@ public class SkinningControl extends AbstractControl implements Cloneable, JmeCl
 
 
         VertexBuffer nb = mesh.getBuffer(Type.Normal);
         VertexBuffer nb = mesh.getBuffer(Type.Normal);
 
 
-        FloatBuffer fnb = (FloatBuffer) nb.getData();
-        fnb.rewind();
-
+        FloatBuffer fnb = (nb == null) ? null : (FloatBuffer) nb.getData();
+        if (fnb != null) {
+            fnb.rewind();
+        }
 
 
         FloatBuffer ftb = (FloatBuffer) tb.getData();
         FloatBuffer ftb = (FloatBuffer) tb.getData();
         ftb.rewind();
         ftb.rewind();
@@ -615,7 +619,9 @@ public class SkinningControl extends AbstractControl implements Cloneable, JmeCl
             bufLength = Math.min(posBuf.length, fvb.remaining());
             bufLength = Math.min(posBuf.length, fvb.remaining());
             tanLength = Math.min(tanBuf.length, ftb.remaining());
             tanLength = Math.min(tanBuf.length, ftb.remaining());
             fvb.get(posBuf, 0, bufLength);
             fvb.get(posBuf, 0, bufLength);
-            fnb.get(normBuf, 0, bufLength);
+            if (fnb != null) {
+                fnb.get(normBuf, 0, bufLength);
+            }
             ftb.get(tanBuf, 0, tanLength);
             ftb.get(tanBuf, 0, tanLength);
             int verts = bufLength / 3;
             int verts = bufLength / 3;
             int idxPositions = 0;
             int idxPositions = 0;
@@ -688,8 +694,10 @@ public class SkinningControl extends AbstractControl implements Cloneable, JmeCl
 
 
             fvb.position(fvb.position() - bufLength);
             fvb.position(fvb.position() - bufLength);
             fvb.put(posBuf, 0, bufLength);
             fvb.put(posBuf, 0, bufLength);
-            fnb.position(fnb.position() - bufLength);
-            fnb.put(normBuf, 0, bufLength);
+            if (fnb != null) {
+                fnb.position(fnb.position() - bufLength);
+                fnb.put(normBuf, 0, bufLength);
+            }
             ftb.position(ftb.position() - tanLength);
             ftb.position(ftb.position() - tanLength);
             ftb.put(tanBuf, 0, tanLength);
             ftb.put(tanBuf, 0, tanLength);
         }
         }
@@ -697,7 +705,9 @@ public class SkinningControl extends AbstractControl implements Cloneable, JmeCl
         vars.release();
         vars.release();
 
 
         vb.updateData(fvb);
         vb.updateData(fvb);
-        nb.updateData(fnb);
+        if (nb != null) {
+            nb.updateData(fnb);
+        }
         tb.updateData(ftb);
         tb.updateData(ftb);
     }
     }
 
 

+ 9 - 0
jme3-core/src/main/java/com/jme3/anim/TransformTrack.java

@@ -301,6 +301,15 @@ public class TransformTrack implements AnimTrack<Transform> {
         }
         }
     }
     }
 
 
+    /**
+     * Access the FrameInterpolator.
+     *
+     * @return the pre-existing instance or null
+     */
+    public FrameInterpolator getFrameInterpolator() {
+        return interpolator;
+    }
+
     /**
     /**
      * Replaces the frame interpolator.
      * Replaces the frame interpolator.
      *
      *

+ 133 - 21
jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java

@@ -1,3 +1,34 @@
+/*
+ * Copyright (c) 2009-2023 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;
 
 
 import com.jme3.anim.AnimationMask;
 import com.jme3.anim.AnimationMask;
@@ -5,14 +36,43 @@ import com.jme3.anim.tween.Tween;
 import com.jme3.util.clone.Cloner;
 import com.jme3.util.clone.Cloner;
 import com.jme3.util.clone.JmeCloneable;
 import com.jme3.util.clone.JmeCloneable;
 
 
+/**
+ * Wraps an array of Tween actions into an action object.
+ * 
+ * <p>
+ * Notes :
+ * <li> The sequence of tweens is determined by {@link com.jme3.anim.tween.Tweens} utility class and the {@link BaseAction} interpolates that sequence. </li>
+ * <li> This implementation mimics the {@link com.jme3.anim.tween.AbstractTween}, but it delegates the interpolation method {@link Tween#interpolate(double)}
+ * to the {@link BlendableAction} class. </li>
+ * </p>
+ * 
+ * Created by Nehon.
+ *
+ * @see BlendableAction
+ * @see BaseAction
+ */
 public abstract class Action implements JmeCloneable, Tween {
 public abstract class Action implements JmeCloneable, Tween {
-
+    
+    /**
+     * A sequence of actions which wraps given tween actions.
+     */
     protected Action[] actions;
     protected Action[] actions;
     private double length;
     private double length;
     private double speed = 1;
     private double speed = 1;
     private AnimationMask mask;
     private AnimationMask mask;
     private boolean forward = true;
     private boolean forward = true;
-
+    
+    /**
+     * Instantiates an action object that wraps a tween actions array by extracting their actions to the collection {@link Action#actions}.
+     * <p>
+     * Notes :
+     * <li> If intentions are to wrap some tween actions, then subclasses have to call this constructor, examples : {@link BlendableAction} and {@link BlendAction}. </li>
+     * <li> If intentions are to make an implementation of {@link Action} that shouldn't wrap tweens of actions, then subclasses shouldn't call this
+     * constructor, examples : {@link ClipAction} and {@link BaseAction}. </li>
+     * </p> 
+     *
+     * @param tweens the tween actions to be wrapped (not null).
+     */    
     protected Action(Tween... tweens) {
     protected Action(Tween... tweens) {
         this.actions = new Action[tweens.length];
         this.actions = new Action[tweens.length];
         for (int i = 0; i < tweens.length; i++) {
         for (int i = 0; i < tweens.length; i++) {
@@ -24,14 +84,19 @@ public abstract class Action implements JmeCloneable, Tween {
             }
             }
         }
         }
     }
     }
-
+    
+    /**
+     * Retrieves the length (the duration) of the current action.
+     *
+     * @return the length of the action in seconds.
+     */
     @Override
     @Override
     public double getLength() {
     public double getLength() {
         return length;
         return length;
     }
     }
 
 
     /**
     /**
-     * Alter the length (duration) of this Action.  This can be used to extend
+     * Alters the length (duration) of this Action. This can be used to extend
      * or truncate an Action.
      * or truncate an Action.
      *
      *
      * @param length the desired length (in unscaled seconds, default=0)
      * @param length the desired length (in unscaled seconds, default=0)
@@ -40,44 +105,66 @@ public abstract class Action implements JmeCloneable, Tween {
         this.length = length;
         this.length = length;
     }
     }
 
 
+    /**
+     * Retrieves the speedup factor applied by the layer for this action.
+     *
+     * @see Action#setSpeed(double) for detailed documentation
+     * @return the speed of frames.
+     */
     public double getSpeed() {
     public double getSpeed() {
         return speed;
         return speed;
     }
     }
 
 
+    /**
+     * Alters the speedup factor applied by the layer running this action.
+     * <p>
+     * Notes:
+     * <li> This factor controls the animation direction, if the speed is a positive value then the animation will run forward and vice versa. </li>
+     * <li> The speed factor gets applied, inside the {@link com.jme3.anim.AnimLayer}, on each interpolation step by this formula : time += tpf * action.getSpeed() * composer.globalSpeed. </li>
+     * <li> Default speed is 1.0, it plays the animation clips at their normal speed. </li>
+     * <li> Setting the speed factor to Zero will stop the animation, while setting it to a negative number will play the animation in a backward fashion. </li>
+     * </p>
+     * 
+     * @param speed the speed of frames.
+     */
     public void setSpeed(double speed) {
     public void setSpeed(double speed) {
         this.speed = speed;
         this.speed = speed;
-        if( speed < 0){
+        if (speed < 0) {
             setForward(false);
             setForward(false);
         } else {
         } else {
             setForward(true);
             setForward(true);
         }
         }
     }
     }
 
 
+    /**
+     * Retrieves the animation mask for this action.
+     * The animation mask controls which part of the model would be animated. A model part can be
+     * registered using a {@link com.jme3.anim.Joint}.
+     *
+     * @return the animation mask instance, or null if this action will animate the entire model
+     * @see com.jme3.anim.AnimLayer to adjust the animation mask to control which part will be animated
+     */
     public AnimationMask getMask() {
     public AnimationMask getMask() {
         return mask;
         return mask;
     }
     }
 
 
+    /**
+     * Meant for internal use only.
+     *
+     * <p> 
+     * Note: This method can be invoked from the user code only if this Action is wrapped by a {@link BaseAction} and
+     * the {@link BaseAction#isMaskPropagationEnabled()} is false.
+     * </p>
+     *
+     * @param mask an animation mask to be applied to this action.
+     * @see com.jme3.anim.AnimLayer to adjust the animation mask to control which part will be animated
+     */
     public void setMask(AnimationMask mask) {
     public void setMask(AnimationMask mask) {
         this.mask = mask;
         this.mask = mask;
     }
     }
 
 
-    protected boolean isForward() {
-        return forward;
-    }
-
-    protected void setForward(boolean forward) {
-        if(this.forward == forward){
-            return;
-        }
-        this.forward = forward;
-        for (Action action : actions) {
-            action.setForward(forward);
-        }
-
-    }
-
     /**
     /**
-     * Create a shallow clone for the JME cloner.
+     * Creates a shallow clone for the JME cloner.
      *
      *
      * @return a new action (not null)
      * @return a new action (not null)
      */
      */
@@ -105,4 +192,29 @@ public abstract class Action implements JmeCloneable, Tween {
         actions = cloner.clone(actions);
         actions = cloner.clone(actions);
         mask = cloner.clone(mask);
         mask = cloner.clone(mask);
     }
     }
+    
+    /**
+     * Tests whether the Action is running in the "forward" mode.
+     *
+     * @return true if the animation action is running forward, false otherwise.
+     */
+    protected boolean isForward() {
+        return forward;
+    }
+
+    /**
+     * Adjusts the forward flag which controls the animation action directionality.
+     *
+     * @param forward true to run the animation forward, false otherwise.
+     * @see Action#setSpeed(double) to change the directionality of the tween actions, negative numbers play the animation in a backward fashion
+     */
+    protected void setForward(boolean forward) {
+        if (this.forward == forward) {
+            return;
+        }
+        this.forward = forward;
+        for (Action action : actions) {
+            action.setForward(forward);
+        }
+    }
 }
 }

+ 60 - 15
jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2009-2022 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
@@ -35,14 +35,40 @@ import com.jme3.anim.AnimationMask;
 import com.jme3.anim.tween.ContainsTweens;
 import com.jme3.anim.tween.ContainsTweens;
 import com.jme3.anim.tween.Tween;
 import com.jme3.anim.tween.Tween;
 import com.jme3.util.SafeArrayList;
 import com.jme3.util.SafeArrayList;
-
 import java.util.List;
 import java.util.List;
 
 
+/**
+ * A simple implementation for the abstract class {@link Action} to provide a wrapper for a {@link Tween}.
+ * Internally, it is used as a helper class for {@link Action} to extract and gather actions from a tween and interpolate it.
+ * <p>
+ * An example showing two clip actions running in parallel at 2x of their ordinary speed
+ * by the help of BaseAction on a new Animation Layer :
+ * <pre class="prettyprint">
+ * //create a base action from a tween.
+ * final BaseAction action = new BaseAction(Tweens.parallel(clipAction0, clipAction1));
+ * //set the action properties - utilized within the #{@link Action} class.
+ * baseAction.setSpeed(2f);
+ * //register the action as an observer to the animComposer control.
+ * animComposer.addAction("basicAction", action);
+ * //make a new Layer for a basic armature mask
+ * animComposer.makeLayer(ActionState.class.getSimpleName(), new ArmatureMask());
+ * //run the action within this layer
+ * animComposer.setCurrentAction("basicAction", ActionState.class.getSimpleName());
+ * </pre>
+ * </p>
+ * Created by Nehon.
+ */
 public class BaseAction extends Action {
 public class BaseAction extends Action {
 
 
     final private Tween tween;
     final private Tween tween;
     private boolean maskPropagationEnabled = true;
     private boolean maskPropagationEnabled = true;
 
 
+    /**
+     * Instantiates an action from a tween by extracting the actions from a tween
+     * to a list of sub-actions to be interpolated later.
+     *
+     * @param tween a tween to extract the actions from (not null).
+     */
     public BaseAction(Tween tween) {
     public BaseAction(Tween tween) {
         this.tween = tween;
         this.tween = tween;
         setLength(tween.getLength());
         setLength(tween.getLength());
@@ -52,33 +78,35 @@ public class BaseAction extends Action {
         subActions.toArray(actions);
         subActions.toArray(actions);
     }
     }
 
 
-    private void gatherActions(Tween tween, List<Action> subActions) {
-        if (tween instanceof Action) {
-            subActions.add((Action) tween);
-        } else if (tween instanceof ContainsTweens) {
-            Tween[] tweens = ((ContainsTweens) tween).getTweens();
-            for (Tween t : tweens) {
-                gatherActions(t, subActions);
-            }
-        }
-    }
-
     /**
     /**
-     * @return true if mask propagation to child actions is enabled else returns false
+     * Tests whether the animation mask is applied to the wrapped actions {@link BaseAction#actions}.
+     *
+     * @return true if mask propagation to child actions is enabled else returns false.
      */
      */
     public boolean isMaskPropagationEnabled() {
     public boolean isMaskPropagationEnabled() {
         return maskPropagationEnabled;
         return maskPropagationEnabled;
     }
     }
 
 
     /**
     /**
+     * Determines whether to apply the animation mask to the wrapped or child actions {@link BaseAction#actions}.
      *
      *
      * @param maskPropagationEnabled If true, then mask set by AnimLayer will be
      * @param maskPropagationEnabled If true, then mask set by AnimLayer will be
-     *                               forwarded to all child actions (Default=true)
+     *                               forwarded to all child actions (Default=true).
      */
      */
     public void setMaskPropagationEnabled(boolean maskPropagationEnabled) {
     public void setMaskPropagationEnabled(boolean maskPropagationEnabled) {
         this.maskPropagationEnabled = maskPropagationEnabled;
         this.maskPropagationEnabled = maskPropagationEnabled;
     }
     }
 
 
+    /**
+     * Sets the animation mask which determines which part of the model will 
+     * be animated by the animation layer. If the {@link BaseAction#isMaskPropagationEnabled()} is false, setting
+     * the mask attribute will not affect the actions under this base action. Setting this to 'null' will animate
+     * the entire model.
+     *
+     * @param mask an animation mask to be applied to this action (nullable).
+     * @see com.jme3.anim.AnimLayer to adjust the animation mask to control which part will be animated
+     * @see BaseAction#setMaskPropagationEnabled(boolean)
+     */
     @Override
     @Override
     public void setMask(AnimationMask mask) {
     public void setMask(AnimationMask mask) {
         super.setMask(mask);
         super.setMask(mask);
@@ -94,4 +122,21 @@ public class BaseAction extends Action {
     public boolean interpolate(double t) {
     public boolean interpolate(double t) {
         return tween.interpolate(t);
         return tween.interpolate(t);
     }
     }
+    
+    /**
+     * Extracts the actions from a tween into a list.
+     *
+     * @param tween      the tween to extract the actions from (not null).
+     * @param subActions a collection to gather the extracted actions (not null).
+     */
+    private void gatherActions(Tween tween, List<Action> subActions) {
+        if (tween instanceof Action) {
+            subActions.add((Action) tween);
+        } else if (tween instanceof ContainsTweens) {
+            Tween[] tweens = ((ContainsTweens) tween).getTweens();
+            for (Tween t : tweens) {
+                gatherActions(t, subActions);
+            }
+        }
+    }
 }
 }

+ 50 - 8
jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java

@@ -1,15 +1,52 @@
+/*
+ * 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.anim.tween.action;
 package com.jme3.anim.tween.action;
 
 
-import com.jme3.anim.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import com.jme3.anim.AnimClip;
+import com.jme3.anim.AnimTrack;
+import com.jme3.anim.MorphTrack;
+import com.jme3.anim.TransformTrack;
+import com.jme3.anim.tween.action.BlendableAction;
 import com.jme3.anim.util.HasLocalTransform;
 import com.jme3.anim.util.HasLocalTransform;
 import com.jme3.math.Transform;
 import com.jme3.math.Transform;
 import com.jme3.scene.Geometry;
 import com.jme3.scene.Geometry;
 import com.jme3.util.clone.Cloner;
 import com.jme3.util.clone.Cloner;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
 
 
 public class ClipAction extends BlendableAction {
 public class ClipAction extends BlendableAction {
+    
     private AnimClip clip;
     private AnimClip clip;
     private Transform transform = new Transform();
     private Transform transform = new Transform();
 
 
@@ -59,8 +96,13 @@ public class ClipAction extends BlendableAction {
 //        }
 //        }
     }
     }
 
 
-    public void reset() {
-
+    /**
+     * Gets the animation clip associated with this action.
+     * 
+     * @return The animation clip
+     */
+    public AnimClip getAnimClip() {
+        return clip;
     }
     }
 
 
     @Override
     @Override
@@ -100,8 +142,8 @@ public class ClipAction extends BlendableAction {
         try {
         try {
             ClipAction clone = (ClipAction) super.clone();
             ClipAction clone = (ClipAction) super.clone();
             return clone;
             return clone;
-        } catch (CloneNotSupportedException exception) {
-            throw new RuntimeException(exception);
+        } catch (CloneNotSupportedException ex) {
+            throw new RuntimeException(ex);
         }
         }
     }
     }
 
 

+ 34 - 2
jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java

@@ -1,3 +1,34 @@
+/*
+ * 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.anim.util;
 package com.jme3.anim.util;
 
 
 import com.jme3.anim.*;
 import com.jme3.anim.*;
@@ -10,8 +41,8 @@ import java.util.*;
 
 
 public class AnimMigrationUtils {
 public class AnimMigrationUtils {
 
 
-    final private static AnimControlVisitor animControlVisitor = new AnimControlVisitor();
-    final private static SkeletonControlVisitor skeletonControlVisitor = new SkeletonControlVisitor();
+    private static final AnimControlVisitor animControlVisitor = new AnimControlVisitor();
+    private static final SkeletonControlVisitor skeletonControlVisitor = new SkeletonControlVisitor();
 
 
     /**
     /**
      * A private constructor to inhibit instantiation of this class.
      * A private constructor to inhibit instantiation of this class.
@@ -64,6 +95,7 @@ public class AnimMigrationUtils {
 
 
                 Armature armature = new Armature(joints);
                 Armature armature = new Armature(joints);
                 armature.saveBindPose();
                 armature.saveBindPose();
+                armature.saveInitialPose();
                 skeletonArmatureMap.put(skeleton, armature);
                 skeletonArmatureMap.put(skeleton, armature);
 
 
                 List<TransformTrack> tracks = new ArrayList<>();
                 List<TransformTrack> tracks = new ArrayList<>();

+ 31 - 0
jme3-core/src/main/java/com/jme3/anim/util/HasLocalTransform.java

@@ -1,3 +1,34 @@
+/*
+ * 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.anim.util;
 package com.jme3.anim.util;
 
 
 import com.jme3.export.Savable;
 import com.jme3.export.Savable;

+ 31 - 0
jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java

@@ -1,3 +1,34 @@
+/*
+ * 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.anim.util;
 package com.jme3.anim.util;
 
 
 import com.jme3.anim.Joint;
 import com.jme3.anim.Joint;

+ 31 - 0
jme3-core/src/main/java/com/jme3/anim/util/Primitives.java

@@ -1,3 +1,34 @@
+/*
+ * 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.anim.util;
 package com.jme3.anim.util;
 
 
 import java.util.Collections;
 import java.util.Collections;

+ 31 - 0
jme3-core/src/main/java/com/jme3/anim/util/Weighted.java

@@ -1,3 +1,34 @@
+/*
+ * 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.anim.util;
 package com.jme3.anim.util;
 
 
 import com.jme3.anim.tween.action.Action;
 import com.jme3.anim.tween.action.Action;

+ 25 - 13
jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2009-2021 jMonkeyEngine
+ * Copyright (c) 2009-2023 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
@@ -321,15 +321,20 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
                 VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition);
                 VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition);
                 VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal);
                 VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal);
                 VertexBuffer pos = mesh.getBuffer(Type.Position);
                 VertexBuffer pos = mesh.getBuffer(Type.Position);
-                VertexBuffer norm = mesh.getBuffer(Type.Normal);
                 FloatBuffer pb = (FloatBuffer) pos.getData();
                 FloatBuffer pb = (FloatBuffer) pos.getData();
-                FloatBuffer nb = (FloatBuffer) norm.getData();
                 FloatBuffer bpb = (FloatBuffer) bindPos.getData();
                 FloatBuffer bpb = (FloatBuffer) bindPos.getData();
-                FloatBuffer bnb = (FloatBuffer) bindNorm.getData();
                 pb.clear();
                 pb.clear();
-                nb.clear();
                 bpb.clear();
                 bpb.clear();
-                bnb.clear();
+
+                // reset bind normals if there is a BindPoseNormal buffer
+                if (bindNorm != null) {
+                    VertexBuffer norm = mesh.getBuffer(Type.Normal);
+                    FloatBuffer nb = (FloatBuffer) norm.getData();
+                    FloatBuffer bnb = (FloatBuffer) bindNorm.getData();
+                    nb.clear();
+                    bnb.clear();
+                    nb.put(bnb).clear();
+                }
 
 
                 //reset bind tangents if there is a bind tangent buffer
                 //reset bind tangents if there is a bind tangent buffer
                 VertexBuffer bindTangents = mesh.getBuffer(Type.BindPoseTangent);
                 VertexBuffer bindTangents = mesh.getBuffer(Type.BindPoseTangent);
@@ -343,7 +348,6 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
                 }
                 }
 
 
                 pb.put(bpb).clear();
                 pb.put(bpb).clear();
-                nb.put(bnb).clear();
             }
             }
         }
         }
     }
     }
@@ -574,8 +578,10 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
 
 
         VertexBuffer nb = mesh.getBuffer(Type.Normal);
         VertexBuffer nb = mesh.getBuffer(Type.Normal);
 
 
-        FloatBuffer fnb = (FloatBuffer) nb.getData();
-        fnb.rewind();
+        FloatBuffer fnb = (nb == null) ? null : (FloatBuffer) nb.getData();
+        if (fnb != null) {
+            fnb.rewind();
+        }
 
 
         FloatBuffer ftb = (FloatBuffer) tb.getData();
         FloatBuffer ftb = (FloatBuffer) tb.getData();
         ftb.rewind();
         ftb.rewind();
@@ -603,7 +609,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
             bufLength = Math.min(posBuf.length, fvb.remaining());
             bufLength = Math.min(posBuf.length, fvb.remaining());
             tanLength = Math.min(tanBuf.length, ftb.remaining());
             tanLength = Math.min(tanBuf.length, ftb.remaining());
             fvb.get(posBuf, 0, bufLength);
             fvb.get(posBuf, 0, bufLength);
-            fnb.get(normBuf, 0, bufLength);
+            if (fnb != null) {
+                fnb.get(normBuf, 0, bufLength);
+            }
             ftb.get(tanBuf, 0, tanLength);
             ftb.get(tanBuf, 0, tanLength);
             int verts = bufLength / 3;
             int verts = bufLength / 3;
             int idxPositions = 0;
             int idxPositions = 0;
@@ -676,8 +684,10 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
 
 
             fvb.position(fvb.position() - bufLength);
             fvb.position(fvb.position() - bufLength);
             fvb.put(posBuf, 0, bufLength);
             fvb.put(posBuf, 0, bufLength);
-            fnb.position(fnb.position() - bufLength);
-            fnb.put(normBuf, 0, bufLength);
+            if (fnb != null) {
+                fnb.position(fnb.position() - bufLength);
+                fnb.put(normBuf, 0, bufLength);
+            }
             ftb.position(ftb.position() - tanLength);
             ftb.position(ftb.position() - tanLength);
             ftb.put(tanBuf, 0, tanLength);
             ftb.put(tanBuf, 0, tanLength);
         }
         }
@@ -685,7 +695,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
         vars.release();
         vars.release();
 
 
         vb.updateData(fvb);
         vb.updateData(fvb);
-        nb.updateData(fnb);
+        if (nb != null) {
+            nb.updateData(fnb);
+        }
         tb.updateData(ftb);
         tb.updateData(ftb);
     }
     }
 
 

+ 236 - 0
jme3-core/src/main/java/com/jme3/app/state/CompositeAppState.java

@@ -0,0 +1,236 @@
+/*
+ * 
+ * Copyright (c) 2014-2024 jMonkeyEngine
+ * Copied with Paul Speed's permission from: https://github.com/Simsilica/SiO2
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions 
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright 
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ * 2. 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.
+ * 
+ * 3. Neither the name of the copyright holder 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 HOLDER 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.app.state;
+
+import com.jme3.app.Application;
+import com.jme3.util.SafeArrayList;
+
+/**
+ *  An AppState that manages a set of child app states, making sure
+ *  they are attached/detached and optional enabled/disabled with the
+ *  parent state.
+ *
+ *  @author    Paul Speed
+ */
+public class CompositeAppState extends BaseAppState {
+
+    private final SafeArrayList<AppStateEntry> states = new SafeArrayList<>(AppStateEntry.class);
+    private boolean childrenEnabled;
+    
+    /**
+     *  Since we manage attachmend/detachment possibly before
+     *  initialization, we need to keep track of the stateManager we
+     *  were given in stateAttached() in case we have to attach another
+     *  child prior to initialization (but after we're attached).
+     *  It's possible that we should actually be waiting for initialize
+     *  to add these but I feel like there was some reason I did it this 
+     *  way originally.  Past-me did not leave any clues.
+     */
+    private AppStateManager stateManager;
+    private boolean attached;  
+    
+    public CompositeAppState(AppState... states) {
+        for (AppState a : states) {
+            this.states.add(new AppStateEntry(a, false));
+        }
+    }
+
+    private int indexOf(AppState state) {
+        for (int i = 0; i < states.size(); i++) {
+            AppStateEntry e = states.get(i);
+            if (e.state == state) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private AppStateEntry entry(AppState state) {
+        for (AppStateEntry e : states.getArray()) {
+            if (e.state == state) {
+                return e;
+            }
+        }
+        return null;
+    }
+
+    protected <T extends AppState> T addChild(T state) {
+        return addChild(state, false);
+    }
+    
+    protected <T extends AppState> T addChild(T state, boolean overrideEnable) {
+        if (indexOf(state) >= 0) {
+            return state;
+        }
+        states.add(new AppStateEntry(state, overrideEnable));
+        if (attached) {
+            stateManager.attach(state);
+        }
+        return state;   
+    }
+ 
+    protected void removeChild( AppState state ) {
+        int index = indexOf(state);
+        if( index < 0 ) {
+            return;
+        }
+        states.remove(index);
+        if( attached ) {
+            stateManager.detach(state);
+        }
+    }
+    
+    protected <T extends AppState> T getChild( Class<T> stateType ) {
+        for( AppStateEntry e : states.getArray() ) {
+            if( stateType.isInstance(e.state) ) {
+                return stateType.cast(e.state);
+            }
+        }
+        return null;
+    }
+
+    protected void clearChildren() {
+        for( AppStateEntry e : states.getArray() ) {
+            removeChild(e.state);
+        }
+    }
+    
+    @Override 
+    public void stateAttached(AppStateManager stateManager) {
+        this.stateManager = stateManager;
+        for (AppStateEntry e : states.getArray()) {
+            stateManager.attach(e.state);
+        }
+        this.attached = true;
+    }
+    
+    @Override
+    public void stateDetached(AppStateManager stateManager) {
+        // Reverse order
+        for (int i = states.size() - 1; i >= 0; i--) {
+            stateManager.detach(states.get(i).state);
+        }
+        this.attached = false;
+        this.stateManager = null;
+    }
+
+    protected void setChildrenEnabled(boolean b) {
+        if(childrenEnabled == b) {
+            return;
+        }
+        childrenEnabled = b;
+        for (AppStateEntry e : states.getArray()) {
+            e.setEnabled(b);
+        }
+    }
+
+    /**
+     *  Overrides the automatic synching of a child's enabled state.
+     *  When override is true, a child will remember its old state when
+     *  the parent's enabled state is false so that when the parent is
+     *  re-enabled the child can resume its previous enabled state.  This
+     *  is useful for the cases where a child may want to be disabled
+     *  independent of the parent... and then not automatically become
+     *  enabled just because the parent does.
+     *  Currently, the parent's disabled state always disables the children,
+     *  too.  Override is about remembering the child's state before that
+     *  happened and restoring it when the 'family' is enabled again as a whole.
+     */
+    public void setOverrideEnabled(AppState state, boolean override) {
+        AppStateEntry e = entry(state);
+        if (e == null) {
+            throw new IllegalArgumentException("State not managed:" + state);
+        }
+        if (override) {
+            e.override = true;
+        } else {
+            e.override = false;
+            e.state.setEnabled(isEnabled());
+        }   
+    }
+
+    @Override
+    protected void initialize(Application app) {
+    }
+
+    @Override
+    protected void cleanup(Application app) {
+    }
+
+    @Override
+    protected void onEnable() {
+        setChildrenEnabled(true);
+    }
+
+    @Override
+    protected void onDisable() {
+        setChildrenEnabled(false);
+    }
+    
+    private class AppStateEntry {
+        AppState state;
+        boolean enabled;
+        boolean override;
+        
+        public AppStateEntry(AppState state, boolean overrideEnable) {
+            this.state = state;
+            this.override = overrideEnable;
+            this.enabled = state.isEnabled();
+        }
+        
+        public void setEnabled(boolean b) {
+ 
+            if (override) {
+                if (b) {
+                    // Set it to whatever its enabled state
+                    // was before going disabled last time.
+                    state.setEnabled(enabled);
+                } else {
+                    // We are going to set enabled to false
+                    // but keep track of what it was before we did
+                    // that
+                    this.enabled = state.isEnabled();
+                    state.setEnabled(false);
+                }               
+            } else {
+                // Just synch it always
+                state.setEnabled(b);
+            }
+        }
+    }
+}
+

+ 1 - 4
jme3-core/src/main/java/com/jme3/asset/ImplHandler.java

@@ -124,10 +124,7 @@ final class ImplHandler {
             } catch (InstantiationException | IllegalAccessException
             } catch (InstantiationException | IllegalAccessException
                     | IllegalArgumentException | InvocationTargetException
                     | IllegalArgumentException | InvocationTargetException
                     | NoSuchMethodException | SecurityException ex) {
                     | NoSuchMethodException | SecurityException ex) {
-                logger.log(Level.SEVERE, "Cannot create locator of type {0}, does"
-                        + " the class have an empty and publicly accessible"
-                        + " constructor?", type.getName());
-                logger.throwing(type.getName(), "<init>", ex);
+                logger.log(Level.SEVERE, "An exception occurred while instantiating asset locator: " + type.getName(), ex);
             }
             }
             return null;
             return null;
         }
         }

+ 1 - 2
jme3-core/src/main/java/com/jme3/asset/cache/WeakRefCloneAssetCache.java

@@ -60,8 +60,7 @@ public class WeakRefCloneAssetCache implements AssetCache {
      * Maps cloned key to AssetRef which has a weak ref to the original
      * Maps cloned key to AssetRef which has a weak ref to the original
      * key and a strong ref to the original asset.
      * key and a strong ref to the original asset.
      */
      */
-    private final ConcurrentHashMap<AssetKey, AssetRef> smartCache
-            = new ConcurrentHashMap<>();
+    private final ConcurrentHashMap<AssetKey, AssetRef> smartCache = new ConcurrentHashMap<>();
 
 
     /**
     /**
      * Stored in the ReferenceQueue to find out when originalKey is collected
      * Stored in the ReferenceQueue to find out when originalKey is collected

+ 1 - 1
jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java

@@ -55,7 +55,7 @@ public class ALAudioRenderer implements AudioRenderer, Runnable {
     // which is exactly 1 second of audio.
     // which is exactly 1 second of audio.
     private static final int BUFFER_SIZE = 35280;
     private static final int BUFFER_SIZE = 35280;
     private static final int STREAMING_BUFFER_COUNT = 5;
     private static final int STREAMING_BUFFER_COUNT = 5;
-    private final static int MAX_NUM_CHANNELS = 64;
+    private static final int MAX_NUM_CHANNELS = 64;
     private IntBuffer ib = BufferUtils.createIntBuffer(1);
     private IntBuffer ib = BufferUtils.createIntBuffer(1);
     private final FloatBuffer fb = BufferUtils.createVector3Buffer(2);
     private final FloatBuffer fb = BufferUtils.createVector3Buffer(2);
     private final ByteBuffer nativeBuf = BufferUtils.createByteBuffer(BUFFER_SIZE);
     private final ByteBuffer nativeBuf = BufferUtils.createByteBuffer(BUFFER_SIZE);

+ 72 - 1
jme3-core/src/main/java/com/jme3/bounding/BoundingBox.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
@@ -46,6 +46,7 @@ import com.jme3.util.TempVars;
 import java.io.IOException;
 import java.io.IOException;
 import java.nio.FloatBuffer;
 import java.nio.FloatBuffer;
 //import com.jme.scene.TriMesh;
 //import com.jme.scene.TriMesh;
+import java.util.Objects;
 
 
 /**
 /**
  * <code>BoundingBox</code> describes a bounding volume as an axis-aligned box.
  * <code>BoundingBox</code> describes a bounding volume as an axis-aligned box.
@@ -587,6 +588,76 @@ public class BoundingBox extends BoundingVolume {
         return rVal;
         return rVal;
     }
     }
 
 
+    /**
+     * Tests for exact equality with the argument, distinguishing -0 from 0. If
+     * {@code other} is null, false is returned. Either way, the current
+     * instance is unaffected.
+     *
+     * @param other the object to compare (may be null, unaffected)
+     * @return true if {@code this} and {@code other} have identical values,
+     *     otherwise false
+     */
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof BoundingBox)) {
+            return false;
+        }
+
+        if (this == other) {
+            return true;
+        }
+
+        BoundingBox otherBoundingBox = (BoundingBox) other;
+        if (Float.compare(xExtent, otherBoundingBox.xExtent) != 0) {
+            return false;
+        } else if (Float.compare(yExtent, otherBoundingBox.yExtent) != 0) {
+            return false;
+        } else if (Float.compare(zExtent, otherBoundingBox.zExtent) != 0) {
+            return false;
+        } else {
+            return super.equals(otherBoundingBox);
+        }
+    }
+
+    /**
+     * Returns a hash code. If two bounding boxes have identical values, they
+     * will have the same hash code. The current instance is unaffected.
+     *
+     * @return a 32-bit value for use in hashing
+     */
+    @Override
+    public int hashCode() {
+        int hash = Objects.hash(xExtent, yExtent, zExtent);
+        hash = 59 * hash + super.hashCode();
+
+        return hash;
+    }
+
+    /**
+     * Tests for approximate equality with the specified bounding box, using the
+     * specified tolerance. If {@code other} is null, false is returned. Either
+     * way, the current instance is unaffected.
+     *
+     * @param aabb the bounding box to compare (unaffected) or null for none
+     * @param epsilon the tolerance for each component
+     * @return true if all components are within tolerance, otherwise false
+     */
+    public boolean isSimilar(BoundingBox aabb, float epsilon) {
+        if (aabb == null) {
+            return false;
+        } else if (Float.compare(Math.abs(aabb.xExtent - xExtent), epsilon) > 0) {
+            return false;
+        } else if (Float.compare(Math.abs(aabb.yExtent - yExtent), epsilon) > 0) {
+            return false;
+        } else if (Float.compare(Math.abs(aabb.zExtent - zExtent), epsilon) > 0) {
+            return false;
+        } else if (!center.isSimilar(aabb.getCenter(), epsilon)) {
+            return false;
+        }
+        // The checkPlane field is ignored.
+        return true;
+    }
+
     /**
     /**
      * <code>toString</code> returns the string representation of this object.
      * <code>toString</code> returns the string representation of this object.
      * The form is: "[Center: vector xExtent: X.XX yExtent: Y.YY zExtent:
      * The form is: "[Center: vector xExtent: X.XX yExtent: Y.YY zExtent:

+ 64 - 1
jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.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
@@ -43,6 +43,7 @@ import com.jme3.util.BufferUtils;
 import com.jme3.util.TempVars;
 import com.jme3.util.TempVars;
 import java.io.IOException;
 import java.io.IOException;
 import java.nio.FloatBuffer;
 import java.nio.FloatBuffer;
+import java.util.Objects;
 import java.util.logging.Level;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.logging.Logger;
 
 
@@ -651,6 +652,68 @@ public class BoundingSphere extends BoundingVolume {
         return new BoundingSphere(radius, center.clone());
         return new BoundingSphere(radius, center.clone());
     }
     }
 
 
+    /**
+     * Tests for exact equality with the argument, distinguishing -0 from 0. If
+     * {@code other} is null, false is returned. Either way, the current
+     * instance is unaffected.
+     *
+     * @param other the object to compare (may be null, unaffected)
+     * @return true if {@code this} and {@code other} have identical values,
+     *     otherwise false
+     */
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof BoundingSphere)) {
+            return false;
+        }
+
+        if (this == other) {
+            return true;
+        }
+
+        BoundingSphere otherBoundingSphere = (BoundingSphere) other;
+        if (Float.compare(radius, otherBoundingSphere.getRadius()) != 0) {
+            return false;
+        } else {
+            return super.equals(otherBoundingSphere);
+        }
+    }
+
+    /**
+     * Returns a hash code. If two bounding boxes have identical values, they
+     * will have the same hash code. The current instance is unaffected.
+     *
+     * @return a 32-bit value for use in hashing
+     */
+    @Override
+    public int hashCode() {
+        int hash = Objects.hash(radius);
+        hash = 59 * hash + super.hashCode();
+
+        return hash;
+    }
+
+    /**
+     * Tests for approximate equality with the specified bounding sphere, using
+     * the specified tolerance. If {@code other} is null, false is returned.
+     * Either way, the current instance is unaffected.
+     *
+     * @param sphere the bounding sphere to compare (unaffected) or null for none
+     * @param epsilon the tolerance for each component
+     * @return true if all components are within tolerance, otherwise false
+     */
+    public boolean isSimilar(BoundingSphere sphere, float epsilon) {
+        if (sphere == null) {
+            return false;
+        } else if (Float.compare(Math.abs(sphere.getRadius() - radius), epsilon) > 0) {
+            return false;
+        } else if (!center.isSimilar(sphere.getCenter(), epsilon)) {
+            return false;
+        }
+        // The checkPlane field is ignored.
+        return true;
+    }
+
     /**
     /**
      * <code>toString</code> returns the string representation of this object.
      * <code>toString</code> returns the string representation of this object.
      * The form is: "Radius: RRR.SSSS Center: vector".
      * The form is: "Radius: RRR.SSSS Center: vector".

+ 44 - 1
jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.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
@@ -40,6 +40,7 @@ import com.jme3.math.*;
 import com.jme3.util.TempVars;
 import com.jme3.util.TempVars;
 import java.io.IOException;
 import java.io.IOException;
 import java.nio.FloatBuffer;
 import java.nio.FloatBuffer;
+import java.util.Objects;
 
 
 /**
 /**
  * <code>BoundingVolume</code> defines an interface for dealing with
  * <code>BoundingVolume</code> defines an interface for dealing with
@@ -180,6 +181,48 @@ public abstract class BoundingVolume implements Savable, Cloneable, Collidable {
      */
      */
     public abstract BoundingVolume clone(BoundingVolume store);
     public abstract BoundingVolume clone(BoundingVolume store);
 
 
+    /**
+     * Tests for exact equality with the argument, distinguishing -0 from 0. If
+     * {@code other} is null, false is returned. Either way, the current
+     * instance is unaffected.
+     *
+     * @param other the object to compare (may be null, unaffected)
+     * @return true if {@code this} and {@code other} have identical values,
+     *     otherwise false
+     */
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof BoundingVolume)) {
+            return false;
+        }
+
+        if (this == other) {
+            return true;
+        }
+
+        BoundingVolume otherBoundingVolume = (BoundingVolume) other;
+        if (!center.equals(otherBoundingVolume.getCenter())) {
+            return false;
+        }
+        // The checkPlane field is ignored.
+
+        return true;
+    }
+
+    /**
+     * Returns a hash code. If two bounding volumes have identical values, they
+     * will have the same hash code. The current instance is unaffected.
+     *
+     * @return a 32-bit value for use in hashing
+     */
+    @Override
+    public int hashCode() {
+        int hash = Objects.hash(center);
+        // The checkPlane field is ignored.
+
+        return hash;
+    }
+
     public final Vector3f getCenter() {
     public final Vector3f getCenter() {
         return center;
         return center;
     }
     }

+ 1 - 1
jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java

@@ -93,7 +93,7 @@ public class Cinematic extends AbstractCinematicEvent implements AppState {
     private Node scene;
     private Node scene;
     protected TimeLine timeLine = new TimeLine();
     protected TimeLine timeLine = new TimeLine();
     private int lastFetchedKeyFrame = -1;
     private int lastFetchedKeyFrame = -1;
-    final private List<CinematicEvent> cinematicEvents = new ArrayList<>();
+    private final List<CinematicEvent> cinematicEvents = new ArrayList<>();
     private Map<String, CameraNode> cameras = new HashMap<>();
     private Map<String, CameraNode> cameras = new HashMap<>();
     private CameraNode currentCam;
     private CameraNode currentCam;
     private boolean initialized = false;
     private boolean initialized = false;

+ 1 - 1
jme3-core/src/main/java/com/jme3/cinematic/events/AnimEvent.java

@@ -53,7 +53,7 @@ import java.util.logging.Logger;
  */
  */
 public class AnimEvent extends AbstractCinematicEvent {
 public class AnimEvent extends AbstractCinematicEvent {
 
 
-    final public static Logger logger
+    public static final Logger logger
             = Logger.getLogger(AnimEvent.class.getName());
             = Logger.getLogger(AnimEvent.class.getName());
 
 
     /*
     /*

+ 14 - 9
jme3-core/src/main/java/com/jme3/collision/bih/BIHNode.java

@@ -411,15 +411,20 @@ public final class BIHNode implements Savable {
                         t = t_world;
                         t = t_world;
                     }
                     }
 
 
-                    Vector3f contactNormal = Triangle.computeTriangleNormal(v1, v2, v3, null);
-                    Vector3f contactPoint = new Vector3f(d).multLocal(t).addLocal(o);
-                    float worldSpaceDist = o.distance(contactPoint);
-
-                    CollisionResult cr = new CollisionResult(contactPoint, worldSpaceDist);
-                    cr.setContactNormal(contactNormal);
-                    cr.setTriangleIndex(tree.getTriangleIndex(i));
-                    results.addCollision(cr);
-                    cols++;
+                    // this second isInfinite test is unlikely to fail but due to numeric precision it might
+                    // be the case that in local coordinates it just hits and in world coordinates it just misses
+                    // this filters those cases out (treating them as misses).
+                    if (!Float.isInfinite(t)){
+                        Vector3f contactNormal = Triangle.computeTriangleNormal(v1, v2, v3, null);
+                        Vector3f contactPoint = new Vector3f(d).multLocal(t).addLocal(o);
+                        float worldSpaceDist = o.distance(contactPoint);
+
+                        CollisionResult cr = new CollisionResult(contactPoint, worldSpaceDist);
+                        cr.setContactNormal(contactNormal);
+                        cr.setTriangleIndex(tree.getTriangleIndex(i));
+                        results.addCollision(cr);
+                        cols++;
+                    }
                 }
                 }
             }
             }
         }
         }

+ 351 - 0
jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java

@@ -0,0 +1,351 @@
+/*
+ * Copyright (c) 2009-2023 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.environment;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
+import com.jme3.asset.AssetManager;
+import com.jme3.environment.baker.IBLGLEnvBakerLight;
+import com.jme3.environment.baker.IBLHybridEnvBakerLight;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.light.LightProbe;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.Control;
+import com.jme3.texture.Image.Format;
+
+/**
+ * A control that automatically handles environment bake and rebake including
+ * only tagged spatials.
+ * 
+ * Simple usage example: <code>
+ * 1. Load a scene
+ *    Node scene=(Node)assetManager.loadModel("Scenes/MyScene.j3o"); 
+ * 2. Add one or more EnvironmentProbeControl to the root of the scene
+ *    EnvironmentProbeControl ec1=new EnvironmentProbeControl(assetManager, 512);
+ *    //  EnvironmentProbeControl ec2=new EnvironmentProbeControl(assetManager, 512);
+ * 2b. (optional) Set the position of the probes
+ *    ec1.setPosition(new Vector3f(0,0,0));
+ *    // ec2.setPosition(new Vector3f(0,0,10));
+ * 3. Tag the spatials that are part of the environment
+ *    scene.deepFirstTraversal(s->{
+ *        if(s.getUserData("isEnvNode")!=null){
+ *          EnvironmentProbeControl.tagGlobal(s);
+ *          // or ec1.tag(s); 
+ *          //    ec2.tag(s);
+ *        }
+ *    });
+ *</code>
+ * 
+ * @author Riccardo Balbo
+ */
+public class EnvironmentProbeControl extends LightProbe implements Control {
+    private static AtomicInteger instanceCounter = new AtomicInteger(0);
+
+    private AssetManager assetManager;
+    private boolean bakeNeeded = true;
+    private int envMapSize = 256;
+    private Spatial spatial;
+    private boolean requiredSavableResults = false;
+    private float frustumNear = 0.001f, frustumFar = 1000f;
+    private String uuid = "none";
+    private boolean enabled = true;
+
+    private Predicate<Geometry> filter = (s) -> {
+        return s.getUserData("tags.env") != null || s.getUserData("tags.env.env" + uuid) != null;
+    };
+
+    protected EnvironmentProbeControl() {
+        super();
+        uuid = System.currentTimeMillis() + "_" + instanceCounter.getAndIncrement();
+        this.setAreaType(AreaType.Spherical);
+        this.getArea().setRadius(Float.MAX_VALUE);
+    }
+
+    /**
+     * Creates a new environment probe control.
+     * 
+     * @param assetManager
+     *            the asset manager used to load the shaders needed for the
+     *            baking
+     * @param size
+     *            the size of side of the resulting cube map (eg. 1024)
+     */
+    public EnvironmentProbeControl(AssetManager assetManager, int size) {
+        this();
+        this.envMapSize = size;
+        this.assetManager = assetManager;        
+    }
+
+    /**
+     * Tags the specified spatial as part of the environment for this EnvironmentProbeControl.
+     * Only tagged spatials will be rendered in the environment map.
+     * 
+     * @param s
+     *            the spatial
+     */
+    public void tag(Spatial s) {
+        if (s instanceof Node) {
+            Node n = (Node) s;
+            for (Spatial sx : n.getChildren()) {
+                tag(sx);
+            }
+        } else if (s instanceof Geometry) {
+            s.setUserData("tags.env.env" + uuid, true);
+        }
+    }
+
+    /**
+     * Untags the specified spatial as part of the environment for this
+     * EnvironmentProbeControl.
+     * 
+     * @param s
+     *            the spatial
+     */
+    public void untag(Spatial s) {
+        if (s instanceof Node) {
+            Node n = (Node) s;
+            for (Spatial sx : n.getChildren()) {
+                untag(sx);
+            }
+        } else if (s instanceof Geometry) {
+            s.setUserData("tags.env.env" + uuid, null);
+        }
+    }
+
+    /**
+     * Tags the specified spatial as part of the environment for every EnvironmentProbeControl.
+     * Only tagged spatials will be rendered in the environment map.
+     * 
+     * @param s
+     *            the spatial
+     */
+    public static void tagGlobal(Spatial s) {
+        if (s instanceof Node) {
+            Node n = (Node) s;
+            for (Spatial sx : n.getChildren()) {
+                tagGlobal(sx);
+            }
+        } else if (s instanceof Geometry) {
+            s.setUserData("tags.env", true);
+        }
+    }
+
+    /**
+     * Untags the specified spatial as part of the environment for every
+     * EnvironmentProbeControl.
+     * 
+     * @param s the spatial
+     */
+    public static void untagGlobal(Spatial s) {
+        if (s instanceof Node) {
+            Node n = (Node) s;
+            for (Spatial sx : n.getChildren()) {
+                untagGlobal(sx);
+            }
+        } else if (s instanceof Geometry) {
+            s.setUserData("tags.env", null);
+        }
+    }
+
+    @Override
+    public Control cloneForSpatial(Spatial spatial) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Requests savable results from the baking process. This will make the
+     * baking process slower and more memory intensive but will allow to
+     * serialize the results with the control.
+     * 
+     * @param v
+     *            true to enable (default: false)
+     */
+    public void setRequiredSavableResults(boolean v) {
+        requiredSavableResults = v;
+    }
+
+    /**
+     * Returns true if savable results are required by this control.
+     * 
+     * @return true if savable results are required.
+     */
+    public boolean isRequiredSavableResults() {
+        return requiredSavableResults;
+    }
+
+    @Override
+    public void setSpatial(Spatial spatial) {
+        if (this.spatial != null && spatial != null && spatial != this.spatial) {
+            throw new IllegalStateException("This control has already been added to a Spatial");
+        }
+        this.spatial = spatial;
+        if (spatial != null) spatial.addLight(this);
+    }
+
+    @Override
+    public void update(float tpf) {
+
+    }
+
+    @Override
+    public void render(RenderManager rm, ViewPort vp) {
+        if (!isEnabled()) return;
+        if (bakeNeeded) {
+            bakeNeeded = false;
+            rebakeNow(rm);
+        }
+    }
+
+    /**
+     * Schedules a rebake of the environment map.
+     */
+    public void rebake() {
+        bakeNeeded = true;
+    }
+
+    /**
+     * Sets the minimum distance to render.
+     * 
+     * @param frustumNear the minimum distance to render
+     */
+    public void setFrustumNear(float frustumNear) {
+        this.frustumNear = frustumNear;
+    }
+
+    /**
+     * Sets the maximum distance to render.
+     * 
+     * @param frustumFar the maximum distance to render
+     */
+    public void setFrustumFar(float frustumFar) {
+        this.frustumFar = frustumFar;
+    }
+
+    /**
+     * Gets the minimum distance to render.
+     * 
+     * @return frustum near
+     */
+    public float getFrustumNear() {
+        return frustumNear;
+    }
+
+    /**
+     * Gets the maximum distance to render.
+     * 
+     * @return frustum far
+     */
+    public float getFrustumFar() {
+        return frustumFar;
+    }
+
+    /**
+     * Sets the asset manager used to load the shaders needed for the baking.
+     * 
+     * @param assetManager the asset manager
+     */
+    public void setAssetManager(AssetManager assetManager) {
+        this.assetManager = assetManager;
+    }
+
+    void rebakeNow(RenderManager renderManager) {
+        IBLHybridEnvBakerLight baker = new IBLGLEnvBakerLight(renderManager, assetManager, Format.RGB16F, Format.Depth,
+                envMapSize, envMapSize);
+                    
+        baker.setTexturePulling(isRequiredSavableResults());
+        baker.bakeEnvironment(spatial, getPosition(), frustumNear, frustumFar, filter);
+        baker.bakeSpecularIBL();
+        baker.bakeSphericalHarmonicsCoefficients();
+
+        setPrefilteredMap(baker.getSpecularIBL());
+
+        int[] mipSizes = getPrefilteredEnvMap().getImage().getMipMapSizes();
+        setNbMipMaps(mipSizes != null ? mipSizes.length : 1);
+
+        setShCoeffs(baker.getSphericalHarmonicsCoefficients());
+        setPosition(Vector3f.ZERO);
+        setReady(true);
+
+        baker.clean();
+    }
+    
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public Spatial getSpatial() {
+        return spatial;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(enabled, "enabled", true);
+        oc.write(spatial, "spatial", null);
+        oc.write(envMapSize, "size", 256);
+        oc.write(requiredSavableResults, "requiredSavableResults", false);
+        oc.write(bakeNeeded, "bakeNeeded", true);
+        oc.write(frustumFar, "frustumFar", 1000f);
+        oc.write(frustumNear, "frustumNear", 0.001f);
+        oc.write(uuid, "envProbeControlUUID", "none");
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        enabled = ic.readBoolean("enabled", true);
+        spatial = (Spatial) ic.readSavable("spatial", null);
+        envMapSize = ic.readInt("size", 256);
+        requiredSavableResults = ic.readBoolean("requiredSavableResults", false);
+        bakeNeeded = ic.readBoolean("bakeNeeded", true);
+        assetManager = im.getAssetManager();
+        frustumFar = ic.readFloat("frustumFar", 1000f);
+        frustumNear = ic.readFloat("frustumNear", 0.001f);
+        uuid = ic.readString("envProbeControlUUID", "none");
+    }
+
+}

+ 122 - 0
jme3-core/src/main/java/com/jme3/environment/FastLightProbeFactory.java

@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2009-2023 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.environment;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.environment.baker.IBLGLEnvBakerLight;
+import com.jme3.environment.util.EnvMapUtils;
+import com.jme3.light.LightProbe;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.texture.Image.Format;
+
+/**
+ * A faster LightProbeFactory that uses GPU accelerated algorithms.
+ * This is the GPU version of @{link LightProbeFactory} and should be generally preferred.
+ * 
+ * For common use cases where the probe is baking the scene or part of the scene around it, it
+ * is advised to use the @{link EnvironmentProbeControl} instead since it does automatically most of the 
+ * boilerplate work.
+ * 
+ * 
+ * @author Riccardo Balbo
+ */
+public class FastLightProbeFactory {
+
+    /**
+     * Creates a LightProbe with the given EnvironmentCamera in the given scene.
+     * 
+     * @param rm
+     *            The RenderManager
+     * @param am
+     *            The AssetManager
+     * @param size
+     *            The size of the probe
+     * @param pos
+     *            The position of the probe
+     * @param frustumNear
+     *            The near frustum of the probe
+     * @param frustumFar
+     *            The far frustum of the probe
+     * @param scene
+     *            The scene to bake
+     * @return The baked LightProbe
+     */
+    public static LightProbe makeProbe(RenderManager rm, AssetManager am, int size, Vector3f pos, float frustumNear, float frustumFar, Spatial scene) {
+        IBLGLEnvBakerLight baker = new IBLGLEnvBakerLight(rm, am, Format.RGB16F, Format.Depth, size, size);
+
+        baker.setTexturePulling(true);
+        baker.bakeEnvironment(scene, pos, frustumNear, frustumFar, null);
+        baker.bakeSpecularIBL();
+        baker.bakeSphericalHarmonicsCoefficients();
+
+        LightProbe probe = new LightProbe();
+
+        probe.setPosition(pos);
+        probe.setPrefilteredMap(baker.getSpecularIBL());
+
+        int[] mipSizes = probe.getPrefilteredEnvMap().getImage().getMipMapSizes();
+        probe.setNbMipMaps(mipSizes != null ? mipSizes.length : 1);
+
+        probe.setShCoeffs(baker.getSphericalHarmonicsCoefficients());
+        probe.setReady(true);
+
+        baker.clean();
+
+        return probe;
+
+    }
+
+    /**
+     * For debuging purposes only Will return a Node meant to be added to a GUI
+     * presenting the 2 cube maps in a cross pattern with all the mip maps.
+     *
+     * @param manager
+     *            the asset manager
+     * @return a debug node
+     */
+    public static Node getDebugGui(AssetManager manager, LightProbe probe) {
+        if (!probe.isReady()) {
+            throw new UnsupportedOperationException("This EnvProbe is not ready yet, try to test isReady()");
+        }
+
+        Node debugNode = new Node("debug gui probe");
+        Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(probe.getPrefilteredEnvMap(), manager);
+        debugNode.attachChild(debugPfemCm);
+        debugPfemCm.setLocalTranslation(520, 0, 0);
+
+        return debugNode;
+    }
+
+}

+ 89 - 0
jme3-core/src/main/java/com/jme3/environment/baker/EnvBaker.java

@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2009-2023 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.environment.baker;
+
+import java.util.function.Predicate;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.texture.TextureCubeMap;
+
+/**
+ * An environment baker to bake a 3d environment into a cubemap
+ *
+ * @author Riccardo Balbo
+ */
+public interface EnvBaker {
+    /**
+     * Bakes the environment.
+     * 
+     * @param scene
+     *            The scene to bake
+     * @param position
+     *            The position of the camera
+     * @param frustumNear
+     *            The near frustum
+     * @param frustumFar
+     *            The far frustum
+     * @param filter
+     *            A filter to select which geometries to bake
+     */
+    public void bakeEnvironment(Spatial scene, Vector3f position, float frustumNear, float frustumFar, Predicate<Geometry> filter);
+
+    /**
+     * Gets the environment map.
+     * 
+     * @return The environment map
+     */
+    public TextureCubeMap getEnvMap();
+
+    /**
+     * Cleans the environment baker This method should be called when the baker
+     * is no longer needed It will clean up all the resources.
+     */
+    public void clean();
+
+    /**
+     * Specifies whether textures should be pulled from the GPU.
+     * 
+     * @param v
+     */
+    public void setTexturePulling(boolean v);
+
+    /**
+     * Gets if textures should be pulled from the GPU.
+     * 
+     * @return
+     */
+    public boolean isTexturePulling();
+}

+ 293 - 0
jme3-core/src/main/java/com/jme3/environment/baker/GenericEnvBaker.java

@@ -0,0 +1,293 @@
+/*
+ * Copyright (c) 2009-2023 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.environment.baker;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import com.jme3.asset.AssetManager;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Texture;
+import com.jme3.texture.FrameBuffer.FrameBufferTarget;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture.MagFilter;
+import com.jme3.texture.Texture.MinFilter;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.texture.TextureCubeMap;
+import com.jme3.texture.image.ColorSpace;
+import com.jme3.util.BufferUtils;
+
+/**
+ * Render the environment into a cubemap
+ *
+ * @author Riccardo Balbo
+ */
+public abstract class GenericEnvBaker implements EnvBaker {
+    private static final Logger LOG = Logger.getLogger(GenericEnvBaker.class.getName());
+
+    protected static Vector3f[] axisX = new Vector3f[6];
+    protected static Vector3f[] axisY = new Vector3f[6];
+    protected static Vector3f[] axisZ = new Vector3f[6];
+    static {
+        // PositiveX axis(left, up, direction)
+        axisX[0] = Vector3f.UNIT_Z.mult(1.0F);
+        axisY[0] = Vector3f.UNIT_Y.mult(-1.0F);
+        axisZ[0] = Vector3f.UNIT_X.mult(1.0F);
+        // NegativeX
+        axisX[1] = Vector3f.UNIT_Z.mult(-1.0F);
+        axisY[1] = Vector3f.UNIT_Y.mult(-1.0F);
+        axisZ[1] = Vector3f.UNIT_X.mult(-1.0F);
+        // PositiveY
+        axisX[2] = Vector3f.UNIT_X.mult(-1.0F);
+        axisY[2] = Vector3f.UNIT_Z.mult(1.0F);
+        axisZ[2] = Vector3f.UNIT_Y.mult(1.0F);
+        // NegativeY
+        axisX[3] = Vector3f.UNIT_X.mult(-1.0F);
+        axisY[3] = Vector3f.UNIT_Z.mult(-1.0F);
+        axisZ[3] = Vector3f.UNIT_Y.mult(-1.0F);
+        // PositiveZ
+        axisX[4] = Vector3f.UNIT_X.mult(-1.0F);
+        axisY[4] = Vector3f.UNIT_Y.mult(-1.0F);
+        axisZ[4] = Vector3f.UNIT_Z;
+        // NegativeZ
+        axisX[5] = Vector3f.UNIT_X.mult(1.0F);
+        axisY[5] = Vector3f.UNIT_Y.mult(-1.0F);
+        axisZ[5] = Vector3f.UNIT_Z.mult(-1.0F);
+    }
+
+    protected TextureCubeMap envMap;
+    protected Format depthFormat;
+
+    protected final RenderManager renderManager;
+    protected final AssetManager assetManager;
+    protected final Camera cam;
+    protected boolean texturePulling = false;
+    protected List<ByteArrayOutputStream> bos = new ArrayList<>();
+
+    protected GenericEnvBaker(RenderManager rm, AssetManager am, Format colorFormat, Format depthFormat, int env_size) {
+        this.depthFormat = depthFormat;
+
+        renderManager = rm;
+        assetManager = am;
+
+        cam = new Camera(128, 128);
+
+        envMap = new TextureCubeMap(env_size, env_size, colorFormat);
+        envMap.setMagFilter(MagFilter.Bilinear);
+        envMap.setMinFilter(MinFilter.BilinearNoMipMaps);
+        envMap.setWrap(WrapMode.EdgeClamp);
+        envMap.getImage().setColorSpace(ColorSpace.Linear);
+    }
+
+    @Override
+    public void setTexturePulling(boolean v) {
+        texturePulling = v;
+    }
+
+    @Override
+    public boolean isTexturePulling() {
+        return texturePulling;
+    }
+
+    public TextureCubeMap getEnvMap() {
+        return envMap;
+    }
+
+    /**
+     * Updates the internal camera to face the given cubemap face
+     * and return it.
+     * 
+     * @param faceId
+     *            the id of the face (0-5)
+     * @param w
+     *            width of the camera
+     * @param h
+     *            height of the camera
+     * @param position
+     *            position of the camera
+     * @param frustumNear
+     *            near frustum
+     * @param frustumFar
+     *            far frustum
+     * @return The updated camera
+     */
+    protected Camera updateAndGetInternalCamera(int faceId, int w, int h, Vector3f position, float frustumNear, float frustumFar) {
+        cam.resize(w, h, false);
+        cam.setLocation(position);
+        cam.setFrustumPerspective(90.0F, 1F, frustumNear, frustumFar);
+        cam.setRotation(new Quaternion().fromAxes(axisX[faceId], axisY[faceId], axisZ[faceId]));
+        return cam;
+    }
+
+    @Override
+    public void clean() {
+
+    }
+
+    @Override
+    public void bakeEnvironment(Spatial scene, Vector3f position, float frustumNear, float frustumFar, Predicate<Geometry> filter) {
+        FrameBuffer envbakers[] = new FrameBuffer[6];
+        for (int i = 0; i < 6; i++) {
+            envbakers[i] = new FrameBuffer(envMap.getImage().getWidth(), envMap.getImage().getHeight(), 1);
+            envbakers[i].setDepthTarget(FrameBufferTarget.newTarget(depthFormat));
+            envbakers[i].setSrgb(false);
+            envbakers[i].addColorTarget(FrameBufferTarget.newTarget(envMap).face(TextureCubeMap.Face.values()[i]));
+        }
+
+        if (isTexturePulling()) {
+            startPulling();
+        }
+
+        for (int i = 0; i < 6; i++) {
+            FrameBuffer envbaker = envbakers[i];
+
+            ViewPort viewPort = new ViewPort("EnvBaker", updateAndGetInternalCamera(i, envbaker.getWidth(), envbaker.getHeight(), position, frustumNear, frustumFar));
+            viewPort.setClearFlags(true, true, true);
+            viewPort.setBackgroundColor(ColorRGBA.Pink);
+
+            viewPort.setOutputFrameBuffer(envbaker);
+            viewPort.clearScenes();
+            viewPort.attachScene(scene);
+
+            scene.updateLogicalState(0);
+            scene.updateGeometricState();
+
+            Predicate<Geometry> ofilter = renderManager.getRenderFilter();
+
+            renderManager.setRenderFilter(filter);
+            renderManager.renderViewPort(viewPort, 0.16f);
+            renderManager.setRenderFilter(ofilter);
+
+            if (isTexturePulling()) {
+                pull(envbaker, envMap, i);
+            }
+
+        }
+
+        if (isTexturePulling()) {
+            endPulling(envMap);
+        }
+
+        envMap.getImage().clearUpdateNeeded();
+
+        for (int i = 0; i < 6; i++) {
+            envbakers[i].dispose();
+        }
+    }
+
+    /**
+     * Starts pulling the data from the framebuffer into the texture.
+     */
+    protected void startPulling() {
+        bos.clear();
+    }
+
+    /**
+     * Pulls the data from the framebuffer into the texture Nb. mipmaps must be
+     * pulled sequentially on the same faceId.
+     * 
+     * @param fb
+     *            the framebuffer to pull from
+     * @param env
+     *            the texture to pull into
+     * @param faceId
+     *            id of face if cubemap or 0 otherwise
+     * @return the ByteBuffer containing the pulled data
+     */
+    protected ByteBuffer pull(FrameBuffer fb, Texture env, int faceId) {
+
+        if (fb.getColorTarget().getFormat() != env.getImage().getFormat())
+            throw new IllegalArgumentException("Format mismatch: " + fb.getColorTarget().getFormat() + "!=" + env.getImage().getFormat());
+
+        ByteBuffer face = BufferUtils.createByteBuffer(fb.getWidth() * fb.getHeight() * (fb.getColorTarget().getFormat().getBitsPerPixel() / 8));
+        renderManager.getRenderer().readFrameBufferWithFormat(fb, face, fb.getColorTarget().getFormat());
+        face.rewind();
+
+        while (bos.size() <= faceId) {
+            bos.add(null);
+        }
+
+        ByteArrayOutputStream bo = bos.get(faceId);
+        if (bo == null) {
+            bos.set(faceId, bo = new ByteArrayOutputStream());
+        }
+        try {
+            byte array[] = new byte[face.limit()];
+            face.get(array);
+            bo.write(array);
+        } catch (Exception ex) {
+            LOG.log(Level.SEVERE, null, ex);
+        }
+        return face;
+    }
+
+    /**
+     * Ends pulling the data into the texture
+     * 
+     * @param tx
+     *            the texture to pull into
+     */
+    protected void endPulling(Texture tx) {
+        for (int i = 0; i < bos.size(); i++) {
+            ByteArrayOutputStream bo = bos.get(i);
+            if (bo != null) {
+                ByteBuffer faceMip = ByteBuffer.wrap(bo.toByteArray());
+                tx.getImage().setData(i, faceMip);
+            } else {
+                LOG.log(Level.SEVERE, "Missing face {0}. Pulling incomplete!", i);
+            }
+        }
+        bos.clear();
+        tx.getImage().clearUpdateNeeded();
+    }
+
+    protected int limitMips(int nbMipMaps, int baseW, int baseH, RenderManager rm) {
+        if (nbMipMaps > 6) {
+            nbMipMaps = 6;
+        }
+        return nbMipMaps;
+    }
+
+}

+ 74 - 0
jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBaker.java

@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2009-2023 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.environment.baker;
+
+import com.jme3.texture.Texture2D;
+import com.jme3.texture.TextureCubeMap;
+
+/**
+ * An environment baker, but this one is for Imaged Base Lighting.
+ *
+ * @author Riccardo Balbo
+ */
+public interface IBLEnvBaker extends EnvBaker {
+    /**
+     * Generates the BRDF texture.
+     * 
+     * @return The BRDF texture
+     */
+    public Texture2D genBRTF();
+
+    /**
+     * Bakes the irradiance map.
+     */
+    public void bakeIrradiance();
+
+    /**
+     * Bakes the specular IBL map.
+     */
+    public void bakeSpecularIBL();
+
+    /**
+     * Gets the specular IBL map.
+     * 
+     * @return The specular IBL map
+     */
+    public TextureCubeMap getSpecularIBL();
+
+    /**
+     * Gets the irradiance map.
+     * 
+     * @return The irradiance map
+     */
+    public TextureCubeMap getIrradiance();
+}

+ 52 - 0
jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBakerLight.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009-2023 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.environment.baker;
+
+import com.jme3.math.Vector3f;
+import com.jme3.texture.TextureCubeMap;
+
+/**
+ * An environment baker for IBL, that uses spherical harmonics for irradiance.
+ * 
+ * @author Riccardo Balbo
+ */
+public interface IBLEnvBakerLight extends EnvBaker {
+
+    public void bakeSpecularIBL();
+
+    public void bakeSphericalHarmonicsCoefficients();
+
+    public TextureCubeMap getSpecularIBL();
+
+    public Vector3f[] getSphericalHarmonicsCoefficients();
+}

+ 296 - 0
jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBaker.java

@@ -0,0 +1,296 @@
+/*
+ * 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.environment.baker;
+
+import java.util.Arrays;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture.MagFilter;
+import com.jme3.texture.Texture.MinFilter;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.texture.Texture2D;
+import com.jme3.texture.TextureCubeMap;
+import com.jme3.texture.FrameBuffer.FrameBufferTarget;
+import com.jme3.texture.image.ColorSpace;
+import com.jme3.ui.Picture;
+
+/**
+ * Fully accelerated env baker for IBL that runs entirely on the GPU
+ * 
+ * @author Riccardo Balbo
+ */
+public class IBLGLEnvBaker extends GenericEnvBaker implements IBLEnvBaker {
+    private static final Logger LOGGER = Logger.getLogger(IBLGLEnvBakerLight.class.getName());
+
+    protected Texture2D brtf;
+    protected TextureCubeMap irradiance;
+    protected TextureCubeMap specular;
+
+    /**
+     * Create a new IBL env baker
+     * @param rm The render manager used to render the env scene
+     * @param am The asset manager used to load the baking shaders
+     * @param format  The format of the color buffers
+     * @param depthFormat The format of the depth buffers
+     * @param env_size The size in pixels of the output environment cube map (eg. 1024)
+     * @param specular_size The size in pixels of the output specular cube map (eg. 1024)
+     * @param irradiance_size The size in pixels of the output irradiance cube map (eg. 512)
+     * @param brtf_size The size in pixels of the output brtf map (eg. 512)
+     */
+    public IBLGLEnvBaker(RenderManager rm, AssetManager am, Format format, Format depthFormat, int env_size, int specular_size, int irradiance_size, int brtf_size) {
+        super(rm, am, format, depthFormat, env_size);
+
+        irradiance = new TextureCubeMap(irradiance_size, irradiance_size, format);
+        irradiance.setMagFilter(MagFilter.Bilinear);
+        irradiance.setMinFilter(MinFilter.BilinearNoMipMaps);
+        irradiance.setWrap(WrapMode.EdgeClamp);
+        irradiance.getImage().setColorSpace(ColorSpace.Linear);
+
+        specular = new TextureCubeMap(specular_size, specular_size, format);
+        specular.setMagFilter(MagFilter.Bilinear);
+        specular.setMinFilter(MinFilter.Trilinear);
+        specular.setWrap(WrapMode.EdgeClamp);
+        specular.getImage().setColorSpace(ColorSpace.Linear);
+
+        int nbMipMaps = (int) (Math.log(specular_size) / Math.log(2) + 1);
+        nbMipMaps = limitMips(nbMipMaps, specular.getImage().getWidth(), specular.getImage().getHeight(), rm);
+
+        int[] sizes = new int[nbMipMaps];
+        for (int i = 0; i < nbMipMaps; i++) {
+            int size = (int) FastMath.pow(2, nbMipMaps - 1 - i);
+            sizes[i] = size * size * (specular.getImage().getFormat().getBitsPerPixel() / 8);
+        }
+        specular.getImage().setMipMapSizes(sizes);
+
+        brtf = new Texture2D(brtf_size, brtf_size, format);
+        brtf.setMagFilter(MagFilter.Bilinear);
+        brtf.setMinFilter(MinFilter.BilinearNoMipMaps);
+        brtf.setWrap(WrapMode.EdgeClamp);
+        brtf.getImage().setColorSpace(ColorSpace.Linear);
+    }
+
+    @Override
+    public TextureCubeMap getSpecularIBL() {
+        return specular;
+    }
+
+    @Override
+    public TextureCubeMap getIrradiance() {
+        return irradiance;
+    }
+
+    private void bakeSpecularIBL(int mip, float roughness, Material mat, Geometry screen) throws Exception {
+        mat.setFloat("Roughness", roughness);
+
+        int mipWidth = (int) (specular.getImage().getWidth() * FastMath.pow(0.5f, mip));
+        int mipHeight = (int) (specular.getImage().getHeight() * FastMath.pow(0.5f, mip));
+
+        FrameBuffer specularbakers[] = new FrameBuffer[6];
+        for (int i = 0; i < 6; i++) {
+            specularbakers[i] = new FrameBuffer(mipWidth, mipHeight, 1);
+            specularbakers[i].setSrgb(false);
+            specularbakers[i].addColorTarget(FrameBufferTarget.newTarget(specular).level(mip).face(i));
+            specularbakers[i].setMipMapsGenerationHint(false);
+        }
+
+        for (int i = 0; i < 6; i++) {
+            FrameBuffer specularbaker = specularbakers[i];
+            mat.setInt("FaceId", i);
+
+            screen.updateLogicalState(0);
+            screen.updateGeometricState();
+
+            renderManager.setCamera(updateAndGetInternalCamera(i, specularbaker.getWidth(), specularbaker.getHeight(), Vector3f.ZERO, 1, 1000), false);
+            renderManager.getRenderer().setFrameBuffer(specularbaker);
+            renderManager.renderGeometry(screen);
+
+            if (isTexturePulling()) {
+                pull(specularbaker, specular, i);
+            }
+
+        }
+        for (int i = 0; i < 6; i++) {
+            specularbakers[i].dispose();
+        }
+    }
+
+    @Override
+    public void bakeSpecularIBL() {
+        Box boxm = new Box(1, 1, 1);
+        Geometry screen = new Geometry("BakeBox", boxm);
+
+        Material mat = new Material(assetManager, "Common/IBL/IBLKernels.j3md");
+        mat.setBoolean("UseSpecularIBL", true);
+        mat.setTexture("EnvMap", envMap);
+        screen.setMaterial(mat);
+
+        if (isTexturePulling()) {
+            startPulling();
+        }
+
+        int mip = 0;
+        for (; mip < specular.getImage().getMipMapSizes().length; mip++) {
+            try {
+                float roughness = (float) mip / (float) (specular.getImage().getMipMapSizes().length - 1);
+                bakeSpecularIBL(mip, roughness, mat, screen);
+            } catch (Exception e) {
+                LOGGER.log(Level.WARNING, "Error while computing mip level " + mip, e);
+                break;
+            }
+        }
+
+        if (mip < specular.getImage().getMipMapSizes().length) {
+
+            int[] sizes = specular.getImage().getMipMapSizes();
+            sizes = Arrays.copyOf(sizes, mip);
+            specular.getImage().setMipMapSizes(sizes);
+            specular.getImage().setMipmapsGenerated(true);
+            if (sizes.length <= 1) {
+                try {
+                    LOGGER.log(Level.WARNING, "Workaround driver BUG: only one mip level available, regenerate it with higher roughness (shiny fix)");
+                    bakeSpecularIBL(0, 1f, mat, screen);
+                } catch (Exception e) {
+                    LOGGER.log(Level.FINE, "Error while recomputing mip level 0", e);
+                }
+            }
+        }
+
+        if (isTexturePulling()) {
+            endPulling(specular);
+        }
+        specular.getImage().clearUpdateNeeded();
+
+    }
+
+    @Override
+    public Texture2D genBRTF() {
+
+        Picture screen = new Picture("BakeScreen", true);
+        screen.setWidth(1);
+        screen.setHeight(1);
+
+        FrameBuffer brtfbaker = new FrameBuffer(brtf.getImage().getWidth(), brtf.getImage().getHeight(), 1);
+        brtfbaker.setSrgb(false);
+        brtfbaker.addColorTarget(FrameBufferTarget.newTarget(brtf));
+
+        if (isTexturePulling()) {
+            startPulling();
+        }
+
+        Camera envcam = updateAndGetInternalCamera(0, brtf.getImage().getWidth(), brtf.getImage().getHeight(), Vector3f.ZERO, 1, 1000);
+
+        Material mat = new Material(assetManager, "Common/IBL/IBLKernels.j3md");
+        mat.setBoolean("UseBRDF", true);
+        screen.setMaterial(mat);
+
+        renderManager.getRenderer().setFrameBuffer(brtfbaker);
+        renderManager.setCamera(envcam, false);
+
+        screen.updateLogicalState(0);
+        screen.updateGeometricState();
+        renderManager.renderGeometry(screen);
+
+        if (isTexturePulling()) {
+            pull(brtfbaker, brtf, 0);
+        }
+
+        brtfbaker.dispose();
+
+        if (isTexturePulling()) {
+            endPulling(brtf);
+        }
+        brtf.getImage().clearUpdateNeeded();
+
+        return brtf;
+    }
+
+    @Override
+    public void bakeIrradiance() {
+
+        Box boxm = new Box(1, 1, 1);
+        Geometry screen = new Geometry("BakeBox", boxm);
+
+        FrameBuffer irradiancebaker = new FrameBuffer(irradiance.getImage().getWidth(), irradiance.getImage().getHeight(), 1);
+        irradiancebaker.setSrgb(false);
+
+        if (isTexturePulling()) {
+            startPulling();
+        }
+
+        for (int i = 0; i < 6; i++) {
+            irradiancebaker.addColorTarget(
+                    FrameBufferTarget.newTarget(irradiance).face(TextureCubeMap.Face.values()[i]));
+        }
+
+        Material mat = new Material(assetManager, "Common/IBL/IBLKernels.j3md");
+        mat.setBoolean("UseIrradiance", true);
+        mat.setTexture("EnvMap", envMap);
+        screen.setMaterial(mat);
+
+        for (int i = 0; i < 6; i++) {
+            irradiancebaker.setTargetIndex(i);
+
+            mat.setInt("FaceId", i);
+
+            screen.updateLogicalState(0);
+            screen.updateGeometricState();
+
+            renderManager.setCamera(updateAndGetInternalCamera(i, irradiancebaker.getWidth(), irradiancebaker.getHeight(), Vector3f.ZERO, 1, 1000), false);
+            renderManager.getRenderer().setFrameBuffer(irradiancebaker);
+            renderManager.renderGeometry(screen);
+
+            if (isTexturePulling()) {
+                pull(irradiancebaker, irradiance, i);
+            }
+        }
+
+        irradiancebaker.dispose();
+
+        if (isTexturePulling()) {
+            endPulling(irradiance);
+        }
+        irradiance.getImage().clearUpdateNeeded();
+
+    }
+
+}

+ 176 - 0
jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBakerLight.java

@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2009-2023 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.environment.baker;
+
+import java.nio.ByteBuffer;
+import java.util.logging.Logger;
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture2D;
+import com.jme3.texture.FrameBuffer.FrameBufferTarget;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.image.ColorSpace;
+import com.jme3.texture.image.ImageRaster;
+import com.jme3.util.BufferUtils;
+
+/**
+ * Fully accelerated env baker for IBL that bakes the specular map and spherical
+ * harmonics on the GPU.
+ * 
+ * This is lighter on VRAM but it is not as parallelized as IBLGLEnvBaker
+ * 
+ * @author Riccardo Balbo
+ */
+public class IBLGLEnvBakerLight extends IBLHybridEnvBakerLight {
+    private static final int NUM_SH_COEFFICIENT = 9;
+    private static final Logger LOG = Logger.getLogger(IBLGLEnvBakerLight.class.getName());
+
+    /**
+     * Create a new IBL env baker
+     * 
+     * @param rm
+     *            The render manager used to render the env scene
+     * @param am
+     *            The asset manager used to load the baking shaders
+     * @param format
+     *            The format of the color buffers
+     * @param depthFormat
+     *            The format of the depth buffers
+     * @param env_size
+     *            The size in pixels of the output environment cube map (eg.
+     *            1024)
+     * @param specular_size
+     *            The size in pixels of the output specular cube map (eg. 1024)
+     */
+    public IBLGLEnvBakerLight(RenderManager rm, AssetManager am, Format format, Format depthFormat, int env_size, int specular_size) {
+        super(rm, am, format, depthFormat, env_size, specular_size);
+    }
+
+    @Override
+    public boolean isTexturePulling() {
+        return this.texturePulling;
+    }
+
+    @Override
+    public void bakeSphericalHarmonicsCoefficients() {
+        Box boxm = new Box(1, 1, 1);
+        Geometry screen = new Geometry("BakeBox", boxm);
+
+        Material mat = new Material(assetManager, "Common/IBLSphH/IBLSphH.j3md");
+        mat.setTexture("Texture", envMap);
+        mat.setVector2("Resolution", new Vector2f(envMap.getImage().getWidth(), envMap.getImage().getHeight()));
+        screen.setMaterial(mat);
+
+        float remapMaxValue = 0;
+        Format format = Format.RGBA32F;
+        if (!renderManager.getRenderer().getCaps().contains(Caps.FloatColorBufferRGBA)) {
+            LOG.warning("Float textures not supported, using RGB8 instead. This may cause accuracy issues.");
+            format = Format.RGBA8;
+            remapMaxValue = 0.05f;
+        }
+
+        if (remapMaxValue > 0) {
+            mat.setFloat("RemapMaxValue", remapMaxValue);
+        } else {
+            mat.clearParam("RemapMaxValue");
+        }
+
+        Texture2D shCoefTx[] = { new Texture2D(NUM_SH_COEFFICIENT, 1, 1, format), new Texture2D(NUM_SH_COEFFICIENT, 1, 1, format) };
+
+        FrameBuffer shbaker[] = { new FrameBuffer(NUM_SH_COEFFICIENT, 1, 1), new FrameBuffer(NUM_SH_COEFFICIENT, 1, 1) };
+        shbaker[0].setSrgb(false);
+        shbaker[0].addColorTarget(FrameBufferTarget.newTarget(shCoefTx[0]));
+
+        shbaker[1].setSrgb(false);
+        shbaker[1].addColorTarget(FrameBufferTarget.newTarget(shCoefTx[1]));
+
+        int renderOnT = -1;
+
+        for (int faceId = 0; faceId < 6; faceId++) {
+            if (renderOnT != -1) {
+                int s = renderOnT;
+                renderOnT = renderOnT == 0 ? 1 : 0;
+                mat.setTexture("ShCoef", shCoefTx[s]);
+                mat.setInt("FaceId", faceId);
+            } else {
+                renderOnT = 0;
+            }
+
+            screen.updateLogicalState(0);
+            screen.updateGeometricState();
+
+            renderManager.setCamera(updateAndGetInternalCamera(0, shbaker[renderOnT].getWidth(), shbaker[renderOnT].getHeight(), Vector3f.ZERO, 1, 1000), false);
+            renderManager.getRenderer().setFrameBuffer(shbaker[renderOnT]);
+            renderManager.renderGeometry(screen);
+        }
+
+        ByteBuffer shCoefRaw = BufferUtils.createByteBuffer(NUM_SH_COEFFICIENT * 1 * (shbaker[renderOnT].getColorTarget().getFormat().getBitsPerPixel() / 8));
+        renderManager.getRenderer().readFrameBufferWithFormat(shbaker[renderOnT], shCoefRaw, shbaker[renderOnT].getColorTarget().getFormat());
+        shCoefRaw.rewind();
+
+        Image img = new Image(format, NUM_SH_COEFFICIENT, 1, shCoefRaw, ColorSpace.Linear);
+        ImageRaster imgr = ImageRaster.create(img);
+
+        shCoef = new Vector3f[NUM_SH_COEFFICIENT];
+        float weightAccum = 0.0f;
+
+        for (int i = 0; i < shCoef.length; i++) {
+            ColorRGBA c = imgr.getPixel(i, 0);
+            shCoef[i] = new Vector3f(c.r, c.g, c.b);
+            if (weightAccum == 0) weightAccum = c.a;
+            else if (weightAccum != c.a) {
+                LOG.warning("SH weight is not uniform, this may cause issues.");
+            }
+
+        }
+
+        if (remapMaxValue > 0) weightAccum /= remapMaxValue;
+
+        for (int i = 0; i < NUM_SH_COEFFICIENT; ++i) {
+            if (remapMaxValue > 0) shCoef[i].divideLocal(remapMaxValue);
+            shCoef[i].multLocal(4.0f * FastMath.PI / weightAccum);
+        }
+
+        img.dispose();
+
+    }
+}

+ 209 - 0
jme3-core/src/main/java/com/jme3/environment/baker/IBLHybridEnvBakerLight.java

@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2009-2023 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.environment.baker;
+
+import java.util.Arrays;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import com.jme3.asset.AssetManager;
+import com.jme3.environment.util.EnvMapUtils;
+import com.jme3.material.Material;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.TextureCubeMap;
+import com.jme3.texture.FrameBuffer.FrameBufferTarget;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture.MagFilter;
+import com.jme3.texture.Texture.MinFilter;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.texture.image.ColorSpace;
+
+/**
+ * An env baker for IBL that bakes the specular map on the GPU and uses
+ * spherical harmonics generated on the CPU for the irradiance map.
+ * 
+ * This is lighter on VRAM but uses the CPU to compute the irradiance map.
+ * 
+ * @author Riccardo Balbo
+ */
+public class IBLHybridEnvBakerLight extends GenericEnvBaker implements IBLEnvBakerLight {
+    private static final Logger LOGGER = Logger.getLogger(IBLHybridEnvBakerLight.class.getName());
+    protected TextureCubeMap specular;
+    protected Vector3f[] shCoef;
+
+    /**
+     * Create a new IBL env baker
+     * 
+     * @param rm
+     *            The render manager used to render the env scene
+     * @param am
+     *            The asset manager used to load the baking shaders
+     * @param format
+     *            The format of the color buffers
+     * @param depthFormat
+     *            The format of the depth buffers
+     * @param env_size
+     *            The size in pixels of the output environment cube map (eg.
+     *            1024)
+     * @param specular_size
+     *            The size in pixels of the output specular cube map (eg. 1024)
+     */
+    public IBLHybridEnvBakerLight(RenderManager rm, AssetManager am, Format format, Format depthFormat, int env_size, int specular_size) {
+        super(rm, am, format, depthFormat, env_size);
+
+        specular = new TextureCubeMap(specular_size, specular_size, format);
+        specular.setWrap(WrapMode.EdgeClamp);
+        specular.setMagFilter(MagFilter.Bilinear);
+        specular.setMinFilter(MinFilter.Trilinear);
+        specular.getImage().setColorSpace(ColorSpace.Linear);
+
+        int nbMipMaps = (int) (Math.log(specular_size) / Math.log(2) + 1);
+        nbMipMaps = limitMips(nbMipMaps, specular.getImage().getWidth(), specular.getImage().getHeight(), rm);
+
+        int[] sizes = new int[nbMipMaps];
+        for (int i = 0; i < nbMipMaps; i++) {
+            int size = (int) FastMath.pow(2, nbMipMaps - 1 - i);
+            sizes[i] = size * size * (specular.getImage().getFormat().getBitsPerPixel() / 8);
+        }
+        specular.getImage().setMipMapSizes(sizes);
+        specular.getImage().setMipmapsGenerated(true);
+
+    }
+
+    @Override
+    public boolean isTexturePulling() { // always pull textures from gpu
+        return true;
+    }
+
+    private void bakeSpecularIBL(int mip, float roughness, Material mat, Geometry screen) throws Exception {
+        mat.setFloat("Roughness", roughness);
+
+        int mipWidth = (int) (specular.getImage().getWidth() * FastMath.pow(0.5f, mip));
+        int mipHeight = (int) (specular.getImage().getHeight() * FastMath.pow(0.5f, mip));
+
+        FrameBuffer specularbakers[] = new FrameBuffer[6];
+        for (int i = 0; i < 6; i++) {
+            specularbakers[i] = new FrameBuffer(mipWidth, mipHeight, 1);
+            specularbakers[i].setSrgb(false);
+            specularbakers[i].addColorTarget(FrameBufferTarget.newTarget(specular).level(mip).face(i));
+            specularbakers[i].setMipMapsGenerationHint(false);
+        }
+
+        for (int i = 0; i < 6; i++) {
+            FrameBuffer specularbaker = specularbakers[i];
+            mat.setInt("FaceId", i);
+
+            screen.updateLogicalState(0);
+            screen.updateGeometricState();
+
+            renderManager.setCamera(updateAndGetInternalCamera(i, specularbaker.getWidth(), specularbaker.getHeight(), Vector3f.ZERO, 1, 1000), false);
+            renderManager.getRenderer().setFrameBuffer(specularbaker);
+            renderManager.renderGeometry(screen);
+
+            if (isTexturePulling()) {
+                pull(specularbaker, specular, i);
+            }
+
+        }
+        for (int i = 0; i < 6; i++) {
+            specularbakers[i].dispose();
+        }
+    }
+
+    @Override
+    public void bakeSpecularIBL() {
+        Box boxm = new Box(1, 1, 1);
+        Geometry screen = new Geometry("BakeBox", boxm);
+
+        Material mat = new Material(assetManager, "Common/IBL/IBLKernels.j3md");
+        mat.setBoolean("UseSpecularIBL", true);
+        mat.setTexture("EnvMap", envMap);
+        screen.setMaterial(mat);
+
+        if (isTexturePulling()) {
+          startPulling();  
+        } 
+
+        int mip = 0;
+        for (; mip < specular.getImage().getMipMapSizes().length; mip++) {
+            try {
+                float roughness = (float) mip / (float) (specular.getImage().getMipMapSizes().length - 1);
+                bakeSpecularIBL(mip, roughness, mat, screen);
+            } catch (Exception e) {
+                LOGGER.log(Level.WARNING, "Error while computing mip level " + mip, e);
+                break;
+            }
+        }
+
+        if (mip < specular.getImage().getMipMapSizes().length) {
+
+            int[] sizes = specular.getImage().getMipMapSizes();
+            sizes = Arrays.copyOf(sizes, mip);
+            specular.getImage().setMipMapSizes(sizes);
+            specular.getImage().setMipmapsGenerated(true);
+            if (sizes.length <= 1) {
+                try {
+                    LOGGER.log(Level.WARNING, "Workaround driver BUG: only one mip level available, regenerate it with higher roughness (shiny fix)");
+                    bakeSpecularIBL(0, 1f, mat, screen);
+                } catch (Exception e) {
+                    LOGGER.log(Level.FINE, "Error while recomputing mip level 0", e);
+                }
+            }
+        }
+
+        if (isTexturePulling()) {
+            endPulling(specular);
+        }
+        specular.getImage().clearUpdateNeeded();
+
+    }
+
+    @Override
+    public TextureCubeMap getSpecularIBL() {
+        return specular;
+    }
+
+    @Override
+    public void bakeSphericalHarmonicsCoefficients() {
+        shCoef = EnvMapUtils.getSphericalHarmonicsCoefficents(getEnvMap());
+    }
+
+    @Override
+    public Vector3f[] getSphericalHarmonicsCoefficients() {
+        return shCoef;
+    }
+}

+ 5 - 17
jme3-core/src/main/java/com/jme3/environment/util/BoundingSphereDebug.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
@@ -56,14 +56,6 @@ public class BoundingSphereDebug extends Mesh {
     protected int radialSamples = 32;
     protected int radialSamples = 32;
     protected boolean useEvenSlices;
     protected boolean useEvenSlices;
     protected boolean interior;
     protected boolean interior;
-    /**
-     * the distance from the center point each point falls on
-     */
-    public float radius;
-
-    public float getRadius() {
-        return radius;
-    }
 
 
     public BoundingSphereDebug() {
     public BoundingSphereDebug() {
         setGeometryData();
         setGeometryData();
@@ -151,27 +143,23 @@ public class BoundingSphereDebug extends Mesh {
             if (segDone == radialSamples || segDone == radialSamples * 2) {
             if (segDone == radialSamples || segDone == radialSamples * 2) {
                 idx++;
                 idx++;
             }
             }
-
         }
         }
-
     }
     }
-
     
     
     /**
     /**
      * Convenience factory method that creates a debug bounding-sphere geometry
      * Convenience factory method that creates a debug bounding-sphere geometry
+     * 
      * @param assetManager the assetManager
      * @param assetManager the assetManager
      * @return the bounding sphere debug geometry.
      * @return the bounding sphere debug geometry.
      */
      */
     public static Geometry createDebugSphere(AssetManager assetManager) {
     public static Geometry createDebugSphere(AssetManager assetManager) {
-        BoundingSphereDebug b = new BoundingSphereDebug();
-        Geometry geom = new Geometry("BoundingDebug", b);
-
+        BoundingSphereDebug mesh = new BoundingSphereDebug();
+        Geometry geom = new Geometry("BoundingDebug", mesh);
         Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
         Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
         mat.setBoolean("VertexColor", true);
         mat.setBoolean("VertexColor", true);
         mat.getAdditionalRenderState().setWireframe(true);
         mat.getAdditionalRenderState().setWireframe(true);
-        
         geom.setMaterial(mat);
         geom.setMaterial(mat);
         return geom;
         return geom;
-
     }
     }
+    
 }
 }

+ 1 - 1
jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java

@@ -57,7 +57,7 @@ import java.util.logging.Logger;
  */
  */
 public class SavableClassUtil {
 public class SavableClassUtil {
 
 
-    private final static HashMap<String, String> CLASS_REMAPPINGS = new HashMap<>();
+    private static final HashMap<String, String> CLASS_REMAPPINGS = new HashMap<>();
 
 
     private static void addRemapping(String oldClass, Class<? extends Savable> newClass) {
     private static void addRemapping(String oldClass, Class<? extends Savable> newClass) {
         CLASS_REMAPPINGS.put(oldClass, newClass.getName());
         CLASS_REMAPPINGS.put(oldClass, newClass.getName());

+ 1 - 1
jme3-core/src/main/java/com/jme3/font/BitmapCharacterSet.java

@@ -43,7 +43,7 @@ public class BitmapCharacterSet implements Savable {
     private int renderedSize;
     private int renderedSize;
     private int width;
     private int width;
     private int height;
     private int height;
-    final private IntMap<IntMap<BitmapCharacter>> characters;
+    private final IntMap<IntMap<BitmapCharacter>> characters;
     private int pageSize;
     private int pageSize;
 
 
     @Override
     @Override

+ 1 - 1
jme3-core/src/main/java/com/jme3/font/ColorTags.java

@@ -47,7 +47,7 @@ import java.util.regex.Pattern;
 class ColorTags {
 class ColorTags {
     private static final Pattern colorPattern = Pattern.compile("\\\\#([0-9a-fA-F]{8})#|\\\\#([0-9a-fA-F]{6})#|" +
     private static final Pattern colorPattern = Pattern.compile("\\\\#([0-9a-fA-F]{8})#|\\\\#([0-9a-fA-F]{6})#|" +
                                                                 "\\\\#([0-9a-fA-F]{4})#|\\\\#([0-9a-fA-F]{3})#");
                                                                 "\\\\#([0-9a-fA-F]{4})#|\\\\#([0-9a-fA-F]{3})#");
-    final private LinkedList<Range> colors = new LinkedList<>();
+    private final LinkedList<Range> colors = new LinkedList<>();
     private String text;
     private String text;
     private String original;
     private String original;
     private float baseAlpha = -1;
     private float baseAlpha = -1;

+ 1 - 1
jme3-core/src/main/java/com/jme3/font/LetterQuad.java

@@ -66,7 +66,7 @@ class LetterQuad {
     private LetterQuad next;
     private LetterQuad next;
     private int colorInt = 0xFFFFFFFF;
     private int colorInt = 0xFFFFFFFF;
 
 
-    final private boolean rightToLeft;
+    private final boolean rightToLeft;
     private float alignX;
     private float alignX;
     private float alignY;
     private float alignY;
     private float sizeScale = 1;
     private float sizeScale = 1;

+ 2 - 2
jme3-core/src/main/java/com/jme3/font/Letters.java

@@ -47,10 +47,10 @@ class Letters {
     private final LetterQuad tail;
     private final LetterQuad tail;
     private final BitmapFont font;
     private final BitmapFont font;
     private LetterQuad current;
     private LetterQuad current;
-    final private StringBlock block;
+    private final StringBlock block;
     private float totalWidth;
     private float totalWidth;
     private float totalHeight;
     private float totalHeight;
-    final private ColorTags colorTags = new ColorTags();
+    private final ColorTags colorTags = new ColorTags();
     private ColorRGBA baseColor = null;
     private ColorRGBA baseColor = null;
     private float baseAlpha = -1;
     private float baseAlpha = -1;
     private String plainText;
     private String plainText;

+ 6 - 6
jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java

@@ -42,13 +42,13 @@ import java.util.*;
  */
  */
 public abstract class AbstractJoystick implements Joystick {
 public abstract class AbstractJoystick implements Joystick {
 
 
-    final private InputManager inputManager;
-    final private JoyInput joyInput;
-    final private int joyId;
-    final private String name;
+    private final InputManager inputManager;
+    private final JoyInput joyInput;
+    private final int joyId;
+    private final String name;
 
 
-    final private List<JoystickAxis> axes = new ArrayList<>();
-    final private List<JoystickButton> buttons = new ArrayList<>();
+    private final List<JoystickAxis> axes = new ArrayList<>();
+    private final List<JoystickButton> buttons = new ArrayList<>();
 
 
     /**
     /**
      * Creates a new joystick instance. Only used internally.
      * Creates a new joystick instance. Only used internally.

+ 21 - 21
jme3-core/src/main/java/com/jme3/input/CameraInput.java

@@ -44,37 +44,37 @@ public class CameraInput {
      * Chase camera mapping for moving down. Default assigned to
      * Chase camera mapping for moving down. Default assigned to
      * MouseInput.AXIS_Y direction depending on the invertYaxis configuration
      * MouseInput.AXIS_Y direction depending on the invertYaxis configuration
      */
      */
-    public final static String CHASECAM_DOWN = "ChaseCamDown";
+    public static final String CHASECAM_DOWN = "ChaseCamDown";
     /**
     /**
      * Chase camera mapping for moving up. Default assigned to MouseInput.AXIS_Y
      * Chase camera mapping for moving up. Default assigned to MouseInput.AXIS_Y
      * direction depending on the invertYaxis configuration
      * direction depending on the invertYaxis configuration
      */
      */
-    public final static String CHASECAM_UP = "ChaseCamUp";
+    public static final String CHASECAM_UP = "ChaseCamUp";
     /**
     /**
      * Chase camera mapping for zooming in. Default assigned to
      * Chase camera mapping for zooming in. Default assigned to
      * MouseInput.AXIS_WHEEL direction positive
      * MouseInput.AXIS_WHEEL direction positive
      */
      */
-    public final static String CHASECAM_ZOOMIN = "ChaseCamZoomIn";
+    public static final String CHASECAM_ZOOMIN = "ChaseCamZoomIn";
     /**
     /**
      * Chase camera mapping for zooming out. Default assigned to
      * Chase camera mapping for zooming out. Default assigned to
      * MouseInput.AXIS_WHEEL direction negative
      * MouseInput.AXIS_WHEEL direction negative
      */
      */
-    public final static String CHASECAM_ZOOMOUT = "ChaseCamZoomOut";
+    public static final String CHASECAM_ZOOMOUT = "ChaseCamZoomOut";
     /**
     /**
      * Chase camera mapping for moving left. Default assigned to
      * Chase camera mapping for moving left. Default assigned to
      * MouseInput.AXIS_X direction depending on the invertXaxis configuration
      * MouseInput.AXIS_X direction depending on the invertXaxis configuration
      */
      */
-    public final static String CHASECAM_MOVELEFT = "ChaseCamMoveLeft";
+    public static final String CHASECAM_MOVELEFT = "ChaseCamMoveLeft";
     /**
     /**
      * Chase camera mapping for moving right. Default assigned to
      * Chase camera mapping for moving right. Default assigned to
      * MouseInput.AXIS_X direction depending on the invertXaxis configuration
      * MouseInput.AXIS_X direction depending on the invertXaxis configuration
      */
      */
-    public final static String CHASECAM_MOVERIGHT = "ChaseCamMoveRight";
+    public static final String CHASECAM_MOVERIGHT = "ChaseCamMoveRight";
     /**
     /**
      * Chase camera mapping to initiate the rotation of the cam. Default assigned
      * Chase camera mapping to initiate the rotation of the cam. Default assigned
      * to MouseInput.BUTTON_LEFT and MouseInput.BUTTON_RIGHT
      * to MouseInput.BUTTON_LEFT and MouseInput.BUTTON_RIGHT
      */
      */
-    public final static String CHASECAM_TOGGLEROTATE = "ChaseCamToggleRotate";
+    public static final String CHASECAM_TOGGLEROTATE = "ChaseCamToggleRotate";
     
     
         
         
     
     
@@ -83,63 +83,63 @@ public class CameraInput {
      * Fly camera mapping to look left. Default assigned to MouseInput.AXIS_X,
      * Fly camera mapping to look left. Default assigned to MouseInput.AXIS_X,
      * direction negative
      * direction negative
      */
      */
-    public final static String FLYCAM_LEFT = "FLYCAM_Left";
+    public static final String FLYCAM_LEFT = "FLYCAM_Left";
     /**
     /**
      * Fly camera mapping to look right. Default assigned to MouseInput.AXIS_X,
      * Fly camera mapping to look right. Default assigned to MouseInput.AXIS_X,
      * direction positive
      * direction positive
      */
      */
-    public final static String FLYCAM_RIGHT = "FLYCAM_Right";
+    public static final String FLYCAM_RIGHT = "FLYCAM_Right";
     /**
     /**
      * Fly camera mapping to look up. Default assigned to MouseInput.AXIS_Y,
      * Fly camera mapping to look up. Default assigned to MouseInput.AXIS_Y,
      * direction positive
      * direction positive
      */
      */
-    public final static String FLYCAM_UP = "FLYCAM_Up";
+    public static final String FLYCAM_UP = "FLYCAM_Up";
     /**
     /**
      * Fly camera mapping to look down. Default assigned to MouseInput.AXIS_Y,
      * Fly camera mapping to look down. Default assigned to MouseInput.AXIS_Y,
      * direction negative
      * direction negative
      */
      */
-    public final static String FLYCAM_DOWN = "FLYCAM_Down";
+    public static final String FLYCAM_DOWN = "FLYCAM_Down";
     /**
     /**
      * Fly camera mapping to move left. Default assigned to KeyInput.KEY_A   
      * Fly camera mapping to move left. Default assigned to KeyInput.KEY_A   
      */
      */
-    public final static String FLYCAM_STRAFELEFT = "FLYCAM_StrafeLeft";
+    public static final String FLYCAM_STRAFELEFT = "FLYCAM_StrafeLeft";
     /**
     /**
      * Fly camera mapping to move right. Default assigned to KeyInput.KEY_D  
      * Fly camera mapping to move right. Default assigned to KeyInput.KEY_D  
      */
      */
-    public final static String FLYCAM_STRAFERIGHT = "FLYCAM_StrafeRight";
+    public static final String FLYCAM_STRAFERIGHT = "FLYCAM_StrafeRight";
     /**
     /**
      * Fly camera mapping to move forward. Default assigned to KeyInput.KEY_W   
      * Fly camera mapping to move forward. Default assigned to KeyInput.KEY_W   
      */
      */
-    public final static String FLYCAM_FORWARD = "FLYCAM_Forward";
+    public static final String FLYCAM_FORWARD = "FLYCAM_Forward";
     /**
     /**
      * Fly camera mapping to move backward. Default assigned to KeyInput.KEY_S   
      * Fly camera mapping to move backward. Default assigned to KeyInput.KEY_S   
      */
      */
-    public final static String FLYCAM_BACKWARD = "FLYCAM_Backward";
+    public static final String FLYCAM_BACKWARD = "FLYCAM_Backward";
     /**
     /**
      * Fly camera mapping to zoom in. Default assigned to MouseInput.AXIS_WHEEL,
      * Fly camera mapping to zoom in. Default assigned to MouseInput.AXIS_WHEEL,
      * direction positive
      * direction positive
      */
      */
-    public final static String FLYCAM_ZOOMIN = "FLYCAM_ZoomIn";
+    public static final String FLYCAM_ZOOMIN = "FLYCAM_ZoomIn";
     /**
     /**
      * Fly camera mapping to zoom in. Default assigned to MouseInput.AXIS_WHEEL,
      * Fly camera mapping to zoom in. Default assigned to MouseInput.AXIS_WHEEL,
      * direction negative
      * direction negative
      */
      */
-    public final static String FLYCAM_ZOOMOUT = "FLYCAM_ZoomOut";
+    public static final String FLYCAM_ZOOMOUT = "FLYCAM_ZoomOut";
     /**
     /**
      * Fly camera mapping to toggle rotation. Default assigned to 
      * Fly camera mapping to toggle rotation. Default assigned to 
      * MouseInput.BUTTON_LEFT   
      * MouseInput.BUTTON_LEFT   
      */
      */
-    public final static String FLYCAM_ROTATEDRAG = "FLYCAM_RotateDrag";
+    public static final String FLYCAM_ROTATEDRAG = "FLYCAM_RotateDrag";
     /**
     /**
      * Fly camera mapping to move up. Default assigned to KeyInput.KEY_Q   
      * Fly camera mapping to move up. Default assigned to KeyInput.KEY_Q   
      */
      */
-    public final static String FLYCAM_RISE = "FLYCAM_Rise";
+    public static final String FLYCAM_RISE = "FLYCAM_Rise";
     /**
     /**
      * Fly camera mapping to move down. Default assigned to KeyInput.KEY_W   
      * Fly camera mapping to move down. Default assigned to KeyInput.KEY_W   
      */
      */
-    public final static String FLYCAM_LOWER = "FLYCAM_Lower";
+    public static final String FLYCAM_LOWER = "FLYCAM_Lower";
     
     
-    public final static String FLYCAM_INVERTY = "FLYCAM_InvertY";
+    public static final String FLYCAM_INVERTY = "FLYCAM_InvertY";
     
     
     /**
     /**
      * A private constructor to inhibit instantiation of this class.
      * A private constructor to inhibit instantiation of this class.

+ 7 - 7
jme3-core/src/main/java/com/jme3/input/ChaseCamera.java

@@ -105,37 +105,37 @@ public class ChaseCamera implements ActionListener, AnalogListener, Control, Jme
      * @deprecated use {@link CameraInput#CHASECAM_DOWN}
      * @deprecated use {@link CameraInput#CHASECAM_DOWN}
      */
      */
     @Deprecated
     @Deprecated
-    public final static String ChaseCamDown = "ChaseCamDown";
+    public static final String ChaseCamDown = "ChaseCamDown";
     /**
     /**
      * @deprecated use {@link CameraInput#CHASECAM_UP}
      * @deprecated use {@link CameraInput#CHASECAM_UP}
      */
      */
     @Deprecated
     @Deprecated
-    public final static String ChaseCamUp = "ChaseCamUp";
+    public static final String ChaseCamUp = "ChaseCamUp";
     /**
     /**
      * @deprecated use {@link CameraInput#CHASECAM_ZOOMIN}
      * @deprecated use {@link CameraInput#CHASECAM_ZOOMIN}
      */
      */
     @Deprecated
     @Deprecated
-    public final static String ChaseCamZoomIn = "ChaseCamZoomIn";
+    public static final String ChaseCamZoomIn = "ChaseCamZoomIn";
     /**
     /**
      * @deprecated use {@link CameraInput#CHASECAM_ZOOMOUT}
      * @deprecated use {@link CameraInput#CHASECAM_ZOOMOUT}
      */
      */
     @Deprecated
     @Deprecated
-    public final static String ChaseCamZoomOut = "ChaseCamZoomOut";
+    public static final String ChaseCamZoomOut = "ChaseCamZoomOut";
     /**
     /**
      * @deprecated use {@link CameraInput#CHASECAM_MOVELEFT}
      * @deprecated use {@link CameraInput#CHASECAM_MOVELEFT}
      */
      */
     @Deprecated
     @Deprecated
-    public final static String ChaseCamMoveLeft = "ChaseCamMoveLeft";
+    public static final String ChaseCamMoveLeft = "ChaseCamMoveLeft";
     /**
     /**
      * @deprecated use {@link CameraInput#CHASECAM_MOVERIGHT}
      * @deprecated use {@link CameraInput#CHASECAM_MOVERIGHT}
      */
      */
     @Deprecated
     @Deprecated
-    public final static String ChaseCamMoveRight = "ChaseCamMoveRight";
+    public static final String ChaseCamMoveRight = "ChaseCamMoveRight";
     /**
     /**
      * @deprecated use {@link CameraInput#CHASECAM_TOGGLEROTATE}
      * @deprecated use {@link CameraInput#CHASECAM_TOGGLEROTATE}
      */
      */
     @Deprecated
     @Deprecated
-    public final static String ChaseCamToggleRotate = "ChaseCamToggleRotate";
+    public static final String ChaseCamToggleRotate = "ChaseCamToggleRotate";
 
 
     protected boolean zoomin;
     protected boolean zoomin;
     protected boolean hideCursorOnRotate = true;
     protected boolean hideCursorOnRotate = true;

+ 7 - 7
jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java

@@ -40,13 +40,13 @@ import com.jme3.input.controls.JoyAxisTrigger;
  */
  */
 public class DefaultJoystickAxis implements JoystickAxis {
 public class DefaultJoystickAxis implements JoystickAxis {
 
 
-    final private InputManager inputManager;
-    final private Joystick parent;
-    final private int axisIndex;
-    final private String name;
-    final private String logicalId;
-    final private boolean isAnalog;
-    final private boolean isRelative;
+    private final InputManager inputManager;
+    private final Joystick parent;
+    private final int axisIndex;
+    private final String name;
+    private final String logicalId;
+    private final boolean isAnalog;
+    private final boolean isRelative;
     private float deadZone;
     private float deadZone;
 
 
     /**
     /**

+ 5 - 5
jme3-core/src/main/java/com/jme3/input/DefaultJoystickButton.java

@@ -40,11 +40,11 @@ import com.jme3.input.controls.JoyButtonTrigger;
  */
  */
 public class DefaultJoystickButton implements JoystickButton {
 public class DefaultJoystickButton implements JoystickButton {
 
 
-    final private InputManager inputManager;
-    final private Joystick parent;
-    final private int buttonIndex;
-    final private String name;
-    final private String logicalId;
+    private final InputManager inputManager;
+    private final Joystick parent;
+    private final int buttonIndex;
+    private final String name;
+    private final String logicalId;
 
 
     public DefaultJoystickButton(InputManager inputManager, Joystick parent, int buttonIndex,
     public DefaultJoystickButton(InputManager inputManager, Joystick parent, int buttonIndex,
             String name, String logicalId) {
             String name, String logicalId) {

+ 1 - 1
jme3-core/src/main/java/com/jme3/input/FlyByCamera.java

@@ -56,7 +56,7 @@ import com.jme3.renderer.Camera;
  */
  */
 public class FlyByCamera implements AnalogListener, ActionListener {
 public class FlyByCamera implements AnalogListener, ActionListener {
 
 
-    final private static String[] mappings = new String[]{
+    private static final String[] mappings = new String[]{
         CameraInput.FLYCAM_LEFT,
         CameraInput.FLYCAM_LEFT,
         CameraInput.FLYCAM_RIGHT,
         CameraInput.FLYCAM_RIGHT,
         CameraInput.FLYCAM_UP,
         CameraInput.FLYCAM_UP,

+ 2 - 2
jme3-core/src/main/java/com/jme3/input/JoystickCompatibilityMappings.java

@@ -72,8 +72,8 @@ public class JoystickCompatibilityMappings {
     private static Map<String, Map<String, String>> buttonMappings = new HashMap<String, Map<String, String>>();
     private static Map<String, Map<String, String>> buttonMappings = new HashMap<String, Map<String, String>>();
 
 
     // Remaps names by regex.
     // Remaps names by regex.
-    final private static Map<Pattern, String> nameRemappings = new HashMap<>();
-    final private static Map<String, String> nameCache = new HashMap<>();
+    private static final Map<Pattern, String> nameRemappings = new HashMap<>();
+    private static final Map<String, String> nameCache = new HashMap<>();
 
 
     static {
     static {
         loadDefaultMappings();
         loadDefaultMappings();

+ 2 - 2
jme3-core/src/main/java/com/jme3/input/event/JoyButtonEvent.java

@@ -41,8 +41,8 @@ import com.jme3.input.JoystickButton;
  */
  */
 public class JoyButtonEvent extends InputEvent {
 public class JoyButtonEvent extends InputEvent {
 
 
-    final private JoystickButton button;
-    final private boolean pressed;
+    private final JoystickButton button;
+    private final boolean pressed;
 
 
     public JoyButtonEvent(JoystickButton button, boolean pressed) {
     public JoyButtonEvent(JoystickButton button, boolean pressed) {
         this.button = button;
         this.button = button;

+ 4 - 4
jme3-core/src/main/java/com/jme3/input/event/KeyInputEvent.java

@@ -40,10 +40,10 @@ import com.jme3.input.KeyInput;
  */
  */
 public class KeyInputEvent extends InputEvent {
 public class KeyInputEvent extends InputEvent {
 
 
-    final private int keyCode;
-    final private char keyChar;
-    final private boolean pressed;
-    final private boolean repeating;
+    private final int keyCode;
+    private final char keyChar;
+    private final boolean pressed;
+    private final boolean repeating;
 
 
     public KeyInputEvent(int keyCode, char keyChar, boolean pressed, boolean repeating) {
     public KeyInputEvent(int keyCode, char keyChar, boolean pressed, boolean repeating) {
         this.keyCode = keyCode;
         this.keyCode = keyCode;

+ 4 - 4
jme3-core/src/main/java/com/jme3/input/event/MouseButtonEvent.java

@@ -40,10 +40,10 @@ import com.jme3.input.MouseInput;
  */
  */
 public class MouseButtonEvent extends InputEvent {
 public class MouseButtonEvent extends InputEvent {
 
 
-    final private int x;
-    final private int y;
-    final private int btnIndex;
-    final private boolean pressed;
+    private final int x;
+    private final int y;
+    private final int btnIndex;
+    private final boolean pressed;
 
 
     public MouseButtonEvent(int btnIndex, boolean pressed, int x, int y) {
     public MouseButtonEvent(int btnIndex, boolean pressed, int x, int y) {
         this.btnIndex = btnIndex;
         this.btnIndex = btnIndex;

+ 1 - 1
jme3-core/src/main/java/com/jme3/input/event/MouseMotionEvent.java

@@ -40,7 +40,7 @@ package com.jme3.input.event;
  */
  */
 public class MouseMotionEvent extends InputEvent {
 public class MouseMotionEvent extends InputEvent {
 
 
-    final private int x, y, dx, dy, wheel, deltaWheel;
+    private final int x, y, dx, dy, wheel, deltaWheel;
 
 
     public MouseMotionEvent(int x, int y, int dx, int dy, int wheel, int deltaWheel) {
     public MouseMotionEvent(int x, int y, int dx, int dy, int wheel, int deltaWheel) {
         this.x = x;
         this.x = x;

+ 1 - 1
jme3-core/src/main/java/com/jme3/light/Light.java

@@ -87,7 +87,7 @@ public abstract class Light implements Savable, Cloneable {
         Probe(4);
         Probe(4);
                 
                 
 
 
-        final private int typeId;
+        private final int typeId;
 
 
         Type(int type){
         Type(int type){
             this.typeId = type;
             this.typeId = type;

+ 1 - 1
jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java

@@ -13,7 +13,7 @@ public class SphereProbeArea implements ProbeArea {
 
 
     private Vector3f center = new Vector3f();
     private Vector3f center = new Vector3f();
     private float radius = 1;
     private float radius = 1;
-    final private Matrix4f uniformMatrix = new Matrix4f();
+    private final Matrix4f uniformMatrix = new Matrix4f();
 
 
     public SphereProbeArea() {
     public SphereProbeArea() {
     }
     }

+ 1 - 1
jme3-core/src/main/java/com/jme3/light/WeightedProbeBlendingStrategy.java

@@ -45,7 +45,7 @@ import java.util.List;
  */
  */
 public class WeightedProbeBlendingStrategy implements LightProbeBlendingStrategy {
 public class WeightedProbeBlendingStrategy implements LightProbeBlendingStrategy {
 
 
-    private final static int MAX_PROBES = 3;
+    private static final int MAX_PROBES = 3;
     List<LightProbe> lightProbes = new ArrayList<>();
     List<LightProbe> lightProbes = new ArrayList<>();
 
 
     @Override
     @Override

+ 41 - 30
jme3-core/src/main/java/com/jme3/material/MatParam.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
@@ -31,16 +31,26 @@
  */
  */
 package com.jme3.material;
 package com.jme3.material;
 
 
+import java.io.IOException;
+import java.util.Arrays;
+
 import com.jme3.asset.TextureKey;
 import com.jme3.asset.TextureKey;
-import com.jme3.export.*;
-import com.jme3.math.*;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.math.Vector4f;
 import com.jme3.shader.VarType;
 import com.jme3.shader.VarType;
 import com.jme3.texture.Texture;
 import com.jme3.texture.Texture;
 import com.jme3.texture.Texture.WrapMode;
 import com.jme3.texture.Texture.WrapMode;
 
 
-import java.io.IOException;
-import java.util.Arrays;
-
 /**
 /**
  * Describes a material parameter. This is used for both defining a name and type
  * Describes a material parameter. This is used for both defining a name and type
  * as well as a material parameter value.
  * as well as a material parameter value.
@@ -58,8 +68,8 @@ public class MatParam implements Savable, Cloneable {
     /**
     /**
      * Create a new material parameter. For internal use only.
      * Create a new material parameter. For internal use only.
      *
      *
-     * @param type the type of the parameter
-     * @param name the desired parameter name
+     * @param type  the type of the parameter
+     * @param name  the desired parameter name
      * @param value the desired parameter value (alias created)
      * @param value the desired parameter value (alias created)
      */
      */
     public MatParam(VarType type, String name, Object value) {
     public MatParam(VarType type, String name, Object value) {
@@ -75,20 +85,19 @@ public class MatParam implements Savable, Cloneable {
     protected MatParam() {
     protected MatParam() {
     }
     }
 
 
-
     public boolean isTypeCheckEnabled() {
     public boolean isTypeCheckEnabled() {
         return typeCheck;
         return typeCheck;
     }
     }
 
 
-
     /**
     /**
      * Enable type check for this param.
      * Enable type check for this param.
      * When type check is enabled a RuntimeException is thrown if 
      * When type check is enabled a RuntimeException is thrown if 
      * an object of the wrong type is passed to setValue.
      * an object of the wrong type is passed to setValue.
-     * @param v (default = true)
+     * 
+     * @param typeCheck (default = true)
      */
      */
-    public void setTypeCheckEnabled(boolean v) {
-        typeCheck = v;
+    public void setTypeCheckEnabled(boolean typeCheck) {
+        this.typeCheck = typeCheck;
     }
     }
 
 
     /**
     /**
@@ -102,6 +111,7 @@ public class MatParam implements Savable, Cloneable {
 
 
     /**
     /**
      * Returns the name of the material parameter.
      * Returns the name of the material parameter.
+     * 
      * @return the name of the material parameter.
      * @return the name of the material parameter.
      */
      */
     public String getName() {
     public String getName() {
@@ -158,15 +168,16 @@ public class MatParam implements Savable, Cloneable {
                     }
                     }
                 }
                 }
                 if (!valid) {
                 if (!valid) {
-                    throw new RuntimeException("Trying to assign a value of type " + value.getClass() + " to " + this.getName() + " of type " + type.name() + ". Valid types are "
-                            + Arrays.deepToString(type.getJavaType()));
+                    throw new RuntimeException("Trying to assign a value of type " + value.getClass() 
+                            + " to " + this.getName() 
+                            + " of type " + type.name() 
+                            + ". Valid types are " + Arrays.deepToString(type.getJavaType()));
                 }
                 }
             }
             }
         }
         }
         this.value = value;
         this.value = value;
     }
     }
 
 
-
     /**
     /**
      * Returns the material parameter value as it would appear in a J3M
      * Returns the material parameter value as it would appear in a J3M
      * file. E.g.
      * file. E.g.
@@ -274,12 +285,12 @@ When arrays can be inserted in J3M files
             case TextureCubeMap:
             case TextureCubeMap:
                 Texture texVal = (Texture) value;
                 Texture texVal = (Texture) value;
                 TextureKey texKey = (TextureKey) texVal.getKey();
                 TextureKey texKey = (TextureKey) texVal.getKey();
-                if (texKey == null){
-                  //throw new UnsupportedOperationException("The specified MatParam cannot be represented in J3M");
+                if (texKey == null) {
+                    // throw new UnsupportedOperationException("The specified MatParam cannot be represented in J3M");
                     // this is used in toString and the above line causes blender materials to throw this exception.
                     // this is used in toString and the above line causes blender materials to throw this exception.
                     // toStrings should be very robust IMO as even debuggers often invoke toString and logging code
                     // toStrings should be very robust IMO as even debuggers often invoke toString and logging code
                     // often does as well, even implicitly.
                     // often does as well, even implicitly.
-                    return texVal+":returned null key";
+                    return texVal + ":returned null key";
                 }
                 }
 
 
                 String ret = "";
                 String ret = "";
@@ -287,22 +298,22 @@ When arrays can be inserted in J3M files
                     ret += "Flip ";
                     ret += "Flip ";
                 }
                 }
 
 
-                //Wrap mode
+                // Wrap mode
                 ret += getWrapMode(texVal, Texture.WrapAxis.S);
                 ret += getWrapMode(texVal, Texture.WrapAxis.S);
                 ret += getWrapMode(texVal, Texture.WrapAxis.T);
                 ret += getWrapMode(texVal, Texture.WrapAxis.T);
                 ret += getWrapMode(texVal, Texture.WrapAxis.R);
                 ret += getWrapMode(texVal, Texture.WrapAxis.R);
 
 
-                //Min and Mag filter
-                Texture.MinFilter def =  Texture.MinFilter.BilinearNoMipMaps;
-                if(texVal.getImage().hasMipmaps() || texKey.isGenerateMips()){
+                // Min and Mag filter
+                Texture.MinFilter def = Texture.MinFilter.BilinearNoMipMaps;
+                if (texVal.getImage().hasMipmaps() || texKey.isGenerateMips()) {
                     def = Texture.MinFilter.Trilinear;
                     def = Texture.MinFilter.Trilinear;
                 }
                 }
-                if(texVal.getMinFilter() != def){
-                    ret += "Min" + texVal.getMinFilter().name()+ " ";
+                if (texVal.getMinFilter() != def) {
+                    ret += "Min" + texVal.getMinFilter().name() + " ";
                 }
                 }
 
 
-                if(texVal.getMagFilter() != Texture.MagFilter.Bilinear){
-                    ret += "Mag" + texVal.getMagFilter().name()+ " ";
+                if (texVal.getMagFilter() != Texture.MagFilter.Bilinear) {
+                    ret += "Mag" + texVal.getMagFilter().name() + " ";
                 }
                 }
 
 
                 return ret + "\"" + texKey.getName() + "\"";
                 return ret + "\"" + texKey.getName() + "\"";
@@ -315,12 +326,12 @@ When arrays can be inserted in J3M files
         WrapMode mode = WrapMode.EdgeClamp;
         WrapMode mode = WrapMode.EdgeClamp;
         try {
         try {
             mode = texVal.getWrap(axis);
             mode = texVal.getWrap(axis);
-        } catch (IllegalArgumentException e) {
-            //this axis doesn't exist on the texture
+        } catch (IllegalArgumentException ex) {
+            // this axis doesn't exist on the texture
             return "";
             return "";
         }
         }
         if (mode != WrapMode.EdgeClamp) {
         if (mode != WrapMode.EdgeClamp) {
-            return"Wrap"+ mode.name() + "_" + axis.name() + " ";
+            return "Wrap" + mode.name() + "_" + axis.name() + " ";
         }
         }
         return "";
         return "";
     }
     }

+ 40 - 24
jme3-core/src/main/java/com/jme3/material/MatParamTexture.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
@@ -40,51 +40,67 @@ import com.jme3.texture.Texture;
 import com.jme3.texture.image.ColorSpace;
 import com.jme3.texture.image.ColorSpace;
 import java.io.IOException;
 import java.io.IOException;
 
 
+/**
+ * A material parameter that holds a reference to a texture and its required color space.
+ * This class extends {@link MatParam} to provide texture specific functionalities.
+ */
 public class MatParamTexture extends MatParam {
 public class MatParamTexture extends MatParam {
 
 
-    private Texture texture;
     private ColorSpace colorSpace;
     private ColorSpace colorSpace;
 
 
+    /**
+     * Constructs a new MatParamTexture instance with the specified type, name,
+     * texture, and color space.
+     *
+     * @param type       the type of the material parameter
+     * @param name       the name of the parameter
+     * @param texture    the texture associated with this parameter
+     * @param colorSpace the required color space for the texture
+     */
     public MatParamTexture(VarType type, String name, Texture texture, ColorSpace colorSpace) {
     public MatParamTexture(VarType type, String name, Texture texture, ColorSpace colorSpace) {
         super(type, name, texture);
         super(type, name, texture);
-        this.texture = texture;
         this.colorSpace = colorSpace;
         this.colorSpace = colorSpace;
     }
     }
 
 
+    /**
+     * Serialization only. Do not use.
+     */
     public MatParamTexture() {
     public MatParamTexture() {
     }
     }
 
 
+    /**
+     * Retrieves the texture associated with this material parameter.
+     *
+     * @return the texture object
+     */
     public Texture getTextureValue() {
     public Texture getTextureValue() {
-        return texture;
+        return (Texture) getValue();
     }
     }
 
 
+    /**
+     * Sets the texture associated with this material parameter.
+     *
+     * @param value the texture object to set
+     * @throws RuntimeException if the provided value is not a {@link Texture}
+     */
     public void setTextureValue(Texture value) {
     public void setTextureValue(Texture value) {
-        this.value = value;
-        this.texture = value;
-    }
-    
-    @Override
-    public void setValue(Object value) {
-        if (!(value instanceof Texture)) {
-            throw new IllegalArgumentException("value must be a texture object");
-        }
-        this.value = value;
-        this.texture = (Texture) value;
+        setValue(value);
     }
     }
 
 
     /**
     /**
+     * Gets the required color space for this texture parameter.
      * 
      * 
-     * @return the color space required by this texture param
+     * @return the required color space ({@link ColorSpace})
      */
      */
     public ColorSpace getColorSpace() {
     public ColorSpace getColorSpace() {
         return colorSpace;
         return colorSpace;
     }
     }
 
 
     /**
     /**
-     * Set to {@link ColorSpace#Linear} if the texture color space has to be forced to linear 
-     * instead of sRGB
+     * Set to {@link ColorSpace#Linear} if the texture color space has to be forced
+     * to linear instead of sRGB.
+     * 
      * @param colorSpace the desired color space
      * @param colorSpace the desired color space
-     * @see ColorSpace
      */
      */
     public void setColorSpace(ColorSpace colorSpace) {
     public void setColorSpace(ColorSpace colorSpace) {
         this.colorSpace = colorSpace;
         this.colorSpace = colorSpace;
@@ -94,17 +110,17 @@ public class MatParamTexture extends MatParam {
     public void write(JmeExporter ex) throws IOException {
     public void write(JmeExporter ex) throws IOException {
         super.write(ex);
         super.write(ex);
         OutputCapsule oc = ex.getCapsule(this);
         OutputCapsule oc = ex.getCapsule(this);
-        oc.write(0, "texture_unit", -1);
-        oc.write(texture, "texture", null); // For backwards compatibility
-
         oc.write(colorSpace, "colorSpace", null);
         oc.write(colorSpace, "colorSpace", null);
+        // For backwards compatibility
+        oc.write(0, "texture_unit", -1);
+        oc.write((Texture) value, "texture", null);
     }
     }
 
 
     @Override
     @Override
     public void read(JmeImporter im) throws IOException {
     public void read(JmeImporter im) throws IOException {
         super.read(im);
         super.read(im);
         InputCapsule ic = im.getCapsule(this);
         InputCapsule ic = im.getCapsule(this);
-        texture = (Texture) value;
         colorSpace = ic.readEnum("colorSpace", ColorSpace.class, null);
         colorSpace = ic.readEnum("colorSpace", ColorSpace.class, null);
     }
     }
-}
+    
+}

+ 93 - 76
jme3-core/src/main/java/com/jme3/material/Material.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
@@ -47,6 +47,7 @@ import com.jme3.renderer.TextureUnitException;
 import com.jme3.renderer.queue.RenderQueue.Bucket;
 import com.jme3.renderer.queue.RenderQueue.Bucket;
 import com.jme3.scene.Geometry;
 import com.jme3.scene.Geometry;
 import com.jme3.shader.*;
 import com.jme3.shader.*;
+import com.jme3.shader.bufferobject.BufferObject;
 import com.jme3.texture.Image;
 import com.jme3.texture.Image;
 import com.jme3.texture.Texture;
 import com.jme3.texture.Texture;
 import com.jme3.texture.image.ColorSpace;
 import com.jme3.texture.image.ColorSpace;
@@ -82,11 +83,21 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
     private Technique technique;
     private Technique technique;
     private HashMap<String, Technique> techniques = new HashMap<>();
     private HashMap<String, Technique> techniques = new HashMap<>();
     private RenderState additionalState = null;
     private RenderState additionalState = null;
-    final private RenderState mergedRenderState = new RenderState();
+    private final RenderState mergedRenderState = new RenderState();
     private boolean transparent = false;
     private boolean transparent = false;
     private boolean receivesShadows = false;
     private boolean receivesShadows = false;
     private int sortingId = -1;
     private int sortingId = -1;
 
 
+    /**
+     * Track bind ids for textures and buffers
+     * Used internally 
+     */
+    public static class BindUnits {
+        public int textureUnit = 0;
+        public int bufferUnit = 0;
+    }
+    private BindUnits bindUnits = new BindUnits();
+
     public Material(MaterialDef def) {
     public Material(MaterialDef def) {
         if (def == null) {
         if (def == null) {
             throw new IllegalArgumentException("Material definition cannot be null");
             throw new IllegalArgumentException("Material definition cannot be null");
@@ -506,6 +517,18 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         }
         }
     }
     }
 
 
+    /**
+     * Pass a parameter to the material shader.
+     *
+     * @param name the name of the parameter defined in the material definition (j3md)
+     * @param value the value of the parameter
+     */
+    public void setParam(String name, Object value) {
+        MatParam p = getMaterialDef().getMaterialParam(name);
+        setParam(name, p.getVarType(), value);
+    }
+
+
     /**
     /**
      * Clear a parameter from this material. The parameter must exist
      * Clear a parameter from this material. The parameter must exist
      * @param name the name of the parameter to clear
      * @param name the name of the parameter to clear
@@ -685,8 +708,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
      * @param value the buffer object.
      * @param value the buffer object.
      */
      */
     public void setUniformBufferObject(final String name, final BufferObject value) {
     public void setUniformBufferObject(final String name, final BufferObject value) {
-        value.setBufferType(BufferObject.BufferType.UniformBufferObject);
-        setParam(name, VarType.BufferObject, value);
+        setParam(name, VarType.UniformBufferObject, value);
     }
     }
 
 
     /**
     /**
@@ -696,8 +718,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
      * @param value the buffer object.
      * @param value the buffer object.
      */
      */
     public void setShaderStorageBufferObject(final String name, final BufferObject value) {
     public void setShaderStorageBufferObject(final String name, final BufferObject value) {
-        value.setBufferType(BufferObject.BufferType.ShaderStorageBufferObject);
-        setParam(name, VarType.BufferObject, value);
+        setParam(name, VarType.ShaderStorageBufferObject, value);
     }
     }
 
 
     /**
     /**
@@ -797,7 +818,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         sortingId = -1;
         sortingId = -1;
     }
     }
 
 
-    private int applyOverrides(Renderer renderer, Shader shader, SafeArrayList<MatParamOverride> overrides, int unit) {
+    private void applyOverrides(Renderer renderer, Shader shader, SafeArrayList<MatParamOverride> overrides, BindUnits bindUnits) {
         for (MatParamOverride override : overrides.getArray()) {
         for (MatParamOverride override : overrides.getArray()) {
             VarType type = override.getVarType();
             VarType type = override.getVarType();
 
 
@@ -810,36 +831,64 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
             Uniform uniform = shader.getUniform(override.getPrefixedName());
             Uniform uniform = shader.getUniform(override.getPrefixedName());
 
 
             if (override.getValue() != null) {
             if (override.getValue() != null) {
-                if (type.isTextureType()) {
-                    try {
-                        renderer.setTexture(unit, (Texture) override.getValue());
-                    } catch (TextureUnitException exception) {
-                        int numTexParams = unit + 1;
-                        String message = "Too many texture parameters ("
-                                + numTexParams + ") assigned\n to " + toString();
-                        throw new IllegalStateException(message);
-                    }
-                    uniform.setValue(VarType.Int, unit);
-                    unit++;
-                } else {
-                    uniform.setValue(type, override.getValue());
-                }
+                updateShaderMaterialParameter(renderer, type, shader, override, bindUnits, true);
             } else {
             } else {
                 uniform.clearValue();
                 uniform.clearValue();
             }
             }
         }
         }
-        return unit;
     }
     }
 
 
-    private int updateShaderMaterialParameters(Renderer renderer, Shader shader,
-                                               SafeArrayList<MatParamOverride> worldOverrides, SafeArrayList<MatParamOverride> forcedOverrides) {
 
 
-        int unit = 0;
+    private void updateShaderMaterialParameter(Renderer renderer, VarType type, Shader shader, MatParam param, BindUnits unit, boolean override) {
+        if (type == VarType.UniformBufferObject || type == VarType.ShaderStorageBufferObject) {
+            ShaderBufferBlock bufferBlock = shader.getBufferBlock(param.getPrefixedName());
+            BufferObject bufferObject = (BufferObject) param.getValue();
+
+            ShaderBufferBlock.BufferType btype;
+            if (type == VarType.ShaderStorageBufferObject) {
+                btype = ShaderBufferBlock.BufferType.ShaderStorageBufferObject;
+                bufferBlock.setBufferObject(btype, bufferObject);
+                renderer.setShaderStorageBufferObject(unit.bufferUnit, bufferObject); // TODO: probably not needed
+            } else {
+                btype = ShaderBufferBlock.BufferType.UniformBufferObject;
+                bufferBlock.setBufferObject(btype, bufferObject);
+                renderer.setUniformBufferObject(unit.bufferUnit, bufferObject); // TODO: probably not needed
+            }
+            unit.bufferUnit++;
+        } else {
+            Uniform uniform = shader.getUniform(param.getPrefixedName());
+            if (!override && uniform.isSetByCurrentMaterial()) return;
+
+            if (type.isTextureType()) {
+                try {
+                    renderer.setTexture(unit.textureUnit, (Texture) param.getValue());
+                } catch (TextureUnitException exception) {
+                    int numTexParams = unit.textureUnit + 1;
+                    String message = "Too many texture parameters (" + numTexParams + ") assigned\n to " + toString();
+                    throw new IllegalStateException(message);
+                }
+                uniform.setValue(VarType.Int, unit.textureUnit);
+                unit.textureUnit++;
+            } else {
+                uniform.setValue(type, param.getValue());
+            }
+        }
+    }
+
+
+
+
+    private BindUnits updateShaderMaterialParameters(Renderer renderer, Shader shader, SafeArrayList<MatParamOverride> worldOverrides,
+            SafeArrayList<MatParamOverride> forcedOverrides) {
+
+        bindUnits.textureUnit = 0;
+        bindUnits.bufferUnit = 0;
+
         if (worldOverrides != null) {
         if (worldOverrides != null) {
-            unit = applyOverrides(renderer, shader, worldOverrides, unit);
+            applyOverrides(renderer, shader, worldOverrides, bindUnits);
         }
         }
         if (forcedOverrides != null) {
         if (forcedOverrides != null) {
-            unit = applyOverrides(renderer, shader, forcedOverrides, unit);
+            applyOverrides(renderer, shader, forcedOverrides, bindUnits);
         }
         }
 
 
         for (int i = 0; i < paramValues.size(); i++) {
         for (int i = 0; i < paramValues.size(); i++) {
@@ -847,66 +896,34 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
             MatParam param = paramValues.getValue(i);
             MatParam param = paramValues.getValue(i);
             VarType type = param.getVarType();
             VarType type = param.getVarType();
 
 
-            if (isBO(type)) {
-
-                final ShaderBufferBlock bufferBlock = shader.getBufferBlock(param.getPrefixedName());
-                bufferBlock.setBufferObject((BufferObject) param.getValue());
-
-            } else {
-
-                Uniform uniform = shader.getUniform(param.getPrefixedName());
-                if (uniform.isSetByCurrentMaterial()) {
-                    continue;
-                }
-
-                if (type.isTextureType()) {
-                    try {
-                        renderer.setTexture(unit, (Texture) param.getValue());
-                    } catch (TextureUnitException exception) {
-                        int numTexParams = unit + 1;
-                        String message = "Too many texture parameters ("
-                                + numTexParams + ") assigned\n to " + toString();
-                        throw new IllegalStateException(message);
-                    }
-                    uniform.setValue(VarType.Int, unit);
-                    unit++;
-                } else {
-                    uniform.setValue(type, param.getValue());
-                }
-            }
+            updateShaderMaterialParameter(renderer, type, shader, param, bindUnits, false);
         }
         }
 
 
-        //TODO HACKY HACK remove this when texture unit is handled by the uniform.
-        return unit;
+        // TODO HACKY HACK remove this when texture unit is handled by the
+        // uniform.
+        return bindUnits;
     }
     }
 
 
-    /**
-     * Returns true if the type is Buffer Object's type.
-     *
-     * @param type the material parameter type.
-     * @return true if the type is Buffer Object's type.
-     */
-    private boolean isBO(final VarType type) {
-        return type == VarType.BufferObject;
-    }
+
 
 
     private void updateRenderState(Geometry geometry, RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) {
     private void updateRenderState(Geometry geometry, RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) {
+        RenderState finalRenderState;
         if (renderManager.getForcedRenderState() != null) {
         if (renderManager.getForcedRenderState() != null) {
-            mergedRenderState.copyFrom(renderManager.getForcedRenderState());
+            finalRenderState = mergedRenderState.copyFrom(renderManager.getForcedRenderState());
         } else if (techniqueDef.getRenderState() != null) {
         } else if (techniqueDef.getRenderState() != null) {
-            mergedRenderState.copyFrom(RenderState.DEFAULT);
-            techniqueDef.getRenderState().copyMergedTo(additionalState, mergedRenderState);
+            finalRenderState = mergedRenderState.copyFrom(RenderState.DEFAULT);
+            finalRenderState = techniqueDef.getRenderState().copyMergedTo(additionalState, finalRenderState);
         } else {
         } else {
-            mergedRenderState.copyFrom(RenderState.DEFAULT);
-            RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState);
+            finalRenderState = mergedRenderState.copyFrom(RenderState.DEFAULT);
+            finalRenderState = RenderState.DEFAULT.copyMergedTo(additionalState, finalRenderState);
         }
         }
         // test if the face cull mode should be flipped before render
         // test if the face cull mode should be flipped before render
-        if (mergedRenderState.isFaceCullFlippable() && isNormalsBackward(geometry.getWorldScale())) {
-            mergedRenderState.flipFaceCull();
+        if (finalRenderState.isFaceCullFlippable() && isNormalsBackward(geometry.getWorldScale())) {
+            finalRenderState.flipFaceCull();
         }
         }
-        renderer.applyRenderState(mergedRenderState);
+        renderer.applyRenderState(finalRenderState);
     }
     }
-    
+
     /**
     /**
      * Returns true if the geometry world scale indicates that normals will be backward.
      * Returns true if the geometry world scale indicates that normals will be backward.
      * @param scalar geometry world scale
      * @param scalar geometry world scale
@@ -1064,13 +1081,13 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         renderManager.updateUniformBindings(shader);
         renderManager.updateUniformBindings(shader);
         
         
         // Set material parameters
         // Set material parameters
-        int unit = updateShaderMaterialParameters(renderer, shader, overrides, renderManager.getForcedMatParams());
+        BindUnits units = updateShaderMaterialParameters(renderer, shader, overrides, renderManager.getForcedMatParams());
 
 
         // Clear any uniforms not changed by material.
         // Clear any uniforms not changed by material.
         resetUniformsNotSetByCurrent(shader);
         resetUniformsNotSetByCurrent(shader);
         
         
         // Delegate rendering to the technique
         // Delegate rendering to the technique
-        technique.render(renderManager, shader, geometry, lights, unit);
+        technique.render(renderManager, shader, geometry, lights, units);
     }
     }
 
 
     /**
     /**

+ 20 - 3
jme3-core/src/main/java/com/jme3/material/RenderState.java

@@ -1511,6 +1511,10 @@ public class RenderState implements Cloneable, Savable {
             hash = 79 * hash + (this.backStencilDepthPassOperation != null ? this.backStencilDepthPassOperation.hashCode() : 0);
             hash = 79 * hash + (this.backStencilDepthPassOperation != null ? this.backStencilDepthPassOperation.hashCode() : 0);
             hash = 79 * hash + (this.frontStencilFunction != null ? this.frontStencilFunction.hashCode() : 0);
             hash = 79 * hash + (this.frontStencilFunction != null ? this.frontStencilFunction.hashCode() : 0);
             hash = 79 * hash + (this.backStencilFunction != null ? this.backStencilFunction.hashCode() : 0);
             hash = 79 * hash + (this.backStencilFunction != null ? this.backStencilFunction.hashCode() : 0);
+            hash = 79 * hash + (this.frontStencilMask);
+            hash = 79 * hash + (this.frontStencilReference);
+            hash = 79 * hash + (this.backStencilMask);
+            hash = 79 * hash + (this.backStencilReference);
             hash = 79 * hash + Float.floatToIntBits(this.lineWidth);
             hash = 79 * hash + Float.floatToIntBits(this.lineWidth);
             
             
             hash = 79 * hash + this.sfactorRGB.hashCode();
             hash = 79 * hash + this.sfactorRGB.hashCode();
@@ -1623,6 +1627,11 @@ public class RenderState implements Cloneable, Savable {
 
 
             state.frontStencilFunction = additionalState.frontStencilFunction;
             state.frontStencilFunction = additionalState.frontStencilFunction;
             state.backStencilFunction = additionalState.backStencilFunction;
             state.backStencilFunction = additionalState.backStencilFunction;
+
+            state.frontStencilMask = additionalState.frontStencilMask;
+            state.frontStencilReference = additionalState.frontStencilMask;
+            state.backStencilMask = additionalState.backStencilMask;
+            state.backStencilReference = additionalState.backStencilMask;
         } else {
         } else {
             state.stencilTest = stencilTest;
             state.stencilTest = stencilTest;
 
 
@@ -1636,6 +1645,11 @@ public class RenderState implements Cloneable, Savable {
 
 
             state.frontStencilFunction = frontStencilFunction;
             state.frontStencilFunction = frontStencilFunction;
             state.backStencilFunction = backStencilFunction;
             state.backStencilFunction = backStencilFunction;
+
+            state.frontStencilMask = frontStencilMask;
+            state.frontStencilReference = frontStencilMask;
+            state.backStencilMask = backStencilMask;
+            state.backStencilReference = backStencilMask;
         }
         }
         if (additionalState.applyLineWidth) {
         if (additionalState.applyLineWidth) {
             state.lineWidth = additionalState.lineWidth;
             state.lineWidth = additionalState.lineWidth;
@@ -1665,6 +1679,10 @@ public class RenderState implements Cloneable, Savable {
         backStencilDepthPassOperation = state.backStencilDepthPassOperation;
         backStencilDepthPassOperation = state.backStencilDepthPassOperation;
         frontStencilFunction = state.frontStencilFunction;
         frontStencilFunction = state.frontStencilFunction;
         backStencilFunction = state.backStencilFunction;
         backStencilFunction = state.backStencilFunction;
+        frontStencilMask = state.frontStencilMask;
+        frontStencilReference = state.frontStencilReference;
+        backStencilMask = state.backStencilMask;
+        backStencilReference = state.backStencilReference;
         blendEquationAlpha = state.blendEquationAlpha;
         blendEquationAlpha = state.blendEquationAlpha;
         blendEquation = state.blendEquation;
         blendEquation = state.blendEquation;
         depthFunc = state.depthFunc;
         depthFunc = state.depthFunc;
@@ -1692,7 +1710,7 @@ public class RenderState implements Cloneable, Savable {
      * This method is more precise than {@link #set(com.jme3.material.RenderState)}.
      * This method is more precise than {@link #set(com.jme3.material.RenderState)}.
      * @param state state to copy from
      * @param state state to copy from
      */
      */
-    public void copyFrom(RenderState state) {
+    public RenderState copyFrom(RenderState state) {
         this.applyBlendMode = state.applyBlendMode;
         this.applyBlendMode = state.applyBlendMode;
         this.applyColorWrite = state.applyColorWrite;
         this.applyColorWrite = state.applyColorWrite;
         this.applyCullMode = state.applyCullMode;
         this.applyCullMode = state.applyCullMode;
@@ -1734,6 +1752,7 @@ public class RenderState implements Cloneable, Savable {
         this.sfactorRGB = state.sfactorRGB;
         this.sfactorRGB = state.sfactorRGB;
         this.stencilTest = state.stencilTest;
         this.stencilTest = state.stencilTest;
         this.wireframe = state.wireframe;
         this.wireframe = state.wireframe;
+        return this;
     }
     }
 
 
     @Override
     @Override
@@ -1767,8 +1786,6 @@ public class RenderState implements Cloneable, Savable {
      * {@code Front} and {@code Front} becomes {@code Back}.
      * {@code Front} and {@code Front} becomes {@code Back}.
      * <p>{@code FrontAndBack} and {@code Off} are unaffected. This is important
      * <p>{@code FrontAndBack} and {@code Off} are unaffected. This is important
      * for flipping the cull mode when normal vectors are found to be backward.
      * for flipping the cull mode when normal vectors are found to be backward.
-     * @param cull
-     * @return flipped cull mode
      */
      */
     public void flipFaceCull() {
     public void flipFaceCull() {
         switch (cullMode) {
         switch (cullMode) {

+ 4 - 3
jme3-core/src/main/java/com/jme3/material/Technique.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
@@ -33,6 +33,7 @@ package com.jme3.material;
 
 
 import com.jme3.asset.AssetManager;
 import com.jme3.asset.AssetManager;
 import com.jme3.light.LightList;
 import com.jme3.light.LightList;
+import com.jme3.material.Material.BindUnits;
 import com.jme3.material.TechniqueDef.LightMode;
 import com.jme3.material.TechniqueDef.LightMode;
 import com.jme3.material.logic.TechniqueDefLogic;
 import com.jme3.material.logic.TechniqueDefLogic;
 import com.jme3.renderer.Caps;
 import com.jme3.renderer.Caps;
@@ -162,9 +163,9 @@ public final class Technique {
      * @param lights Lights which influence the geometry.
      * @param lights Lights which influence the geometry.
      * @param lastTexUnit the index of the most recently used texture unit
      * @param lastTexUnit the index of the most recently used texture unit
      */
      */
-    void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
+    void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) {
         TechniqueDefLogic logic = def.getLogic();
         TechniqueDefLogic logic = def.getLogic();
-        logic.render(renderManager, shader, geometry, lights, lastTexUnit);
+        logic.render(renderManager, shader, geometry, lights, lastBindUnits);
     }
     }
     
     
     /**
     /**

+ 1 - 1
jme3-core/src/main/java/com/jme3/material/TechniqueDef.java

@@ -824,4 +824,4 @@ public class TechniqueDef implements Savable, Cloneable {
 
 
         return clone;
         return clone;
     }
     }
-}
+}

+ 3 - 2
jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.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
@@ -34,6 +34,7 @@ package com.jme3.material.logic;
 import com.jme3.asset.AssetManager;
 import com.jme3.asset.AssetManager;
 import com.jme3.light.*;
 import com.jme3.light.*;
 import com.jme3.material.TechniqueDef;
 import com.jme3.material.TechniqueDef;
+import com.jme3.material.Material.BindUnits;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.ColorRGBA;
 import com.jme3.renderer.Caps;
 import com.jme3.renderer.Caps;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.RenderManager;
@@ -91,7 +92,7 @@ public class DefaultTechniqueDefLogic implements TechniqueDefLogic {
 
 
 
 
     @Override
     @Override
-    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) {
         Renderer renderer = renderManager.getRenderer();
         Renderer renderer = renderManager.getRenderer();
         renderer.setShader(shader);
         renderer.setShader(shader);
         renderMeshFromGeometry(renderer, geometry);
         renderMeshFromGeometry(renderer, geometry);

+ 3 - 2
jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.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
@@ -38,6 +38,7 @@ import com.jme3.light.PointLight;
 import com.jme3.light.SpotLight;
 import com.jme3.light.SpotLight;
 import com.jme3.material.RenderState;
 import com.jme3.material.RenderState;
 import com.jme3.material.TechniqueDef;
 import com.jme3.material.TechniqueDef;
+import com.jme3.material.Material.BindUnits;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.Quaternion;
 import com.jme3.math.Quaternion;
 import com.jme3.math.Vector3f;
 import com.jme3.math.Vector3f;
@@ -67,7 +68,7 @@ public final class MultiPassLightingLogic extends DefaultTechniqueDefLogic {
     }
     }
 
 
     @Override
     @Override
-    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) {
         Renderer r = renderManager.getRenderer();
         Renderer r = renderManager.getRenderer();
         Uniform lightDir = shader.getUniform("g_LightDirection");
         Uniform lightDir = shader.getUniform("g_LightDirection");
         Uniform lightColor = shader.getUniform("g_LightColor");
         Uniform lightColor = shader.getUniform("g_LightColor");

+ 6 - 6
jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.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
@@ -34,6 +34,7 @@ package com.jme3.material.logic;
 import com.jme3.asset.AssetManager;
 import com.jme3.asset.AssetManager;
 import com.jme3.light.*;
 import com.jme3.light.*;
 import com.jme3.material.*;
 import com.jme3.material.*;
+import com.jme3.material.Material.BindUnits;
 import com.jme3.material.RenderState.BlendMode;
 import com.jme3.material.RenderState.BlendMode;
 import com.jme3.math.*;
 import com.jme3.math.*;
 import com.jme3.renderer.*;
 import com.jme3.renderer.*;
@@ -54,7 +55,7 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
 
 
     private boolean useAmbientLight;
     private boolean useAmbientLight;
     private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
     private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
-    final private List<LightProbe> lightProbes = new ArrayList<>(3);
+    private final List<LightProbe> lightProbes = new ArrayList<>(3);
 
 
     static {
     static {
         ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive);
         ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive);
@@ -262,22 +263,21 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
     }
     }
 
 
     @Override
     @Override
-    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) {
         int nbRenderedLights = 0;
         int nbRenderedLights = 0;
         Renderer renderer = renderManager.getRenderer();
         Renderer renderer = renderManager.getRenderer();
         int batchSize = renderManager.getSinglePassLightBatchSize();
         int batchSize = renderManager.getSinglePassLightBatchSize();
         if (lights.size() == 0) {
         if (lights.size() == 0) {
-            updateLightListUniforms(shader, geometry, lights,batchSize, renderManager, 0, lastTexUnit);
+            updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, 0, lastBindUnits.textureUnit);
             renderer.setShader(shader);
             renderer.setShader(shader);
             renderMeshFromGeometry(renderer, geometry);
             renderMeshFromGeometry(renderer, geometry);
         } else {
         } else {
             while (nbRenderedLights < lights.size()) {
             while (nbRenderedLights < lights.size()) {
-                nbRenderedLights = updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, nbRenderedLights, lastTexUnit);
+                nbRenderedLights = updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, nbRenderedLights, lastBindUnits.textureUnit);
                 renderer.setShader(shader);
                 renderer.setShader(shader);
                 renderMeshFromGeometry(renderer, geometry);
                 renderMeshFromGeometry(renderer, geometry);
             }
             }
         }
         }
-        return;
     }
     }
 
 
     protected void extractIndirectLights(LightList lightList, boolean removeLights) {
     protected void extractIndirectLights(LightList lightList, boolean removeLights) {

+ 3 - 2
jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.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
@@ -40,6 +40,7 @@ import com.jme3.light.SpotLight;
 import com.jme3.material.RenderState;
 import com.jme3.material.RenderState;
 import com.jme3.material.RenderState.BlendMode;
 import com.jme3.material.RenderState.BlendMode;
 import com.jme3.material.TechniqueDef;
 import com.jme3.material.TechniqueDef;
+import com.jme3.material.Material.BindUnits;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.Vector3f;
 import com.jme3.math.Vector3f;
 import com.jme3.math.Vector4f;
 import com.jme3.math.Vector4f;
@@ -206,7 +207,7 @@ public final class SinglePassLightingLogic extends DefaultTechniqueDefLogic {
     }
     }
 
 
     @Override
     @Override
-    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) {
         int nbRenderedLights = 0;
         int nbRenderedLights = 0;
         Renderer renderer = renderManager.getRenderer();
         Renderer renderer = renderManager.getRenderer();
         int batchSize = renderManager.getSinglePassLightBatchSize();
         int batchSize = renderManager.getSinglePassLightBatchSize();

+ 3 - 2
jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.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
@@ -38,6 +38,7 @@ import com.jme3.light.LightList;
 import com.jme3.light.PointLight;
 import com.jme3.light.PointLight;
 import com.jme3.light.SpotLight;
 import com.jme3.light.SpotLight;
 import com.jme3.material.TechniqueDef;
 import com.jme3.material.TechniqueDef;
+import com.jme3.material.Material.BindUnits;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.Matrix4f;
 import com.jme3.math.Matrix4f;
 import com.jme3.math.Vector3f;
 import com.jme3.math.Vector3f;
@@ -171,7 +172,7 @@ public final class StaticPassLightingLogic extends DefaultTechniqueDefLogic {
     }
     }
 
 
     @Override
     @Override
-    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) {
         Renderer renderer = renderManager.getRenderer();
         Renderer renderer = renderManager.getRenderer();
         Matrix4f viewMatrix = renderManager.getCurrentCamera().getViewMatrix();
         Matrix4f viewMatrix = renderManager.getCurrentCamera().getViewMatrix();
         updateLightListUniforms(viewMatrix, shader, lights);
         updateLightListUniforms(viewMatrix, shader, lights);

+ 3 - 2
jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.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
@@ -33,6 +33,7 @@ package com.jme3.material.logic;
 
 
 import com.jme3.asset.AssetManager;
 import com.jme3.asset.AssetManager;
 import com.jme3.light.LightList;
 import com.jme3.light.LightList;
+import com.jme3.material.Material.BindUnits;
 import com.jme3.renderer.Caps;
 import com.jme3.renderer.Caps;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.RenderManager;
 import com.jme3.scene.Geometry;
 import com.jme3.scene.Geometry;
@@ -92,5 +93,5 @@ public interface TechniqueDefLogic {
      * @param lights Lights which influence the geometry.
      * @param lights Lights which influence the geometry.
      * @param lastTexUnit the index of the most recently used texture unit
      * @param lastTexUnit the index of the most recently used texture unit
      */
      */
-    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit);
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits);
 }
 }

+ 17 - 1
jme3-core/src/main/java/com/jme3/math/AbstractTriangle.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2009-2020 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
@@ -79,4 +79,20 @@ public abstract class AbstractTriangle implements Collidable {
     public int collideWith(Collidable other, CollisionResults results) {
     public int collideWith(Collidable other, CollisionResults results) {
         return other.collideWith(this, results);
         return other.collideWith(this, results);
     }
     }
+
+    /**
+     * Returns a string representation of the triangle, which is unaffected. For
+     * example, a {@link com.jme3.math.Triangle} joining (1,0,0) and (0,1,0)
+     * with (0,0,1) is represented by:
+     * <pre>
+     * Triangle [V1: (1.0, 0.0, 0.0)  V2: (0.0, 1.0, 0.0)  V3: (0.0, 0.0, 1.0)]
+     * </pre>
+     *
+     * @return the string representation (not null, not empty)
+     */
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + " [V1: " + get1() + "  V2: "
+                + get2() + "  V3: " + get3() + "]";
+    }
 }
 }

+ 10 - 1
jme3-core/src/main/java/com/jme3/math/FastMath.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
@@ -1135,4 +1135,13 @@ final public class FastMath {
     public static float unInterpolateLinear(float value, float min, float max) {
     public static float unInterpolateLinear(float value, float min, float max) {
         return (value - min) / (max - min);
         return (value - min) / (max - min);
     }
     }
+
+    /**
+     * Round n to a multiple of p
+     */
+    public static int toMultipleOf(int n, int p) {
+        return ((n - 1) | (p - 1)) + 1;
+    }
+
+
 }
 }

+ 17 - 1
jme3-core/src/main/java/com/jme3/math/Line.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2009-2020 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
@@ -274,4 +274,20 @@ public class Line implements Savable, Cloneable, java.io.Serializable {
             throw new AssertionError();
             throw new AssertionError();
         }
         }
     }
     }
+
+    /**
+     * Returns a string representation of the Line, which is unaffected. For
+     * example, a line with origin (1,0,0) and direction (0,1,0) is represented
+     * by:
+     * <pre>
+     * Line [Origin: (1.0, 0.0, 0.0)  Direction: (0.0, 1.0, 0.0)]
+     * </pre>
+     *
+     * @return the string representation (not null, not empty)
+     */
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + " [Origin: " + origin
+                + "  Direction: " + direction + "]";
+    }
 }
 }

+ 18 - 1
jme3-core/src/main/java/com/jme3/math/LineSegment.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2009-2020 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
@@ -720,6 +720,23 @@ public class LineSegment implements Cloneable, Savable, java.io.Serializable {
         }
         }
     }
     }
 
 
+    /**
+     * Returns a string representation of the LineSegment, which is unaffected.
+     * For example, a segment extending from (1,0,0) to (1,1,0) is represented
+     * by:
+     * <pre>
+     * LineSegment [Origin: (1.0, 0.0, 0.0)  Direction: (0.0, 1.0, 0.0)  Extent: 1.0]
+     * </pre>
+     *
+     * @return the string representation (not null, not empty)
+     */
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + " [Origin: " + origin
+                + "  Direction: " + direction + "  Extent: " + extent + "]";
+    }
+
+    /**
     /**
     /**
      * <p>Evaluates whether a given point is contained within the axis aligned bounding box
      * <p>Evaluates whether a given point is contained within the axis aligned bounding box
      * that contains this LineSegment.</p><p>This function is float error aware.</p>
      * that contains this LineSegment.</p><p>This function is float error aware.</p>

+ 19 - 1
jme3-core/src/main/java/com/jme3/math/Matrix4f.java

@@ -1802,6 +1802,9 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable
     /**
     /**
      * Determine the rotation component of this 3-D coordinate transform.
      * Determine the rotation component of this 3-D coordinate transform.
      *
      *
+     * <p>Assumes (but does not verify) that the transform consists entirely of
+     * translation, rotation, and positive scaling -- no reflection or shear.
+     *
      * @return a new rotation Quaternion
      * @return a new rotation Quaternion
      */
      */
     public Quaternion toRotationQuat() {
     public Quaternion toRotationQuat() {
@@ -1813,6 +1816,9 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable
     /**
     /**
      * Returns the rotation component of the coordinate transform.
      * Returns the rotation component of the coordinate transform.
      *
      *
+     * <p>Assumes (but does not verify) that the transform consists entirely of
+     * translation, rotation, and positive scaling -- no reflection or shear.
+     *
      * @param q storage for the result (not null, modified)
      * @param q storage for the result (not null, modified)
      * @return the rotation component (in {@code q}) for chaining
      * @return the rotation component (in {@code q}) for chaining
      */
      */
@@ -1824,7 +1830,10 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable
     /**
     /**
      * Determine the rotation component of this 3-D coordinate transform.
      * Determine the rotation component of this 3-D coordinate transform.
      *
      *
-     * @return a new rotation Matrix3f
+     * <p>If the transform includes scaling or reflection or shear, the result
+     * might not be a valid rotation matrix.
+     *
+     * @return a new Matrix3f
      */
      */
     public Matrix3f toRotationMatrix() {
     public Matrix3f toRotationMatrix() {
         return new Matrix3f(m00, m01, m02, m10, m11, m12, m20, m21, m22);
         return new Matrix3f(m00, m01, m02, m10, m11, m12, m20, m21, m22);
@@ -1833,6 +1842,9 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable
     /**
     /**
      * Determines the rotation component of the coordinate transform.
      * Determines the rotation component of the coordinate transform.
      *
      *
+     * <p>If the transform includes scaling or reflection or shear, the result
+     * might not be a valid rotation matrix.
+     *
      * @param mat storage for the result (not null, modified)
      * @param mat storage for the result (not null, modified)
      */
      */
     public void toRotationMatrix(Matrix3f mat) {
     public void toRotationMatrix(Matrix3f mat) {
@@ -1850,6 +1862,9 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable
     /**
     /**
      * Determine the scale component of this 3-D coordinate transform.
      * Determine the scale component of this 3-D coordinate transform.
      *
      *
+     * <p>All components of the result will be non-negative, even if the
+     * coordinate transform includes reflection.
+     *
      * @return a new Vector3f
      * @return a new Vector3f
      */
      */
     public Vector3f toScaleVector() {
     public Vector3f toScaleVector() {
@@ -1861,6 +1876,9 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable
     /**
     /**
      * Determines the scale component of the coordinate transform.
      * Determines the scale component of the coordinate transform.
      *
      *
+     * <p>All components of the result will be non-negative, even if the
+     * coordinate transform includes reflection.
+     *
      * @param store storage for the result (not null, modified)
      * @param store storage for the result (not null, modified)
      * @return the scale factors (in {@code store}) for chaining
      * @return the scale factors (in {@code store}) for chaining
      */
      */

+ 35 - 5
jme3-core/src/main/java/com/jme3/math/Quaternion.java

@@ -356,8 +356,10 @@ public final class Quaternion implements Savable, Cloneable, java.io.Serializabl
     }
     }
 
 
     /**
     /**
-     * Sets the quaternion from the specified rotation matrix. Does not verify
-     * that the argument is a valid rotation matrix.
+     * Sets the quaternion from the specified rotation matrix.
+     *
+     * <p>Does not verify that the argument is a valid rotation matrix.
+     * Positive scaling is compensated for, but not reflection or shear.
      *
      *
      * @param matrix the input matrix (not null, unaffected)
      * @param matrix the input matrix (not null, unaffected)
      * @return the (modified) current instance (for chaining)
      * @return the (modified) current instance (for chaining)
@@ -369,7 +371,9 @@ public final class Quaternion implements Savable, Cloneable, java.io.Serializabl
 
 
     /**
     /**
      * Sets the quaternion from a rotation matrix with the specified elements.
      * Sets the quaternion from a rotation matrix with the specified elements.
-     * Does not verify that the arguments form a valid rotation matrix.
+     *
+     * <p>Does not verify that the arguments form a valid rotation matrix.
+     * Positive scaling is compensated for, but not reflection or shear.
      *
      *
      * @param m00 the matrix element in row 0, column 0
      * @param m00 the matrix element in row 0, column 0
      * @param m01 the matrix element in row 0, column 1
      * @param m01 the matrix element in row 0, column 1
@@ -385,7 +389,7 @@ public final class Quaternion implements Savable, Cloneable, java.io.Serializabl
     public Quaternion fromRotationMatrix(float m00, float m01, float m02,
     public Quaternion fromRotationMatrix(float m00, float m01, float m02,
             float m10, float m11, float m12, float m20, float m21, float m22) {
             float m10, float m11, float m12, float m20, float m21, float m22) {
         // first normalize the forward (F), up (U) and side (S) vectors of the rotation matrix
         // first normalize the forward (F), up (U) and side (S) vectors of the rotation matrix
-        // so that the scale does not affect the rotation
+        // so that positive scaling does not affect the rotation
         float lengthSquared = m00 * m00 + m10 * m10 + m20 * m20;
         float lengthSquared = m00 * m00 + m10 * m10 + m20 * m20;
         if (lengthSquared != 1f && lengthSquared != 0f) {
         if (lengthSquared != 1f && lengthSquared != 0f) {
             lengthSquared = 1.0f / FastMath.sqrt(lengthSquared);
             lengthSquared = 1.0f / FastMath.sqrt(lengthSquared);
@@ -564,7 +568,7 @@ public final class Quaternion implements Savable, Cloneable, java.io.Serializabl
      * current instance is unaffected.
      * current instance is unaffected.
      *
      *
      * <p>Note: preserves the translation and scaling components of
      * <p>Note: preserves the translation and scaling components of
-     * {@code result}.
+     * {@code result} unless {@code result} includes reflection.
      *
      *
      * <p>Note: the result is created from a normalized version of the current
      * <p>Note: the result is created from a normalized version of the current
      * instance.
      * instance.
@@ -1013,6 +1017,9 @@ public final class Quaternion implements Savable, Cloneable, java.io.Serializabl
     /**
     /**
      * Applies the rotation represented by the argument to the current instance.
      * Applies the rotation represented by the argument to the current instance.
      *
      *
+     * <p>Does not verify that {@code matrix} is a valid rotation matrix.
+     * Positive scaling is compensated for, but not reflection or shear.
+     *
      * @param matrix the rotation matrix to apply (not null, unaffected)
      * @param matrix the rotation matrix to apply (not null, unaffected)
      */
      */
     public void apply(Matrix3f matrix) {
     public void apply(Matrix3f matrix) {
@@ -1601,4 +1608,27 @@ public final class Quaternion implements Savable, Cloneable, java.io.Serializabl
             throw new AssertionError(); // can not happen
             throw new AssertionError(); // can not happen
         }
         }
     }
     }
+
+    /**
+     * Tests whether the argument is a valid quaternion, returning false if it's
+     * null or if any component is NaN or infinite.
+     *
+     * @param quaternion the quaternion to test (unaffected)
+     * @return true if non-null and finite, otherwise false
+     */
+    public static boolean isValidQuaternion(Quaternion quaternion) {
+        if (quaternion == null) {
+            return false;
+        }
+        if (Float.isNaN(quaternion.x)
+                || Float.isNaN(quaternion.y)
+                || Float.isNaN(quaternion.z)
+                || Float.isNaN(quaternion.w)) {
+            return false;
+        }
+        return !Float.isInfinite(quaternion.x)
+                && !Float.isInfinite(quaternion.y)
+                && !Float.isInfinite(quaternion.z)
+                && !Float.isInfinite(quaternion.w);
+    }
 }
 }

+ 16 - 1
jme3-core/src/main/java/com/jme3/math/Rectangle.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
@@ -253,4 +253,19 @@ public final class Rectangle implements Savable, Cloneable, java.io.Serializable
             throw new AssertionError();
             throw new AssertionError();
         }
         }
     }
     }
+
+    /**
+     * Returns a string representation of the Recatangle, which is unaffected.
+     * For example, a rectangle with vertices at (1,0,0), (2,0,0), (1,2,0), and
+     * (2,2,0) is represented by:
+     * <pre>
+     * Rectangle [A: (1.0, 0.0, 0.0)  B: (2.0, 0.0, 0.0)  C: (1.0, 2.0, 0.0)]
+     * </pre>
+     *
+     * @return the string representation (not null, not empty)
+     */
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + " [A: " + a + "  B: " + b + "  C: " + c + "]";
+    }
 }
 }

+ 1 - 1
jme3-core/src/main/java/com/jme3/math/Ring.java

@@ -47,7 +47,7 @@ public final class Ring implements Savable, Cloneable, java.io.Serializable {
 
 
     private Vector3f center, up;
     private Vector3f center, up;
     private float innerRadius, outerRadius;
     private float innerRadius, outerRadius;
-    private transient static Vector3f b1 = new Vector3f(), b2 = new Vector3f();
+    private static transient Vector3f b1 = new Vector3f(), b2 = new Vector3f();
 
 
     /**
     /**
      * Constructor creates a new <code>Ring</code> lying on the XZ plane,
      * Constructor creates a new <code>Ring</code> lying on the XZ plane,

+ 14 - 1
jme3-core/src/main/java/com/jme3/math/Transform.java

@@ -121,6 +121,7 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
      * @return the (modified) current instance (for chaining)
      * @return the (modified) current instance (for chaining)
      */
      */
     public Transform setRotation(Quaternion rot) {
     public Transform setRotation(Quaternion rot) {
+        assert Quaternion.isValidQuaternion(rot) : "Invalid rotation " + rot;
         this.rot.set(rot);
         this.rot.set(rot);
         return this;
         return this;
     }
     }
@@ -132,6 +133,7 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
      * @return the (modified) current instance (for chaining)
      * @return the (modified) current instance (for chaining)
      */
      */
     public Transform setTranslation(Vector3f trans) {
     public Transform setTranslation(Vector3f trans) {
+        assert Vector3f.isValidVector(trans) : "Invalid translation " + trans;
         this.translation.set(trans);
         this.translation.set(trans);
         return this;
         return this;
     }
     }
@@ -152,6 +154,7 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
      * @return the (modified) current instance (for chaining)
      * @return the (modified) current instance (for chaining)
      */
      */
     public Transform setScale(Vector3f scale) {
     public Transform setScale(Vector3f scale) {
+        assert Vector3f.isValidVector(scale) : "Invalid scale " + scale;
         this.scale.set(scale);
         this.scale.set(scale);
         return this;
         return this;
     }
     }
@@ -163,6 +166,7 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
      * @return the (modified) current instance (for chaining)
      * @return the (modified) current instance (for chaining)
      */
      */
     public Transform setScale(float scale) {
     public Transform setScale(float scale) {
+        assert Float.isFinite(scale) : "Invalid scale " + scale;
         this.scale.set(scale, scale, scale);
         this.scale.set(scale, scale, scale);
         return this;
         return this;
     }
     }
@@ -286,6 +290,7 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
      * @return the (modified) current instance (for chaining)
      * @return the (modified) current instance (for chaining)
      */
      */
     public Transform setTranslation(float x, float y, float z) {
     public Transform setTranslation(float x, float y, float z) {
+        assert Float.isFinite(x) && Float.isFinite(y) && Float.isFinite(z) : "Invalid translation " + x + ", " + y + ", " + z;
         translation.set(x, y, z);
         translation.set(x, y, z);
         return this;
         return this;
     }
     }
@@ -299,6 +304,7 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
      * @return the (modified) current instance (for chaining)
      * @return the (modified) current instance (for chaining)
      */
      */
     public Transform setScale(float x, float y, float z) {
     public Transform setScale(float x, float y, float z) {
+        assert Float.isFinite(x) && Float.isFinite(y) && Float.isFinite(z) : "Invalid scale " + x + ", " + y + ", " + z;
         scale.set(x, y, z);
         scale.set(x, y, z);
         return this;
         return this;
     }
     }
@@ -389,10 +395,13 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
     }
     }
 
 
     /**
     /**
-     * Sets the current instance from a transform matrix. Any shear in the
+     * Sets the current instance from a transform matrix. Any reflection or shear in the
      * matrix is lost -- in other words, it may not be possible to recreate the
      * matrix is lost -- in other words, it may not be possible to recreate the
      * original matrix from the result.
      * original matrix from the result.
      *
      *
+     * <p>After this method is invoked, all components of {@code scale} will be
+     * non-negative, even if {@code mat} includes reflection.
+     *
      * @param mat the input matrix (not null, unaffected)
      * @param mat the input matrix (not null, unaffected)
      */
      */
     public void fromTransformMatrix(Matrix4f mat) {
     public void fromTransformMatrix(Matrix4f mat) {
@@ -406,6 +415,10 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
     /**
     /**
      * Returns the inverse. The current instance is unaffected.
      * Returns the inverse. The current instance is unaffected.
      *
      *
+     * <p>Assumes (but does not verify) that the scale factors are all positive.
+     * If any component of {@code scale} is negative or zero, the result is
+     * undefined.
+     *
      * @return a new Transform
      * @return a new Transform
      */
      */
     public Transform invert() {
     public Transform invert() {

+ 24 - 0
jme3-core/src/main/java/com/jme3/math/Vector2f.java

@@ -54,10 +54,34 @@ public final class Vector2f implements Savable, Cloneable, java.io.Serializable
      * Shared instance of the all-zero vector (0,0). Do not modify!
      * Shared instance of the all-zero vector (0,0). Do not modify!
      */
      */
     public static final Vector2f ZERO = new Vector2f(0f, 0f);
     public static final Vector2f ZERO = new Vector2f(0f, 0f);
+    /**
+     * Shared instance of the all-NaN vector (NaN,NaN). Do not modify!
+     */
+    public static final Vector2f NAN = new Vector2f(Float.NaN, Float.NaN);
+    /**
+     * Shared instance of the +X direction (1,0). Do not modify!
+     */
+    public static final Vector2f UNIT_X = new Vector2f(1, 0);
+    /**
+     * Shared instance of the +Y direction (0,1). Do not modify!
+     */
+    public static final Vector2f UNIT_Y = new Vector2f(0, 1);
     /**
     /**
      * Shared instance of the all-ones vector (1,1). Do not modify!
      * Shared instance of the all-ones vector (1,1). Do not modify!
      */
      */
     public static final Vector2f UNIT_XY = new Vector2f(1f, 1f);
     public static final Vector2f UNIT_XY = new Vector2f(1f, 1f);
+    /**
+     * Shared instance of the all-plus-infinity vector (+Inf,+Inf). Do not modify!
+     */
+    public static final Vector2f POSITIVE_INFINITY = new Vector2f(
+            Float.POSITIVE_INFINITY,
+            Float.POSITIVE_INFINITY);
+    /**
+     * Shared instance of the all-negative-infinity vector (-Inf,-Inf). Do not modify!
+     */
+    public static final Vector2f NEGATIVE_INFINITY = new Vector2f(
+            Float.NEGATIVE_INFINITY,
+            Float.NEGATIVE_INFINITY);
     /**
     /**
      * The first (X) component.
      * The first (X) component.
      */
      */

+ 8 - 8
jme3-core/src/main/java/com/jme3/math/Vector3f.java

@@ -52,32 +52,32 @@ public final class Vector3f implements Savable, Cloneable, java.io.Serializable
     /**
     /**
      * Shared instance of the all-zero vector (0,0,0). Do not modify!
      * Shared instance of the all-zero vector (0,0,0). Do not modify!
      */
      */
-    public final static Vector3f ZERO = new Vector3f(0, 0, 0);
+    public static final Vector3f ZERO = new Vector3f(0, 0, 0);
     /**
     /**
      * Shared instance of the all-NaN vector (NaN,NaN,NaN). Do not modify!
      * Shared instance of the all-NaN vector (NaN,NaN,NaN). Do not modify!
      */
      */
-    public final static Vector3f NAN = new Vector3f(Float.NaN, Float.NaN, Float.NaN);
+    public static final Vector3f NAN = new Vector3f(Float.NaN, Float.NaN, Float.NaN);
     /**
     /**
      * Shared instance of the +X direction (1,0,0). Do not modify!
      * Shared instance of the +X direction (1,0,0). Do not modify!
      */
      */
-    public final static Vector3f UNIT_X = new Vector3f(1, 0, 0);
+    public static final Vector3f UNIT_X = new Vector3f(1, 0, 0);
     /**
     /**
      * Shared instance of the +Y direction (0,1,0). Do not modify!
      * Shared instance of the +Y direction (0,1,0). Do not modify!
      */
      */
-    public final static Vector3f UNIT_Y = new Vector3f(0, 1, 0);
+    public static final Vector3f UNIT_Y = new Vector3f(0, 1, 0);
     /**
     /**
      * Shared instance of the +Z direction (0,0,1). Do not modify!
      * Shared instance of the +Z direction (0,0,1). Do not modify!
      */
      */
-    public final static Vector3f UNIT_Z = new Vector3f(0, 0, 1);
+    public static final Vector3f UNIT_Z = new Vector3f(0, 0, 1);
     /**
     /**
      * Shared instance of the all-ones vector (1,1,1). Do not modify!
      * Shared instance of the all-ones vector (1,1,1). Do not modify!
      */
      */
-    public final static Vector3f UNIT_XYZ = new Vector3f(1, 1, 1);
+    public static final Vector3f UNIT_XYZ = new Vector3f(1, 1, 1);
     /**
     /**
      * Shared instance of the all-plus-infinity vector (+Inf,+Inf,+Inf). Do not
      * Shared instance of the all-plus-infinity vector (+Inf,+Inf,+Inf). Do not
      * modify!
      * modify!
      */
      */
-    public final static Vector3f POSITIVE_INFINITY = new Vector3f(
+    public static final Vector3f POSITIVE_INFINITY = new Vector3f(
             Float.POSITIVE_INFINITY,
             Float.POSITIVE_INFINITY,
             Float.POSITIVE_INFINITY,
             Float.POSITIVE_INFINITY,
             Float.POSITIVE_INFINITY);
             Float.POSITIVE_INFINITY);
@@ -85,7 +85,7 @@ public final class Vector3f implements Savable, Cloneable, java.io.Serializable
      * Shared instance of the all-negative-infinity vector (-Inf,-Inf,-Inf). Do
      * Shared instance of the all-negative-infinity vector (-Inf,-Inf,-Inf). Do
      * not modify!
      * not modify!
      */
      */
-    public final static Vector3f NEGATIVE_INFINITY = new Vector3f(
+    public static final Vector3f NEGATIVE_INFINITY = new Vector3f(
             Float.NEGATIVE_INFINITY,
             Float.NEGATIVE_INFINITY,
             Float.NEGATIVE_INFINITY,
             Float.NEGATIVE_INFINITY,
             Float.NEGATIVE_INFINITY);
             Float.NEGATIVE_INFINITY);

+ 9 - 9
jme3-core/src/main/java/com/jme3/math/Vector4f.java

@@ -51,36 +51,36 @@ public final class Vector4f implements Savable, Cloneable, java.io.Serializable
     /**
     /**
      * shared instance of the all-zero vector (0,0,0,0) - Do not modify!
      * shared instance of the all-zero vector (0,0,0,0) - Do not modify!
      */
      */
-    public final static Vector4f ZERO = new Vector4f(0, 0, 0, 0);
+    public static final Vector4f ZERO = new Vector4f(0, 0, 0, 0);
     /**
     /**
      * shared instance of the all-NaN vector (NaN,NaN,NaN,NaN) - Do not modify!
      * shared instance of the all-NaN vector (NaN,NaN,NaN,NaN) - Do not modify!
      */
      */
-    public final static Vector4f NAN = new Vector4f(Float.NaN, Float.NaN, Float.NaN, Float.NaN);
+    public static final Vector4f NAN = new Vector4f(Float.NaN, Float.NaN, Float.NaN, Float.NaN);
     /**
     /**
      * shared instance of the +X direction (1,0,0,0) - Do not modify!
      * shared instance of the +X direction (1,0,0,0) - Do not modify!
      */
      */
-    public final static Vector4f UNIT_X = new Vector4f(1, 0, 0, 0);
+    public static final Vector4f UNIT_X = new Vector4f(1, 0, 0, 0);
     /**
     /**
      * shared instance of the +Y direction (0,1,0,0) - Do not modify!
      * shared instance of the +Y direction (0,1,0,0) - Do not modify!
      */
      */
-    public final static Vector4f UNIT_Y = new Vector4f(0, 1, 0, 0);
+    public static final Vector4f UNIT_Y = new Vector4f(0, 1, 0, 0);
     /**
     /**
      * shared instance of the +Z direction (0,0,1,0) - Do not modify!
      * shared instance of the +Z direction (0,0,1,0) - Do not modify!
      */
      */
-    public final static Vector4f UNIT_Z = new Vector4f(0, 0, 1, 0);
+    public static final Vector4f UNIT_Z = new Vector4f(0, 0, 1, 0);
     /**
     /**
      * shared instance of the +W direction (0,0,0,1) - Do not modify!
      * shared instance of the +W direction (0,0,0,1) - Do not modify!
      */
      */
-    public final static Vector4f UNIT_W = new Vector4f(0, 0, 0, 1);
+    public static final Vector4f UNIT_W = new Vector4f(0, 0, 0, 1);
     /**
     /**
      * shared instance of the all-ones vector (1,1,1,1) - Do not modify!
      * shared instance of the all-ones vector (1,1,1,1) - Do not modify!
      */
      */
-    public final static Vector4f UNIT_XYZW = new Vector4f(1, 1, 1, 1);
+    public static final Vector4f UNIT_XYZW = new Vector4f(1, 1, 1, 1);
     /**
     /**
      * shared instance of the all-plus-infinity vector (+Inf,+Inf,+Inf,+Inf)
      * shared instance of the all-plus-infinity vector (+Inf,+Inf,+Inf,+Inf)
      * - Do not modify!
      * - Do not modify!
      */
      */
-    public final static Vector4f POSITIVE_INFINITY = new Vector4f(
+    public static final Vector4f POSITIVE_INFINITY = new Vector4f(
             Float.POSITIVE_INFINITY,
             Float.POSITIVE_INFINITY,
             Float.POSITIVE_INFINITY,
             Float.POSITIVE_INFINITY,
             Float.POSITIVE_INFINITY,
             Float.POSITIVE_INFINITY,
@@ -89,7 +89,7 @@ public final class Vector4f implements Savable, Cloneable, java.io.Serializable
      * shared instance of the all-negative-infinity vector (-Inf,-Inf,-Inf,-Inf)
      * shared instance of the all-negative-infinity vector (-Inf,-Inf,-Inf,-Inf)
      * - Do not modify!
      * - Do not modify!
      */
      */
-    public final static Vector4f NEGATIVE_INFINITY = new Vector4f(
+    public static final Vector4f NEGATIVE_INFINITY = new Vector4f(
             Float.NEGATIVE_INFINITY,
             Float.NEGATIVE_INFINITY,
             Float.NEGATIVE_INFINITY,
             Float.NEGATIVE_INFINITY,
             Float.NEGATIVE_INFINITY,
             Float.NEGATIVE_INFINITY,

+ 2 - 2
jme3-core/src/main/java/com/jme3/opencl/OpenCLObjectManager.java

@@ -53,8 +53,8 @@ public class OpenCLObjectManager {
         return INSTANCE;
         return INSTANCE;
     }
     }
     
     
-    final private ReferenceQueue<Object> refQueue = new ReferenceQueue<>();
-    final private HashSet<OpenCLObjectRef> activeObjects = new HashSet<>();
+    private final ReferenceQueue<Object> refQueue = new ReferenceQueue<>();
+    private final HashSet<OpenCLObjectRef> activeObjects = new HashSet<>();
     
     
     private static class OpenCLObjectRef extends PhantomReference<Object> {
     private static class OpenCLObjectRef extends PhantomReference<Object> {
         
         

+ 1 - 1
jme3-core/src/main/java/com/jme3/post/HDRRenderer.java

@@ -95,7 +95,7 @@ public class HDRRenderer implements SceneProcessor {
 
 
     private MinFilter fbMinFilter = MinFilter.BilinearNoMipMaps;
     private MinFilter fbMinFilter = MinFilter.BilinearNoMipMaps;
     private MagFilter fbMagFilter = MagFilter.Bilinear;
     private MagFilter fbMagFilter = MagFilter.Bilinear;
-    final private AssetManager manager;
+    private final AssetManager manager;
 
 
     private boolean enabled = true;
     private boolean enabled = true;
 
 

+ 2 - 2
jme3-core/src/main/java/com/jme3/post/PreDepthProcessor.java

@@ -49,8 +49,8 @@ public class PreDepthProcessor implements SceneProcessor {
 
 
     private RenderManager rm;
     private RenderManager rm;
     private ViewPort vp;
     private ViewPort vp;
-    final private Material preDepth;
-    final private RenderState forcedRS;
+    private final Material preDepth;
+    private final RenderState forcedRS;
 
 
     public PreDepthProcessor(AssetManager assetManager){
     public PreDepthProcessor(AssetManager assetManager){
         preDepth = new Material(assetManager, "Common/MatDefs/Shadow/PreShadow.j3md");
         preDepth = new Material(assetManager, "Common/MatDefs/Shadow/PreShadow.j3md");

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно