Browse Source

Merge remote-tracking branch 'origin/master'

iwgeric 9 years ago
parent
commit
e837336116
100 changed files with 5238 additions and 4073 deletions
  1. 13 129
      .gitignore
  2. 6 0
      common.gradle
  3. 0 136
      jme3-android/src/main/java/com/jme3/asset/AndroidImageInfo.java
  4. 0 533
      jme3-android/src/main/java/com/jme3/audio/android/AndroidMediaPlayerAudioRenderer.java
  5. 5 0
      jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java
  6. 0 591
      jme3-android/src/main/java/com/jme3/renderer/android/TextureUtil.java
  7. 0 23
      jme3-android/src/main/java/com/jme3/texture/plugins/AndroidImageLoader.java
  8. 1 1
      jme3-android/src/main/java/jme3test/android/SimpleTexturedTest.java
  9. 15 6
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java
  10. 21 16
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java
  11. 33 1
      jme3-bullet-native/build.gradle
  12. 21 2
      jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java
  13. 0 42
      jme3-core/src/main/java/com/jme3/animation/SpatialAnimation.java
  14. 2 1
      jme3-core/src/main/java/com/jme3/app/SimpleApplication.java
  15. 0 9
      jme3-core/src/main/java/com/jme3/asset/AssetManager.java
  16. 0 33
      jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java
  17. 0 36
      jme3-core/src/main/java/com/jme3/asset/TextureKey.java
  18. 104 0
      jme3-core/src/main/java/com/jme3/audio/AudioListenerState.java
  19. 38 6
      jme3-core/src/main/java/com/jme3/audio/AudioNode.java
  20. 3 3
      jme3-core/src/main/java/com/jme3/audio/Listener.java
  21. 5 4
      jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java
  22. 2 1
      jme3-core/src/main/java/com/jme3/cinematic/MotionPath.java
  23. 3 2
      jme3-core/src/main/java/com/jme3/effect/ParticleMesh.java
  24. 3 3
      jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java
  25. 0 4
      jme3-core/src/main/java/com/jme3/material/MatParam.java
  26. 151 0
      jme3-core/src/main/java/com/jme3/material/MatParamOverride.java
  27. 3 25
      jme3-core/src/main/java/com/jme3/material/MatParamTexture.java
  28. 186 437
      jme3-core/src/main/java/com/jme3/material/Material.java
  29. 16 31
      jme3-core/src/main/java/com/jme3/material/MaterialDef.java
  30. 252 195
      jme3-core/src/main/java/com/jme3/material/RenderState.java
  31. 109 144
      jme3-core/src/main/java/com/jme3/material/Technique.java
  32. 228 77
      jme3-core/src/main/java/com/jme3/material/TechniqueDef.java
  33. 97 0
      jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java
  34. 178 0
      jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java
  35. 218 0
      jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java
  36. 182 0
      jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java
  37. 97 0
      jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java
  38. 6 6
      jme3-core/src/main/java/com/jme3/renderer/Camera.java
  39. 8 25
      jme3-core/src/main/java/com/jme3/renderer/Limits.java
  40. 11 15
      jme3-core/src/main/java/com/jme3/renderer/RenderContext.java
  41. 92 35
      jme3-core/src/main/java/com/jme3/renderer/RenderManager.java
  42. 23 1
      jme3-core/src/main/java/com/jme3/renderer/Renderer.java
  43. 144 136
      jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java
  44. 5 0
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java
  45. 5 0
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java
  46. 92 21
      jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java
  47. 10 0
      jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java
  48. 3 2
      jme3-core/src/main/java/com/jme3/renderer/queue/OpaqueComparator.java
  49. 13 12
      jme3-core/src/main/java/com/jme3/scene/Mesh.java
  50. 18 26
      jme3-core/src/main/java/com/jme3/scene/Node.java
  51. 125 35
      jme3-core/src/main/java/com/jme3/scene/Spatial.java
  52. 2 2
      jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java
  53. 0 8
      jme3-core/src/main/java/com/jme3/scene/debug/WireBox.java
  54. 179 286
      jme3-core/src/main/java/com/jme3/shader/DefineList.java
  55. 56 23
      jme3-core/src/main/java/com/jme3/shader/Shader.java
  56. 30 17
      jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java
  57. 0 201
      jme3-core/src/main/java/com/jme3/shader/ShaderKey.java
  58. 82 33
      jme3-core/src/main/java/com/jme3/shader/Uniform.java
  59. 3 2
      jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java
  60. 2 7
      jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java
  61. 2 7
      jme3-core/src/main/java/com/jme3/shadow/PssmShadowRenderer.java
  62. 4 3
      jme3-core/src/main/java/com/jme3/system/NullContext.java
  63. 17 3
      jme3-core/src/main/java/com/jme3/system/NullRenderer.java
  64. 1 1
      jme3-core/src/main/java/com/jme3/texture/image/LastTextureState.java
  65. 2 2
      jme3-core/src/main/java/com/jme3/util/clone/Cloner.java
  66. 7 14
      jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md
  67. 2 2
      jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md
  68. 1 2
      jme3-core/src/main/resources/com/jme3/asset/General.cfg
  69. 86 22
      jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java
  70. 5 4
      jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java
  71. 1 2
      jme3-core/src/plugins/java/com/jme3/scene/plugins/MTLLoader.java
  72. 18 10
      jme3-core/src/test/java/com/jme3/asset/LoadShaderSourceTest.java
  73. 600 0
      jme3-core/src/test/java/com/jme3/material/MaterialMatParamOverrideTest.java
  74. 171 0
      jme3-core/src/test/java/com/jme3/material/MaterialTest.java
  75. 29 1
      jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java
  76. 100 0
      jme3-core/src/test/java/com/jme3/material/plugins/LoadJ3mdTest.java
  77. 38 0
      jme3-core/src/test/java/com/jme3/math/FastMathTest.java
  78. 343 0
      jme3-core/src/test/java/com/jme3/renderer/OpaqueComparatorTest.java
  79. 173 0
      jme3-core/src/test/java/com/jme3/scene/MPOTestUtils.java
  80. 278 0
      jme3-core/src/test/java/com/jme3/scene/SceneMatParamOverrideTest.java
  81. 300 0
      jme3-core/src/test/java/com/jme3/shader/DefineListTest.java
  82. 37 35
      jme3-core/src/test/java/com/jme3/system/MockJmeSystemDelegate.java
  83. 21 21
      jme3-core/src/test/java/com/jme3/system/TestUtil.java
  84. 6 0
      jme3-core/src/test/resources/no-default-technique.j3md
  85. 8 0
      jme3-core/src/test/resources/no-shader-specified.j3md
  86. 10 0
      jme3-core/src/test/resources/same-name-technique.j3md
  87. 34 0
      jme3-core/src/test/resources/testMatDef.j3md
  88. 0 1
      jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java
  89. 12 12
      jme3-core/src/tools/java/jme3tools/shadercheck/ShaderCheck.java
  90. 18 24
      jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java
  91. 0 359
      jme3-desktop/src/main/java/com/jme3/system/Natives.java
  92. 3 1
      jme3-examples/build.gradle
  93. 2 1
      jme3-examples/src/main/java/jme3test/animation/TestCameraMotionPath.java
  94. 2 1
      jme3-examples/src/main/java/jme3test/animation/TestCinematic.java
  95. 2 1
      jme3-examples/src/main/java/jme3test/animation/TestMotionPath.java
  96. 1 1
      jme3-examples/src/main/java/jme3test/app/TestAppStateLifeCycle.java
  97. 1 1
      jme3-examples/src/main/java/jme3test/app/TestBareBonesApp.java
  98. 0 156
      jme3-examples/src/main/java/jme3test/app/TestNativeLoader.java
  99. 1 1
      jme3-examples/src/main/java/jme3test/app/TestReleaseDirectMemory.java
  100. 1 1
      jme3-examples/src/main/java/jme3test/audio/TestAmbient.java

+ 13 - 129
.gitignore

@@ -1,34 +1,22 @@
+**/nbproject/private/
 /.gradle/
-/.nb-gradle/private/
-/.nb-gradle/profiles/private/
+/.nb-gradle/
 /.idea/
 /dist/
 /build/
+/bin/
 /netbeans/
-/sdk/jdks/local/
-/jme3-core/build/
+/.classpath
+/.project
+/.settings
+*.dll
+*.so
+*.jnilib
+*.dylib
+*.iml
+.DS_Store
 /jme3-core/src/main/resources/com/jme3/system/version.properties
-/jme3-plugins/build/
-/jme3-desktop/build/
-/jme3-android-native/build/
-/jme3-android/build/
-/jme3-android-examples/build/
-/jme3-blender/build/
-/jme3-effects/build/
-/jme3-bullet/build/
-/jme3-terrain/build/
-/jme3-bullet-native/build/
-/jme3-bullet-native-android/build/
-/jme3-jogg/build/
-/jme3-jbullet/build/
-/jme3-lwjgl/build/
-/jme3-networking/build/
-/jme3-niftygui/build/
-/jme3-testdata/build/
-/jme3-examples/build/
-/jme3-jogl/build/
-/jme3-ios/build/
-/jme3-gl-autogen/build/
+/jme3-*/build/
 /jme3-bullet-native/bullet.zip
 /jme3-bullet-native/bullet-2.82-r2704/
 /jme3-android-native/openal-soft/
@@ -38,113 +26,9 @@
 /jme3-android-native/src/native/jme_decode/com_jme3_audio_plugins_NativeVorbisFile.h
 /jme3-android-native/src/native/jme_decode/com_jme3_texture_plugins_AndroidNativeImageLoader.h
 /jme3-android-native/stb_image.h
-/sdk/jme3-tests-template/src/com/jme3/gde/templates/tests/JmeTestsProject.zip
-/sdk/jme3-tests-template/src/com/jme3/gde/templates/tests/JME3TestsAndroidProject.zip
-/sdk/jme3-project-testdata/release/
-/sdk/JME3TestsTemplateAndroid/src/jme3test/
-/sdk/JME3TestsTemplate/src/jme3test/
-/sdk/build/
-/sdk/jme3-core-baselibs/release/
-/sdk/jme3-core-libraries/release/
-/sdk/jme3-project-baselibs/release/
-/sdk/jme3-project-libraries/release/
-/sdk/jme3-codepalette/build/
-/sdk/jme3-core-libraries/build/
-/sdk/jme3-code-check/build/
-/sdk/jme3-core-baselibs/build/
-/sdk/jme3-documentation/build/
-/sdk/jme3-core-updatecenters/build/
-/sdk/jme3-project-testdata/build/
-/sdk/jme3-project-libraries/build/
-/sdk/jme3-project-baselibs/build/
-/sdk/jme3-templates/build/
-/sdk/jme3-texture-editor/build/
-/sdk/jme3-tests-template/build/
-/sdk/jme3-upgrader/build/
-/sdk/jme3-core/build/
-/sdk/jme3-obfuscate/build/
-/sdk/jme3-gui/build/
-/sdk/jme3-cinematics/build/
-/sdk/jme3-terrain-editor/build/
-/sdk/jme3-lwjgl-applet/build/
-/sdk/jme3-blender/build/
-/sdk/jme3-navmesh-gen/build/
-/sdk/jme3-angelfont/build/
-/sdk/jme3-materialeditor/build/
-/sdk/jme3-android/build/
-/sdk/jme3-desktop-executables/build/
-/sdk/jme3-ogrexml/build/
-/sdk/jme3-ogretools/build/
-/sdk/jme3-scenecomposer/build/
-/sdk/jme3-assetpack-support/build/
-/sdk/jme3-model-importer/build/
-/sdk/jme3-wavefront/build/
-/sdk/jme3-vehicle-creator/build/
-/sdk/jme3-welcome-screen/build/
-/sdk/jme3-glsl-support/build/
-/sdk/jme3-dark-laf/build/
-/sdk/nbproject/private/
-/sdk/jme3-scenecomposer/nbproject/private/
-/sdk/jme3-core/nbproject/private/
-/sdk/jme3-core-baselibs/nbproject/private/
-/sdk/jme3-welcome-screen/nbproject/private/
-/sdk/jme3-lwjgl-applet/nbproject/private/
-/sdk/jme3-ogrexml/nbproject/private/
-/sdk/jme3-upgrader/nbproject/private/
-/sdk/jme3-obfuscate/nbproject/private/
-/sdk/jme3-navmesh-gen/nbproject/private/
-/sdk/jme3-wavefront/nbproject/private/
-/sdk/jme3-project-libraries/nbproject/private/
-/sdk/jme3-ogretools/nbproject/private/
-/sdk/jme3-assetpack-support/nbproject/private/
-/sdk/jme3-cinematics/nbproject/private/
-/sdk/jme3-model-importer/nbproject/private/
-/sdk/jme3-desktop-executables/nbproject/private/
-/sdk/jme3-glsl-support/nbproject/private/
-/sdk/jme3-android/nbproject/private/
-/sdk/jme3-angelfont/nbproject/private/
-/sdk/jme3-codepalette/nbproject/private/
-/sdk/jme3-documentation/nbproject/private/
-/sdk/jme3-vehicle-creator/nbproject/private/
-/sdk/jme3-code-check/nbproject/private/
-/sdk/jme3-blender/nbproject/private/
-/sdk/jme3-core-libraries/nbproject/private/
-/sdk/jme3-core-updatecenters/nbproject/private/
-/sdk/jme3-gui/nbproject/private/
-/sdk/jme3-materialeditor/nbproject/private/
-/sdk/jme3-project-baselibs/nbproject/private/
-/sdk/jme3-project-testdata/nbproject/private/
-/sdk/jme3-templates/nbproject/private/
-/sdk/jme3-terrain-editor/nbproject/private/
-/sdk/jme3-tests-template/nbproject/private/
-/sdk/jme3-texture-editor/nbproject/private/
-/sdk/JME3TestsTemplate/nbproject/private/
-/sdk/JME3TestsTemplateAndroid/nbproject/private/
-/bin
-/.classpath
-/.project
-/.settings
-*.dll
-*.so
-*.jnilib
-*.dylib
-*.iml
-.DS_Store
-/sdk/dist/
 !/jme3-bullet-native/libs/native/windows/x86_64/bulletjme.dll
 !/jme3-bullet-native/libs/native/windows/x86/bulletjme.dll
 !/jme3-bullet-native/libs/native/osx/x86/libbulletjme.dylib
 !/jme3-bullet-native/libs/native/osx/x86_64/libbulletjme.dylib
 !/jme3-bullet-native/libs/native/linux/x86/libbulletjme.so
 !/jme3-bullet-native/libs/native/linux/x86_64/libbulletjme.so
-/.nb-gradle/
-/sdk/ant-jme/nbproject/private/
-/sdk/nbi/stub/ext/engine/nbproject/private/
-/sdk/nbi/stub/ext/components/products/jdk/nbproject/private/
-/sdk/nbi/stub/ext/components/products/blender/nbproject/private/
-/sdk/nbi/stub/ext/components/products/helloworld/nbproject/private/
-/sdk/BasicGameTemplate/nbproject/private/
-/sdk/nbi/stub/ext/components/products/jdk/build/
-/sdk/nbi/stub/ext/components/products/jdk/dist/
-/sdk/jme3-dark-laf/nbproject/private/
-jme3-lwjgl3/build/

+ 6 - 0
common.gradle

@@ -51,6 +51,12 @@ javadoc {
     }
 }
 
+test {
+    testLogging {
+        exceptionFormat = 'full'
+    }
+}
+
 task sourcesJar(type: Jar, dependsOn: classes, description: 'Creates a jar from the source files.') {
     classifier = 'sources'
     from sourceSets*.allSource

+ 0 - 136
jme3-android/src/main/java/com/jme3/asset/AndroidImageInfo.java

@@ -1,136 +0,0 @@
-package com.jme3.asset;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Matrix;
-import com.jme3.math.ColorRGBA;
-import com.jme3.texture.Image;
-import com.jme3.texture.Image.Format;
-import com.jme3.texture.image.ImageRaster;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
-  * <code>AndroidImageInfo</code> is set in a jME3 image via the {@link Image#setEfficentData(java.lang.Object) }
-  * method to retrieve a {@link Bitmap} when it is needed by the renderer. 
-  * User code may extend <code>AndroidImageInfo</code> and provide their own implementation of the 
-  * {@link AndroidImageInfo#loadBitmap()} method to acquire a bitmap by their own means.
-  *
-  * @author Kirill Vainer
-  */
-@Deprecated
-public class AndroidImageInfo extends ImageRaster {
-    
-    private static final Logger logger = Logger.getLogger(AndroidImageInfo.class.getName());
-    
-    protected AssetInfo assetInfo;
-    protected Bitmap bitmap;
-    protected Format format;
-
-    public AndroidImageInfo(AssetInfo assetInfo) {
-        this.assetInfo = assetInfo;
-    }
-    
-    public Bitmap getBitmap(){
-        if (bitmap == null || bitmap.isRecycled()){
-            try {
-                loadBitmap();
-            } catch (IOException ex) {
-                // If called first inside AssetManager, the error will propagate
-                // correctly. Assuming that if the first calls succeeds
-                // then subsequent calls will as well.
-                throw new AssetLoadException("Failed to load image " + assetInfo.getKey(), ex);
-            }
-        }
-        return bitmap;
-    }
-    
-    public void notifyBitmapUploaded() {
-        // Default function is to recycle the bitmap.
-        if (bitmap != null && !bitmap.isRecycled()) {
-            bitmap.recycle();
-            bitmap = null;
-            logger.log(Level.FINE, "Bitmap was deleted. ");
-        }
-    }
-    
-    public Format getFormat(){
-        return format;
-    }
-    
-    @Override
-    public int getWidth() {
-        return getBitmap().getWidth();
-    }
-
-    @Override
-    public int getHeight() {
-        return getBitmap().getHeight();
-    }
-    
-    @Override
-    public void setPixel(int x, int y, ColorRGBA color) {
-        getBitmap().setPixel(x, y, color.asIntARGB());
-    }
-
-    @Override
-    public ColorRGBA getPixel(int x, int y, ColorRGBA store) {
-        if (store == null) {
-            store = new ColorRGBA();
-        }
-        store.fromIntARGB(getBitmap().getPixel(x, y));
-        return store;
-    }
-    
-    /**
-     * Loads the bitmap directly from the asset info, possibly updating
-     * or creating the image object.
-     */
-    protected void loadBitmap() throws IOException{
-        InputStream in = null;
-        try {
-            in = assetInfo.openStream();
-            bitmap = BitmapFactory.decodeStream(in);
-            if (bitmap == null) {
-                throw new IOException("Failed to load image: " + assetInfo.getKey().getName());
-            }
-        } finally {
-            if (in != null) {
-                in.close();
-            }
-        }
-
-        switch (bitmap.getConfig()) {
-            case ALPHA_8:
-                format = Image.Format.Alpha8;
-                break;
-            case ARGB_8888:
-                format = Image.Format.RGBA8;
-                break;
-            case RGB_565:
-                format = Image.Format.RGB565;
-                break;
-            default:
-                // This should still work as long
-                // as renderer doesn't check format
-                // but just loads bitmap directly.
-                format = null;
-        }
-
-        TextureKey texKey = (TextureKey) assetInfo.getKey();
-        if (texKey.isFlipY()) {
-            // Flip the image, then delete the old one.
-            Matrix flipMat = new Matrix();
-            flipMat.preScale(1.0f, -1.0f);
-            Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), flipMat, false);
-            bitmap.recycle();
-            bitmap = newBitmap;
-
-            if (bitmap == null) {
-                throw new IOException("Failed to flip image: " + texKey);
-            }
-        }  
-    }
-}

+ 0 - 533
jme3-android/src/main/java/com/jme3/audio/android/AndroidMediaPlayerAudioRenderer.java

@@ -1,533 +0,0 @@
-/*
- * Copyright (c) 2009-2012 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.audio.android;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.AssetManager;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.media.SoundPool;
-import com.jme3.asset.AssetKey;
-import com.jme3.audio.*;
-import com.jme3.audio.AudioSource.Status;
-import com.jme3.audio.openal.ALAudioRenderer;
-import com.jme3.math.FastMath;
-import com.jme3.math.Vector3f;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * This class is the android implementation for {@link AudioRenderer}
- *
- * @author larynx
- * @author plan_rich
- * 
- * @deprecated No longer supported due to too many limitations. 
- * Please use the generic {@link ALAudioRenderer} instead.
- */
-@Deprecated
-public class AndroidMediaPlayerAudioRenderer implements AudioRenderer,
-        SoundPool.OnLoadCompleteListener, MediaPlayer.OnCompletionListener {
-
-    private static final Logger logger = Logger.getLogger(AndroidMediaPlayerAudioRenderer.class.getName());
-    private final static int MAX_NUM_CHANNELS = 16;
-    private final HashMap<AudioSource, MediaPlayer> musicPlaying = new HashMap<AudioSource, MediaPlayer>();
-    private SoundPool soundPool = null;
-    private final Vector3f listenerPosition = new Vector3f();
-    // For temp use
-    private final Vector3f distanceVector = new Vector3f();
-    private final AssetManager assetManager;
-    private HashMap<Integer, AudioSource> soundpoolStillLoading = new HashMap<Integer, AudioSource>();
-    private Listener listener;
-    private boolean audioDisabled = false;
-    private final AudioManager manager;
-
-    public AndroidMediaPlayerAudioRenderer(Activity context) {
-        manager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-        context.setVolumeControlStream(AudioManager.STREAM_MUSIC);
-        assetManager = context.getAssets();
-    }
-
-    @Override
-    public void initialize() {
-        soundPool = new SoundPool(MAX_NUM_CHANNELS, AudioManager.STREAM_MUSIC,
-                0);
-        soundPool.setOnLoadCompleteListener(this);
-    }
-
-    @Override
-    public void updateSourceParam(AudioSource src, AudioParam param) {
-        if (audioDisabled) {
-            return;
-        }
-
-        if (src.getChannel() < 0) {
-            return;
-        }
-
-        switch (param) {
-            case Position:
-                if (!src.isPositional()) {
-                    return;
-                }
-
-                Vector3f pos = src.getPosition();
-                break;
-            case Velocity:
-                if (!src.isPositional()) {
-                    return;
-                }
-
-                Vector3f vel = src.getVelocity();
-                break;
-            case MaxDistance:
-                if (!src.isPositional()) {
-                    return;
-                }
-                break;
-            case RefDistance:
-                if (!src.isPositional()) {
-                    return;
-                }
-                break;
-            case ReverbFilter:
-                if (!src.isPositional() || !src.isReverbEnabled()) {
-                    return;
-                }
-                break;
-            case ReverbEnabled:
-                if (!src.isPositional()) {
-                    return;
-                }
-
-                if (src.isReverbEnabled()) {
-                    updateSourceParam(src, AudioParam.ReverbFilter);
-                }
-                break;
-            case IsPositional:
-                break;
-            case Direction:
-                if (!src.isDirectional()) {
-                    return;
-                }
-
-                Vector3f dir = src.getDirection();
-                break;
-            case InnerAngle:
-                if (!src.isDirectional()) {
-                    return;
-                }
-                break;
-            case OuterAngle:
-                if (!src.isDirectional()) {
-                    return;
-                }
-                break;
-            case IsDirectional:
-                if (src.isDirectional()) {
-                    updateSourceParam(src, AudioParam.Direction);
-                    updateSourceParam(src, AudioParam.InnerAngle);
-                    updateSourceParam(src, AudioParam.OuterAngle);
-                } else {
-                }
-                break;
-            case DryFilter:
-                if (src.getDryFilter() != null) {
-                    Filter f = src.getDryFilter();
-                    if (f.isUpdateNeeded()) {
-                        // updateFilter(f);
-                    }
-                }
-                break;
-            case Looping:
-                if (src.isLooping()) {
-                }
-                break;
-            case Volume:
-                MediaPlayer mp = musicPlaying.get(src);
-                if (mp != null) {
-                    mp.setVolume(src.getVolume(), src.getVolume());
-                } else {
-                    soundPool.setVolume(src.getChannel(), src.getVolume(),
-                            src.getVolume());
-                }
-
-                break;
-            case Pitch:
-
-                break;
-        }
-
-    }
-
-    @Override
-    public void updateListenerParam(Listener listener, ListenerParam param) {
-        if (audioDisabled) {
-            return;
-        }
-
-        switch (param) {
-            case Position:
-                listenerPosition.set(listener.getLocation());
-                break;
-            case Rotation:
-                Vector3f dir = listener.getDirection();
-                Vector3f up = listener.getUp();
-
-                break;
-            case Velocity:
-                Vector3f vel = listener.getVelocity();
-
-                break;
-            case Volume:
-                // alListenerf(AL_GAIN, listener.getVolume());
-                break;
-        }
-
-    }
-
-    @Override
-    public void update(float tpf) {
-        float distance;
-        float volume;
-
-        // Loop over all mediaplayers
-        for (AudioSource src : musicPlaying.keySet()) {
-
-            MediaPlayer mp = musicPlaying.get(src);
-
-            // Calc the distance to the listener
-            distanceVector.set(listenerPosition);
-            distanceVector.subtractLocal(src.getPosition());
-            distance = FastMath.abs(distanceVector.length());
-
-            if (distance < src.getRefDistance()) {
-                distance = src.getRefDistance();
-            }
-            if (distance > src.getMaxDistance()) {
-                distance = src.getMaxDistance();
-            }
-            volume = src.getRefDistance() / distance;
-
-            AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
-
-            if (FastMath.abs(audioData.getCurrentVolume() - volume) > FastMath.FLT_EPSILON) {
-                // Left / Right channel get the same volume by now, only
-                // positional
-                mp.setVolume(volume, volume);
-
-                audioData.setCurrentVolume(volume);
-            }
-
-        }
-    }
-
-    public void setListener(Listener listener) {
-        if (audioDisabled) {
-            return;
-        }
-
-        if (this.listener != null) {
-            // previous listener no longer associated with current
-            // renderer
-            this.listener.setRenderer(null);
-        }
-
-        this.listener = listener;
-        this.listener.setRenderer(this);
-
-    }
-
-    @Override
-    public void cleanup() {
-        // Cleanup sound pool
-        if (soundPool != null) {
-            soundPool.release();
-            soundPool = null;
-        }
-
-        // Cleanup media player
-        for (AudioSource src : musicPlaying.keySet()) {
-            MediaPlayer mp = musicPlaying.get(src);
-            {
-                mp.stop();
-                mp.release();
-                src.setStatus(Status.Stopped);
-            }
-        }
-        musicPlaying.clear();
-    }
-
-    @Override
-    public void onCompletion(MediaPlayer mp) {
-        if (mp.isPlaying()) {
-            mp.seekTo(0);
-            mp.stop();
-        }
-            // XXX: This has bad performance -> maybe change overall structure of
-            // mediaplayer in this audiorenderer?
-            for (AudioSource src : musicPlaying.keySet()) {
-                if (musicPlaying.get(src) == mp) {
-                    src.setStatus(Status.Stopped);
-                    break;
-                }
-            }
-
-    }
-
-    /**
-     * Plays using the {@link SoundPool} of Android. Due to hard limitation of
-     * the SoundPool: After playing more instances of the sound you only have
-     * the channel of the last played instance.
-     *
-     * It is not possible to get information about the state of the soundpool of
-     * a specific streamid, so removing is not possilbe -&gt; noone knows when
-     * sound finished.
-     */
-    public void playSourceInstance(AudioSource src) {
-        if (audioDisabled) {
-            return;
-        }
-
-        AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
-
-        if (!(audioData.getAssetKey() instanceof AudioKey)) {
-            throw new IllegalArgumentException("Asset is not a AudioKey");
-        }
-
-        AudioKey assetKey = (AudioKey) audioData.getAssetKey();
-
-        try {
-
-            if (audioData.getId() < 0) { // found something to load
-                int soundId = soundPool.load(
-                        assetManager.openFd(assetKey.getName()), 1);
-                audioData.setId(soundId);
-            }
-
-            int channel = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f);
-
-            if (channel == 0) {
-                soundpoolStillLoading.put(audioData.getId(), src);
-            } else {
-                if (src.getStatus() != Status.Stopped) {
-                    soundPool.stop(channel);
-                    src.setStatus(Status.Stopped);
-                }
-                src.setChannel(channel); // receive a channel at the last
-                setSourceParams(src);
-                // playing at least
-
-
-            }
-        } catch (IOException e) {
-            logger.log(Level.SEVERE,
-                    "Failed to load sound " + assetKey.getName(), e);
-            audioData.setId(-1);
-        }
-    }
-
-    @Override
-    public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
-        AudioSource src = soundpoolStillLoading.remove(sampleId);
-
-        if (src == null) {
-            logger.warning("Something went terribly wrong! onLoadComplete"
-                    + " had sampleId which was not in the HashMap of loading items");
-            return;
-        }
-
-        AudioData audioData = src.getAudioData();
-
-        // load was successfull
-        if (status == 0) {
-            int channelIndex;
-            channelIndex = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f);
-            src.setChannel(channelIndex);
-            setSourceParams(src);
-        }
-    }
-
-    public void playSource(AudioSource src) {
-        if (audioDisabled) {
-            return;
-        }
-
-        AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
-
-        MediaPlayer mp = musicPlaying.get(src);
-        if (mp == null) {
-            mp = new MediaPlayer();
-            mp.setOnCompletionListener(this);
-            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
-        }
-
-        try {
-            if (src.getStatus() == Status.Stopped) {
-                mp.reset();
-                AssetKey<?> key = audioData.getAssetKey();
-
-                AssetFileDescriptor afd = assetManager.openFd(key.getName()); // assetKey.getName()
-                mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
-                        afd.getLength());
-                mp.prepare();
-                setSourceParams(src, mp);
-                src.setChannel(0);
-                src.setStatus(Status.Playing);
-                musicPlaying.put(src, mp);
-                mp.start();
-            } else {
-                mp.start();
-            }
-        } catch (IllegalStateException e) {
-            e.printStackTrace();
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-
-    private void setSourceParams(AudioSource src, MediaPlayer mp) {
-        mp.setLooping(src.isLooping());
-        mp.setVolume(src.getVolume(), src.getVolume());
-        //src.getDryFilter();
-    }
-
-    private void setSourceParams(AudioSource src) {
-        soundPool.setLoop(src.getChannel(), src.isLooping() ? -1 : 0);
-        soundPool.setVolume(src.getChannel(), src.getVolume(), src.getVolume());
-    }
-
-    /**
-     * Pause the current playing sounds. Both from the {@link SoundPool} and the
-     * active {@link MediaPlayer}s
-     */
-    public void pauseAll() {
-        if (soundPool != null) {
-            soundPool.autoPause();
-            for (MediaPlayer mp : musicPlaying.values()) {
-                if(mp.isPlaying()){
-                    mp.pause();
-                }
-            }
-        }
-    }
-
-    /**
-     * Resume all paused sounds.
-     */
-    public void resumeAll() {
-        if (soundPool != null) {
-            soundPool.autoResume();
-            for (MediaPlayer mp : musicPlaying.values()) {
-                mp.start(); //no resume -> api says call start to resume
-            }
-        }
-    }
-
-    public void pauseSource(AudioSource src) {
-        if (audioDisabled) {
-            return;
-        }
-
-        MediaPlayer mp = musicPlaying.get(src);
-        if (mp != null) {
-            mp.pause();
-            src.setStatus(Status.Paused);
-        } else {
-            int channel = src.getChannel();
-            if (channel != -1) {
-                soundPool.pause(channel); // is not very likley to make
-            }											// something useful :)
-        }
-    }
-
-    public void stopSource(AudioSource src) {
-        if (audioDisabled) {
-            return;
-        }
-
-        // can be stream or buffer -> so try to get mediaplayer
-        // if there is non try to stop soundpool
-        MediaPlayer mp = musicPlaying.get(src);
-        if (mp != null) {
-            mp.stop();
-            mp.reset();
-            src.setStatus(Status.Stopped);
-        } else {
-            int channel = src.getChannel();
-            if (channel != -1) {
-                soundPool.pause(channel); // is not very likley to make
-                // something useful :)
-            }
-        }
-
-    }
-
-    @Override
-    public void deleteAudioData(AudioData ad) {
-
-        for (AudioSource src : musicPlaying.keySet()) {
-            if (src.getAudioData() == ad) {
-                MediaPlayer mp = musicPlaying.remove(src);
-                mp.stop();
-                mp.release();
-                src.setStatus(Status.Stopped);
-                src.setChannel(-1);
-                ad.setId(-1);
-                break;
-            }
-        }
-
-        if (ad.getId() > 0) {
-            soundPool.unload(ad.getId());
-            ad.setId(-1);
-        }
-    }
-
-    @Override
-    public void setEnvironment(Environment env) {
-        // not yet supported
-    }
-
-    @Override
-    public void deleteFilter(Filter filter) {
-    }
-
-    @Override
-    public float getSourcePlaybackTime(AudioSource src) {
-        throw new UnsupportedOperationException("Not supported yet.");
-    }
-}

+ 5 - 0
jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java

@@ -523,4 +523,9 @@ public class AndroidGL implements GL, GLExt, GLFbo {
     public Object glFenceSync(int condition, int flags) {
         throw new UnsupportedOperationException("OpenGL ES 2 does not support sync fences");
     }
+
+    @Override
+    public void glBlendEquationSeparate(int colorMode, int alphaMode) {
+        GLES20.glBlendEquationSeparate(colorMode, alphaMode);
+    }
 }

+ 0 - 591
jme3-android/src/main/java/com/jme3/renderer/android/TextureUtil.java

@@ -1,591 +0,0 @@
-/*
- * Copyright (c) 2009-2015 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.renderer.android;
-
-import android.graphics.Bitmap;
-import android.opengl.ETC1;
-import android.opengl.ETC1Util.ETC1Texture;
-import android.opengl.GLES20;
-import android.opengl.GLUtils;
-import com.jme3.asset.AndroidImageInfo;
-import com.jme3.renderer.RendererException;
-import com.jme3.texture.Image;
-import com.jme3.texture.Image.Format;
-import com.jme3.util.BufferUtils;
-import java.nio.ByteBuffer;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * @deprecated Should not be used anymore. Use {@link GLRenderer} instead.
- */
-@Deprecated
-public class TextureUtil {
-
-    private static final Logger logger = Logger.getLogger(TextureUtil.class.getName());
-    //TODO Make this configurable through appSettings
-    public static boolean ENABLE_COMPRESSION = true;
-    private static boolean NPOT = false;
-    private static boolean ETC1support = false;
-    private static boolean DXT1 = false;
-    private static boolean DEPTH24_STENCIL8 = false;
-    private static boolean DEPTH_TEXTURE = false;
-    private static boolean RGBA8 = false;
-    
-    // Same constant used by both GL_ARM_rgba8 and GL_OES_rgb8_rgba8.
-    private static final int GL_RGBA8 = 0x8058;
-    
-    private static final int GL_DXT1 = 0x83F0;
-    private static final int GL_DXT1A = 0x83F1;
-    
-    private static final int GL_DEPTH_STENCIL_OES = 0x84F9;
-    private static final int GL_UNSIGNED_INT_24_8_OES = 0x84FA;
-    private static final int GL_DEPTH24_STENCIL8_OES = 0x88F0;
-
-    public static void loadTextureFeatures(String extensionString) {
-        ETC1support = extensionString.contains("GL_OES_compressed_ETC1_RGB8_texture");
-        DEPTH24_STENCIL8 = extensionString.contains("GL_OES_packed_depth_stencil");
-        NPOT = extensionString.contains("GL_IMG_texture_npot") 
-                || extensionString.contains("GL_OES_texture_npot") 
-                || extensionString.contains("GL_NV_texture_npot_2D_mipmap");
-        
-        DXT1 = extensionString.contains("GL_EXT_texture_compression_dxt1");
-        DEPTH_TEXTURE = extensionString.contains("GL_OES_depth_texture");
-        
-        RGBA8 = extensionString.contains("GL_ARM_rgba8") ||
-                extensionString.contains("GL_OES_rgb8_rgba8");
-        
-        logger.log(Level.FINE, "Supports ETC1? {0}", ETC1support);
-        logger.log(Level.FINE, "Supports DEPTH24_STENCIL8? {0}", DEPTH24_STENCIL8);
-        logger.log(Level.FINE, "Supports NPOT? {0}", NPOT);
-        logger.log(Level.FINE, "Supports DXT1? {0}", DXT1);
-        logger.log(Level.FINE, "Supports DEPTH_TEXTURE? {0}", DEPTH_TEXTURE);
-        logger.log(Level.FINE, "Supports RGBA8? {0}", RGBA8);
-    }
-
-    private static void buildMipmap(Bitmap bitmap, boolean compress) {
-        int level = 0;
-        int height = bitmap.getHeight();
-        int width = bitmap.getWidth();
-
-        logger.log(Level.FINEST, " - Generating mipmaps for bitmap using SOFTWARE");
-
-        GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
-
-        while (height >= 1 || width >= 1) {
-            //First of all, generate the texture from our bitmap and set it to the according level
-            if (compress) {
-                logger.log(Level.FINEST, " - Uploading LOD level {0} ({1}x{2}) with compression.", new Object[]{level, width, height});
-                uploadBitmapAsCompressed(GLES20.GL_TEXTURE_2D, level, bitmap, false, 0, 0);
-            } else {
-                logger.log(Level.FINEST, " - Uploading LOD level {0} ({1}x{2}) directly.", new Object[]{level, width, height});
-                GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, level, bitmap, 0);
-            }
-
-            if (height == 1 || width == 1) {
-                break;
-            }
-
-            //Increase the mipmap level
-            height /= 2;
-            width /= 2;
-            Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap, width, height, true);
-
-            // Recycle any bitmaps created as a result of scaling the bitmap.
-            // Do not recycle the original image (mipmap level 0)
-            if (level != 0) {
-                bitmap.recycle();
-            }
-
-            bitmap = bitmap2;
-
-            level++;
-        }
-    }
-
-    private static void uploadBitmapAsCompressed(int target, int level, Bitmap bitmap, boolean subTexture, int x, int y) {
-        if (bitmap.hasAlpha()) {
-            logger.log(Level.FINEST, " - Uploading bitmap directly. Cannot compress as alpha present.");
-            if (subTexture) {
-                GLUtils.texSubImage2D(target, level, x, y, bitmap);
-                RendererUtil.checkGLError();
-            } else {
-                GLUtils.texImage2D(target, level, bitmap, 0);
-                RendererUtil.checkGLError();
-            }
-        } else {
-            // Convert to RGB565
-            int bytesPerPixel = 2;
-            Bitmap rgb565 = bitmap.copy(Bitmap.Config.RGB_565, true);
-
-            // Put texture data into ByteBuffer
-            ByteBuffer inputImage = BufferUtils.createByteBuffer(bitmap.getRowBytes() * bitmap.getHeight());
-            rgb565.copyPixelsToBuffer(inputImage);
-            inputImage.position(0);
-
-            // Delete the copied RGB565 image
-            rgb565.recycle();
-
-            // Encode the image into the output bytebuffer
-            int encodedImageSize = ETC1.getEncodedDataSize(bitmap.getWidth(), bitmap.getHeight());
-            ByteBuffer compressedImage = BufferUtils.createByteBuffer(encodedImageSize);
-            ETC1.encodeImage(inputImage, bitmap.getWidth(),
-                    bitmap.getHeight(),
-                    bytesPerPixel,
-                    bytesPerPixel * bitmap.getWidth(),
-                    compressedImage);
-
-            // Delete the input image buffer
-            BufferUtils.destroyDirectBuffer(inputImage);
-
-            // Create an ETC1Texture from the compressed image data
-            ETC1Texture etc1tex = new ETC1Texture(bitmap.getWidth(), bitmap.getHeight(), compressedImage);
-
-            // Upload the ETC1Texture
-            if (bytesPerPixel == 2) {
-                int oldSize = (bitmap.getRowBytes() * bitmap.getHeight());
-                int newSize = compressedImage.capacity();
-                logger.log(Level.FINEST, " - Uploading compressed image to GL, oldSize = {0}, newSize = {1}, ratio = {2}", new Object[]{oldSize, newSize, (float) oldSize / newSize});
-                if (subTexture) {
-                    GLES20.glCompressedTexSubImage2D(target,
-                            level,
-                            x, y,
-                            bitmap.getWidth(),
-                            bitmap.getHeight(),
-                            ETC1.ETC1_RGB8_OES,
-                            etc1tex.getData().capacity(),
-                            etc1tex.getData());
-                    
-                    RendererUtil.checkGLError();
-                } else {
-                    GLES20.glCompressedTexImage2D(target,
-                            level,
-                            ETC1.ETC1_RGB8_OES,
-                            bitmap.getWidth(),
-                            bitmap.getHeight(),
-                            0,
-                            etc1tex.getData().capacity(),
-                            etc1tex.getData());
-                    
-                    RendererUtil.checkGLError();
-                }
-
-//                ETC1Util.loadTexture(target, level, 0, GLES20.GL_RGB,
-//                        GLES20.GL_UNSIGNED_SHORT_5_6_5, etc1Texture);
-//            } else if (bytesPerPixel == 3) {
-//                ETC1Util.loadTexture(target, level, 0, GLES20.GL_RGB,
-//                        GLES20.GL_UNSIGNED_BYTE, etc1Texture);
-            }
-
-            BufferUtils.destroyDirectBuffer(compressedImage);
-        }
-    }
-
-    /**
-     * <code>uploadTextureBitmap</code> uploads a native android bitmap
-     */
-    public static void uploadTextureBitmap(final int target, Bitmap bitmap, boolean needMips) {
-        uploadTextureBitmap(target, bitmap, needMips, false, 0, 0);
-    }
-
-    /**
-     * <code>uploadTextureBitmap</code> uploads a native android bitmap
-     */
-    public static void uploadTextureBitmap(final int target, Bitmap bitmap, boolean needMips, boolean subTexture, int x, int y) {
-        boolean recycleBitmap = false;
-        //TODO, maybe this should raise an exception when NPOT is not supported
-
-        boolean willCompress = ENABLE_COMPRESSION && ETC1support && !bitmap.hasAlpha();
-        if (needMips && willCompress) {
-            // Image is compressed and mipmaps are desired, generate them
-            // using software.
-            buildMipmap(bitmap, willCompress);
-        } else {
-            if (willCompress) {
-                // Image is compressed but mipmaps are not desired, upload directly.
-                logger.log(Level.FINEST, " - Uploading compressed bitmap. Mipmaps are not generated.");
-                uploadBitmapAsCompressed(target, 0, bitmap, subTexture, x, y);
-
-            } else {
-                // Image is not compressed, mipmaps may or may not be desired.
-                logger.log(Level.FINEST, " - Uploading bitmap directly.{0}",
-                        (needMips
-                        ? " Mipmaps will be generated in HARDWARE"
-                        : " Mipmaps are not generated."));
-                if (subTexture) {
-                    System.err.println("x : " + x + " y :" + y + " , " + bitmap.getWidth() + "/" + bitmap.getHeight());
-                    GLUtils.texSubImage2D(target, 0, x, y, bitmap);
-                    RendererUtil.checkGLError();
-                } else {
-                    GLUtils.texImage2D(target, 0, bitmap, 0);
-                    RendererUtil.checkGLError();
-                }
-
-                if (needMips) {
-                    // No pregenerated mips available,
-                    // generate from base level if required
-                    GLES20.glGenerateMipmap(target);
-                    RendererUtil.checkGLError();
-                }
-            }
-        }
-
-        if (recycleBitmap) {
-            bitmap.recycle();
-        }
-    }
-
-    public static void uploadTextureAny(Image img, int target, int index, boolean needMips) {
-        if (img.getEfficentData() instanceof AndroidImageInfo) {
-            logger.log(Level.FINEST, " === Uploading image {0}. Using BITMAP PATH === ", img);
-            // If image was loaded from asset manager, use fast path
-            AndroidImageInfo imageInfo = (AndroidImageInfo) img.getEfficentData();
-            uploadTextureBitmap(target, imageInfo.getBitmap(), needMips);
-        } else {
-            logger.log(Level.FINEST, " === Uploading image {0}. Using BUFFER PATH === ", img);
-            boolean wantGeneratedMips = needMips && !img.hasMipmaps();
-            if (wantGeneratedMips && img.getFormat().isCompressed()) {
-                logger.log(Level.WARNING, "Generating mipmaps is only"
-                        + " supported for Bitmap based or non-compressed images!");
-            }
-
-            // Upload using slower path
-            logger.log(Level.FINEST, " - Uploading bitmap directly.{0}",
-                    (wantGeneratedMips
-                    ? " Mipmaps will be generated in HARDWARE"
-                    : " Mipmaps are not generated."));
-            
-            uploadTexture(img, target, index);
-
-            // Image was uploaded using slower path, since it is not compressed,
-            // then compress it
-            if (wantGeneratedMips) {
-                // No pregenerated mips available,
-                // generate from base level if required
-                GLES20.glGenerateMipmap(target);
-            }
-        }
-    }
-
-    private static void unsupportedFormat(Format fmt) {
-        throw new UnsupportedOperationException("The image format '" + fmt + "' is unsupported by the video hardware.");
-    }
-
-    public static AndroidGLImageFormat getImageFormat(Format fmt, boolean forRenderBuffer)
-            throws UnsupportedOperationException {
-        AndroidGLImageFormat imageFormat = new AndroidGLImageFormat();
-        switch (fmt) {
-            case Depth32:
-            case Depth32F:
-                throw new UnsupportedOperationException("The image format '"
-                        + fmt + "' is not supported by OpenGL ES 2.0 specification.");
-            case Alpha8:
-                imageFormat.format = GLES20.GL_ALPHA;
-                imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE;
-                if (RGBA8) {
-                    imageFormat.renderBufferStorageFormat = GL_RGBA8;
-                } else {
-                    // Highest precision alpha supported by vanilla OGLES2
-                    imageFormat.renderBufferStorageFormat = GLES20.GL_RGBA4;
-                }
-                break;
-            case Luminance8:
-                imageFormat.format = GLES20.GL_LUMINANCE;
-                imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE;
-                if (RGBA8) {
-                    imageFormat.renderBufferStorageFormat = GL_RGBA8;
-                } else {
-                    // Highest precision luminance supported by vanilla OGLES2
-                    imageFormat.renderBufferStorageFormat = GLES20.GL_RGB565;
-                }
-                break;
-            case Luminance8Alpha8:
-                imageFormat.format = GLES20.GL_LUMINANCE_ALPHA;
-                imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE;
-                if (RGBA8) {
-                    imageFormat.renderBufferStorageFormat = GL_RGBA8;
-                } else {
-                    imageFormat.renderBufferStorageFormat = GLES20.GL_RGBA4;
-                }
-                break;
-            case RGB565:
-                imageFormat.format = GLES20.GL_RGB;
-                imageFormat.dataType = GLES20.GL_UNSIGNED_SHORT_5_6_5;
-                imageFormat.renderBufferStorageFormat = GLES20.GL_RGB565;
-                break;
-            case RGB5A1:
-                imageFormat.format = GLES20.GL_RGBA;
-                imageFormat.dataType = GLES20.GL_UNSIGNED_SHORT_5_5_5_1;
-                imageFormat.renderBufferStorageFormat = GLES20.GL_RGB5_A1;
-                break;
-            case RGB8:
-                imageFormat.format = GLES20.GL_RGB;
-                imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE;
-                if (RGBA8) {
-                    imageFormat.renderBufferStorageFormat = GL_RGBA8;
-                } else {
-                    // Fallback: Use RGB565 if RGBA8 is not available.
-                    imageFormat.renderBufferStorageFormat = GLES20.GL_RGB565;
-                }
-                break;
-            case BGR8:
-                imageFormat.format = GLES20.GL_RGB;
-                imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE;
-                if (RGBA8) {
-                    imageFormat.renderBufferStorageFormat = GL_RGBA8;
-                } else {
-                    imageFormat.renderBufferStorageFormat = GLES20.GL_RGB565;
-                }
-                break;
-            case RGBA8:
-                imageFormat.format = GLES20.GL_RGBA;
-                imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE;
-                if (RGBA8) {
-                    imageFormat.renderBufferStorageFormat = GL_RGBA8;
-                } else {
-                    imageFormat.renderBufferStorageFormat = GLES20.GL_RGBA4;
-                }
-                break;
-            case Depth:
-            case Depth16:
-                if (!DEPTH_TEXTURE && !forRenderBuffer) {
-                    unsupportedFormat(fmt);
-                }
-                imageFormat.format = GLES20.GL_DEPTH_COMPONENT;
-                imageFormat.dataType = GLES20.GL_UNSIGNED_SHORT;
-                imageFormat.renderBufferStorageFormat = GLES20.GL_DEPTH_COMPONENT16;
-                break;
-            case Depth24:
-            case Depth24Stencil8:
-                if (!DEPTH_TEXTURE) {
-                    unsupportedFormat(fmt);
-                }
-                if (DEPTH24_STENCIL8) {
-                    // NEW: True Depth24 + Stencil8 format.
-                    imageFormat.format = GL_DEPTH_STENCIL_OES;
-                    imageFormat.dataType = GL_UNSIGNED_INT_24_8_OES;
-                    imageFormat.renderBufferStorageFormat = GL_DEPTH24_STENCIL8_OES;
-                } else {
-                    // Vanilla OGLES2, only Depth16 available.
-                    imageFormat.format = GLES20.GL_DEPTH_COMPONENT;
-                    imageFormat.dataType = GLES20.GL_UNSIGNED_SHORT;
-                    imageFormat.renderBufferStorageFormat = GLES20.GL_DEPTH_COMPONENT16;
-                }
-                break;
-            case DXT1:
-                if (!DXT1) {
-                    unsupportedFormat(fmt);
-                }
-                imageFormat.format = GL_DXT1;
-                imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE;
-                imageFormat.compress = true;
-                break;
-            case DXT1A:
-                if (!DXT1) {
-                    unsupportedFormat(fmt);
-                }
-                imageFormat.format = GL_DXT1A;
-                imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE;
-                imageFormat.compress = true;
-                break;
-            default:
-                throw new UnsupportedOperationException("Unrecognized format: " + fmt);
-        }
-        return imageFormat;
-    }
-
-    public static class AndroidGLImageFormat {
-
-        boolean compress = false;
-        int format = -1;
-        int renderBufferStorageFormat = -1;
-        int dataType = -1;
-    }
-
-    private static void uploadTexture(Image img,
-            int target,
-            int index) {
-
-        if (img.getEfficentData() instanceof AndroidImageInfo) {
-            throw new RendererException("This image uses efficient data. "
-                    + "Use uploadTextureBitmap instead.");
-        }
-
-        // Otherwise upload image directly.
-        // Prefer to only use power of 2 textures here to avoid errors.
-        Image.Format fmt = img.getFormat();
-        ByteBuffer data;
-        if (index >= 0 || img.getData() != null && img.getData().size() > 0) {
-            data = img.getData(index);
-        } else {
-            data = null;
-        }
-
-        int width = img.getWidth();
-        int height = img.getHeight();
-
-        if (!NPOT && img.isNPOT()) {
-            // Check if texture is POT
-            throw new RendererException("Non-power-of-2 textures "
-                    + "are not supported by the video hardware "
-                    + "and no scaling path available for image: " + img);
-        }
-        AndroidGLImageFormat imageFormat = getImageFormat(fmt, false);
-
-        if (data != null) {
-            GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
-        }
-
-        int[] mipSizes = img.getMipMapSizes();
-        int pos = 0;
-        if (mipSizes == null) {
-            if (data != null) {
-                mipSizes = new int[]{data.capacity()};
-            } else {
-                mipSizes = new int[]{width * height * fmt.getBitsPerPixel() / 8};
-            }
-        }
-
-        for (int i = 0; i < mipSizes.length; i++) {
-            int mipWidth = Math.max(1, width >> i);
-            int mipHeight = Math.max(1, height >> i);
-
-            if (data != null) {
-                data.position(pos);
-                data.limit(pos + mipSizes[i]);
-            }
-
-            if (imageFormat.compress && data != null) {
-                GLES20.glCompressedTexImage2D(target,
-                        i,
-                        imageFormat.format,
-                        mipWidth,
-                        mipHeight,
-                        0,
-                        data.remaining(),
-                        data);
-            } else {
-                GLES20.glTexImage2D(target,
-                        i,
-                        imageFormat.format,
-                        mipWidth,
-                        mipHeight,
-                        0,
-                        imageFormat.format,
-                        imageFormat.dataType,
-                        data);
-            }
-
-            pos += mipSizes[i];
-        }
-    }
-
-    /**
-     * Update the texture currently bound to target at with data from the given
-     * Image at position x and y. The parameter index is used as the zoffset in
-     * case a 3d texture or texture 2d array is being updated.
-     *
-     * @param image Image with the source data (this data will be put into the
-     * texture)
-     * @param target the target texture
-     * @param index the mipmap level to update
-     * @param x the x position where to put the image in the texture
-     * @param y the y position where to put the image in the texture
-     */
-    public static void uploadSubTexture(
-            Image img,
-            int target,
-            int index,
-            int x,
-            int y) {
-        if (img.getEfficentData() instanceof AndroidImageInfo) {
-            AndroidImageInfo imageInfo = (AndroidImageInfo) img.getEfficentData();
-            uploadTextureBitmap(target, imageInfo.getBitmap(), true, true, x, y);
-            return;
-        }
-
-        // Otherwise upload image directly.
-        // Prefer to only use power of 2 textures here to avoid errors.
-        Image.Format fmt = img.getFormat();
-        ByteBuffer data;
-        if (index >= 0 || img.getData() != null && img.getData().size() > 0) {
-            data = img.getData(index);
-        } else {
-            data = null;
-        }
-
-        int width = img.getWidth();
-        int height = img.getHeight();
-
-        if (!NPOT && img.isNPOT()) {
-            // Check if texture is POT
-            throw new RendererException("Non-power-of-2 textures "
-                    + "are not supported by the video hardware "
-                    + "and no scaling path available for image: " + img);
-        }
-        AndroidGLImageFormat imageFormat = getImageFormat(fmt, false);
-
-        if (data != null) {
-            GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
-        }
-
-        int[] mipSizes = img.getMipMapSizes();
-        int pos = 0;
-        if (mipSizes == null) {
-            if (data != null) {
-                mipSizes = new int[]{data.capacity()};
-            } else {
-                mipSizes = new int[]{width * height * fmt.getBitsPerPixel() / 8};
-            }
-        }
-
-        for (int i = 0; i < mipSizes.length; i++) {
-            int mipWidth = Math.max(1, width >> i);
-            int mipHeight = Math.max(1, height >> i);
-
-            if (data != null) {
-                data.position(pos);
-                data.limit(pos + mipSizes[i]);
-            }
-
-            if (imageFormat.compress && data != null) {
-                GLES20.glCompressedTexSubImage2D(target, i, x, y, mipWidth, mipHeight, imageFormat.format, data.remaining(), data);
-                RendererUtil.checkGLError();
-            } else {
-                GLES20.glTexSubImage2D(target, i, x, y, mipWidth, mipHeight, imageFormat.format, imageFormat.dataType, data);
-                RendererUtil.checkGLError();
-            }
-
-            pos += mipSizes[i];
-        }
-    }
-}

+ 0 - 23
jme3-android/src/main/java/com/jme3/texture/plugins/AndroidImageLoader.java

@@ -1,23 +0,0 @@
-package com.jme3.texture.plugins;
-
-import android.graphics.Bitmap;
-import com.jme3.asset.AndroidImageInfo;
-import com.jme3.asset.AssetInfo;
-import com.jme3.asset.AssetLoader;
-import com.jme3.texture.Image;
-import com.jme3.texture.image.ColorSpace;
-import java.io.IOException;
-
-@Deprecated
-public class AndroidImageLoader implements AssetLoader {
-
-    public Object load(AssetInfo info) throws IOException {
-        AndroidImageInfo imageInfo = new AndroidImageInfo(info);
-        Bitmap bitmap = imageInfo.getBitmap();
-        
-        Image image = new Image(imageInfo.getFormat(), bitmap.getWidth(), bitmap.getHeight(), null, ColorSpace.sRGB);
-        
-        image.setEfficentData(imageInfo);
-        return image;
-    }
-}

+ 1 - 1
jme3-android/src/main/java/jme3test/android/SimpleTexturedTest.java

@@ -46,7 +46,7 @@ public class SimpleTexturedTest extends SimpleApplication {
 
 
 		shapeSphere = new Sphere(16, 16, .5f);
-		shapeBox = new Box(Vector3f.ZERO, 0.3f, 0.3f, 0.3f);
+            shapeBox = new Box(0.3f, 0.3f, 0.3f);
 
 
 	//	ModelConverter.optimize(geom);

+ 15 - 6
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java

@@ -1,6 +1,7 @@
 package com.jme3.scene.plugins.blender.materials;
 
 import java.io.IOException;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -157,14 +158,14 @@ public final class MaterialContext implements Savable {
         }
 
         // applying textures
+        int textureIndex = 0;
         if (loadedTextures != null && loadedTextures.size() > 0) {
-            int textureIndex = 0;
             if (loadedTextures.size() > TextureHelper.TEXCOORD_TYPES.length) {
                 LOGGER.log(Level.WARNING, "The blender file has defined more than {0} different textures. JME supports only {0} UV mappings.", TextureHelper.TEXCOORD_TYPES.length);
             }
             for (CombinedTexture combinedTexture : loadedTextures) {
                 if (textureIndex < TextureHelper.TEXCOORD_TYPES.length) {
-                    combinedTexture.flatten(geometry, geometriesOMA, userDefinedUVCoordinates, blenderContext);
+                    String usedUserUVSet = combinedTexture.flatten(geometry, geometriesOMA, userDefinedUVCoordinates, blenderContext);
 
                     this.setTexture(material, combinedTexture.getMappingType(), combinedTexture.getResultTexture());
                     List<Vector2f> uvs = combinedTexture.getResultUVS();
@@ -173,13 +174,19 @@ public final class MaterialContext implements Savable {
                         uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()])));
                         geometry.getMesh().setBuffer(uvCoordsBuffer);
                     }//uvs might be null if the user assigned non existing UV coordinates group name to the mesh (this should be fixed in blender file)
+                    
+                    if(usedUserUVSet != null) {
+                    	userDefinedUVCoordinates = new HashMap<>(userDefinedUVCoordinates);
+                    	userDefinedUVCoordinates.remove(usedUserUVSet);
+                    }
                 } else {
                     LOGGER.log(Level.WARNING, "The texture could not be applied because JME only supports up to {0} different UV's.", TextureHelper.TEXCOORD_TYPES.length);
                 }
             }
-        } else if (userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) {
-            LOGGER.fine("No textures found for the mesh, but UV coordinates are applied.");
-            int textureIndex = 0;
+        }
+
+        if (userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) {
+            LOGGER.fine("Storing unused, user defined UV coordinates sets.");
             if (userDefinedUVCoordinates.size() > TextureHelper.TEXCOORD_TYPES.length) {
                 LOGGER.log(Level.WARNING, "The blender file has defined more than {0} different UV coordinates for the mesh. JME supports only {0} UV coordinates buffers.", TextureHelper.TEXCOORD_TYPES.length);
             }
@@ -190,7 +197,9 @@ public final class MaterialContext implements Savable {
                     uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()])));
                     geometry.getMesh().setBuffer(uvCoordsBuffer);
                 } else {
-                    LOGGER.log(Level.WARNING, "The texture could not be applied because JME only supports up to {0} different UV's.", TextureHelper.TEXCOORD_TYPES.length);
+                    LOGGER.log(Level.WARNING, "The user's UV set named: '{0}' could not be stored because JME only supports up to {1} different UV's.", new Object[] {
+                		entry.getKey(), TextureHelper.TEXCOORD_TYPES.length
+                    });
                 }
             }
         }

+ 21 - 16
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java

@@ -119,22 +119,24 @@ public class CombinedTexture {
         }
     }
 
-    /**
-     * This method flattens the texture and creates a single result of Texture2D
-     * type.
-     * 
-     * @param geometry
-     *            the geometry the texture is created for
-     * @param geometriesOMA
-     *            the old memory address of the geometries list that the given
-     *            geometry belongs to (needed for bounding box creation)
-     * @param userDefinedUVCoordinates
-     *            the UV's defined by user (null or zero length table if none
-     *            were defined)
-     * @param blenderContext
-     *            the blender context
-     */
-    public void flatten(Geometry geometry, Long geometriesOMA, Map<String, List<Vector2f>> userDefinedUVCoordinates, BlenderContext blenderContext) {
+	/**
+	 * This method flattens the texture and creates a single result of Texture2D
+	 * type.
+	 * 
+	 * @param geometry
+	 *            the geometry the texture is created for
+	 * @param geometriesOMA
+	 *            the old memory address of the geometries list that the given
+	 *            geometry belongs to (needed for bounding box creation)
+	 * @param userDefinedUVCoordinates
+	 *            the UV's defined by user (null or zero length table if none
+	 *            were defined)
+	 * @param blenderContext
+	 *            the blender context
+	 * @return the name of the user UV coordinates used (null if the UV's were
+	 *         generated)
+	 */
+    public String flatten(Geometry geometry, Long geometriesOMA, Map<String, List<Vector2f>> userDefinedUVCoordinates, BlenderContext blenderContext) {
         Mesh mesh = geometry.getMesh();
         Texture previousTexture = null;
         UVCoordinatesType masterUVCoordinatesType = null;
@@ -226,6 +228,7 @@ public class CombinedTexture {
             }
             resultUVS = ((TriangulatedTexture) resultTexture).getResultUVS();
             resultTexture = ((TriangulatedTexture) resultTexture).getResultTexture();
+            masterUserUVSetName = null;
         }
 
         // setting additional data
@@ -234,6 +237,8 @@ public class CombinedTexture {
         // otherwise ugly lines appear between the mesh faces
         resultTexture.setMagFilter(MagFilter.Nearest);
         resultTexture.setMinFilter(MinFilter.NearestNoMipMaps);
+        
+        return masterUserUVSetName;
     }
 
     /**

+ 33 - 1
jme3-bullet-native/build.gradle

@@ -34,7 +34,14 @@ libraries {
 //                linker.args "-static-libstdc++"
             } else if (targetPlatform.operatingSystem.name == "windows") {
                 if (toolChain in Gcc) {
-                    cppCompiler.args '-I', "${org.gradle.internal.jvm.Jvm.current().javaHome}/include/win32"
+                    if (toolChain.name.startsWith('mingw')) {
+                        cppCompiler.args '-I', "${org.gradle.internal.jvm.Jvm.current().javaHome}/include/linux"
+                    } else {
+                        cppCompiler.args '-I', "${org.gradle.internal.jvm.Jvm.current().javaHome}/include/win32"
+                    }
+                    cppCompiler.args "-fpermissive"
+                    cppCompiler.args "-static"
+                    linker.args "-static"
                 }
                 else if (toolChain in VisualCpp) {
                     cppCompiler.args "/I${org.gradle.internal.jvm.Jvm.current().javaHome}\\include\\win32"
@@ -76,6 +83,31 @@ sourceSets {
 
 // Set of target platforms, will be available based on build system
 model {
+
+    toolChains {
+        gcc(Gcc)
+        mingw_x86(Gcc) {
+            eachPlatform() {
+                cCompiler.executable "i686-w64-mingw32-gcc"
+                cppCompiler.executable "i686-w64-mingw32-g++"
+                linker.executable "i686-w64-mingw32-g++"
+                assembler.executable "i686-w64-mingw32-g++"
+                staticLibArchiver.executable "i686-w64-mingw32-gcc-ar"
+            }
+            target("windows_x86")
+        }
+        mingw_x86_64(Gcc) {
+            eachPlatform() {
+                cCompiler.executable "x86_64-w64-mingw32-gcc"
+                cppCompiler.executable "x86_64-w64-mingw32-g++"
+                linker.executable "x86_64-w64-mingw32-g++"
+                assembler.executable "x86_64-w64-mingw32-g++"
+                staticLibArchiver.executable "x86_64-w64-mingw32-gcc-ar"
+            }
+            target("windows_x86_64")
+        }
+    }
+
     platforms{
 //    osx_universal { // TODO: universal binary doesn't work?
 //        architecture 'x86_64'

+ 21 - 2
jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java

@@ -111,7 +111,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
      * Material references used for hardware skinning
      */
     private Set<Material> materials = new HashSet<Material>();
-
+    
     /**
      * Serialization only. Do not use.
      */
@@ -204,6 +204,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
      * @param skeleton the skeleton
      */
     public SkeletonControl(Skeleton skeleton) {
+        if (skeleton == null) {
+            throw new IllegalArgumentException("skeleton cannot be null");
+        }
         this.skeleton = skeleton;
     }
 
@@ -406,7 +409,23 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
         // Not automatic set cloning yet
         Set<Material> newMaterials = new HashSet<Material>();
         for( Material m : this.materials ) {
-            newMaterials.add(cloner.clone(m));
+            Material mClone = cloner.clone(m);
+            newMaterials.add(mClone);
+            if( mClone != m ) {
+                // Material was really cloned so clear the bone matrices in case
+                // this is hardware skinned.  This allows a local version to be
+                // used and will be reset on the material.  Really this just avoids
+                // the 'safety' check in controlRenderHardware().  Right now material
+                // doesn't clone itself with the cloner (and doesn't clone its parameters)
+                // else this would be unnecessary.
+                MatParam boneMatrices = mClone.getParam("BoneMatrices");
+                
+                // ...because for some strange reason you can't clear a non-existant 
+                // parameter.
+                if( boneMatrices != null ) {                    
+                    mClone.clearParam("BoneMatrices");
+                }
+            }
         }
         this.materials = newMaterials;
     }

+ 0 - 42
jme3-core/src/main/java/com/jme3/animation/SpatialAnimation.java

@@ -1,42 +0,0 @@
-/*
- * Copyright (c) 2009-2012 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.animation;
-
-/**
- * @deprecated use Animation instead with tracks of selected type (ie. BoneTrack, SpatialTrack, MeshTrack)
- */
-@Deprecated
-public class SpatialAnimation extends Animation {
-    public SpatialAnimation(String name, float length) {
-        super(name, length);
-    }
-}

+ 2 - 1
jme3-core/src/main/java/com/jme3/app/SimpleApplication.java

@@ -32,6 +32,7 @@
 package com.jme3.app;
 
 import com.jme3.app.state.AppState;
+import com.jme3.audio.AudioListenerState;
 import com.jme3.font.BitmapFont;
 import com.jme3.font.BitmapText;
 import com.jme3.input.FlyByCamera;
@@ -96,7 +97,7 @@ public abstract class SimpleApplication extends LegacyApplication {
     }
 
     public SimpleApplication() {
-        this( new StatsAppState(), new FlyCamAppState(), new DebugKeysAppState() );
+        this(new StatsAppState(), new FlyCamAppState(), new AudioListenerState(), new DebugKeysAppState());
     }
 
     public SimpleApplication( AppState... initialStates ) {

+ 0 - 9
jme3-core/src/main/java/com/jme3/asset/AssetManager.java

@@ -41,9 +41,7 @@ import com.jme3.post.FilterPostProcessor;
 import com.jme3.renderer.Caps;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.plugins.OBJLoader;
-import com.jme3.shader.Shader;
 import com.jme3.shader.ShaderGenerator;
-import com.jme3.shader.ShaderKey;
 import com.jme3.texture.Texture;
 import com.jme3.texture.plugins.TGALoader;
 import java.io.IOException;
@@ -320,13 +318,6 @@ public interface AssetManager {
      */
     public Material loadMaterial(String name);
 
-    /**
-     * Loads shader file(s), shouldn't be used by end-user in most cases.
-     *
-     * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
-     */
-    public Shader loadShader(ShaderKey key);
-
     /**
      * Load a font file. Font files are in AngelCode text format,
      * and are with the extension "fnt".

+ 0 - 33
jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java

@@ -32,7 +32,6 @@
 package com.jme3.asset;
 
 import com.jme3.asset.cache.AssetCache;
-import com.jme3.asset.cache.SimpleAssetCache;
 import com.jme3.audio.AudioData;
 import com.jme3.audio.AudioKey;
 import com.jme3.font.BitmapFont;
@@ -42,9 +41,7 @@ import com.jme3.renderer.Caps;
 import com.jme3.scene.Spatial;
 import com.jme3.shader.Glsl100ShaderGenerator;
 import com.jme3.shader.Glsl150ShaderGenerator;
-import com.jme3.shader.Shader;
 import com.jme3.shader.ShaderGenerator;
-import com.jme3.shader.ShaderKey;
 import com.jme3.system.JmeSystem;
 import com.jme3.texture.Texture;
 import java.io.IOException;
@@ -431,36 +428,6 @@ public class DesktopAssetManager implements AssetManager {
         return loadFilter(new FilterKey(name));
     }
 
-    /**
-     * Load a vertex/fragment shader combo.
-     *
-     * @param key
-     * @return the loaded {@link Shader}
-     */
-    public Shader loadShader(ShaderKey key){
-        // cache abuse in method
-        // that doesn't use loaders/locators
-        AssetCache cache = handler.getCache(SimpleAssetCache.class);
-        Shader shader = (Shader) cache.getFromCache(key);
-        if (shader == null){
-            if (key.isUsesShaderNodes()) {
-                if(shaderGenerator == null){
-                    throw new UnsupportedOperationException("ShaderGenerator was not initialized, make sure assetManager.getGenerator(caps) has been called");
-                }
-                shader = shaderGenerator.generateShader();
-            } else {
-                shader = new Shader();
-                shader.initialize();
-                for (Shader.ShaderType shaderType : key.getUsedShaderPrograms()) {
-                    shader.addSource(shaderType,key.getShaderProgramName(shaderType),(String) loadAsset(new AssetKey(key.getShaderProgramName(shaderType))),key.getDefines().getCompiled(),key.getShaderProgramLanguage(shaderType));
-                }
-            }
-
-            cache.addToCache(key, shader);
-        }
-        return shader;
-    }
-
     /**
      * {@inheritDoc}
      */

+ 0 - 36
jme3-core/src/main/java/com/jme3/asset/TextureKey.java

@@ -123,24 +123,6 @@ public class TextureKey extends AssetKey<Texture> {
         this.anisotropy = anisotropy;
     }
 
-    /**
-     * @deprecated Use {@link #setTextureTypeHint(com.jme3.texture.Texture.Type) }
-     * instead.
-     */
-    @Deprecated
-    public boolean isAsCube() {
-        return textureTypeHint == Type.CubeMap;
-    }
-
-    /**
-     * @deprecated Use {@link #setTextureTypeHint(com.jme3.texture.Texture.Type) }
-     * instead.
-     */
-    @Deprecated
-    public void setAsCube(boolean asCube) {
-        textureTypeHint = asCube ? Type.CubeMap : Type.TwoDimensional;
-    }
-
     public boolean isGenerateMips() {
         return generateMips;
     }
@@ -149,24 +131,6 @@ public class TextureKey extends AssetKey<Texture> {
         this.generateMips = generateMips;
     }
 
-    /**
-     * @deprecated Use {@link #setTextureTypeHint(com.jme3.texture.Texture.Type) }
-     * instead.
-     */
-    @Deprecated
-    public boolean isAsTexture3D() {
-        return textureTypeHint == Type.ThreeDimensional;
-    }
-
-    /**
-     * @deprecated Use {@link #setTextureTypeHint(com.jme3.texture.Texture.Type) }
-     * instead.
-     */
-    @Deprecated
-    public void setAsTexture3D(boolean asTexture3D) {
-        textureTypeHint = asTexture3D ? Type.ThreeDimensional : Type.TwoDimensional;
-    }
-
     /**
      * The type of texture expected to be returned.
      * 

+ 104 - 0
jme3-core/src/main/java/com/jme3/audio/AudioListenerState.java

@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2009-2016 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.audio;
+
+import com.jme3.app.Application;
+import com.jme3.app.state.BaseAppState;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+
+/**
+ * <code>AudioListenerState</code> updates the audio listener's position,
+ * orientation, and velocity from a {@link Camera}.
+ *
+ * @author Kirill Vainer
+ */
+public class AudioListenerState extends BaseAppState {
+
+    private Listener listener;
+    private Camera camera;
+    private float lastTpf;
+
+    public AudioListenerState() {
+    }
+
+    @Override
+    protected void initialize(Application app) {
+        this.camera = app.getCamera();
+        this.listener = app.getListener();
+    }
+
+    @Override
+    protected void cleanup(Application app) {
+    }
+
+    @Override
+    public void update(float tpf) {
+        lastTpf = tpf;
+    }
+
+    @Override
+    public void render(RenderManager rm) {
+        if (!isEnabled()) {
+            return;
+        }
+
+        Vector3f lastLocation = listener.getLocation();
+        Vector3f currentLocation = camera.getLocation();
+        Vector3f velocity = listener.getVelocity();
+
+        if (!lastLocation.equals(currentLocation)) {
+            velocity.set(currentLocation).subtractLocal(lastLocation);
+            velocity.multLocal(1f / lastTpf);
+            listener.setLocation(currentLocation);
+            listener.setVelocity(velocity);
+        } else if (!velocity.equals(Vector3f.ZERO)) {
+            listener.setVelocity(Vector3f.ZERO);
+        }
+
+        Quaternion lastRotation = listener.getRotation();
+        Quaternion currentRotation = camera.getRotation();
+        if (!lastRotation.equals(currentRotation)) {
+            listener.setRotation(currentRotation);
+        }
+    }
+
+    @Override
+    protected void onEnable() {
+    }
+
+    @Override
+    protected void onDisable() {
+    }
+}

+ 38 - 6
jme3-core/src/main/java/com/jme3/audio/AudioNode.java

@@ -78,6 +78,7 @@ public class AudioNode extends Node implements AudioSource {
     protected transient AudioData data = null;
     protected transient volatile AudioSource.Status status = AudioSource.Status.Stopped;
     protected transient volatile int channel = -1;
+    protected Vector3f previousWorldTranslation = Vector3f.NAN;
     protected Vector3f velocity = new Vector3f();
     protected boolean reverbEnabled = false;
     protected float maxDistance = 200; // 200 meters
@@ -88,6 +89,8 @@ public class AudioNode extends Node implements AudioSource {
     protected float innerAngle = 360;
     protected float outerAngle = 360;
     protected boolean positional = true;
+    protected boolean velocityFromTranslation = false;
+    protected float lastTpf;
 
     /**
      * <code>Status</code> indicates the current status of the audio node.
@@ -702,17 +705,44 @@ public class AudioNode extends Node implements AudioSource {
         }
     }
 
+    public boolean isVelocityFromTranslation() {
+        return velocityFromTranslation;
+    }
+
+    public void setVelocityFromTranslation(boolean velocityFromTranslation) {
+        this.velocityFromTranslation = velocityFromTranslation;
+    }
+
     @Override
-    public void updateGeometricState(){
-        boolean updatePos = false;
-        if ((refreshFlags & RF_TRANSFORM) != 0){
-            updatePos = true;
-        }
+    public void updateLogicalState(float tpf) {
+        super.updateLogicalState(tpf);
+        lastTpf = tpf;
+    }
 
+    @Override
+    public void updateGeometricState() {
         super.updateGeometricState();
 
-        if (updatePos && channel >= 0)
+        if (channel < 0) {
+            return;
+        }
+
+        Vector3f currentWorldTranslation = worldTransform.getTranslation();
+
+        if (Float.isNaN(previousWorldTranslation.x)
+                || !previousWorldTranslation.equals(currentWorldTranslation)) {
+
             getRenderer().updateSourceParam(this, AudioParam.Position);
+
+            if (velocityFromTranslation) {
+                velocity.set(currentWorldTranslation).subtractLocal(previousWorldTranslation);
+                velocity.multLocal(1f / lastTpf);
+
+                getRenderer().updateSourceParam(this, AudioParam.Velocity);
+            }
+
+            previousWorldTranslation.set(currentWorldTranslation);
+        }
     }
 
     @Override
@@ -772,6 +802,7 @@ public class AudioNode extends Node implements AudioSource {
         oc.write(outerAngle, "outer_angle", 360);
 
         oc.write(positional, "positional", false);
+        oc.write(velocityFromTranslation, "velocity_from_translation", false);
     }
 
     @Override
@@ -806,6 +837,7 @@ public class AudioNode extends Node implements AudioSource {
         outerAngle = ic.readFloat("outer_angle", 360);
 
         positional = ic.readBoolean("positional", false);
+        velocityFromTranslation = ic.readBoolean("velocity_from_translation", false);
 
         if (audioKey != null) {
             try {

+ 3 - 3
jme3-core/src/main/java/com/jme3/audio/Listener.java

@@ -36,9 +36,9 @@ import com.jme3.math.Vector3f;
 
 public class Listener {
 
-    private Vector3f location;
-    private Vector3f velocity;
-    private Quaternion rotation;
+    private final Vector3f location;
+    private final Vector3f velocity;
+    private final Quaternion rotation;
     private float volume = 1;
     private AudioRenderer renderer;
 

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

@@ -904,11 +904,12 @@ public class ALAudioRenderer implements AudioRenderer, Runnable {
                     } else {
                         // Buffer finished playing.
                         if (src.isLooping()) {
-                            throw new AssertionError("Unexpected state: " + 
-                                                     "A looping sound has stopped playing");
-                        } else {
-                            reclaimChannel = true;
+                            // When a device is disconnected, all sources
+                            // will enter the "stopped" state.
+                            logger.warning("A looping sound has stopped playing");
                         }
+
+                        reclaimChannel = true;
                     }
                     
                     if (reclaimChannel) {

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

@@ -121,7 +121,8 @@ public class MotionPath implements Savable {
             Material m = assetManager.loadMaterial("Common/Materials/RedColor.j3m");
             for (Iterator<Vector3f> it = spline.getControlPoints().iterator(); it.hasNext();) {
                 Vector3f cp = it.next();
-                Geometry geo = new Geometry("box", new Box(cp, 0.3f, 0.3f, 0.3f));
+                Geometry geo = new Geometry("box", new Box(0.3f, 0.3f, 0.3f));
+                geo.setLocalTranslation(cp);
                 geo.setMaterial(m);
                 debugNode.attachChild(geo);
 

+ 3 - 2
jme3-core/src/main/java/com/jme3/effect/ParticleMesh.java

@@ -50,8 +50,9 @@ public abstract class ParticleMesh extends Mesh {
     public enum Type {
         /**
          * The particle mesh is composed of points. Each particle is a point.
-         * This can be used in conjuction with {@link RenderState#setPointSprite(boolean) point sprites}
-         * to render particles the usual way.
+         * Note that point based particles do not support certain features such
+         * as {@link ParticleEmitter#setRotateSpeed(float) rotation}, and
+         * {@link ParticleEmitter#setFacingVelocity(boolean) velocity following}.
          */
         Point,
         

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

@@ -54,10 +54,10 @@ import java.util.logging.Logger;
  */
 public class SavableClassUtil {
 
-    private final static HashMap<String, String> classRemappings = new HashMap<String, String>();
+    private final static HashMap<String, String> CLASS_REMAPPINGS = new HashMap<>();
     
     private static void addRemapping(String oldClass, Class<? extends Savable> newClass){
-        classRemappings.put(oldClass, newClass.getName());
+        CLASS_REMAPPINGS.put(oldClass, newClass.getName());
     }
     
     static {
@@ -74,7 +74,7 @@ public class SavableClassUtil {
     }
     
     private static String remapClass(String className) throws ClassNotFoundException {
-        String result = classRemappings.get(className);
+        String result = CLASS_REMAPPINGS.get(className);
         if (result == null) {
             return className;
         } else {

+ 0 - 4
jme3-core/src/main/java/com/jme3/material/MatParam.java

@@ -34,7 +34,6 @@ package com.jme3.material;
 import com.jme3.asset.TextureKey;
 import com.jme3.export.*;
 import com.jme3.math.*;
-import com.jme3.renderer.Renderer;
 import com.jme3.shader.VarType;
 import com.jme3.texture.Texture;
 import com.jme3.texture.Texture.WrapMode;
@@ -129,9 +128,6 @@ public class MatParam implements Savable, Cloneable {
         this.value = value;
     }
 
-    void apply(Renderer r, Technique technique) {
-        technique.updateUniformParam(getPrefixedName(), getVarType(), getValue());
-    }
 
     /**
      * Returns the material parameter value as it would appear in a J3M

+ 151 - 0
jme3-core/src/main/java/com/jme3/material/MatParamOverride.java

@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2009-2016 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.material;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.scene.Spatial;
+import com.jme3.shader.VarType;
+import java.io.IOException;
+
+/**
+ * <code>MatParamOverride</code> is a mechanism by which
+ * {@link MatParam material parameters} can be overridden on the scene graph.
+ * <p>
+ * A scene branch which has a <code>MatParamOverride</code> applied to it will
+ * cause all material parameters with the same name and type to have their value
+ * replaced with the value set on the <code>MatParamOverride</code>. If those
+ * parameters are mapped to a define, then the define will be overridden as well
+ * using the same rules as the ones used for regular material parameters.
+ * <p>
+ * <code>MatParamOverrides</code> are applied to a {@link Spatial} via the
+ * {@link Spatial#addMatParamOverride(com.jme3.material.MatParamOverride)}
+ * method. They are propagated to child <code>Spatials</code> via
+ * {@link Spatial#updateGeometricState()} similar to how lights are propagated.
+ * <p>
+ * Example:<br>
+ * <pre>
+ * {@code
+ *
+ * Geometry box = new Geometry("Box", new Box(1,1,1));
+ * Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ * mat.setColor("Color", ColorRGBA.Blue);
+ * box.setMaterial(mat);
+ * rootNode.attachChild(box);
+ *
+ * // ... later ...
+ * MatParamOverride override = new MatParamOverride(Type.Vector4, "Color", ColorRGBA.Red);
+ * rootNode.addMatParamOverride(override);
+ *
+ * // After adding the override to the root node, the box becomes red.
+ * }
+ * </pre>
+ *
+ * @author Kirill Vainer
+ * @see Spatial#addMatParamOverride(com.jme3.material.MatParamOverride)
+ * @see Spatial#getWorldMatParamOverrides()
+ */
+public final class MatParamOverride extends MatParam {
+
+    private boolean enabled = true;
+
+    /**
+     * Serialization only. Do not use.
+     */
+    public MatParamOverride() {
+        super();
+    }
+
+    /**
+     * Create a new <code>MatParamOverride</code>.
+     *
+     * Overrides are created enabled by default.
+     *
+     * @param type The type of parameter.
+     * @param name The name of the parameter.
+     * @param value The value to set the material parameter to.
+     */
+    public MatParamOverride(VarType type, String name, Object value) {
+        super(type, name, value);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return super.equals(obj) && this.enabled == ((MatParamOverride) obj).enabled;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = super.hashCode();
+        hash = 59 * hash + (enabled ? 1 : 0);
+        return hash;
+    }
+
+    /**
+     * Determine if the <code>MatParamOverride</code> is enabled or disabled.
+     *
+     * @return true if enabled, false if disabled.
+     * @see #setEnabled(boolean)
+     */
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    /**
+     * Enable or disable this <code>MatParamOverride</code>.
+     *
+     * When disabled, the override will continue to propagate through the scene
+     * graph like before, but it will have no effect on materials. Overrides are
+     * enabled by default.
+     *
+     * @param enabled Whether to enable or disable this override.
+     */
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(enabled, "enabled", true);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        enabled = ic.readBoolean("enabled", true);
+    }
+}

+ 3 - 25
jme3-core/src/main/java/com/jme3/material/MatParamTexture.java

@@ -35,7 +35,6 @@ import com.jme3.export.InputCapsule;
 import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeImporter;
 import com.jme3.export.OutputCapsule;
-import com.jme3.renderer.Renderer;
 import com.jme3.shader.VarType;
 import com.jme3.texture.Texture;
 import com.jme3.texture.image.ColorSpace;
@@ -44,13 +43,11 @@ import java.io.IOException;
 public class MatParamTexture extends MatParam {
 
     private Texture texture;
-    private int unit;
     private ColorSpace colorSpace;
 
-    public MatParamTexture(VarType type, String name, Texture texture, int unit, ColorSpace colorSpace) {
+    public MatParamTexture(VarType type, String name, Texture texture, ColorSpace colorSpace) {
         super(type, name, texture);
         this.texture = texture;
-        this.unit = unit;
         this.colorSpace = colorSpace;
     }
 
@@ -92,37 +89,18 @@ public class MatParamTexture extends MatParam {
         this.colorSpace = colorSpace;
     }
 
-    public void setUnit(int unit) {
-        this.unit = unit;
-    }
-
-    public int getUnit() {
-        return unit;
-    }
-
-    @Override
-    public void apply(Renderer r, Technique technique) {
-        TechniqueDef techDef = technique.getDef();
-        r.setTexture(getUnit(), getTextureValue());
-        technique.updateUniformParam(getPrefixedName(), getVarType(), getUnit());
-    }
-
     @Override
     public void write(JmeExporter ex) throws IOException {
         super.write(ex);
         OutputCapsule oc = ex.getCapsule(this);
-        oc.write(unit, "texture_unit", -1);
-        
-        // For backwards compat
-        oc.write(texture, "texture", null);
+        oc.write(0, "texture_unit", -1);
+        oc.write(texture, "texture", null); // For backwards compatibility
     }
 
     @Override
     public void read(JmeImporter im) throws IOException {
         super.read(im);
         InputCapsule ic = im.getCapsule(this);
-        unit = ic.readInt("texture_unit", -1);
         texture = (Texture) value;
-        //texture = (Texture) ic.readSavable("texture", null);
     }
 }

+ 186 - 437
jme3-core/src/main/java/com/jme3/material/Material.java

@@ -44,18 +44,16 @@ import com.jme3.math.*;
 import com.jme3.renderer.Caps;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.Renderer;
-import com.jme3.renderer.RendererException;
 import com.jme3.renderer.queue.RenderQueue.Bucket;
 import com.jme3.scene.Geometry;
-import com.jme3.scene.Mesh;
-import com.jme3.scene.instancing.InstancedGeometry;
 import com.jme3.shader.Shader;
 import com.jme3.shader.Uniform;
+import com.jme3.shader.UniformBindingManager;
 import com.jme3.shader.VarType;
+import com.jme3.texture.Image;
 import com.jme3.texture.Texture;
 import com.jme3.texture.image.ColorSpace;
 import com.jme3.util.ListMap;
-import com.jme3.util.TempVars;
 import java.io.IOException;
 import java.util.*;
 import java.util.logging.Level;
@@ -77,32 +75,18 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
     // Version #2: Fixed issue with RenderState.apply*** flags not getting exported
     public static final int SAVABLE_VERSION = 2;
     private static final Logger logger = Logger.getLogger(Material.class.getName());
-    private static final RenderState additiveLight = new RenderState();
-    private static final RenderState depthOnly = new RenderState();
-    private static final Quaternion nullDirLight = new Quaternion(0, -1, 0, -1);
-
-    static {
-        depthOnly.setDepthTest(true);
-        depthOnly.setDepthWrite(true);
-        depthOnly.setFaceCullMode(RenderState.FaceCullMode.Back);
-        depthOnly.setColorWrite(false);
-
-        additiveLight.setBlendMode(RenderState.BlendMode.AlphaAdditive);
-        additiveLight.setDepthWrite(false);
-    }
+
     private AssetKey key;
     private String name;
     private MaterialDef def;
     private ListMap<String, MatParam> paramValues = new ListMap<String, MatParam>();
     private Technique technique;
     private HashMap<String, Technique> techniques = new HashMap<String, Technique>();
-    private int nextTexUnit = 0;
     private RenderState additionalState = null;
     private RenderState mergedRenderState = new RenderState();
     private boolean transparent = false;
     private boolean receivesShadows = false;
     private int sortingId = -1;
-    private transient ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
 
     public Material(MaterialDef def) {
         if (def == null) {
@@ -175,22 +159,29 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
      * @return The sorting ID used for sorting geometries for rendering.
      */
     public int getSortId() {
-        Technique t = getActiveTechnique();
-        if (sortingId == -1 && t != null && t.getShader() != null) {
-            int texId = -1;
+        if (sortingId == -1 && technique != null) {
+            sortingId = technique.getSortId() << 16;
+            int texturesSortId = 17;
             for (int i = 0; i < paramValues.size(); i++) {
                 MatParam param = paramValues.getValue(i);
-                if (param instanceof MatParamTexture) {
-                    MatParamTexture tex = (MatParamTexture) param;
-                    if (tex.getTextureValue() != null && tex.getTextureValue().getImage() != null) {
-                        if (texId == -1) {
-                            texId = 0;
-                        }
-                        texId += tex.getTextureValue().getImage().getId() % 0xff;
-                    }
+                if (!param.getVarType().isTextureType()) {
+                    continue;
+                }
+                Texture texture = (Texture) param.getValue();
+                if (texture == null) {
+                    continue;
+                }
+                Image image = texture.getImage();
+                if (image == null) {
+                    continue;
+                }
+                int textureId = image.getId();
+                if (textureId == -1) {
+                    textureId = 0;
                 }
+                texturesSortId = texturesSortId * 23 + textureId;
             }
-            sortingId = texId + t.getShader().getId() * 1000;
+            sortingId |= texturesSortId & 0xFFFF;
         }
         return sortingId;
     }
@@ -215,6 +206,8 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
                 mat.paramValues.put(entry.getKey(), entry.getValue().clone());
             }
 
+            mat.sortingId = -1;
+            
             return mat;
         } catch (CloneNotSupportedException ex) {
             throw new AssertionError(ex);
@@ -258,8 +251,14 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
             // E.g. if user chose custom technique for one material but
             // uses default technique for other material, the materials
             // are not equal.
-            String thisDefName = this.technique != null ? this.technique.getDef().getName() : "Default";
-            String otherDefName = other.technique != null ? other.technique.getDef().getName() : "Default";
+            String thisDefName = this.technique != null
+                    ? this.technique.getDef().getName()
+                    : TechniqueDef.DEFAULT_TECHNIQUE_NAME;
+
+            String otherDefName = other.technique != null
+                    ? other.technique.getDef().getName()
+                    : TechniqueDef.DEFAULT_TECHNIQUE_NAME;
+
             if (!thisDefName.equals(otherDefName)) {
                 return false;
             }
@@ -444,7 +443,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
      *
      * @see #setParam(java.lang.String, com.jme3.shader.VarType, java.lang.Object)
      */
-    public ListMap getParamsMap() {
+    public ListMap<String, MatParam> getParamsMap() {
         return paramValues;
     }
 
@@ -504,16 +503,6 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
 
         paramValues.remove(name);
         if (matParam instanceof MatParamTexture) {
-            int texUnit = ((MatParamTexture) matParam).getUnit();
-            nextTexUnit--;
-            for (MatParam param : paramValues.values()) {
-                if (param instanceof MatParamTexture) {
-                    MatParamTexture texParam = (MatParamTexture) param;
-                    if (texParam.getUnit() > texUnit) {
-                        texParam.setUnit(texParam.getUnit() - 1);
-                    }
-                }
-            }
             sortingId = -1;
         }
         if (technique != null) {
@@ -556,13 +545,13 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
                         + "Linear using texture.getImage.setColorSpace().",
                         new Object[]{value.getName(), value.getImage().getColorSpace().name(), name});
             }
-            paramValues.put(name, new MatParamTexture(type, name, value, nextTexUnit++, null));
+            paramValues.put(name, new MatParamTexture(type, name, value, null));
         } else {
             val.setTextureValue(value);
         }
 
         if (technique != null) {
-            technique.notifyParamChanged(name, type, nextTexUnit - 1);
+            technique.notifyParamChanged(name, type, value);
         }
 
         // need to recompute sort ID
@@ -695,277 +684,21 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         setParam(name, VarType.Vector4, value);
     }
 
-    private ColorRGBA getAmbientColor(LightList lightList, boolean removeLights) {
-        ambientLightColor.set(0, 0, 0, 1);
-        for (int j = 0; j < lightList.size(); j++) {
-            Light l = lightList.get(j);
-            if (l instanceof AmbientLight) {
-                ambientLightColor.addLocal(l.getColor());
-                if(removeLights){
-                    lightList.remove(l);
-                }
-            }
-        }
-        ambientLightColor.a = 1.0f;
-        return ambientLightColor;
-    }
-
-    private static void renderMeshFromGeometry(Renderer renderer, Geometry geom) {
-        Mesh mesh = geom.getMesh();
-        int lodLevel = geom.getLodLevel();
-        if (geom instanceof InstancedGeometry) {
-            InstancedGeometry instGeom = (InstancedGeometry) geom;
-            int numInstances = instGeom.getActualNumInstances();
-            if (numInstances == 0) {
-                return;
-            }
-            if (renderer.getCaps().contains(Caps.MeshInstancing)) {
-                renderer.renderMesh(mesh, lodLevel, numInstances, instGeom.getAllInstanceData());
-            } else {
-                throw new RendererException("Mesh instancing is not supported by the video hardware");
-            }
-        } else {
-            renderer.renderMesh(mesh, lodLevel, 1, null);
-        }
-    }
-
-    /**
-     * Uploads the lights in the light list as two uniform arrays.<br/><br/> *
-     * <p>
-     * <code>uniform vec4 g_LightColor[numLights];</code><br/> //
-     * g_LightColor.rgb is the diffuse/specular color of the light.<br/> //
-     * g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point, <br/> //
-     * 2 = Spot. <br/> <br/>
-     * <code>uniform vec4 g_LightPosition[numLights];</code><br/> //
-     * g_LightPosition.xyz is the position of the light (for point lights)<br/>
-     * // or the direction of the light (for directional lights).<br/> //
-     * g_LightPosition.w is the inverse radius (1/r) of the light (for
-     * attenuation) <br/> </p>
-     */
-    protected int updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex) {
-        if (numLights == 0) { // this shader does not do lighting, ignore.
-            return 0;
-        }
-
-        Uniform lightData = shader.getUniform("g_LightData");
-        lightData.setVector4Length(numLights * 3);//8 lights * max 3
-        Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
-
-
-        if (startIndex != 0) {
-            // apply additive blending for 2nd and future passes
-            rm.getRenderer().applyRenderState(additiveLight);
-            ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
-        }else{
-            ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList,true));
-        }
-
-        int lightDataIndex = 0;
-        TempVars vars = TempVars.get();
-        Vector4f tmpVec = vars.vect4f1;
-        int curIndex;
-        int endIndex = numLights + startIndex;
-        for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) {
-
-
-                Light l = lightList.get(curIndex);
-                if(l.getType() == Light.Type.Ambient){
-                    endIndex++;
-                    continue;
-                }
-                ColorRGBA color = l.getColor();
-                //Color
-                lightData.setVector4InArray(color.getRed(),
-                        color.getGreen(),
-                        color.getBlue(),
-                        l.getType().getId(),
-                        lightDataIndex);
-                lightDataIndex++;
-
-                switch (l.getType()) {
-                    case Directional:
-                        DirectionalLight dl = (DirectionalLight) l;
-                        Vector3f dir = dl.getDirection();
-                        //Data directly sent in view space to avoid a matrix mult for each pixel
-                        tmpVec.set(dir.getX(), dir.getY(), dir.getZ(), 0.0f);
-                        rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
-//                        tmpVec.divideLocal(tmpVec.w);
-//                        tmpVec.normalizeLocal();
-                        lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), -1, lightDataIndex);
-                        lightDataIndex++;
-                        //PADDING
-                        lightData.setVector4InArray(0,0,0,0, lightDataIndex);
-                        lightDataIndex++;
-                        break;
-                    case Point:
-                        PointLight pl = (PointLight) l;
-                        Vector3f pos = pl.getPosition();
-                        float invRadius = pl.getInvRadius();
-                        tmpVec.set(pos.getX(), pos.getY(), pos.getZ(), 1.0f);
-                        rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
-                        //tmpVec.divideLocal(tmpVec.w);
-                        lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRadius, lightDataIndex);
-                        lightDataIndex++;
-                        //PADDING
-                        lightData.setVector4InArray(0,0,0,0, lightDataIndex);
-                        lightDataIndex++;
-                        break;
-                    case Spot:
-                        SpotLight sl = (SpotLight) l;
-                        Vector3f pos2 = sl.getPosition();
-                        Vector3f dir2 = sl.getDirection();
-                        float invRange = sl.getInvSpotRange();
-                        float spotAngleCos = sl.getPackedAngleCos();
-                        tmpVec.set(pos2.getX(), pos2.getY(), pos2.getZ(),  1.0f);
-                        rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
-                       // tmpVec.divideLocal(tmpVec.w);
-                        lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRange, lightDataIndex);
-                        lightDataIndex++;
-
-                        //We transform the spot direction in view space here to save 5 varying later in the lighting shader
-                        //one vec4 less and a vec4 that becomes a vec3
-                        //the downside is that spotAngleCos decoding happens now in the frag shader.
-                        tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(),  0.0f);
-                        rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
-                        tmpVec.normalizeLocal();
-                        lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex);
-                        lightDataIndex++;
-                        break;
-                    default:
-                        throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
-                }
-        }
-        vars.release();
-        //Padding of unsued buffer space
-        while(lightDataIndex < numLights * 3) {
-            lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex);
-            lightDataIndex++;
-        }
-        return curIndex;
-    }
-
-    protected void renderMultipassLighting(Shader shader, Geometry g, LightList lightList, RenderManager rm) {
-
-        Renderer r = rm.getRenderer();
-        Uniform lightDir = shader.getUniform("g_LightDirection");
-        Uniform lightColor = shader.getUniform("g_LightColor");
-        Uniform lightPos = shader.getUniform("g_LightPosition");
-        Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
-        boolean isFirstLight = true;
-        boolean isSecondLight = false;
-
-        for (int i = 0; i < lightList.size(); i++) {
-            Light l = lightList.get(i);
-            if (l instanceof AmbientLight) {
-                continue;
-            }
-
-            if (isFirstLight) {
-                // set ambient color for first light only
-                ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList, false));
-                isFirstLight = false;
-                isSecondLight = true;
-            } else if (isSecondLight) {
-                ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
-                // apply additive blending for 2nd and future lights
-                r.applyRenderState(additiveLight);
-                isSecondLight = false;
-            }
-
-            TempVars vars = TempVars.get();
-            Quaternion tmpLightDirection = vars.quat1;
-            Quaternion tmpLightPosition = vars.quat2;
-            ColorRGBA tmpLightColor = vars.color;
-            Vector4f tmpVec = vars.vect4f1;
-
-            ColorRGBA color = l.getColor();
-            tmpLightColor.set(color);
-            tmpLightColor.a = l.getType().getId();
-            lightColor.setValue(VarType.Vector4, tmpLightColor);
-
-            switch (l.getType()) {
-                case Directional:
-                    DirectionalLight dl = (DirectionalLight) l;
-                    Vector3f dir = dl.getDirection();
-                    //FIXME : there is an inconstency here due to backward
-                    //compatibility of the lighting shader.
-                    //The directional light direction is passed in the
-                    //LightPosition uniform. The lighting shader needs to be
-                    //reworked though in order to fix this.
-                    tmpLightPosition.set(dir.getX(), dir.getY(), dir.getZ(), -1);
-                    lightPos.setValue(VarType.Vector4, tmpLightPosition);
-                    tmpLightDirection.set(0, 0, 0, 0);
-                    lightDir.setValue(VarType.Vector4, tmpLightDirection);
-                    break;
-                case Point:
-                    PointLight pl = (PointLight) l;
-                    Vector3f pos = pl.getPosition();
-                    float invRadius = pl.getInvRadius();
-
-                    tmpLightPosition.set(pos.getX(), pos.getY(), pos.getZ(), invRadius);
-                    lightPos.setValue(VarType.Vector4, tmpLightPosition);
-                    tmpLightDirection.set(0, 0, 0, 0);
-                    lightDir.setValue(VarType.Vector4, tmpLightDirection);
-                    break;
-                case Spot:
-                    SpotLight sl = (SpotLight) l;
-                    Vector3f pos2 = sl.getPosition();
-                    Vector3f dir2 = sl.getDirection();
-                    float invRange = sl.getInvSpotRange();
-                    float spotAngleCos = sl.getPackedAngleCos();
-
-                    tmpLightPosition.set(pos2.getX(), pos2.getY(), pos2.getZ(), invRange);
-                    lightPos.setValue(VarType.Vector4, tmpLightPosition);
-
-                    //We transform the spot direction in view space here to save 5 varying later in the lighting shader
-                    //one vec4 less and a vec4 that becomes a vec3
-                    //the downside is that spotAngleCos decoding happens now in the frag shader.
-                    tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0);
-                    rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
-                    tmpLightDirection.set(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos);
-
-                    lightDir.setValue(VarType.Vector4, tmpLightDirection);
-
-                    break;
-                default:
-                    throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
-            }
-            vars.release();
-            r.setShader(shader);
-            renderMeshFromGeometry(r, g);
-        }
-
-        if (isFirstLight) {
-            // Either there are no lights at all, or only ambient lights.
-            // Render a dummy "normal light" so we can see the ambient color.
-            ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList, false));
-            lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha);
-            lightPos.setValue(VarType.Vector4, nullDirLight);
-            r.setShader(shader);
-            renderMeshFromGeometry(r, g);
-        }
-    }
-
     /**
      * Select the technique to use for rendering this material.
      * <p>
-     * If <code>name</code> is "Default", then one of the
-     * {@link MaterialDef#getDefaultTechniques() default techniques}
-     * on the material will be selected. Otherwise, the named technique
-     * will be found in the material definition.
-     * <p>
      * Any candidate technique for selection (either default or named)
      * must be verified to be compatible with the system, for that, the
      * <code>renderManager</code> is queried for capabilities.
      *
-     * @param name The name of the technique to select, pass "Default" to
-     * select one of the default techniques.
+     * @param name The name of the technique to select, pass
+     * {@link TechniqueDef#DEFAULT_TECHNIQUE_NAME} to select one of the default
+     * techniques.
      * @param renderManager The {@link RenderManager render manager}
      * to query for capabilities.
      *
-     * @throws IllegalArgumentException If "Default" is passed and no default
-     * techniques are available on the material definition, or if a name
-     * is passed but there's no technique by that name.
+     * @throws IllegalArgumentException If no technique exists with the given
+     * name.
      * @throws UnsupportedOperationException If no candidate technique supports
      * the system capabilities.
      */
@@ -974,49 +707,34 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         Technique tech = techniques.get(name);
         // When choosing technique, we choose one that
         // supports all the caps.
-        EnumSet<Caps> rendererCaps = renderManager.getRenderer().getCaps();
         if (tech == null) {
+            EnumSet<Caps> rendererCaps = renderManager.getRenderer().getCaps();
+            List<TechniqueDef> techDefs = def.getTechniqueDefs(name);
 
-            if (name.equals("Default")) {
-                List<TechniqueDef> techDefs = def.getDefaultTechniques();
-                if (techDefs == null || techDefs.isEmpty()) {
-                    throw new IllegalArgumentException("No default techniques are available on material '" + def.getName() + "'");
-                }
+            if (techDefs == null || techDefs.isEmpty()) {
+                throw new IllegalArgumentException(
+                        String.format("The requested technique %s is not available on material %s", name, def.getName()));
+            }
 
-                TechniqueDef lastTech = null;
-                for (TechniqueDef techDef : techDefs) {
-                    if (rendererCaps.containsAll(techDef.getRequiredCaps())) {
-                        // use the first one that supports all the caps
-                        tech = new Technique(this, techDef);
-                        techniques.put(name, tech);
-                        if(tech.getDef().getLightMode() == renderManager.getPreferredLightMode() ||
-                               tech.getDef().getLightMode() == LightMode.Disable){
-                            break;
-                        }
+            TechniqueDef lastTech = null;
+            for (TechniqueDef techDef : techDefs) {
+                if (rendererCaps.containsAll(techDef.getRequiredCaps())) {
+                    // use the first one that supports all the caps
+                    tech = new Technique(this, techDef);
+                    techniques.put(name, tech);
+                    if (tech.getDef().getLightMode() == renderManager.getPreferredLightMode()
+                            || tech.getDef().getLightMode() == LightMode.Disable) {
+                        break;
                     }
-                    lastTech = techDef;
-                }
-                if (tech == null) {
-                    throw new UnsupportedOperationException("No default technique on material '" + def.getName() + "'\n"
-                            + " is supported by the video hardware. The caps "
-                            + lastTech.getRequiredCaps() + " are required.");
-                }
-
-            } else {
-                // create "special" technique instance
-                TechniqueDef techDef = def.getTechniqueDef(name);
-                if (techDef == null) {
-                    throw new IllegalArgumentException("For material " + def.getName() + ", technique not found: " + name);
-                }
-
-                if (!rendererCaps.containsAll(techDef.getRequiredCaps())) {
-                    throw new UnsupportedOperationException("The explicitly chosen technique '" + name + "' on material '" + def.getName() + "'\n"
-                            + "requires caps " + techDef.getRequiredCaps() + " which are not "
-                            + "supported by the video renderer");
                 }
-
-                tech = new Technique(this, techDef);
-                techniques.put(name, tech);
+                lastTech = techDef;
+            }
+            if (tech == null) {
+                throw new UnsupportedOperationException(
+                        String.format("No technique '%s' on material "
+                                + "'%s' is supported by the video hardware. "
+                                + "The capabilities %s are required.",
+                                name, def.getName(), lastTech.getRequiredCaps()));
             }
         } else if (technique == tech) {
             // attempting to switch to an already
@@ -1025,20 +743,82 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         }
 
         technique = tech;
-        tech.makeCurrent(def.getAssetManager(), true, rendererCaps, renderManager);
+        tech.notifyTechniqueSwitched();
 
         // shader was changed
         sortingId = -1;
     }
 
-    private void autoSelectTechnique(RenderManager rm) {
-        if (technique == null) {
-            selectTechnique("Default", rm);
-        } else {
-            technique.makeCurrent(def.getAssetManager(), false, rm.getRenderer().getCaps(), rm);
+    private int applyOverrides(Renderer renderer, Shader shader, List<MatParamOverride> overrides, int unit) {
+        for (MatParamOverride override : overrides) {
+            VarType type = override.getVarType();
+
+            MatParam paramDef = def.getMaterialParam(override.getName());
+
+            if (paramDef == null || paramDef.getVarType() != type || !override.isEnabled()) {
+                continue;
+            }
+
+            Uniform uniform = shader.getUniform(override.getPrefixedName());
+
+            if (override.getValue() != null) {
+                if (type.isTextureType()) {
+                    renderer.setTexture(unit, (Texture) override.getValue());
+                    uniform.setValue(VarType.Int, unit);
+                    unit++;
+                } else {
+                    uniform.setValue(type, override.getValue());
+                }
+            } else {
+                uniform.clearValue();
+            }
         }
+        return unit;
     }
 
+    private void updateShaderMaterialParameters(Renderer renderer, Shader shader,
+            List<MatParamOverride> worldOverrides, List<MatParamOverride> forcedOverrides) {
+
+        int unit = 0;
+        if (worldOverrides != null) {
+            unit = applyOverrides(renderer, shader, worldOverrides, unit);
+        }
+        if (forcedOverrides != null) {
+            unit = applyOverrides(renderer, shader, forcedOverrides, unit);
+        }
+
+        for (int i = 0; i < paramValues.size(); i++) {
+            MatParam param = paramValues.getValue(i);
+            VarType type = param.getVarType();
+            Uniform uniform = shader.getUniform(param.getPrefixedName());
+
+            if (uniform.isSetByCurrentMaterial()) {
+                continue;
+            }
+
+            if (type.isTextureType()) {
+                renderer.setTexture(unit, (Texture) param.getValue());
+                uniform.setValue(VarType.Int, unit);
+                unit++;
+            } else {
+                uniform.setValue(type, param.getValue());
+            }
+        }
+
+    }
+
+    private void updateRenderState(RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) {
+        if (renderManager.getForcedRenderState() != null) {
+            renderer.applyRenderState(renderManager.getForcedRenderState());
+        } else {
+            if (techniqueDef.getRenderState() != null) {
+                renderer.applyRenderState(techniqueDef.getRenderState().copyMergedTo(additionalState, mergedRenderState));
+            } else {
+                renderer.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState));
+            }
+        }
+    }
+    
     /**
      * Preloads this material for the given render manager.
      * <p>
@@ -1046,20 +826,23 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
      * used for rendering, there won't be any delay since the material has
      * been already been setup for rendering.
      *
-     * @param rm The render manager to preload for
+     * @param renderManager The render manager to preload for
      */
-    public void preload(RenderManager rm) {
-        autoSelectTechnique(rm);
-
-        Renderer r = rm.getRenderer();
-        TechniqueDef techDef = technique.getDef();
+    public void preload(RenderManager renderManager) {
+        if (technique == null) {
+            selectTechnique(TechniqueDef.DEFAULT_TECHNIQUE_NAME, renderManager);
+        }
+        TechniqueDef techniqueDef = technique.getDef();
+        Renderer renderer = renderManager.getRenderer();
+        EnumSet<Caps> rendererCaps = renderer.getCaps();
 
-        Collection<MatParam> params = paramValues.values();
-        for (MatParam param : params) {
-            param.apply(r, technique);
+        if (techniqueDef.isNoRender()) {
+            return;
         }
 
-        r.setShader(technique.getShader());
+        Shader shader = technique.makeCurrent(renderManager, null, null, null, rendererCaps);
+        updateShaderMaterialParameters(renderer, shader, null, null);
+        renderManager.getRenderer().setShader(shader);
     }
 
     private void clearUniformsSetByCurrent(Shader shader) {
@@ -1141,80 +924,46 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
      * </ul>
      * </ul>
      *
-     * @param geom The geometry to render
+     * @param geometry The geometry to render
      * @param lights Presorted and filtered light list to use for rendering
-     * @param rm The render manager requesting the rendering
+     * @param renderManager The render manager requesting the rendering
      */
-    public void render(Geometry geom, LightList lights, RenderManager rm) {
-        autoSelectTechnique(rm);
-        TechniqueDef techDef = technique.getDef();
-
-        if (techDef.isNoRender()) return;
-
-        Renderer r = rm.getRenderer();
-
-        if (rm.getForcedRenderState() != null) {
-            r.applyRenderState(rm.getForcedRenderState());
-        } else {
-            if (techDef.getRenderState() != null) {
-                r.applyRenderState(techDef.getRenderState().copyMergedTo(additionalState, mergedRenderState));
-            } else {
-                r.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState));
-            }
-        }
-
-
-        // update camera and world matrices
-        // NOTE: setWorldTransform should have been called already
-
-        // reset unchanged uniform flag
-        clearUniformsSetByCurrent(technique.getShader());
-        rm.updateUniformBindings(technique.getWorldBindUniforms());
-
-
-        // setup textures and uniforms
-        for (int i = 0; i < paramValues.size(); i++) {
-            MatParam param = paramValues.getValue(i);
-            param.apply(r, technique);
+    public void render(Geometry geometry, LightList lights, RenderManager renderManager) {
+        if (technique == null) {
+            selectTechnique(TechniqueDef.DEFAULT_TECHNIQUE_NAME, renderManager);
         }
-
-        Shader shader = technique.getShader();
-
-        // send lighting information, if needed
-        switch (techDef.getLightMode()) {
-            case Disable:
-                break;
-            case SinglePass:
-                int nbRenderedLights = 0;
-                resetUniformsNotSetByCurrent(shader);
-                if (lights.size() == 0) {
-                    nbRenderedLights = updateLightListUniforms(shader, geom, lights, rm.getSinglePassLightBatchSize(), rm, 0);
-                    r.setShader(shader);
-                    renderMeshFromGeometry(r, geom);
-                } else {
-                    while (nbRenderedLights < lights.size()) {
-                        nbRenderedLights = updateLightListUniforms(shader, geom, lights, rm.getSinglePassLightBatchSize(), rm, nbRenderedLights);
-                        r.setShader(shader);
-                        renderMeshFromGeometry(r, geom);
-                    }
-                }
-                return;
-            case FixedPipeline:
-                throw new IllegalArgumentException("OpenGL1 is not supported");
-            case MultiPass:
-                // NOTE: Special case!
-                resetUniformsNotSetByCurrent(shader);
-                renderMultipassLighting(shader, geom, lights, rm);
-                // very important, notice the return statement!
-                return;
+        
+        TechniqueDef techniqueDef = technique.getDef();
+        Renderer renderer = renderManager.getRenderer();
+        EnumSet<Caps> rendererCaps = renderer.getCaps();
+        
+        if (techniqueDef.isNoRender()) {
+            return;
         }
 
-        // upload and bind shader
-        // any unset uniforms will be set to 0
+        // Apply render state
+        updateRenderState(renderManager, renderer, techniqueDef);
+
+        // Get world overrides
+        List<MatParamOverride> overrides = geometry.getWorldMatParamOverrides();
+
+        // Select shader to use
+        Shader shader = technique.makeCurrent(renderManager, overrides, renderManager.getForcedMatParams(), lights, rendererCaps);
+        
+        // Begin tracking which uniforms were changed by material.
+        clearUniformsSetByCurrent(shader);
+        
+        // Set uniform bindings
+        renderManager.updateUniformBindings(shader);
+        
+        // Set material parameters
+        updateShaderMaterialParameters(renderer, shader, overrides, renderManager.getForcedMatParams());
+        
+        // Clear any uniforms not changed by material.
         resetUniformsNotSetByCurrent(shader);
-        r.setShader(shader);
-
-        renderMeshFromGeometry(r, geom);
+        
+        // Delegate rendering to the technique
+        technique.render(renderManager, shader, geometry, lights);
     }
 
     /**
@@ -1239,6 +988,14 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         oc.write(name, "name", null);
         oc.writeStringSavableMap(paramValues, "parameters", null);
     }
+    
+    @Override
+    public String toString() {
+        return "Material[name=" + name + 
+                ", def=" + def.getName() + 
+                ", tech=" + technique.getDef().getName() + 
+                "]";
+    }
 
     public void read(JmeImporter im) throws IOException {
         InputCapsule ic = im.getCapsule(this);
@@ -1296,11 +1053,6 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
             MatParam param = entry.getValue();
             if (param instanceof MatParamTexture) {
                 MatParamTexture texVal = (MatParamTexture) param;
-
-                if (nextTexUnit < texVal.getUnit() + 1) {
-                    nextTexUnit = texVal.getUnit() + 1;
-                }
-
                 // the texture failed to load for this param
                 // do not add to param values
                 if (texVal.getTextureValue() == null || texVal.getTextureValue().getImage() == null) {
@@ -1335,14 +1087,11 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
             // Try to guess values of "apply" render state based on defaults
             // if value != default then set apply to true
             additionalState.applyPolyOffset = additionalState.offsetEnabled;
-            additionalState.applyAlphaFallOff = additionalState.alphaTest;
-            additionalState.applyAlphaTest = additionalState.alphaTest;
             additionalState.applyBlendMode = additionalState.blendMode != BlendMode.Off;
             additionalState.applyColorWrite = !additionalState.colorWrite;
             additionalState.applyCullMode = additionalState.cullMode != FaceCullMode.Back;
             additionalState.applyDepthTest = !additionalState.depthTest;
             additionalState.applyDepthWrite = !additionalState.depthWrite;
-            additionalState.applyPointSprite = additionalState.pointSprite;
             additionalState.applyStencilTest = additionalState.stencilTest;
             additionalState.applyWireFrame = additionalState.wireframe;
         }

+ 16 - 31
jme3-core/src/main/java/com/jme3/material/MaterialDef.java

@@ -32,6 +32,7 @@
 package com.jme3.material;
 
 import com.jme3.asset.AssetManager;
+import com.jme3.renderer.Caps;
 import com.jme3.shader.VarType;
 import com.jme3.texture.image.ColorSpace;
 import java.util.*;
@@ -51,8 +52,7 @@ public class MaterialDef {
     private String assetName;
     private AssetManager assetManager;
 
-    private List<TechniqueDef> defaultTechs;
-    private Map<String, TechniqueDef> techniques;
+    private Map<String, List<TechniqueDef>> techniques;
     private Map<String, MatParam> matParams;
 
     /**
@@ -70,9 +70,8 @@ public class MaterialDef {
     public MaterialDef(AssetManager assetManager, String name){
         this.assetManager = assetManager;
         this.name = name;
-        techniques = new HashMap<String, TechniqueDef>();
+        techniques = new HashMap<String, List<TechniqueDef>>();
         matParams = new HashMap<String, MatParam>();
-        defaultTechs = new ArrayList<TechniqueDef>();
         logger.log(Level.FINE, "Loaded material definition: {0}", name);
     }
 
@@ -135,7 +134,7 @@ public class MaterialDef {
      * @see ColorSpace
      */
     public void addMaterialParamTexture(VarType type, String name, ColorSpace colorSpace) {
-        matParams.put(name, new MatParamTexture(type, name, null , 0, colorSpace));
+        matParams.put(name, new MatParamTexture(type, name, null, colorSpace));
     }
     
     /**
@@ -164,40 +163,26 @@ public class MaterialDef {
 
     /**
      * Adds a new technique definition to this material definition.
-     * <p>
-     * If the technique name is "Default", it will be added
-     * to the list of {@link MaterialDef#getDefaultTechniques() default techniques}.
-     * 
+     *
      * @param technique The technique definition to add.
      */
     public void addTechniqueDef(TechniqueDef technique) {
-        if (technique.getName().equals("Default")) {
-            defaultTechs.add(technique);
-        } else {
-            techniques.put(technique.getName(), technique);
+        List<TechniqueDef> list = techniques.get(technique.getName());
+        if (list == null) {
+            list = new ArrayList<>();
+            techniques.put(technique.getName(), list);
         }
+        list.add(technique);
     }
 
     /**
-     * Returns a list of all default techniques.
-     * 
-     * @return a list of all default techniques.
-     */
-    public List<TechniqueDef> getDefaultTechniques(){
-        return defaultTechs;
-    }
-
-    /**
-     * Returns a technique definition with the given name.
-     * This does not include default techniques which can be
-     * retrieved via {@link MaterialDef#getDefaultTechniques() }.
-     * 
-     * @param name The name of the technique definition to find
-     * 
-     * @return The technique definition, or null if cannot be found.
+     * Returns technique definitions with the given name.
+       * 
+     * @param name The name of the technique definitions to find
+       * 
+     * @return The technique definitions, or null if cannot be found.
      */
-    public TechniqueDef getTechniqueDef(String name) {
+    public List<TechniqueDef> getTechniqueDefs(String name) {
         return techniques.get(name);
     }
-
 }

+ 252 - 195
jme3-core/src/main/java/com/jme3/material/RenderState.java

@@ -75,12 +75,11 @@ public class RenderState implements Cloneable, Savable {
 
     /**
      * <code>TestFunction</code> specifies the testing function for stencil test
-     * function and alpha test function.
+     * function.
      *
-     * <p>The functions work similarly as described except that for stencil
-     * test function, the reference value given in the stencil command is
-     * the input value while the reference is the value already in the stencil
-     * buffer.
+     * <p>
+     * The reference value given in the stencil command is the input value while
+     * the reference is the value already in the stencil buffer.
      */
     public enum TestFunction {
 
@@ -118,7 +117,94 @@ public class RenderState implements Cloneable, Savable {
         /**
          * The test always passes
          */
-        Always,}
+        Always
+    }
+
+    /**
+     * <code>BlendEquation</code> specifies the blending equation to combine
+     * pixels.
+     */
+    public enum BlendEquation {
+        /**
+         * Sets the blend equation so that the source and destination data are
+         * added. (Default) Clamps to [0,1] Useful for things like antialiasing
+         * and transparency.
+         */
+        Add,
+        /**
+         * Sets the blend equation so that the source and destination data are
+         * subtracted (Src - Dest). Clamps to [0,1] Falls back to Add if
+         * supportsSubtract is false.
+         */
+        Subtract,
+        /**
+         * Same as Subtract, but the order is reversed (Dst - Src). Clamps to
+         * [0,1] Falls back to Add if supportsSubtract is false.
+         */
+        ReverseSubtract,
+        /**
+         * Sets the blend equation so that each component of the result color is
+         * the minimum of the corresponding components of the source and
+         * destination colors. This and Max are useful for applications that
+         * analyze image data (image thresholding against a constant color, for
+         * example). Falls back to Add if supportsMinMax is false.
+         */
+        Min,
+        /**
+         * Sets the blend equation so that each component of the result color is
+         * the maximum of the corresponding components of the source and
+         * destination colors. This and Min are useful for applications that
+         * analyze image data (image thresholding against a constant color, for
+         * example). Falls back to Add if supportsMinMax is false.
+         */
+        Max
+    }
+    
+    /**
+     * <code>BlendEquationAlpha</code> specifies the blending equation to
+     * combine pixels for the alpha component.
+     */
+    public enum BlendEquationAlpha {
+        /**
+         * Sets the blend equation to be the same as the one defined by
+         * {@link #blendEquation}.
+         *
+         */
+        InheritColor,
+        /**
+         * Sets the blend equation so that the source and destination data are
+         * added. (Default) Clamps to [0,1] Useful for things like antialiasing
+         * and transparency.
+         */
+        Add,
+        /**
+         * Sets the blend equation so that the source and destination data are
+         * subtracted (Src - Dest). Clamps to [0,1] Falls back to Add if
+         * supportsSubtract is false.
+         */
+        Subtract,
+        /**
+         * Same as Subtract, but the order is reversed (Dst - Src). Clamps to
+         * [0,1] Falls back to Add if supportsSubtract is false.
+         */
+        ReverseSubtract,
+        /**
+         * Sets the blend equation so that the result alpha is the minimum of
+         * the source alpha and destination alpha. This and Max are useful for
+         * applications that analyze image data (image thresholding against a
+         * constant color, for example). Falls back to Add if supportsMinMax is
+         * false.
+         */
+        Min,
+        /**
+         * sSets the blend equation so that the result alpha is the maximum of
+         * the source alpha and destination alpha. This and Min are useful for
+         * applications that analyze image data (image thresholding against a
+         * constant color, for example). Falls back to Add if supportsMinMax is
+         * false.
+         */
+        Max
+    }
 
     /**
      * <code>BlendMode</code> specifies the blending operation to use.
@@ -276,19 +362,16 @@ public class RenderState implements Cloneable, Savable {
     }
 
     static {
-        ADDITIONAL.applyPointSprite = false;
         ADDITIONAL.applyWireFrame = false;
         ADDITIONAL.applyCullMode = false;
         ADDITIONAL.applyDepthWrite = false;
         ADDITIONAL.applyDepthTest = false;
         ADDITIONAL.applyColorWrite = false;
+        ADDITIONAL.applyBlendEquation = false;
+        ADDITIONAL.applyBlendEquationAlpha = false;
         ADDITIONAL.applyBlendMode = false;
-        ADDITIONAL.applyAlphaTest = false;
-        ADDITIONAL.applyAlphaFallOff = false;
         ADDITIONAL.applyPolyOffset = false;
     }
-    boolean pointSprite = false;
-    boolean applyPointSprite = true;
     boolean wireframe = false;
     boolean applyWireFrame = true;
     FaceCullMode cullMode = FaceCullMode.Back;
@@ -299,12 +382,12 @@ public class RenderState implements Cloneable, Savable {
     boolean applyDepthTest = true;
     boolean colorWrite = true;
     boolean applyColorWrite = true;
+    BlendEquation blendEquation = BlendEquation.Add;
+    boolean applyBlendEquation = true;
+    BlendEquationAlpha blendEquationAlpha = BlendEquationAlpha.InheritColor;
+    boolean applyBlendEquationAlpha = true;
     BlendMode blendMode = BlendMode.Off;
     boolean applyBlendMode = true;
-    boolean alphaTest = false;
-    boolean applyAlphaTest = true;
-    float alphaFallOff = 0;
-    boolean applyAlphaFallOff = true;
     float offsetFactor = 0;
     float offsetUnits = 0;
     boolean offsetEnabled = false;
@@ -315,10 +398,7 @@ public class RenderState implements Cloneable, Savable {
     boolean applyLineWidth = false;
     TestFunction depthFunc = TestFunction.LessOrEqual;
     //by default depth func will be applied anyway if depth test is applied
-    boolean applyDepthFunc = false;    
-    //by default alpha func will be applied anyway if alpha test is applied
-    TestFunction alphaFunc = TestFunction.Greater;    
-    boolean applyAlphaFunc = false;
+    boolean applyDepthFunc = false;
     StencilOperation frontStencilStencilFailOperation = StencilOperation.Keep;
     StencilOperation frontStencilDepthFailOperation = StencilOperation.Keep;
     StencilOperation frontStencilDepthPassOperation = StencilOperation.Keep;
@@ -331,15 +411,13 @@ public class RenderState implements Cloneable, Savable {
 
     public void write(JmeExporter ex) throws IOException {
         OutputCapsule oc = ex.getCapsule(this);
-        oc.write(pointSprite, "pointSprite", false);
+        oc.write(true, "pointSprite", false);
         oc.write(wireframe, "wireframe", false);
         oc.write(cullMode, "cullMode", FaceCullMode.Back);
         oc.write(depthWrite, "depthWrite", true);
         oc.write(depthTest, "depthTest", true);
         oc.write(colorWrite, "colorWrite", true);
         oc.write(blendMode, "blendMode", BlendMode.Off);
-        oc.write(alphaTest, "alphaTest", false);
-        oc.write(alphaFallOff, "alphaFallOff", 0);
         oc.write(offsetEnabled, "offsetEnabled", false);
         oc.write(offsetFactor, "offsetFactor", 0);
         oc.write(offsetUnits, "offsetUnits", 0);
@@ -352,38 +430,34 @@ public class RenderState implements Cloneable, Savable {
         oc.write(backStencilDepthPassOperation, "backStencilDepthPassOperation", StencilOperation.Keep);
         oc.write(frontStencilFunction, "frontStencilFunction", TestFunction.Always);
         oc.write(backStencilFunction, "backStencilFunction", TestFunction.Always);
+        oc.write(blendEquation, "blendEquation", BlendEquation.Add);
+        oc.write(blendEquationAlpha, "blendEquationAlpha", BlendEquationAlpha.InheritColor);
         oc.write(depthFunc, "depthFunc", TestFunction.LessOrEqual);
-        oc.write(alphaFunc, "alphaFunc", TestFunction.Greater);
         oc.write(lineWidth, "lineWidth", 1);
 
         // Only "additional render state" has them set to false by default
-        oc.write(applyPointSprite, "applyPointSprite", true);
         oc.write(applyWireFrame, "applyWireFrame", true);
         oc.write(applyCullMode, "applyCullMode", true);
         oc.write(applyDepthWrite, "applyDepthWrite", true);
         oc.write(applyDepthTest, "applyDepthTest", true);
         oc.write(applyColorWrite, "applyColorWrite", true);
+        oc.write(applyBlendEquation, "applyBlendEquation", true);
+        oc.write(applyBlendEquationAlpha, "applyBlendEquationAlpha", true);
         oc.write(applyBlendMode, "applyBlendMode", true);
-        oc.write(applyAlphaTest, "applyAlphaTest", true);
-        oc.write(applyAlphaFallOff, "applyAlphaFallOff", true);
         oc.write(applyPolyOffset, "applyPolyOffset", true);
         oc.write(applyDepthFunc, "applyDepthFunc", true);
-        oc.write(applyAlphaFunc, "applyAlphaFunc", false);
         oc.write(applyLineWidth, "applyLineWidth", true);
 
     }
 
     public void read(JmeImporter im) throws IOException {
         InputCapsule ic = im.getCapsule(this);
-        pointSprite = ic.readBoolean("pointSprite", false);
         wireframe = ic.readBoolean("wireframe", false);
         cullMode = ic.readEnum("cullMode", FaceCullMode.class, FaceCullMode.Back);
         depthWrite = ic.readBoolean("depthWrite", true);
         depthTest = ic.readBoolean("depthTest", true);
         colorWrite = ic.readBoolean("colorWrite", true);
         blendMode = ic.readEnum("blendMode", BlendMode.class, BlendMode.Off);
-        alphaTest = ic.readBoolean("alphaTest", false);
-        alphaFallOff = ic.readFloat("alphaFallOff", 0);
         offsetEnabled = ic.readBoolean("offsetEnabled", false);
         offsetFactor = ic.readFloat("offsetFactor", 0);
         offsetUnits = ic.readFloat("offsetUnits", 0);
@@ -396,23 +470,22 @@ public class RenderState implements Cloneable, Savable {
         backStencilDepthPassOperation = ic.readEnum("backStencilDepthPassOperation", StencilOperation.class, StencilOperation.Keep);
         frontStencilFunction = ic.readEnum("frontStencilFunction", TestFunction.class, TestFunction.Always);
         backStencilFunction = ic.readEnum("backStencilFunction", TestFunction.class, TestFunction.Always);
+        blendEquation = ic.readEnum("blendEquation", BlendEquation.class, BlendEquation.Add);
+        blendEquationAlpha = ic.readEnum("blendEquationAlpha", BlendEquationAlpha.class, BlendEquationAlpha.InheritColor);
         depthFunc = ic.readEnum("depthFunc", TestFunction.class, TestFunction.LessOrEqual);
-        alphaFunc = ic.readEnum("alphaFunc", TestFunction.class, TestFunction.Greater);
         lineWidth = ic.readFloat("lineWidth", 1);
 
 
-        applyPointSprite = ic.readBoolean("applyPointSprite", true);
         applyWireFrame = ic.readBoolean("applyWireFrame", true);
         applyCullMode = ic.readBoolean("applyCullMode", true);
         applyDepthWrite = ic.readBoolean("applyDepthWrite", true);
         applyDepthTest = ic.readBoolean("applyDepthTest", true);
         applyColorWrite = ic.readBoolean("applyColorWrite", true);
+        applyBlendEquation = ic.readBoolean("applyBlendEquation", true);
+        applyBlendEquationAlpha = ic.readBoolean("applyBlendEquationAlpha", true);
         applyBlendMode = ic.readBoolean("applyBlendMode", true);
-        applyAlphaTest = ic.readBoolean("applyAlphaTest", true);
-        applyAlphaFallOff = ic.readBoolean("applyAlphaFallOff", true);
         applyPolyOffset = ic.readBoolean("applyPolyOffset", true);
         applyDepthFunc = ic.readBoolean("applyDepthFunc", true);
-        applyAlphaFunc = ic.readBoolean("applyAlphaFunc", false);
         applyLineWidth = ic.readBoolean("applyLineWidth", true);
 
         
@@ -433,8 +506,8 @@ public class RenderState implements Cloneable, Savable {
     }
 
     /**
-     * returns true if the given renderState is equall to this one
-     * @param o the renderState to compate to
+     * returns true if the given renderState is equal to this one
+     * @param o the renderState to compare to
      * @return true if the renderStates are equal
      */
     @Override
@@ -446,9 +519,6 @@ public class RenderState implements Cloneable, Savable {
             return false;
         }
         RenderState rs = (RenderState) o;
-        if (pointSprite != rs.pointSprite) {
-            return false;
-        }
 
         if (wireframe != rs.wireframe) {
             return false;
@@ -475,23 +545,19 @@ public class RenderState implements Cloneable, Savable {
             return false;
         }
 
-        if (blendMode != rs.blendMode) {
+        if (blendEquation != rs.blendEquation) {
             return false;
         }
 
-        if (alphaTest != rs.alphaTest) {
+        if (blendEquationAlpha != rs.blendEquationAlpha) {
             return false;
         }
-        if (alphaTest) {
-            if (alphaFunc != rs.alphaFunc) {
-                return false;
-            }
-        }
 
-        if (alphaFallOff != rs.alphaFallOff) {
+        if (blendMode != rs.blendMode) {
             return false;
         }
 
+
         if (offsetEnabled != rs.offsetEnabled) {
             return false;
         }
@@ -544,70 +610,30 @@ public class RenderState implements Cloneable, Savable {
     }
 
     /**
-     * Enables point sprite mode.
-     *
-     * <p>When point sprite is enabled, any meshes
-     * with the type of {@link Mode#Points} will be rendered as 2D quads
-     * with texturing enabled. Fragment shaders can write to the
-     * <code>gl_PointCoord</code> variable to manipulate the texture coordinate
-     * for each pixel. The size of the 2D quad can be controlled by writing
-     * to the <code>gl_PointSize</code> variable in the vertex shader.
-     *
-     * @param pointSprite Enables Point Sprite mode.
+     * @deprecated Does nothing. Point sprite is already enabled by default for
+     * all supported platforms. jME3 does not support rendering conventional
+     * point clouds.
      */
+    @Deprecated
     public void setPointSprite(boolean pointSprite) {
-        applyPointSprite = true;
-        this.pointSprite = pointSprite;
-        cachedHashCode = -1;
     }
 
     /**
-     * Sets the alpha fall off value for alpha testing.
-     *
-     * <p>If the pixel's alpha value is greater than the
-     * <code>alphaFallOff</code> then the pixel will be rendered, otherwise
-     * the pixel will be discarded.
-     * 
-     * Note : Alpha test is deprecated since opengl 3.0 and does not exists in
-     * openglES 2.0.
-     * The prefered way is to use the alphaDiscardThreshold on the material
-     * Or have a shader that discards the pixel when its alpha value meets the
-     * discarding condition.
-     *
-     * @param alphaFallOff The alpha of all rendered pixels must be higher
-     * than this value to be rendered. This value should be between 0 and 1.
-     *
-     * @see RenderState#setAlphaTest(boolean)
+     * @deprecated Does nothing. To use alpha test, set the
+     * <code>AlphaDiscardThreshold</code> material parameter.
+     * @param alphaFallOff does nothing
      */
+    @Deprecated
     public void setAlphaFallOff(float alphaFallOff) {
-        applyAlphaFallOff = true;
-        this.alphaFallOff = alphaFallOff;
-        cachedHashCode = -1;
     }
 
     /**
-     * Enable alpha testing.
-     *
-     * <p>When alpha testing is enabled, all input pixels' alpha are compared
-     * to the {@link RenderState#setAlphaFallOff(float) constant alpha falloff}.
-     * If the input alpha is greater than the falloff, the pixel will be rendered,
-     * otherwise it will be discarded.
-     *
-     * @param alphaTest Set to true to enable alpha testing.
-     * 
-     * Note : Alpha test is deprecated since opengl 3.0 and does not exists in
-     * openglES 2.0.
-     * The prefered way is to use the alphaDiscardThreshold on the material
-     * Or have a shader that discards the pixel when its alpha value meets the
-     * discarding condition.
-     * 
-     *
-     * @see RenderState#setAlphaFallOff(float)
+     * @deprecated Does nothing. To use alpha test, set the
+     * <code>AlphaDiscardThreshold</code> material parameter.
+     * @param alphaTest does nothing
      */
+    @Deprecated
     public void setAlphaTest(boolean alphaTest) {
-        applyAlphaTest = true;
-        this.alphaTest = alphaTest;
-        cachedHashCode = -1;
     }
 
     /**
@@ -663,6 +689,61 @@ public class RenderState implements Cloneable, Savable {
         cachedHashCode = -1;
     }
 
+    /**
+     * Set the blending equation.
+     * <p>
+     * When blending is enabled, (<code>blendMode</code> is not
+     * {@link BlendMode#Off}) the input pixel will be blended with the pixel
+     * already in the color buffer. The blending equation is determined by the
+     * {@link BlendEquation}. For example, the mode {@link BlendMode#Additive}
+     * and {@link BlendEquation#Add} will add the input pixel's color to the
+     * color already in the color buffer:
+     * <br/>
+     * <code>Result = Source Color + Destination Color</code>
+     * <br/>
+     * However, the mode {@link BlendMode#Additive}
+     * and {@link BlendEquation#Subtract} will subtract the input pixel's color to the
+     * color already in the color buffer:
+     * <br/>
+     * <code>Result = Source Color - Destination Color</code>
+     *
+     * @param blendEquation The blend equation to use. 
+     */
+    public void setBlendEquation(BlendEquation blendEquation) {
+        applyBlendEquation = true;
+        this.blendEquation = blendEquation;
+        cachedHashCode = -1;
+    }
+    
+    /**
+     * Set the blending equation for the alpha component.
+     * <p>
+     * When blending is enabled, (<code>blendMode</code> is not
+     * {@link BlendMode#Off}) the input pixel will be blended with the pixel
+     * already in the color buffer. The blending equation is determined by the
+     * {@link BlendEquation} and can be overrode for the alpha component using
+     * the {@link BlendEquationAlpha} . For example, the mode
+     * {@link BlendMode#Additive} and {@link BlendEquationAlpha#Add} will add
+     * the input pixel's alpha to the alpha component already in the color
+     * buffer:
+     * <br/>
+     * <code>Result = Source Alpha + Destination Alpha</code>
+     * <br/>
+     * However, the mode {@link BlendMode#Additive} and
+     * {@link BlendEquationAlpha#Subtract} will subtract the input pixel's alpha
+     * to the alpha component already in the color buffer:
+     * <br/>
+     * <code>Result = Source Alpha - Destination Alpha</code>
+     *
+     * @param blendEquationAlpha The blend equation to use for the alpha
+     *                           component.
+     */
+    public void setBlendEquationAlpha(BlendEquationAlpha blendEquationAlpha) {
+        applyBlendEquationAlpha = true;
+        this.blendEquationAlpha = blendEquationAlpha;
+        cachedHashCode = -1;
+    }
+
     /**
      * Enable depth testing.
      *
@@ -796,24 +877,10 @@ public class RenderState implements Cloneable, Savable {
     }
 
     /**
-     * Sets the alpha comparision function to the given TestFunction
-     * default is Greater (GL_GREATER)
-     * 
-     * Note : Alpha test is deprecated since opengl 3.0 and does not exists in
-     * openglES 2.0.
-     * The prefered way is to use the alphaDiscardThreshold on the material
-     * Or have a shader taht discards the pixel when its alpha value meets the
-     * discarding condition.
-     * 
-     * @see TestFunction
-     * @see RenderState#setAlphaTest(boolean) 
-     * @see RenderState#setAlphaFallOff(float) 
-     * @param alphaFunc the alpha comparision function
+     * @deprecated
      */
-    public void setAlphaFunc(TestFunction alphaFunc) {        
-        applyAlphaFunc = true;
-        this.alphaFunc = alphaFunc;
-        cachedHashCode = -1;
+    @Deprecated
+    public void setAlphaFunc(TestFunction alphaFunc) {
     }
 
     /**
@@ -822,6 +889,9 @@ public class RenderState implements Cloneable, Savable {
      * @param lineWidth the line width.
      */
     public void setLineWidth(float lineWidth) {
+        if (lineWidth < 1f) {
+            throw new IllegalArgumentException("lineWidth must be greater than or equal to 1.0");
+        }
         this.lineWidth = lineWidth;
         this.applyLineWidth = true;
         cachedHashCode = -1;
@@ -988,6 +1058,24 @@ public class RenderState implements Cloneable, Savable {
         return backStencilFunction;
     }
 
+    /**
+     * Retrieve the blend equation.
+     *
+     * @return the blend equation.
+     */
+    public BlendEquation getBlendEquation() {
+        return blendEquation;
+    }
+    
+    /**
+     * Retrieve the blend equation used for the alpha component.
+     *
+     * @return the blend equation for the alpha component.
+     */
+    public BlendEquationAlpha getBlendEquationAlpha() {
+        return blendEquationAlpha;
+    }
+
     /**
      * Retrieve the blend mode.
      *
@@ -998,25 +1086,22 @@ public class RenderState implements Cloneable, Savable {
     }
 
     /**
-     * Check if point sprite mode is enabled
-     *
-     * @return True if point sprite mode is enabled.
-     *
-     * @see RenderState#setPointSprite(boolean)
+     * @return true
+     * @deprecated Always returns true since point sprite is always enabled.
+     * @see #setPointSprite(boolean)
      */
+    @Deprecated
     public boolean isPointSprite() {
-        return pointSprite;
+        return true;
     }
 
     /**
-     * Check if alpha test is enabled.
-     *
-     * @return True if alpha test is enabled.
-     *
-     * @see RenderState#setAlphaTest(boolean)
+     * @deprecated To use alpha test, set the <code>AlphaDiscardThreshold</code>
+     * material parameter.
+     * @return false
      */
     public boolean isAlphaTest() {
-        return alphaTest;
+        return false;
     }
 
     /**
@@ -1108,14 +1193,12 @@ public class RenderState implements Cloneable, Savable {
     }
 
     /**
-     * Retrieve the alpha falloff value.
-     *
-     * @return the alpha falloff value.
-     *
-     * @see RenderState#setAlphaFallOff(float)
+     * @return 0
+     * @deprecated
      */
+    @Deprecated
     public float getAlphaFallOff() {
-        return alphaFallOff;
+        return 0f;
     }
 
     /**
@@ -1130,14 +1213,12 @@ public class RenderState implements Cloneable, Savable {
     }
 
     /**
-     * Retrieve the alpha comparison function
-     *
-     * @return the alpha comparison function
-     *
-     * @see RenderState#setAlphaFunc(com.jme3.material.RenderState.TestFunction)
+     * @return {@link TestFunction#Greater}.
+     * @deprecated
      */
+    @Deprecated
     public TestFunction getAlphaFunc() {
-        return alphaFunc;
+        return TestFunction.Greater;
     }
 
     /**
@@ -1150,16 +1231,17 @@ public class RenderState implements Cloneable, Savable {
     }
 
 
-    public boolean isApplyAlphaFallOff() {
-        return applyAlphaFallOff;
+
+    public boolean isApplyBlendMode() {
+        return applyBlendMode;
     }
 
-    public boolean isApplyAlphaTest() {
-        return applyAlphaTest;
+    public boolean isApplyBlendEquation() {
+        return applyBlendEquation;
     }
 
-    public boolean isApplyBlendMode() {
-        return applyBlendMode;
+    public boolean isApplyBlendEquationAlpha() {
+        return applyBlendEquationAlpha;
     }
 
     public boolean isApplyColorWrite() {
@@ -1178,9 +1260,6 @@ public class RenderState implements Cloneable, Savable {
         return applyDepthWrite;
     }
 
-    public boolean isApplyPointSprite() {
-        return applyPointSprite;
-    }
 
     public boolean isApplyPolyOffset() {
         return applyPolyOffset;
@@ -1194,9 +1273,6 @@ public class RenderState implements Cloneable, Savable {
         return applyDepthFunc;
     }
 
-    public boolean isApplyAlphaFunc() {
-        return applyAlphaFunc;
-    }
 
     public boolean isApplyLineWidth() {
         return applyLineWidth;
@@ -1208,7 +1284,6 @@ public class RenderState implements Cloneable, Savable {
     public int contentHashCode() {
         if (cachedHashCode == -1){
             int hash = 7;
-            hash = 79 * hash + (this.pointSprite ? 1 : 0);
             hash = 79 * hash + (this.wireframe ? 1 : 0);
             hash = 79 * hash + (this.cullMode != null ? this.cullMode.hashCode() : 0);
             hash = 79 * hash + (this.depthWrite ? 1 : 0);
@@ -1216,9 +1291,8 @@ public class RenderState implements Cloneable, Savable {
             hash = 79 * hash + (this.depthFunc != null ? this.depthFunc.hashCode() : 0);
             hash = 79 * hash + (this.colorWrite ? 1 : 0);
             hash = 79 * hash + (this.blendMode != null ? this.blendMode.hashCode() : 0);
-            hash = 79 * hash + (this.alphaTest ? 1 : 0);
-            hash = 79 * hash + (this.alphaFunc != null ? this.alphaFunc.hashCode() : 0);
-            hash = 79 * hash + Float.floatToIntBits(this.alphaFallOff);
+            hash = 79 * hash + (this.blendEquation != null ? this.blendEquation.hashCode() : 0);
+            hash = 79 * hash + (this.blendEquationAlpha != null ? this.blendEquationAlpha.hashCode() : 0);
             hash = 79 * hash + Float.floatToIntBits(this.offsetFactor);
             hash = 79 * hash + Float.floatToIntBits(this.offsetUnits);
             hash = 79 * hash + (this.offsetEnabled ? 1 : 0);
@@ -1263,11 +1337,6 @@ public class RenderState implements Cloneable, Savable {
             return this;
         }
 
-        if (additionalState.applyPointSprite) {
-            state.pointSprite = additionalState.pointSprite;
-        } else {
-            state.pointSprite = pointSprite;
-        }
         if (additionalState.applyWireFrame) {
             state.wireframe = additionalState.wireframe;
         } else {
@@ -1299,27 +1368,22 @@ public class RenderState implements Cloneable, Savable {
         } else {
             state.colorWrite = colorWrite;
         }
-        if (additionalState.applyBlendMode) {
-            state.blendMode = additionalState.blendMode;
+        if (additionalState.applyBlendEquation) {
+            state.blendEquation = additionalState.blendEquation;
         } else {
-            state.blendMode = blendMode;
+            state.blendEquation = blendEquation;
         }
-        if (additionalState.applyAlphaTest) {
-            state.alphaTest = additionalState.alphaTest;
+        if (additionalState.applyBlendEquationAlpha) {
+            state.blendEquationAlpha = additionalState.blendEquationAlpha;
         } else {
-            state.alphaTest = alphaTest;
-        }
-        if (additionalState.applyAlphaFunc) {
-            state.alphaFunc = additionalState.alphaFunc;
+            state.blendEquationAlpha = blendEquationAlpha;
+        }        
+        if (additionalState.applyBlendMode) {
+            state.blendMode = additionalState.blendMode;
         } else {
-            state.alphaFunc = alphaFunc;
+            state.blendMode = blendMode;
         }
 
-        if (additionalState.applyAlphaFallOff) {
-            state.alphaFallOff = additionalState.alphaFallOff;
-        } else {
-            state.alphaFallOff = alphaFallOff;
-        }
         if (additionalState.applyPolyOffset) {
             state.offsetEnabled = additionalState.offsetEnabled;
             state.offsetFactor = additionalState.offsetFactor;
@@ -1364,16 +1428,14 @@ public class RenderState implements Cloneable, Savable {
         state.cachedHashCode = -1;
         return state;
     }
-     public void set(RenderState state) {
-        pointSprite = state.pointSprite;
+
+    public void set(RenderState state) {
         wireframe = state.wireframe;
         cullMode = state.cullMode;
         depthWrite = state.depthWrite;
         depthTest = state.depthTest;
         colorWrite = state.colorWrite;
         blendMode = state.blendMode;
-        alphaTest = state.alphaTest;
-        alphaFallOff = state.alphaFallOff;
         offsetEnabled = state.offsetEnabled;
         offsetFactor = state.offsetFactor;
         offsetUnits = state.offsetUnits;
@@ -1386,30 +1448,27 @@ public class RenderState implements Cloneable, Savable {
         backStencilDepthPassOperation = state.backStencilDepthPassOperation;
         frontStencilFunction = state.frontStencilFunction;
         backStencilFunction = state.backStencilFunction;
+        blendEquationAlpha = state.blendEquationAlpha;
+        blendEquation = state.blendEquation;
         depthFunc = state.depthFunc;
-        alphaFunc = state.alphaFunc;
         lineWidth = state.lineWidth;
 
-        applyPointSprite = true;
         applyWireFrame =  true;
         applyCullMode =  true;
         applyDepthWrite =  true;
         applyDepthTest =  true;
         applyColorWrite = true;
-        applyBlendMode =  true;
-        applyAlphaTest =  true;
-        applyAlphaFallOff =  true;
+        applyBlendEquation =  true;
+        applyBlendEquationAlpha =  true;
+        applyBlendMode = true;
         applyPolyOffset =  true;
-        applyDepthFunc =  true;
-        applyAlphaFunc =  false;
+        applyDepthFunc = true;
         applyLineWidth = true;
     }
 
     @Override
     public String toString() {
         return "RenderState[\n"
-                + "pointSprite=" + pointSprite
-                + "\napplyPointSprite=" + applyPointSprite
                 + "\nwireframe=" + wireframe
                 + "\napplyWireFrame=" + applyWireFrame
                 + "\ncullMode=" + cullMode
@@ -1421,13 +1480,11 @@ public class RenderState implements Cloneable, Savable {
                 + "\napplyDepthTest=" + applyDepthTest
                 + "\ncolorWrite=" + colorWrite
                 + "\napplyColorWrite=" + applyColorWrite
+                + "\nblendEquation=" + blendEquation
+                + "\napplyBlendEquation=" + applyBlendEquation
+                + "\napplyBlendEquationAlpha=" + applyBlendEquationAlpha
                 + "\nblendMode=" + blendMode
                 + "\napplyBlendMode=" + applyBlendMode
-                + "\nalphaTest=" + alphaTest
-                + "\nalphaFunc=" + alphaFunc
-                + "\napplyAlphaTest=" + applyAlphaTest
-                + "\nalphaFallOff=" + alphaFallOff
-                + "\napplyAlphaFallOff=" + applyAlphaFallOff
                 + "\noffsetEnabled=" + offsetEnabled
                 + "\napplyPolyOffset=" + applyPolyOffset
                 + "\noffsetFactor=" + offsetFactor

+ 109 - 144
jme3-core/src/main/java/com/jme3/material/Technique.java

@@ -31,27 +31,30 @@
  */
 package com.jme3.material;
 
+import com.jme3.material.logic.TechniqueDefLogic;
 import com.jme3.asset.AssetManager;
+import com.jme3.light.LightList;
+import com.jme3.material.TechniqueDef.LightMode;
 import com.jme3.renderer.Caps;
 import com.jme3.renderer.RenderManager;
-import com.jme3.shader.*;
+import com.jme3.scene.Geometry;
+import com.jme3.shader.DefineList;
+import com.jme3.shader.Shader;
+import com.jme3.shader.VarType;
+import com.jme3.util.ListMap;
 import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
-import java.util.logging.Logger;
 
 /**
  * Represents a technique instance.
  */
-public class Technique /* implements Savable */ {
+public final class Technique {
 
-    private static final Logger logger = Logger.getLogger(Technique.class.getName());
-    private TechniqueDef def;
-    private Material owner;
-    private ArrayList<Uniform> worldBindUniforms;
-    private DefineList defines;
-    private Shader shader;
-    private boolean needReload = true;
+    private final TechniqueDef def;
+    private final Material owner;
+    private final DefineList paramDefines;
+    private final DefineList dynamicDefines;
 
     /**
      * Creates a new technique instance that implements the given
@@ -63,14 +66,8 @@ public class Technique /* implements Savable */ {
     public Technique(Material owner, TechniqueDef def) {
         this.owner = owner;
         this.def = def;
-        this.worldBindUniforms = new ArrayList<Uniform>();
-        this.defines = new DefineList();
-    }
-
-    /**
-     * Serialization only. Do not use.
-     */
-    public Technique() {
+        this.paramDefines = def.createDefineList();
+        this.dynamicDefines = def.createDefineList();
     }
 
     /**
@@ -85,157 +82,125 @@ public class Technique /* implements Savable */ {
     }
 
     /**
-     * Returns the shader currently used by this technique instance.
-     * <p>
-     * Shaders are typically loaded dynamically when the technique is first
-     * used, therefore, this variable will most likely be null most of the time.
-     * 
-     * @return the shader currently used by this technique instance.
+     * Called by the material to tell the technique a parameter was modified.
+     * Specify <code>null</code> for value if the param is to be cleared.
      */
-    public Shader getShader() {
-        return shader;
-    }
+    final void notifyParamChanged(String paramName, VarType type, Object value) {
+        Integer defineId = def.getShaderParamDefineId(paramName);
+
+        if (defineId == null) {
+            return;
+        }
 
+        paramDefines.set(defineId, type, value);
+    }
+    
     /**
-     * Returns a list of uniforms that implements the world parameters
-     * that were requested by the material definition.
-     * 
-     * @return a list of uniforms implementing the world parameters.
+     * Called by the material to tell the technique that it has been made
+     * current.
+     * The technique updates dynamic defines based on the
+     * currently set material parameters.
      */
-    public List<Uniform> getWorldBindUniforms() {
-        return worldBindUniforms;
+    final void notifyTechniqueSwitched() {
+        ListMap<String, MatParam> paramMap = owner.getParamsMap();
+        paramDefines.clear();
+        for (int i = 0; i < paramMap.size(); i++) {
+            MatParam param = paramMap.getValue(i);
+            notifyParamChanged(param.getName(), param.getVarType(), param.getValue());
+        }
     }
 
-    /**
-     * Called by the material to tell the technique a parameter was modified.
-     * Specify <code>null</code> for value if the param is to be cleared.
-     */
-    void notifyParamChanged(String paramName, VarType type, Object value) {
-        // Check if there's a define binding associated with this
-        // parameter.
-        String defineName = def.getShaderParamDefine(paramName);
-        if (defineName != null) {
-            // There is a define. Change it on the define list.
-            // The "needReload" variable will determine
-            // if the shader will be reloaded when the material
-            // is rendered.
-            
-            if (value == null) {
-                // Clear the define.
-                needReload = defines.remove(defineName) || needReload;
-            } else {
-                // Set the define.
-                needReload = defines.set(defineName, type, value) || needReload;
+    private void applyOverrides(DefineList defineList, List<MatParamOverride> overrides) {
+        for (MatParamOverride override : overrides) {
+            if (!override.isEnabled()) {
+                continue;
+            }
+            Integer defineId = def.getShaderParamDefineId(override.name);
+            if (defineId != null) {
+                if (def.getDefineIdType(defineId) == override.type) {
+                    defineList.set(defineId, override.type, override.value);
+                }
             }
         }
     }
 
-    void updateUniformParam(String paramName, VarType type, Object value) {
-        if (paramName == null) {
-            throw new IllegalArgumentException();
+    /**
+     * Called by the material to determine which shader to use for rendering.
+     * 
+     * The {@link TechniqueDefLogic} is used to determine the shader to use
+     * based on the {@link LightMode}.
+     * 
+     * @param renderManager The render manager for which the shader is to be selected.
+     * @param rendererCaps The renderer capabilities which the shader should support.
+     * @return A compatible shader.
+     */
+    Shader makeCurrent(RenderManager renderManager, List<MatParamOverride> worldOverrides,
+            List<MatParamOverride> forcedOverrides,
+            LightList lights, EnumSet<Caps> rendererCaps) {
+        TechniqueDefLogic logic = def.getLogic();
+        AssetManager assetManager = owner.getMaterialDef().getAssetManager();
+
+        dynamicDefines.clear();
+        dynamicDefines.setAll(paramDefines);
+
+        if (worldOverrides != null) {
+            applyOverrides(dynamicDefines, worldOverrides);
         }
-        
-        Uniform u = shader.getUniform(paramName);
-        switch (type) {
-            case TextureBuffer:
-            case Texture2D: // fall intentional
-            case Texture3D:
-            case TextureArray:
-            case TextureCubeMap:
-            case Int:
-                u.setValue(VarType.Int, value);
-                break;
-            default:
-                u.setValue(type, value);
-                break;
+        if (forcedOverrides != null) {
+            applyOverrides(dynamicDefines, forcedOverrides);
         }
-    }
 
+        return logic.makeCurrent(assetManager, renderManager, rendererCaps, lights, dynamicDefines);
+    }
+    
     /**
-     * Returns true if the technique must be reloaded.
-     * <p>
-     * If a technique needs to reload, then the {@link Material} should
-     * call {@link #makeCurrent(com.jme3.asset.AssetManager) } on this
-     * technique.
+     * Render the technique according to its {@link TechniqueDefLogic}.
      * 
-     * @return true if the technique must be reloaded.
+     * @param renderManager The render manager to perform the rendering against.
+     * @param shader The shader that was selected in 
+     * {@link #makeCurrent(com.jme3.renderer.RenderManager, java.util.EnumSet)}.
+     * @param geometry The geometry to render
+     * @param lights Lights which influence the geometry.
      */
-    public boolean isNeedReload() {
-        return needReload;
+    void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
+        TechniqueDefLogic logic = def.getLogic();
+        logic.render(renderManager, shader, geometry, lights);
     }
-
+    
     /**
-     * Prepares the technique for use by loading the shader and setting
-     * the proper defines based on material parameters.
+     * Get the {@link DefineList} for dynamic defines.
+     * 
+     * Dynamic defines are used to implement material parameter -> define
+     * bindings as well as {@link TechniqueDefLogic} specific functionality.
      * 
-     * @param assetManager The asset manager to use for loading shaders.
+     * @return all dynamic defines.
      */
-    public void makeCurrent(AssetManager assetManager, boolean techniqueSwitched, EnumSet<Caps> rendererCaps, RenderManager rm) {
-        if (techniqueSwitched) {
-            if (defines.update(owner.getParamsMap(), def)) {
-                needReload = true;
-            }
-            if (getDef().getLightMode() == TechniqueDef.LightMode.SinglePass) {
-                defines.set("SINGLE_PASS_LIGHTING", VarType.Boolean, true);
-                defines.set("NB_LIGHTS", VarType.Int, rm.getSinglePassLightBatchSize() * 3);
-            } else {
-                defines.set("SINGLE_PASS_LIGHTING", VarType.Boolean, null);
-            }
-        }
-
-        if (needReload) {
-            loadShader(assetManager,rendererCaps);
-        }
-    }
-
-    private void loadShader(AssetManager manager,EnumSet<Caps> rendererCaps) {
-        
-        ShaderKey key = new ShaderKey(getAllDefines(),def.getShaderProgramLanguages(),def.getShaderProgramNames());
-        
-        if (getDef().isUsingShaderNodes()) {                 
-           manager.getShaderGenerator(rendererCaps).initialize(this);           
-           key.setUsesShaderNodes(true);
-        }   
-        shader = manager.loadShader(key);
-
-        // register the world bound uniforms
-        worldBindUniforms.clear();
-        if (def.getWorldBindings() != null) {
-           for (UniformBinding binding : def.getWorldBindings()) {
-               Uniform uniform = shader.getUniform("g_" + binding.name());
-               uniform.setBinding(binding);
-               worldBindUniforms.add(uniform);
-           }
-        }        
-        needReload = false;
+    public DefineList getDynamicDefines() {
+        return dynamicDefines;
     }
     
     /**
-     * Computes the define list
-     * @return the complete define list
+     * @return nothing.
+     *
+     * @deprecated Preset defines are precompiled into
+     * {@link TechniqueDef#getShaderPrologue()}, whereas dynamic defines are
+     * available via {@link #getParamDefines()}.
      */
+    @Deprecated
     public DefineList getAllDefines() {
-        DefineList allDefines = new DefineList();
-        allDefines.addFrom(def.getShaderPresetDefines());
-        allDefines.addFrom(defines);
-        return allDefines;
-    } 
-    
-    /*
-    public void write(JmeExporter ex) throws IOException {
-        OutputCapsule oc = ex.getCapsule(this);
-        oc.write(def, "def", null);
-        oc.writeSavableArrayList(worldBindUniforms, "worldBindUniforms", null);
-        oc.write(defines, "defines", null);
-        oc.write(shader, "shader", null);
+        throw new UnsupportedOperationException();
     }
-
-    public void read(JmeImporter im) throws IOException {
-        InputCapsule ic = im.getCapsule(this);
-        def = (TechniqueDef) ic.readSavable("def", null);
-        worldBindUniforms = ic.readSavableArrayList("worldBindUniforms", null);
-        defines = (DefineList) ic.readSavable("defines", null);
-        shader = (Shader) ic.readSavable("shader", null);
+    
+    /**
+     * Compute the sort ID. Similar to {@link Object#hashCode()} but used
+     * for sorting geometries for rendering.
+     * 
+     * @return the sort ID for this technique instance.
+     */
+    public int getSortId() {
+        int hash = 17;
+        hash = hash * 23 + def.getSortId();
+        hash = hash * 23 + paramDefines.hashCode();
+        return hash;
     }
-    */
 }

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

@@ -31,9 +31,12 @@
  */
 package com.jme3.material;
 
+import com.jme3.material.logic.TechniqueDefLogic;
+import com.jme3.asset.AssetManager;
 import com.jme3.export.*;
 import com.jme3.renderer.Caps;
 import com.jme3.shader.*;
+import com.jme3.shader.Shader.ShaderType;
 
 import java.io.IOException;
 import java.util.*;
@@ -50,6 +53,14 @@ public class TechniqueDef implements Savable {
      */
     public static final int SAVABLE_VERSION = 1;
 
+    /**
+     * The default technique name.
+     *
+     * The technique with this name is selected if no specific technique is
+     * requested by the user. Currently set to "Default".
+     */
+    public static final String DEFAULT_TECHNIQUE_NAME = "Default";
+
     /**
      * Describes light rendering mode.
      */
@@ -91,13 +102,19 @@ public class TechniqueDef implements Savable {
         PostPass,
     }
 
-    private EnumSet<Caps> requiredCaps = EnumSet.noneOf(Caps.class);
+    private final EnumSet<Caps> requiredCaps = EnumSet.noneOf(Caps.class);
     private String name;
-
+    private int sortId;
+    
     private EnumMap<Shader.ShaderType,String> shaderLanguages;
     private EnumMap<Shader.ShaderType,String> shaderNames;
 
-    private DefineList presetDefines;
+    private String shaderPrologue;
+    private ArrayList<String> defineNames;
+    private ArrayList<VarType> defineTypes;
+    private HashMap<String, Integer> paramToDefineId;
+    private final HashMap<DefineList, Shader> definesToShaderMap;
+    
     private boolean usesNodes = false;
     private List<ShaderNode> shaderNodes;
     private ShaderGenerationInfo shaderGenerationInfo;
@@ -106,10 +123,10 @@ public class TechniqueDef implements Savable {
     private RenderState renderState;
     private RenderState forcedRenderState;
 
-    private LightMode lightMode   = LightMode.Disable;
+    private LightMode lightMode = LightMode.Disable;
     private ShadowMode shadowMode = ShadowMode.Disable;
+    private TechniqueDefLogic logic;
 
-    private HashMap<String, String> defineParams;
     private ArrayList<UniformBinding> worldBinds;
 
     /**
@@ -117,25 +134,38 @@ public class TechniqueDef implements Savable {
      * <p>
      * Used internally by the J3M/J3MD loader.
      *
-     * @param name The name of the technique, should be set to <code>null</code>
-     * for default techniques.
+     * @param name The name of the technique
      */
-    public TechniqueDef(String name){
+    public TechniqueDef(String name, int sortId){
         this();
-        this.name = name == null ? "Default" : name;
+        this.sortId = sortId;
+        this.name = name;
     }
 
     /**
      * Serialization only. Do not use.
      */
-    public TechniqueDef(){
-        shaderLanguages=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
-        shaderNames=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
+    public TechniqueDef() {
+        shaderLanguages = new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
+        shaderNames = new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
+        defineNames = new ArrayList<String>();
+        defineTypes = new ArrayList<VarType>();
+        paramToDefineId = new HashMap<String, Integer>();
+        definesToShaderMap = new HashMap<DefineList, Shader>();
+    }
+    
+    /**
+     * @return A unique sort ID. 
+     * No other technique definition can have the same ID.
+     */
+    public int getSortId() {
+        return sortId;
     }
 
     /**
      * Returns the name of this technique as specified in the J3MD file.
-     * Default techniques have the name "Default".
+     * Default
+     * techniques have the name {@link #DEFAULT_TECHNIQUE_NAME}.
      *
      * @return the name of this technique
      */
@@ -162,7 +192,15 @@ public class TechniqueDef implements Savable {
     public void setLightMode(LightMode lightMode) {
         this.lightMode = lightMode;
     }
+    
+    public void setLogic(TechniqueDefLogic logic) {
+        this.logic = logic;
+    }
 
+    public TechniqueDefLogic getLogic() {
+        return logic;
+    }
+    
     /**
      * Returns the shadow mode.
      * @return the shadow mode.
@@ -224,14 +262,6 @@ public class TechniqueDef implements Savable {
         return noRender;
     }
 
-    /**
-     * @deprecated jME3 always requires shaders now
-     */
-    @Deprecated
-    public boolean isUsingShaders(){
-        return true;
-    }
-
     /**
      * Returns true if this technique uses Shader Nodes, false otherwise.
      *
@@ -273,34 +303,24 @@ public class TechniqueDef implements Savable {
         requiredCaps.add(fragCap);
     }
 
-
     /**
-     * Sets the shaders that this technique definition will use.
-     *
-     * @param shaderNames EnumMap containing all shader names for this stage
-     * @param shaderLanguages EnumMap containing all shader languages for this stage
+     * Set a string which is prepended to every shader used by this technique.
+     * 
+     * Typically this is used for preset defines.
+     * 
+     * @param shaderPrologue The prologue to append before the technique's shaders.
      */
-    public void setShaderFile(EnumMap<Shader.ShaderType, String> shaderNames, EnumMap<Shader.ShaderType, String> shaderLanguages) {
-        requiredCaps.clear();
-
-        for (Shader.ShaderType shaderType : shaderNames.keySet()) {
-            String language = shaderLanguages.get(shaderType);
-            String shaderFile = shaderNames.get(shaderType);
-
-            this.shaderLanguages.put(shaderType, language);
-            this.shaderNames.put(shaderType, shaderFile);
-
-            Caps vertCap = Caps.valueOf(language);
-            requiredCaps.add(vertCap);
-
-            if (shaderType.equals(Shader.ShaderType.Geometry)) {
-                requiredCaps.add(Caps.GeometryShader);
-            } else if (shaderType.equals(Shader.ShaderType.TessellationControl)) {
-                requiredCaps.add(Caps.TesselationShader);
-            }
-        }
+    public void setShaderPrologue(String shaderPrologue) {
+        this.shaderPrologue = shaderPrologue;
     }
-
+    
+    /**
+     * @return the shader prologue which is prepended to every shader.
+     */
+    public String getShaderPrologue() {
+        return shaderPrologue;
+    }
+    
     /**
      * Returns the define name which the given material parameter influences.
      *
@@ -310,60 +330,186 @@ public class TechniqueDef implements Savable {
      * @see #addShaderParamDefine(java.lang.String, java.lang.String)
      */
     public String getShaderParamDefine(String paramName){
-        if (defineParams == null) {
+        Integer defineId = paramToDefineId.get(paramName);
+        if (defineId != null) {
+            return defineNames.get(defineId);
+        } else {
             return null;
         }
-        return defineParams.get(paramName);
+    }
+    
+    /**
+     * Get the define ID for a given material parameter.
+     *
+     * @param paramName The parameter name to look up
+     * @return The define ID, or null if not found.
+     */
+    public Integer getShaderParamDefineId(String paramName) {
+        return paramToDefineId.get(paramName);
     }
 
+    /**
+     * Get the type of a particular define.
+     *
+     * @param defineId The define ID to lookup.
+     * @return The type of the define, or null if not found.
+     */
+    public VarType getDefineIdType(int defineId) {
+        return defineId < defineTypes.size() ? defineTypes.get(defineId) : null;
+    }
+    
     /**
      * Adds a define linked to a material parameter.
      * <p>
      * Any time the material parameter on the parent material is altered,
      * the appropriate define on the technique will be modified as well.
-     * See the method
-     * {@link DefineList#set(java.lang.String, com.jme3.shader.VarType, java.lang.Object) }
-     * on the exact details of how the material parameter changes the define.
+     * When set, the material parameter will be mapped to an integer define, 
+     * typically <code>1</code> if it is set, unless it is an integer or a float,
+     * in which case it will converted into an integer.
      *
      * @param paramName The name of the material parameter to link to.
+     * @param paramType The type of the material parameter to link to.
      * @param defineName The name of the define parameter, e.g. USE_LIGHTING
      */
-    public void addShaderParamDefine(String paramName, String defineName){
-        if (defineParams == null) {
-            defineParams = new HashMap<String, String>();
+    public void addShaderParamDefine(String paramName, VarType paramType, String defineName){
+        int defineId = defineNames.size();
+        
+        if (defineId >= DefineList.MAX_DEFINES) {
+            throw new IllegalStateException("Cannot have more than " + 
+                    DefineList.MAX_DEFINES + " defines on a technique.");
+        }
+        
+        paramToDefineId.put(paramName, defineId);
+        defineNames.add(defineName);
+        defineTypes.add(paramType);
+    }
+
+    /**
+     * Add an unmapped define which can only be set by define ID.
+     * 
+     * Unmapped defines are used by technique renderers to 
+     * configure the shader internally before rendering.
+     * 
+     * @param defineName The define name to create
+     * @return The define ID of the created define
+     */
+    public int addShaderUnmappedDefine(String defineName, VarType defineType) {
+        int defineId = defineNames.size();
+        
+        if (defineId >= DefineList.MAX_DEFINES) {
+            throw new IllegalStateException("Cannot have more than " + 
+                    DefineList.MAX_DEFINES + " defines on a technique.");
         }
-        defineParams.put(paramName, defineName);
+        
+        defineNames.add(defineName);
+        defineTypes.add(defineType);
+        return defineId;
     }
 
     /**
-     * Returns the {@link DefineList} for the preset defines.
+     * Get the names of all defines declared on this technique definition.
      *
-     * @return the {@link DefineList} for the preset defines.
+     * The defines are returned in order of declaration.
      *
-     * @see #addShaderPresetDefine(java.lang.String, com.jme3.shader.VarType, java.lang.Object)
+     * @return the names of all defines declared.
      */
-    public DefineList getShaderPresetDefines() {
-        return presetDefines;
+    public String[] getDefineNames() {
+        return defineNames.toArray(new String[0]);
     }
 
     /**
-     * Adds a preset define.
-     * <p>
-     * Preset defines do not depend upon any parameters to be activated,
-     * they are always passed to the shader as long as this technique is used.
+     * Get the types of all defines declared on this technique definition.
      *
-     * @param defineName The name of the define parameter, e.g. USE_LIGHTING
-     * @param type The type of the define. See
-     * {@link DefineList#set(java.lang.String, com.jme3.shader.VarType, java.lang.Object) }
-     * to see why it matters.
+     * The types are returned in order of declaration.
      *
-     * @param value The value of the define
+     * @return the types of all defines declared.
      */
-    public void addShaderPresetDefine(String defineName, VarType type, Object value){
-        if (presetDefines == null) {
-            presetDefines = new DefineList();
+    public VarType[] getDefineTypes() {
+        return defineTypes.toArray(new VarType[0]);
+    }
+    
+    /**
+     * Create a define list with the size matching the number
+     * of defines on this technique.
+     * 
+     * @return a define list with the size matching the number
+     * of defines on this technique.
+     */
+    public DefineList createDefineList() {
+        return new DefineList(defineNames.size());
+    }
+    
+    private Shader loadShader(AssetManager assetManager, EnumSet<Caps> rendererCaps, DefineList defines) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(shaderPrologue);
+        defines.generateSource(sb, defineNames, defineTypes);
+        String definesSourceCode = sb.toString();
+
+        Shader shader;
+        if (isUsingShaderNodes()) {
+            ShaderGenerator shaderGenerator = assetManager.getShaderGenerator(rendererCaps);
+            if (shaderGenerator == null) {
+                throw new UnsupportedOperationException("ShaderGenerator was not initialized, "
+                        + "make sure assetManager.getGenerator(caps) has been called");
+            }
+            shaderGenerator.initialize(this);
+            shader = shaderGenerator.generateShader(definesSourceCode);
+        } else {
+            shader = new Shader();
+            for (ShaderType type : ShaderType.values()) {
+                String language = shaderLanguages.get(type);
+                String shaderSourceAssetName = shaderNames.get(type);
+                if (language == null || shaderSourceAssetName == null) {
+                    continue;
+                }
+                String shaderSourceCode = (String) assetManager.loadAsset(shaderSourceAssetName);
+                shader.addSource(type, shaderSourceAssetName, shaderSourceCode, definesSourceCode, language);
+            }
+        }
+
+        if (getWorldBindings() != null) {
+           for (UniformBinding binding : getWorldBindings()) {
+               shader.addUniformBinding(binding);
+           }
+        }
+        
+        return shader;
+    }
+    
+    public Shader getShader(AssetManager assetManager, EnumSet<Caps> rendererCaps, DefineList defines) {
+          Shader shader = definesToShaderMap.get(defines);
+          if (shader == null) {
+              shader = loadShader(assetManager, rendererCaps, defines);
+              definesToShaderMap.put(defines.deepClone(), shader);
+          }
+          return shader;
+     }
+    
+    /**
+     * Sets the shaders that this technique definition will use.
+     *
+     * @param shaderNames EnumMap containing all shader names for this stage
+     * @param shaderLanguages EnumMap containing all shader languages for this stage
+     */
+    public void setShaderFile(EnumMap<Shader.ShaderType, String> shaderNames, EnumMap<Shader.ShaderType, String> shaderLanguages) {
+        requiredCaps.clear();
+
+        for (Shader.ShaderType shaderType : shaderNames.keySet()) {
+            String language = shaderLanguages.get(shaderType);
+            String shaderFile = shaderNames.get(shaderType);
+
+            this.shaderLanguages.put(shaderType, language);
+            this.shaderNames.put(shaderType, shaderFile);
+
+            Caps vertCap = Caps.valueOf(language);
+            requiredCaps.add(vertCap);
+
+            if (shaderType.equals(Shader.ShaderType.Geometry)) {
+                requiredCaps.add(Caps.GeometryShader);
+            } else if (shaderType.equals(Shader.ShaderType.TessellationControl)) {
+                requiredCaps.add(Caps.TesselationShader);
+            }
         }
-        presetDefines.set(defineName, type, value);
     }
 
     /**
@@ -467,7 +613,7 @@ public class TechniqueDef implements Savable {
         oc.write(shaderLanguages.get(Shader.ShaderType.TessellationControl), "tsctrlLanguage", null);
         oc.write(shaderLanguages.get(Shader.ShaderType.TessellationEvaluation), "tsevalLanguage", null);
 
-        oc.write(presetDefines, "presetDefines", null);
+        oc.write(shaderPrologue, "shaderPrologue", null);
         oc.write(lightMode, "lightMode", LightMode.Disable);
         oc.write(shadowMode, "shadowMode", ShadowMode.Disable);
         oc.write(renderState, "renderState", null);
@@ -490,7 +636,7 @@ public class TechniqueDef implements Savable {
         shaderNames.put(Shader.ShaderType.Geometry,ic.readString("geomName", null));
         shaderNames.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrlName", null));
         shaderNames.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tsevalName", null));
-        presetDefines = (DefineList) ic.readSavable("presetDefines", null);
+        shaderPrologue = ic.readString("shaderPrologue", null);
         lightMode = ic.readEnum("lightMode", LightMode.class, LightMode.Disable);
         shadowMode = ic.readEnum("shadowMode", ShadowMode.class, ShadowMode.Disable);
         renderState = (RenderState) ic.readSavable("renderState", null);
@@ -547,9 +693,14 @@ public class TechniqueDef implements Savable {
         this.shaderGenerationInfo = shaderGenerationInfo;
     }
 
-    //todo: make toString return something usefull
     @Override
     public String toString() {
-        return "TechniqueDef{" + "requiredCaps=" + requiredCaps + ", name=" + name /*+ ", vertName=" + vertName + ", fragName=" + fragName + ", vertLanguage=" + vertLanguage + ", fragLanguage=" + fragLanguage */+ ", presetDefines=" + presetDefines + ", usesNodes=" + usesNodes + ", shaderNodes=" + shaderNodes + ", shaderGenerationInfo=" + shaderGenerationInfo + ", renderState=" + renderState + ", forcedRenderState=" + forcedRenderState + ", lightMode=" + lightMode + ", shadowMode=" + shadowMode + ", defineParams=" + defineParams + ", worldBinds=" + worldBinds + ", noRender=" + noRender + '}';
+        return "TechniqueDef[name=" + name
+                + ", requiredCaps=" + requiredCaps
+                + ", noRender=" + noRender
+                + ", lightMode=" + lightMode
+                + ", usesNodes=" + usesNodes
+                + ", renderState=" + renderState
+                + ", forcedRenderState=" + forcedRenderState + "]";
     }
 }

+ 97 - 0
jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java

@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2009-2015 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.material.logic;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.Light;
+import com.jme3.light.LightList;
+import com.jme3.material.TechniqueDef;
+import com.jme3.math.ColorRGBA;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.instancing.InstancedGeometry;
+import com.jme3.shader.DefineList;
+import com.jme3.shader.Shader;
+import java.util.EnumSet;
+
+public class DefaultTechniqueDefLogic implements TechniqueDefLogic {
+
+    protected final TechniqueDef techniqueDef;
+
+    public DefaultTechniqueDefLogic(TechniqueDef techniqueDef) {
+        this.techniqueDef = techniqueDef;
+    }
+
+    @Override
+    public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager,
+            EnumSet<Caps> rendererCaps, LightList lights, DefineList defines) {
+        return techniqueDef.getShader(assetManager, rendererCaps, defines);
+    }
+
+    public static void renderMeshFromGeometry(Renderer renderer, Geometry geom) {
+        Mesh mesh = geom.getMesh();
+        int lodLevel = geom.getLodLevel();
+        if (geom instanceof InstancedGeometry) {
+            InstancedGeometry instGeom = (InstancedGeometry) geom;
+            renderer.renderMesh(mesh, lodLevel, instGeom.getActualNumInstances(),
+                    instGeom.getAllInstanceData());
+        } else {
+            renderer.renderMesh(mesh, lodLevel, 1, null);
+        }
+    }
+
+    protected static ColorRGBA getAmbientColor(LightList lightList, boolean removeLights, ColorRGBA ambientLightColor) {
+        ambientLightColor.set(0, 0, 0, 1);
+        for (int j = 0; j < lightList.size(); j++) {
+            Light l = lightList.get(j);
+            if (l instanceof AmbientLight) {
+                ambientLightColor.addLocal(l.getColor());
+                if (removeLights) {
+                    lightList.remove(l);
+                }
+            }
+        }
+        ambientLightColor.a = 1.0f;
+        return ambientLightColor;
+    }
+
+    @Override
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
+        Renderer renderer = renderManager.getRenderer();
+        renderer.setShader(shader);
+        renderMeshFromGeometry(renderer, geometry);
+    }
+}

+ 178 - 0
jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java

@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2009-2015 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.material.logic;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.Light;
+import com.jme3.light.LightList;
+import com.jme3.light.PointLight;
+import com.jme3.light.SpotLight;
+import com.jme3.material.RenderState;
+import com.jme3.material.TechniqueDef;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.math.Vector4f;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.scene.Geometry;
+import com.jme3.shader.DefineList;
+import com.jme3.shader.Shader;
+import com.jme3.shader.Uniform;
+import com.jme3.shader.VarType;
+import com.jme3.util.TempVars;
+import java.util.EnumSet;
+
+public final class MultiPassLightingLogic extends DefaultTechniqueDefLogic {
+
+    private static final RenderState ADDITIVE_LIGHT = new RenderState();
+    private static final Quaternion NULL_DIR_LIGHT = new Quaternion(0, -1, 0, -1);
+    
+    private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
+    
+    static {
+        ADDITIVE_LIGHT.setBlendMode(RenderState.BlendMode.AlphaAdditive);
+        ADDITIVE_LIGHT.setDepthWrite(false);
+    }
+    
+    public MultiPassLightingLogic(TechniqueDef techniqueDef) {
+        super(techniqueDef);
+    }
+
+    @Override
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
+        Renderer r = renderManager.getRenderer();
+        Uniform lightDir = shader.getUniform("g_LightDirection");
+        Uniform lightColor = shader.getUniform("g_LightColor");
+        Uniform lightPos = shader.getUniform("g_LightPosition");
+        Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
+        boolean isFirstLight = true;
+        boolean isSecondLight = false;
+        
+        getAmbientColor(lights, false, ambientLightColor);
+
+        for (int i = 0; i < lights.size(); i++) {
+            Light l = lights.get(i);
+            if (l instanceof AmbientLight) {
+                continue;
+            }
+
+            if (isFirstLight) {
+                // set ambient color for first light only
+                ambientColor.setValue(VarType.Vector4, ambientLightColor);
+                isFirstLight = false;
+                isSecondLight = true;
+            } else if (isSecondLight) {
+                ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
+                // apply additive blending for 2nd and future lights
+                r.applyRenderState(ADDITIVE_LIGHT);
+                isSecondLight = false;
+            }
+
+            TempVars vars = TempVars.get();
+            Quaternion tmpLightDirection = vars.quat1;
+            Quaternion tmpLightPosition = vars.quat2;
+            ColorRGBA tmpLightColor = vars.color;
+            Vector4f tmpVec = vars.vect4f1;
+
+            ColorRGBA color = l.getColor();
+            tmpLightColor.set(color);
+            tmpLightColor.a = l.getType().getId();
+            lightColor.setValue(VarType.Vector4, tmpLightColor);
+
+            switch (l.getType()) {
+                case Directional:
+                    DirectionalLight dl = (DirectionalLight) l;
+                    Vector3f dir = dl.getDirection();
+                    //FIXME : there is an inconstency here due to backward
+                    //compatibility of the lighting shader.
+                    //The directional light direction is passed in the
+                    //LightPosition uniform. The lighting shader needs to be
+                    //reworked though in order to fix this.
+                    tmpLightPosition.set(dir.getX(), dir.getY(), dir.getZ(), -1);
+                    lightPos.setValue(VarType.Vector4, tmpLightPosition);
+                    tmpLightDirection.set(0, 0, 0, 0);
+                    lightDir.setValue(VarType.Vector4, tmpLightDirection);
+                    break;
+                case Point:
+                    PointLight pl = (PointLight) l;
+                    Vector3f pos = pl.getPosition();
+                    float invRadius = pl.getInvRadius();
+
+                    tmpLightPosition.set(pos.getX(), pos.getY(), pos.getZ(), invRadius);
+                    lightPos.setValue(VarType.Vector4, tmpLightPosition);
+                    tmpLightDirection.set(0, 0, 0, 0);
+                    lightDir.setValue(VarType.Vector4, tmpLightDirection);
+                    break;
+                case Spot:
+                    SpotLight sl = (SpotLight) l;
+                    Vector3f pos2 = sl.getPosition();
+                    Vector3f dir2 = sl.getDirection();
+                    float invRange = sl.getInvSpotRange();
+                    float spotAngleCos = sl.getPackedAngleCos();
+
+                    tmpLightPosition.set(pos2.getX(), pos2.getY(), pos2.getZ(), invRange);
+                    lightPos.setValue(VarType.Vector4, tmpLightPosition);
+
+                    //We transform the spot direction in view space here to save 5 varying later in the lighting shader
+                    //one vec4 less and a vec4 that becomes a vec3
+                    //the downside is that spotAngleCos decoding happens now in the frag shader.
+                    tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0);
+                    renderManager.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
+                    tmpLightDirection.set(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos);
+
+                    lightDir.setValue(VarType.Vector4, tmpLightDirection);
+
+                    break;
+                default:
+                    throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
+            }
+            vars.release();
+            r.setShader(shader);
+            renderMeshFromGeometry(r, geometry);
+        }
+
+        if (isFirstLight) {
+            // Either there are no lights at all, or only ambient lights.
+            // Render a dummy "normal light" so we can see the ambient color.
+            ambientColor.setValue(VarType.Vector4, getAmbientColor(lights, false, ambientLightColor));
+            lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha);
+            lightPos.setValue(VarType.Vector4, NULL_DIR_LIGHT);
+            r.setShader(shader);
+            renderMeshFromGeometry(r, geometry);
+        }
+    }
+}

+ 218 - 0
jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java

@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2009-2015 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.material.logic;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.Light;
+import com.jme3.light.LightList;
+import com.jme3.light.PointLight;
+import com.jme3.light.SpotLight;
+import com.jme3.material.RenderState;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.material.TechniqueDef;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.math.Vector4f;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.scene.Geometry;
+import com.jme3.shader.DefineList;
+import com.jme3.shader.Shader;
+import com.jme3.shader.Uniform;
+import com.jme3.shader.VarType;
+import com.jme3.util.TempVars;
+import java.util.EnumSet;
+
+public final class SinglePassLightingLogic extends DefaultTechniqueDefLogic {
+
+    private static final String DEFINE_SINGLE_PASS_LIGHTING = "SINGLE_PASS_LIGHTING";
+    private static final String DEFINE_NB_LIGHTS = "NB_LIGHTS";
+    private static final RenderState ADDITIVE_LIGHT = new RenderState();
+
+    private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
+
+    static {
+        ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive);
+        ADDITIVE_LIGHT.setDepthWrite(false);
+    }
+
+    private final int singlePassLightingDefineId;
+    private final int nbLightsDefineId;
+
+    public SinglePassLightingLogic(TechniqueDef techniqueDef) {
+        super(techniqueDef);
+        singlePassLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_SINGLE_PASS_LIGHTING, VarType.Boolean);
+        nbLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_LIGHTS, VarType.Int);
+    }
+
+    @Override
+    public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager,
+            EnumSet<Caps> rendererCaps, LightList lights, DefineList defines) {
+        defines.set(nbLightsDefineId, renderManager.getSinglePassLightBatchSize() * 3);
+        defines.set(singlePassLightingDefineId, true);
+        return super.makeCurrent(assetManager, renderManager, rendererCaps, lights, defines);
+    }
+
+    /**
+     * Uploads the lights in the light list as two uniform arrays.<br/><br/> *
+     * <p>
+     * <code>uniform vec4 g_LightColor[numLights];</code><br/> //
+     * g_LightColor.rgb is the diffuse/specular color of the light.<br/> //
+     * g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point, <br/> //
+     * 2 = Spot. <br/> <br/>
+     * <code>uniform vec4 g_LightPosition[numLights];</code><br/> //
+     * g_LightPosition.xyz is the position of the light (for point lights)<br/>
+     * // or the direction of the light (for directional lights).<br/> //
+     * g_LightPosition.w is the inverse radius (1/r) of the light (for
+     * attenuation) <br/> </p>
+     */
+    protected int updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex) {
+        if (numLights == 0) { // this shader does not do lighting, ignore.
+            return 0;
+        }
+
+        Uniform lightData = shader.getUniform("g_LightData");
+        lightData.setVector4Length(numLights * 3);//8 lights * max 3
+        Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
+
+
+        if (startIndex != 0) {
+            // apply additive blending for 2nd and future passes
+            rm.getRenderer().applyRenderState(ADDITIVE_LIGHT);
+            ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
+        } else {
+            ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList, true, ambientLightColor));
+        }
+
+        int lightDataIndex = 0;
+        TempVars vars = TempVars.get();
+        Vector4f tmpVec = vars.vect4f1;
+        int curIndex;
+        int endIndex = numLights + startIndex;
+        for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) {
+
+            Light l = lightList.get(curIndex);
+            if (l.getType() == Light.Type.Ambient) {
+                endIndex++;
+                continue;
+            }
+            ColorRGBA color = l.getColor();
+            //Color
+            lightData.setVector4InArray(color.getRed(),
+                    color.getGreen(),
+                    color.getBlue(),
+                    l.getType().getId(),
+                    lightDataIndex);
+            lightDataIndex++;
+
+            switch (l.getType()) {
+                case Directional:
+                    DirectionalLight dl = (DirectionalLight) l;
+                    Vector3f dir = dl.getDirection();
+                    //Data directly sent in view space to avoid a matrix mult for each pixel
+                    tmpVec.set(dir.getX(), dir.getY(), dir.getZ(), 0.0f);
+                    rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
+//                        tmpVec.divideLocal(tmpVec.w);
+//                        tmpVec.normalizeLocal();
+                    lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), -1, lightDataIndex);
+                    lightDataIndex++;
+                    //PADDING
+                    lightData.setVector4InArray(0, 0, 0, 0, lightDataIndex);
+                    lightDataIndex++;
+                    break;
+                case Point:
+                    PointLight pl = (PointLight) l;
+                    Vector3f pos = pl.getPosition();
+                    float invRadius = pl.getInvRadius();
+                    tmpVec.set(pos.getX(), pos.getY(), pos.getZ(), 1.0f);
+                    rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
+                    //tmpVec.divideLocal(tmpVec.w);
+                    lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRadius, lightDataIndex);
+                    lightDataIndex++;
+                    //PADDING
+                    lightData.setVector4InArray(0, 0, 0, 0, lightDataIndex);
+                    lightDataIndex++;
+                    break;
+                case Spot:
+                    SpotLight sl = (SpotLight) l;
+                    Vector3f pos2 = sl.getPosition();
+                    Vector3f dir2 = sl.getDirection();
+                    float invRange = sl.getInvSpotRange();
+                    float spotAngleCos = sl.getPackedAngleCos();
+                    tmpVec.set(pos2.getX(), pos2.getY(), pos2.getZ(), 1.0f);
+                    rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
+                    // tmpVec.divideLocal(tmpVec.w);
+                    lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRange, lightDataIndex);
+                    lightDataIndex++;
+
+                    //We transform the spot direction in view space here to save 5 varying later in the lighting shader
+                    //one vec4 less and a vec4 that becomes a vec3
+                    //the downside is that spotAngleCos decoding happens now in the frag shader.
+                    tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(), 0.0f);
+                    rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
+                    tmpVec.normalizeLocal();
+                    lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex);
+                    lightDataIndex++;
+                    break;
+                default:
+                    throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
+            }
+        }
+        vars.release();
+        //Padding of unsued buffer space
+        while(lightDataIndex < numLights * 3) {
+            lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex);
+            lightDataIndex++;
+        }
+        return curIndex;
+    }
+
+    @Override
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
+        int nbRenderedLights = 0;
+        Renderer renderer = renderManager.getRenderer();
+        int batchSize = renderManager.getSinglePassLightBatchSize();
+        if (lights.size() == 0) {
+            updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, 0);
+            renderer.setShader(shader);
+            renderMeshFromGeometry(renderer, geometry);
+        } else {
+            while (nbRenderedLights < lights.size()) {
+                nbRenderedLights = updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, nbRenderedLights);
+                renderer.setShader(shader);
+                renderMeshFromGeometry(renderer, geometry);
+            }
+        }
+    }
+}

+ 182 - 0
jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java

@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2009-2015 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.material.logic;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.Light;
+import com.jme3.light.LightList;
+import com.jme3.light.PointLight;
+import com.jme3.light.SpotLight;
+import com.jme3.material.TechniqueDef;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.scene.Geometry;
+import com.jme3.shader.DefineList;
+import com.jme3.shader.Shader;
+import com.jme3.shader.Uniform;
+import com.jme3.shader.VarType;
+import java.util.ArrayList;
+import java.util.EnumSet;
+
+/**
+ * Rendering logic for static pass.
+ *
+ * @author Kirill Vainer
+ */
+public final class StaticPassLightingLogic extends DefaultTechniqueDefLogic {
+
+    private static final String DEFINE_NUM_DIR_LIGHTS = "NUM_DIR_LIGHTS";
+    private static final String DEFINE_NUM_POINT_LIGHTS = "NUM_POINT_LIGHTS";
+    private static final String DEFINE_NUM_SPOT_LIGHTS = "NUM_SPOT_LIGHTS";
+
+    private final int numDirLightsDefineId;
+    private final int numPointLightsDefineId;
+    private final int numSpotLightsDefineId;
+
+    private final ArrayList<DirectionalLight> tempDirLights = new ArrayList<DirectionalLight>();
+    private final ArrayList<PointLight> tempPointLights = new ArrayList<PointLight>();
+    private final ArrayList<SpotLight> tempSpotLights = new ArrayList<SpotLight>();
+
+    private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
+    private final Vector3f tempPosition = new Vector3f();
+    private final Vector3f tempDirection = new Vector3f();
+
+    public StaticPassLightingLogic(TechniqueDef techniqueDef) {
+        super(techniqueDef);
+
+        numDirLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NUM_DIR_LIGHTS, VarType.Int);
+        numPointLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NUM_POINT_LIGHTS, VarType.Int);
+        numSpotLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NUM_SPOT_LIGHTS, VarType.Int);
+    }
+
+    @Override
+    public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager,
+            EnumSet<Caps> rendererCaps, LightList lights, DefineList defines) {
+
+        // TODO: if it ever changes that render isn't called
+        // right away with the same geometry after makeCurrent, it would be
+        // a problem.
+        // Do a radix sort.
+        tempDirLights.clear();
+        tempPointLights.clear();
+        tempSpotLights.clear();
+        for (Light light : lights) {
+            switch (light.getType()) {
+                case Directional:
+                    tempDirLights.add((DirectionalLight) light);
+                    break;
+                case Point:
+                    tempPointLights.add((PointLight) light);
+                    break;
+                case Spot:
+                    tempSpotLights.add((SpotLight) light);
+                    break;
+            }
+        }
+
+        defines.set(numDirLightsDefineId, tempDirLights.size());
+        defines.set(numPointLightsDefineId, tempPointLights.size());
+        defines.set(numSpotLightsDefineId, tempSpotLights.size());
+
+        return techniqueDef.getShader(assetManager, rendererCaps, defines);
+    }
+
+    private void transformDirection(Matrix4f viewMatrix, Vector3f direction) {
+        viewMatrix.multNormal(direction, direction);
+    }
+
+    private void transformPosition(Matrix4f viewMatrix, Vector3f location) {
+        viewMatrix.mult(location, location);
+    }
+
+    private void updateLightListUniforms(Matrix4f viewMatrix, Shader shader, LightList lights) {
+        Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
+        ambientColor.setValue(VarType.Vector4, getAmbientColor(lights, true, ambientLightColor));
+
+        Uniform lightData = shader.getUniform("g_LightData");
+
+        int totalSize = tempDirLights.size() * 2
+                + tempPointLights.size() * 2
+                + tempSpotLights.size() * 3;
+        lightData.setVector4Length(totalSize);
+
+        int index = 0;
+        for (DirectionalLight light : tempDirLights) {
+            ColorRGBA color = light.getColor();
+            tempDirection.set(light.getDirection());
+            transformDirection(viewMatrix, tempDirection);
+            lightData.setVector4InArray(color.r, color.g, color.b, 1f, index++);
+            lightData.setVector4InArray(tempDirection.x, tempDirection.y, tempDirection.z, 1f, index++);
+        }
+
+        for (PointLight light : tempPointLights) {
+            ColorRGBA color = light.getColor();
+            tempPosition.set(light.getPosition());
+            float invRadius = light.getInvRadius();
+            transformPosition(viewMatrix, tempPosition);
+            lightData.setVector4InArray(color.r, color.g, color.b, 1f, index++);
+            lightData.setVector4InArray(tempPosition.x, tempPosition.y, tempPosition.z, invRadius, index++);
+        }
+
+        for (SpotLight light : tempSpotLights) {
+            ColorRGBA color = light.getColor();
+            Vector3f pos = light.getPosition();
+            Vector3f dir = light.getDirection();
+
+            tempPosition.set(light.getPosition());
+            tempDirection.set(light.getDirection());
+            transformPosition(viewMatrix, tempPosition);
+            transformDirection(viewMatrix, tempDirection);
+
+            float invRange = light.getInvSpotRange();
+            float spotAngleCos = light.getPackedAngleCos();
+            lightData.setVector4InArray(color.r, color.g, color.b, 1f, index++);
+            lightData.setVector4InArray(tempPosition.x, tempPosition.y, tempPosition.z, invRange, index++);
+            lightData.setVector4InArray(tempDirection.x, tempDirection.y, tempDirection.z, spotAngleCos, index++);
+        }
+    }
+
+    @Override
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
+        Renderer renderer = renderManager.getRenderer();
+        Matrix4f viewMatrix = renderManager.getCurrentCamera().getViewMatrix();
+        updateLightListUniforms(viewMatrix, shader, lights);
+        renderer.setShader(shader);
+        renderMeshFromGeometry(renderer, geometry);
+    }
+
+}

+ 97 - 0
jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java

@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2009-2015 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.material.logic;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.light.LightList;
+import com.jme3.material.TechniqueDef.LightMode;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Geometry;
+import com.jme3.shader.DefineList;
+import com.jme3.shader.Shader;
+import com.jme3.shader.Uniform;
+import com.jme3.shader.UniformBinding;
+import com.jme3.texture.Texture;
+import java.util.EnumSet;
+
+/**
+ * <code>TechniqueDefLogic</code> is used to customize how 
+ * a material should be rendered.
+ * 
+ * Typically used to implement {@link LightMode lighting modes}.
+ * Implementations can register 
+ * {@link TechniqueDef#addShaderUnmappedDefine(java.lang.String) unmapped defines} 
+ * in their constructor and then later set them based on the geometry 
+ * or light environment being rendered.
+ * 
+ * @author Kirill Vainer
+ */
+public interface TechniqueDefLogic {
+    
+    /**
+     * Determine the shader to use for the given geometry / material combination.
+     * 
+     * @param assetManager The asset manager to use for loading shader source code,
+     * shader nodes, and and lookup textures.
+     * @param renderManager The render manager for which rendering is to be performed.
+     * @param rendererCaps Renderer capabilities. The returned shader must
+     * support these capabilities.
+     * @param lights The lights with which the geometry shall be rendered. This
+     * list must not include culled lights.
+     * @param defines The define list used by the technique, any 
+     * {@link TechniqueDef#addShaderUnmappedDefine(java.lang.String) unmapped defines}
+     * should be set here to change shader behavior.
+     * 
+     * @return The shader to use for rendering.
+     */
+    public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager, 
+            EnumSet<Caps> rendererCaps, LightList lights, DefineList defines);
+    
+    /**
+     * Requests that the <code>TechniqueDefLogic</code> renders the given geometry.
+     * 
+     * Fixed material functionality such as {@link RenderState}, 
+     * {@link MatParam material parameters}, and 
+     * {@link UniformBinding uniform bindings}
+     * have already been applied by the material, however, 
+     * {@link RenderState}, {@link Uniform uniforms}, {@link Texture textures},
+     * can still be overriden.
+     * 
+     * @param renderManager The render manager to perform the rendering against.
+     * * @param shader The shader that was selected by this logic in 
+     * {@link #makeCurrent(com.jme3.asset.AssetManager, com.jme3.renderer.RenderManager, java.util.EnumSet, com.jme3.shader.DefineList)}.
+     * @param geometry The geometry to render
+     * @param lights Lights which influence the geometry.
+     */
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights);
+}

+ 6 - 6
jme3-core/src/main/java/com/jme3/renderer/Camera.java

@@ -1005,12 +1005,12 @@ public class Camera implements Savable, Cloneable {
      *
      * NOTE: This method is used internally for culling, for public usage,
      * the plane state of the bounding volume must be saved and restored, e.g:
-     * <code>BoundingVolume bv;<br/>
-     * Camera c;<br/>
-     * int planeState = bv.getPlaneState();<br/>
-     * bv.setPlaneState(0);<br/>
-     * c.contains(bv);<br/>
-     * bv.setPlaneState(plateState);<br/>
+     * <code>BoundingVolume bv;<br>
+     * Camera c;<br>
+     * int planeState = bv.getPlaneState();<br>
+     * bv.setPlaneState(0);<br>
+     * c.contains(bv);<br>
+     * bv.setPlaneState(plateState);<br>
      * </code>
      *
      * @param bound the bound to check for culling

+ 8 - 25
jme3-core/src/main/java/com/jme3/renderer/Limits.java

@@ -32,51 +32,34 @@
 package com.jme3.renderer;
 
 /**
- * <code>Limits</code> allows querying the limits of certain features in 
+ * <code>Limits</code> allows querying the limits of certain features in
  * {@link Renderer}.
  * <p>
  * For example, maximum texture sizes or number of samples.
- * 
+ *
  * @author Kirill Vainer
  */
 public enum Limits {
     /**
-     * Maximum number of vertex texture units, or number of textures
-     * that can be used in the vertex shader.
+     * Maximum number of vertex texture units, or number of textures that can be
+     * used in the vertex shader.
      */
     VertexTextureUnits,
-    
     /**
-     * Maximum number of fragment texture units, or number of textures
-     * that can be used in the fragment shader.
+     * Maximum number of fragment texture units, or number of textures that can
+     * be used in the fragment shader.
      */
     FragmentTextureUnits,
-    
-    FragmentUniforms,
-    
+    FragmentUniformVectors,
+    VertexUniformVectors,
     VertexAttributes,
-    
     FrameBufferSamples,
-    
     FrameBufferAttachments,
-    
     FrameBufferMrtAttachments,
-    
     RenderBufferSize,
-    
     TextureSize,
-    
     CubemapSize,
-    
-    VertexCount,
-    
-    TriangleCount,
-    
     ColorTextureSamples,
-    
     DepthTextureSamples,
-
-    VertexUniformVectors,
-    
     TextureAnisotropy,
 }

+ 11 - 15
jme3-core/src/main/java/com/jme3/renderer/RenderContext.java

@@ -55,16 +55,6 @@ public class RenderContext {
      */
     public boolean depthTestEnabled = false;
 
-    /**
-     * @see RenderState#setAlphaFallOff(float) 
-     */
-    public float alphaTestFallOff = 0f;
-
-    /**
-     * @see RenderState#setAlphaTest(boolean) 
-     */
-    public boolean alphaTestEnabled = false;
-
     /**
      * @see RenderState#setDepthWrite(boolean) 
      */
@@ -111,14 +101,19 @@ public class RenderContext {
     public RenderState.BlendMode blendMode = RenderState.BlendMode.Off;
 
     /**
-     * @see RenderState#setWireframe(boolean) 
+     * @see RenderState#setBlendEquation(com.jme3.material.RenderState.BlendEquation) 
      */
-    public boolean wireframe = false;
+    public RenderState.BlendEquation blendEquation = RenderState.BlendEquation.Add;
+    
+    /**
+     * @see RenderState#setBlendEquationAlpha(com.jme3.material.RenderState.BlendEquationAlpha) 
+     */
+    public RenderState.BlendEquationAlpha blendEquationAlpha = RenderState.BlendEquationAlpha.InheritColor;
 
     /**
-     * @see RenderState#setPointSprite(boolean) 
+     * @see RenderState#setWireframe(boolean) 
      */
-    public boolean pointSprite = false;
+    public boolean wireframe = false;
 
     /**
      * @see Renderer#setShader(com.jme3.shader.Shader) 
@@ -261,7 +256,6 @@ public class RenderContext {
     public void reset(){
         cullMode = RenderState.FaceCullMode.Off;
         depthTestEnabled = false;
-        alphaTestFallOff = 0f;
         depthWriteEnabled = false;
         colorWriteEnabled = false;
         clipRectEnabled = false;
@@ -270,6 +264,8 @@ public class RenderContext {
         polyOffsetUnits = 0;
         pointSize = 1;
         blendMode = RenderState.BlendMode.Off;
+        blendEquation = RenderState.BlendEquation.Add;
+        blendEquationAlpha = RenderState.BlendEquationAlpha.InheritColor;
         wireframe = false;
         boundShaderProgram = 0;
         boundShader = null;

+ 92 - 35
jme3-core/src/main/java/com/jme3/renderer/RenderManager.java

@@ -34,8 +34,13 @@ package com.jme3.renderer;
 import com.jme3.light.DefaultLightFilter;
 import com.jme3.light.LightFilter;
 import com.jme3.light.LightList;
-import com.jme3.material.*;
-import com.jme3.math.Matrix4f;
+import com.jme3.material.MatParamOverride;
+import com.jme3.material.Material;
+import com.jme3.material.MaterialDef;
+import com.jme3.material.RenderState;
+import com.jme3.material.Technique;
+import com.jme3.material.TechniqueDef;
+import com.jme3.math.*;
 import com.jme3.post.SceneProcessor;
 import com.jme3.profile.AppProfiler;
 import com.jme3.profile.AppStep;
@@ -45,13 +50,12 @@ import com.jme3.renderer.queue.RenderQueue;
 import com.jme3.renderer.queue.RenderQueue.Bucket;
 import com.jme3.renderer.queue.RenderQueue.ShadowMode;
 import com.jme3.scene.*;
-import com.jme3.shader.Uniform;
+import com.jme3.shader.Shader;
 import com.jme3.shader.UniformBinding;
 import com.jme3.shader.UniformBindingManager;
 import com.jme3.system.NullRenderer;
 import com.jme3.system.Timer;
 import com.jme3.util.SafeArrayList;
-
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -70,25 +74,26 @@ import java.util.logging.Logger;
 public class RenderManager {
 
     private static final Logger logger = Logger.getLogger(RenderManager.class.getName());
-    private Renderer renderer;
-    private UniformBindingManager uniformBindingManager = new UniformBindingManager();
-    private ArrayList<ViewPort> preViewPorts = new ArrayList<ViewPort>();
-    private ArrayList<ViewPort> viewPorts = new ArrayList<ViewPort>();
-    private ArrayList<ViewPort> postViewPorts = new ArrayList<ViewPort>();
+    private final Renderer renderer;
+    private final UniformBindingManager uniformBindingManager = new UniformBindingManager();
+    private final ArrayList<ViewPort> preViewPorts = new ArrayList<>();
+    private final ArrayList<ViewPort> viewPorts = new ArrayList<>();
+    private final ArrayList<ViewPort> postViewPorts = new ArrayList<>();
     private Camera prevCam = null;
     private Material forcedMaterial = null;
     private String forcedTechnique = null;
     private RenderState forcedRenderState = null;
+    private final List<MatParamOverride> forcedOverrides = new ArrayList<>();
     private int viewX, viewY, viewWidth, viewHeight;
-    private Matrix4f orthoMatrix = new Matrix4f();
-    private LightList filteredLightList = new LightList(null);
-    private String tmpTech;
+    private final Matrix4f orthoMatrix = new Matrix4f();
+    private final LightList filteredLightList = new LightList(null);
     private boolean handleTranlucentBucket = true;
     private AppProfiler prof;
     private LightFilter lightFilter = new DefaultLightFilter();
     private TechniqueDef.LightMode preferredLightMode = TechniqueDef.LightMode.MultiPass;
     private int singlePassLightBatchSize = 1;
 
+
     /**
      * Create a high-level rendering interface over the
      * low-level rendering interface.
@@ -423,6 +428,44 @@ public class RenderManager {
         this.forcedTechnique = forcedTechnique;
     }
 
+    /**
+     * Adds a forced material parameter to use when rendering geometries.
+     * <p>
+     * The provided parameter takes precedence over parameters set on the
+     * material or any overrides that exist in the scene graph that have the
+     * same name.
+     *
+     * @param override The override to add
+     * @see MatParamOverride
+     * @see #removeForcedMatParam(com.jme3.material.MatParamOverride)
+     */
+    public void addForcedMatParam(MatParamOverride override) {
+        forcedOverrides.add(override);
+    }
+
+    /**
+     * Remove a forced material parameter previously added.
+     *
+     * @param override The override to remove.
+     * @see #addForcedMatParam(com.jme3.material.MatParamOverride)
+     */
+    public void removeForcedMatParam(MatParamOverride override) {
+        forcedOverrides.remove(override);
+    }
+
+    /**
+     * Get the forced material parameters applied to rendered geometries.
+     * <p>
+     * Forced parameters can be added via
+     * {@link #addForcedMatParam(com.jme3.material.MatParamOverride)} or removed
+     * via {@link #removeForcedMatParam(com.jme3.material.MatParamOverride)}.
+     *
+     * @return The forced material parameters.
+     */
+    public List<MatParamOverride> getForcedMatParams() {
+        return forcedOverrides;
+    }
+
     /**
      * Enable or disable alpha-to-coverage. 
      * <p>
@@ -480,8 +523,8 @@ public class RenderManager {
      * Updates the given list of uniforms with {@link UniformBinding uniform bindings}
      * based on the current world state.
      */
-    public void updateUniformBindings(List<Uniform> params) {
-        uniformBindingManager.updateUniformBindings(params);
+    public void updateUniformBindings(Shader shader) {
+        uniformBindingManager.updateUniformBindings(shader);
     }
 
     /**
@@ -508,45 +551,54 @@ public class RenderManager {
      * for rendering the material, and the material's own render state is ignored.
      * Otherwise, the material's render state is used as intended.
      * 
-     * @param g The geometry to render
-     * 
+     * @param geom The geometry to render
+       * 
      * @see Technique
      * @see RenderState
      * @see Material#selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) 
      * @see Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager) 
      */
-    public void renderGeometry(Geometry g) {
-        if (g.isIgnoreTransform()) {
+    public void renderGeometry(Geometry geom) {
+        if (geom.isIgnoreTransform()) {
             setWorldMatrix(Matrix4f.IDENTITY);
         } else {
-            setWorldMatrix(g.getWorldMatrix());
+            setWorldMatrix(geom.getWorldMatrix());
         }
         
         // Perform light filtering if we have a light filter.
-        LightList lightList = g.getWorldLightList();
+        LightList lightList = geom.getWorldLightList();
         
         if (lightFilter != null) {
             filteredLightList.clear();
-            lightFilter.filterLights(g, filteredLightList);
+            lightFilter.filterLights(geom, filteredLightList);
             lightList = filteredLightList;
         }
 
+        Material material = geom.getMaterial();
+
         //if forcedTechnique we try to force it for render,
         //if it does not exists in the mat def, we check for forcedMaterial and render the geom if not null
         //else the geom is not rendered
         if (forcedTechnique != null) {
-            if (g.getMaterial().getMaterialDef().getTechniqueDef(forcedTechnique) != null) {
-                tmpTech = g.getMaterial().getActiveTechnique() != null ? g.getMaterial().getActiveTechnique().getDef().getName() : "Default";
-                g.getMaterial().selectTechnique(forcedTechnique, this);
+            MaterialDef matDef = material.getMaterialDef();
+            if (matDef.getTechniqueDefs(forcedTechnique) != null) {
+
+                Technique activeTechnique = material.getActiveTechnique();
+
+                String previousTechniqueName = activeTechnique != null
+                        ? activeTechnique.getDef().getName()
+                        : TechniqueDef.DEFAULT_TECHNIQUE_NAME;
+
+                geom.getMaterial().selectTechnique(forcedTechnique, this);
                 //saving forcedRenderState for future calls
                 RenderState tmpRs = forcedRenderState;
-                if (g.getMaterial().getActiveTechnique().getDef().getForcedRenderState() != null) {
+                if (geom.getMaterial().getActiveTechnique().getDef().getForcedRenderState() != null) {
                     //forcing forced technique renderState
-                    forcedRenderState = g.getMaterial().getActiveTechnique().getDef().getForcedRenderState();
+                    forcedRenderState = geom.getMaterial().getActiveTechnique().getDef().getForcedRenderState();
                 }
                 // use geometry's material
-                g.getMaterial().render(g, lightList, this);
-                g.getMaterial().selectTechnique(tmpTech, this);
+                material.render(geom, lightList, this);
+                material.selectTechnique(previousTechniqueName, this);
 
                 //restoring forcedRenderState
                 forcedRenderState = tmpRs;
@@ -555,13 +607,13 @@ public class RenderManager {
                 //If forcedTechnique does not exists, and forcedMaterial is not set, the geom MUST NOT be rendered
             } else if (forcedMaterial != null) {
                 // use forced material
-                forcedMaterial.render(g, lightList, this);
+                forcedMaterial.render(geom, lightList, this);
             }
         } else if (forcedMaterial != null) {
             // use forced material
-            forcedMaterial.render(g, lightList, this);
+            forcedMaterial.render(geom, lightList, this);
         } else {
-            g.getMaterial().render(g, lightList, this);
+            material.render(geom, lightList, this);
         }
     }
 
@@ -612,7 +664,9 @@ public class RenderManager {
 
             gm.getMaterial().preload(this);
             Mesh mesh = gm.getMesh();
-            if (mesh != null) {
+            if (mesh != null
+                    && mesh.getVertexCount() != 0
+                    && mesh.getTriangleCount() != 0) {
                 for (VertexBuffer vb : mesh.getBufferList().getArray()) {
                     if (vb.getData() != null && vb.getUsage() != VertexBuffer.Usage.CpuOnly) {
                         renderer.updateBufferData(vb);
@@ -637,8 +691,10 @@ public class RenderManager {
      * <p>
      * In addition to enqueuing the visible geometries, this method
      * also scenes which cast or receive shadows, by putting them into the
-     * RenderQueue's {@link RenderQueue#renderShadowQueue(GeometryList, RenderManager, Camera, boolean) shadow queue}.
-     * Each Spatial which has its {@link Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode) shadow mode}
+     * RenderQueue's 
+     * {@link RenderQueue#addToShadowQueue(com.jme3.scene.Geometry, com.jme3.renderer.queue.RenderQueue.ShadowMode) 
+     * shadow queue}. Each Spatial which has its 
+     * {@link Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode) shadow mode}
      * set to not off, will be put into the appropriate shadow queue, note that
      * this process does not check for frustum culling on any 
      * {@link ShadowMode#Cast shadow casters}, as they don't have to be
@@ -985,7 +1041,8 @@ public class RenderManager {
      * (see {@link #renderTranslucentQueue(com.jme3.renderer.ViewPort) })</li>
      * <li>If any objects remained in the render queue, they are removed
      * from the queue. This is generally objects added to the 
-     * {@link RenderQueue#renderShadowQueue(GeometryList, RenderManager, Camera, boolean) shadow queue}
+     * {@link RenderQueue#renderShadowQueue(com.jme3.renderer.queue.RenderQueue.ShadowMode, com.jme3.renderer.RenderManager, com.jme3.renderer.Camera, boolean) 
+     * shadow queue}
      * which were not rendered because of a missing shadow renderer.</li>
      * </ul>
      * 

+ 23 - 1
jme3-core/src/main/java/com/jme3/renderer/Renderer.java

@@ -43,6 +43,7 @@ import com.jme3.texture.Image;
 import com.jme3.texture.Texture;
 import com.jme3.util.NativeObject;
 import java.nio.ByteBuffer;
+import java.util.EnumMap;
 import java.util.EnumSet;
 
 /**
@@ -66,6 +67,13 @@ public interface Renderer {
      */
     public EnumSet<Caps> getCaps();
 
+    /**
+     * Get the limits of the renderer.
+     *
+     * @return The limits of the renderer.
+     */
+    public EnumMap<Limits, Integer> getLimits();
+
     /**
      * The statistics allow tracking of how data
      * per frame, such as number of objects rendered, number of triangles, etc.
@@ -302,7 +310,21 @@ public interface Renderer {
      * @see NativeObject#deleteObject(java.lang.Object) 
      */
     public void cleanup();
-    
+
+    /**
+     * Set the default anisotropic filter level for textures.
+     *
+     * If the
+     * {@link Texture#setAnisotropicFilter(int) texture anisotropic filter} is
+     * set to 0, then the default level is used. Otherwise if the texture level
+     * is 1 or greater, then the texture's value overrides the default value.
+     *
+     * @param level The default anisotropic filter level to use. Default: 1.
+     *
+     * @throws IllegalArgumentException If level is less than 1.
+     */
+    public void setDefaultAnisotropicFilter(int level);
+
     /**
      * Sets the alpha to coverage state.
      * <p>

+ 144 - 136
jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java

@@ -45,142 +45,149 @@ import java.nio.ShortBuffer;
  */
 public interface GL {
 
-	public static final int GL_ALPHA = 0x1906;
-	public static final int GL_ALWAYS = 0x207;
-	public static final int GL_ARRAY_BUFFER = 0x8892;
-	public static final int GL_BACK = 0x405;
-	public static final int GL_BLEND = 0xBE2;
-	public static final int GL_BYTE = 0x1400;
-	public static final int GL_CLAMP_TO_EDGE = 0x812F;
-	public static final int GL_COLOR_BUFFER_BIT = 0x4000;
-	public static final int GL_COMPILE_STATUS = 0x8B81;
-	public static final int GL_CULL_FACE = 0xB44;
-	public static final int GL_DECR = 0x1E03;
-	public static final int GL_DECR_WRAP = 0x8508;
-	public static final int GL_DEPTH_BUFFER_BIT = 0x100;
-	public static final int GL_DEPTH_COMPONENT = 0x1902;
-	public static final int GL_DEPTH_COMPONENT16 = 0x81A5;
-	public static final int GL_DEPTH_TEST = 0xB71;
-	public static final int GL_DOUBLE = 0x140A;
-	public static final int GL_DST_COLOR = 0x306;
-	public static final int GL_DYNAMIC_DRAW = 0x88E8;
-	public static final int GL_ELEMENT_ARRAY_BUFFER = 0x8893;
-	public static final int GL_EQUAL = 0x202;
-	public static final int GL_EXTENSIONS = 0x1F03;
-	public static final int GL_FALSE = 0x0;
-	public static final int GL_FLOAT = 0x1406;
-	public static final int GL_FRAGMENT_SHADER = 0x8B30;
-	public static final int GL_FRONT = 0x404;
-	public static final int GL_FRONT_AND_BACK = 0x408;
-	public static final int GL_GEQUAL = 0x206;
-	public static final int GL_GREATER = 0x204;
-        public static final int GL_GREEN = 0x1904;
-	public static final int GL_INCR = 0x1E02;
-	public static final int GL_INCR_WRAP = 0x8507;
-	public static final int GL_INFO_LOG_LENGTH = 0x8B84;
-	public static final int GL_INT = 0x1404;
-        public static final int GL_INVALID_ENUM = 0x500;
-        public static final int GL_INVALID_VALUE = 0x501;
-        public static final int GL_INVALID_OPERATION = 0x502;
-	public static final int GL_INVERT = 0x150A;
-	public static final int GL_KEEP = 0x1E00;
-	public static final int GL_LEQUAL = 0x203;
-	public static final int GL_LESS = 0x201;
-	public static final int GL_LINEAR = 0x2601;
-	public static final int GL_LINEAR_MIPMAP_LINEAR = 0x2703;
-	public static final int GL_LINEAR_MIPMAP_NEAREST = 0x2701;
-	public static final int GL_LINES = 0x1;
-	public static final int GL_LINE_LOOP = 0x2;
-	public static final int GL_LINE_STRIP = 0x3;
-	public static final int GL_LINK_STATUS = 0x8B82;
-	public static final int GL_LUMINANCE = 0x1909;
-	public static final int GL_LUMINANCE_ALPHA = 0x190A;
-	public static final int GL_MAX_CUBE_MAP_TEXTURE_SIZE = 0x851C;
-	public static final int GL_MAX_TEXTURE_IMAGE_UNITS = 0x8872;
-	public static final int GL_MAX_TEXTURE_SIZE = 0xD33;
-	public static final int GL_MAX_VERTEX_ATTRIBS = 0x8869;
-	public static final int GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x8B4C;
-	public static final int GL_MAX_VERTEX_UNIFORM_COMPONENTS  = 0x8B4A;
-	public static final int GL_MAX_VERTEX_UNIFORM_VECTORS = 0x8DFB;
-	public static final int GL_MIRRORED_REPEAT = 0x8370;
-	public static final int GL_NEAREST = 0x2600;
-	public static final int GL_NEAREST_MIPMAP_LINEAR = 0x2702;
-	public static final int GL_NEAREST_MIPMAP_NEAREST = 0x2700;
-	public static final int GL_NEVER = 0x200;
-        public static final int GL_NO_ERROR = 0x0;
-	public static final int GL_NONE = 0x0;
-	public static final int GL_NOTEQUAL = 0x205;
-	public static final int GL_ONE = 0x1;
-	public static final int GL_ONE_MINUS_DST_COLOR = 0x307;
-	public static final int GL_ONE_MINUS_SRC_ALPHA = 0x303;
-	public static final int GL_ONE_MINUS_SRC_COLOR = 0x301;
-        public static final int GL_OUT_OF_MEMORY = 0x505;
-	public static final int GL_POINTS = 0x0;
-	public static final int GL_POLYGON_OFFSET_FILL = 0x8037;
-        public static final int GL_RED = 0x1903;
-        public static final int GL_RENDERER = 0x1F01;
-	public static final int GL_REPEAT = 0x2901;
-	public static final int GL_REPLACE = 0x1E01;
-	public static final int GL_RGB = 0x1907;
-        public static final int GL_RGB565 = 0x8D62;
-        public static final int GL_RGB5_A1 = 0x8057;
-	public static final int GL_RGBA = 0x1908;
-        public static final int GL_RGBA4 = 0x8056;
-	public static final int GL_SCISSOR_TEST = 0xC11;
-	public static final int GL_SHADING_LANGUAGE_VERSION = 0x8B8C;
-	public static final int GL_SHORT = 0x1402;
-	public static final int GL_SRC_ALPHA = 0x302;
-	public static final int GL_SRC_COLOR = 0x300;
-	public static final int GL_STATIC_DRAW = 0x88E4;
-	public static final int GL_STENCIL_BUFFER_BIT = 0x400;
-	public static final int GL_STENCIL_TEST = 0xB90;
-	public static final int GL_STREAM_DRAW = 0x88E0;
-        public static final int GL_STREAM_READ = 0x88E1;
-	public static final int GL_TEXTURE = 0x1702;
-	public static final int GL_TEXTURE0 = 0x84C0;
-        public static final int GL_TEXTURE1 = 0x84C1;
-        public static final int GL_TEXTURE2 = 0x84C2;
-        public static final int GL_TEXTURE3 = 0x84C3;
-        public static final int GL_TEXTURE4 = 0x84C4;
-        public static final int GL_TEXTURE5 = 0x84C5;
-        public static final int GL_TEXTURE6 = 0x84C6;
-        public static final int GL_TEXTURE7 = 0x84C7;
-        public static final int GL_TEXTURE8 = 0x84C8;
-        public static final int GL_TEXTURE9 = 0x84C9;
-        public static final int GL_TEXTURE10 = 0x84CA;
-        public static final int GL_TEXTURE11 = 0x84CB;
-        public static final int GL_TEXTURE12 = 0x84CC;
-        public static final int GL_TEXTURE13 = 0x84CD;
-        public static final int GL_TEXTURE14 = 0x84CE;
-        public static final int GL_TEXTURE15 = 0x84CF;
-	public static final int GL_TEXTURE_2D = 0xDE1;
-	public static final int GL_TEXTURE_CUBE_MAP = 0x8513;
-	public static final int GL_TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515;
-        public static final int GL_TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516;
-        public static final int GL_TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517;
-        public static final int GL_TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518;
-        public static final int GL_TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519;
-        public static final int GL_TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A;
-        public static final int GL_TEXTURE_BASE_LEVEL = 0x813C;
-	public static final int GL_TEXTURE_MAG_FILTER = 0x2800;
-	public static final int GL_TEXTURE_MAX_LEVEL = 0x813D;
-	public static final int GL_TEXTURE_MIN_FILTER = 0x2801;
-	public static final int GL_TEXTURE_WRAP_S = 0x2802;
-	public static final int GL_TEXTURE_WRAP_T = 0x2803;
-	public static final int GL_TRIANGLES = 0x4;
-	public static final int GL_TRIANGLE_FAN = 0x6;
-	public static final int GL_TRIANGLE_STRIP = 0x5;
-	public static final int GL_TRUE = 0x1;
-	public static final int GL_UNPACK_ALIGNMENT = 0xCF5;
-	public static final int GL_UNSIGNED_BYTE = 0x1401;
-	public static final int GL_UNSIGNED_INT = 0x1405;
-	public static final int GL_UNSIGNED_SHORT = 0x1403;
-        public static final int GL_UNSIGNED_SHORT_5_6_5 = 0x8363;
-	public static final int GL_UNSIGNED_SHORT_5_5_5_1 = 0x8034;
-        public static final int GL_VENDOR = 0x1F00;
-	public static final int GL_VERSION = 0x1F02;
-	public static final int GL_VERTEX_SHADER = 0x8B31;
-	public static final int GL_ZERO = 0x0;
+    public static final int GL_ALPHA = 0x1906;
+    public static final int GL_ALWAYS = 0x207;
+    public static final int GL_ARRAY_BUFFER = 0x8892;
+    public static final int GL_BACK = 0x405;
+    public static final int GL_BLEND = 0xBE2;
+    public static final int GL_BYTE = 0x1400;
+    public static final int GL_CLAMP_TO_EDGE = 0x812F;
+    public static final int GL_COLOR_BUFFER_BIT = 0x4000;
+    public static final int GL_COMPILE_STATUS = 0x8B81;
+    public static final int GL_CULL_FACE = 0xB44;
+    public static final int GL_DECR = 0x1E03;
+    public static final int GL_DECR_WRAP = 0x8508;
+    public static final int GL_DEPTH_BUFFER_BIT = 0x100;
+    public static final int GL_DEPTH_COMPONENT = 0x1902;
+    public static final int GL_DEPTH_COMPONENT16 = 0x81A5;
+    public static final int GL_DEPTH_TEST = 0xB71;
+    public static final int GL_DOUBLE = 0x140A;
+    public static final int GL_DST_COLOR = 0x306;
+    public static final int GL_DYNAMIC_DRAW = 0x88E8;
+    public static final int GL_ELEMENT_ARRAY_BUFFER = 0x8893;
+    public static final int GL_EQUAL = 0x202;
+    public static final int GL_EXTENSIONS = 0x1F03;
+    public static final int GL_FALSE = 0x0;
+    public static final int GL_FLOAT = 0x1406;
+    public static final int GL_FRAGMENT_SHADER = 0x8B30;
+    public static final int GL_FRONT = 0x404;
+    public static final int GL_FUNC_ADD = 0x8006;
+    public static final int GL_FUNC_SUBTRACT = 0x800A;
+    public static final int GL_FUNC_REVERSE_SUBTRACT = 0x800B;
+    public static final int GL_FRONT_AND_BACK = 0x408;
+    public static final int GL_GEQUAL = 0x206;
+    public static final int GL_GREATER = 0x204;
+    public static final int GL_GREEN = 0x1904;
+    public static final int GL_INCR = 0x1E02;
+    public static final int GL_INCR_WRAP = 0x8507;
+    public static final int GL_INFO_LOG_LENGTH = 0x8B84;
+    public static final int GL_INT = 0x1404;
+    public static final int GL_INVALID_ENUM = 0x500;
+    public static final int GL_INVALID_VALUE = 0x501;
+    public static final int GL_INVALID_OPERATION = 0x502;
+    public static final int GL_INVERT = 0x150A;
+    public static final int GL_KEEP = 0x1E00;
+    public static final int GL_LEQUAL = 0x203;
+    public static final int GL_LESS = 0x201;
+    public static final int GL_LINEAR = 0x2601;
+    public static final int GL_LINEAR_MIPMAP_LINEAR = 0x2703;
+    public static final int GL_LINEAR_MIPMAP_NEAREST = 0x2701;
+    public static final int GL_LINES = 0x1;
+    public static final int GL_LINE_LOOP = 0x2;
+    public static final int GL_LINE_STRIP = 0x3;
+    public static final int GL_LINK_STATUS = 0x8B82;
+    public static final int GL_LUMINANCE = 0x1909;
+    public static final int GL_LUMINANCE_ALPHA = 0x190A;
+    public static final int GL_MAX = 0x8008;
+    public static final int GL_MAX_CUBE_MAP_TEXTURE_SIZE = 0x851C;
+    public static final int GL_MAX_FRAGMENT_UNIFORM_COMPONENTS = 0x8B49;
+    public static final int GL_MAX_FRAGMENT_UNIFORM_VECTORS = 0x8DFD;
+    public static final int GL_MAX_TEXTURE_IMAGE_UNITS = 0x8872;
+    public static final int GL_MAX_TEXTURE_SIZE = 0xD33;
+    public static final int GL_MAX_VERTEX_ATTRIBS = 0x8869;
+    public static final int GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x8B4C;
+    public static final int GL_MAX_VERTEX_UNIFORM_COMPONENTS = 0x8B4A;
+    public static final int GL_MAX_VERTEX_UNIFORM_VECTORS = 0x8DFB;
+    public static final int GL_MIRRORED_REPEAT = 0x8370;
+    public static final int GL_MIN = 0x8007;
+    public static final int GL_NEAREST = 0x2600;
+    public static final int GL_NEAREST_MIPMAP_LINEAR = 0x2702;
+    public static final int GL_NEAREST_MIPMAP_NEAREST = 0x2700;
+    public static final int GL_NEVER = 0x200;
+    public static final int GL_NO_ERROR = 0x0;
+    public static final int GL_NONE = 0x0;
+    public static final int GL_NOTEQUAL = 0x205;
+    public static final int GL_ONE = 0x1;
+    public static final int GL_ONE_MINUS_DST_COLOR = 0x307;
+    public static final int GL_ONE_MINUS_SRC_ALPHA = 0x303;
+    public static final int GL_ONE_MINUS_SRC_COLOR = 0x301;
+    public static final int GL_OUT_OF_MEMORY = 0x505;
+    public static final int GL_POINTS = 0x0;
+    public static final int GL_POLYGON_OFFSET_FILL = 0x8037;
+    public static final int GL_RED = 0x1903;
+    public static final int GL_RENDERER = 0x1F01;
+    public static final int GL_REPEAT = 0x2901;
+    public static final int GL_REPLACE = 0x1E01;
+    public static final int GL_RGB = 0x1907;
+    public static final int GL_RGB565 = 0x8D62;
+    public static final int GL_RGB5_A1 = 0x8057;
+    public static final int GL_RGBA = 0x1908;
+    public static final int GL_RGBA4 = 0x8056;
+    public static final int GL_SCISSOR_TEST = 0xC11;
+    public static final int GL_SHADING_LANGUAGE_VERSION = 0x8B8C;
+    public static final int GL_SHORT = 0x1402;
+    public static final int GL_SRC_ALPHA = 0x302;
+    public static final int GL_SRC_COLOR = 0x300;
+    public static final int GL_STATIC_DRAW = 0x88E4;
+    public static final int GL_STENCIL_BUFFER_BIT = 0x400;
+    public static final int GL_STENCIL_TEST = 0xB90;
+    public static final int GL_STREAM_DRAW = 0x88E0;
+    public static final int GL_STREAM_READ = 0x88E1;
+    public static final int GL_TEXTURE = 0x1702;
+    public static final int GL_TEXTURE0 = 0x84C0;
+    public static final int GL_TEXTURE1 = 0x84C1;
+    public static final int GL_TEXTURE2 = 0x84C2;
+    public static final int GL_TEXTURE3 = 0x84C3;
+    public static final int GL_TEXTURE4 = 0x84C4;
+    public static final int GL_TEXTURE5 = 0x84C5;
+    public static final int GL_TEXTURE6 = 0x84C6;
+    public static final int GL_TEXTURE7 = 0x84C7;
+    public static final int GL_TEXTURE8 = 0x84C8;
+    public static final int GL_TEXTURE9 = 0x84C9;
+    public static final int GL_TEXTURE10 = 0x84CA;
+    public static final int GL_TEXTURE11 = 0x84CB;
+    public static final int GL_TEXTURE12 = 0x84CC;
+    public static final int GL_TEXTURE13 = 0x84CD;
+    public static final int GL_TEXTURE14 = 0x84CE;
+    public static final int GL_TEXTURE15 = 0x84CF;
+    public static final int GL_TEXTURE_2D = 0xDE1;
+    public static final int GL_TEXTURE_CUBE_MAP = 0x8513;
+    public static final int GL_TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515;
+    public static final int GL_TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516;
+    public static final int GL_TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517;
+    public static final int GL_TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518;
+    public static final int GL_TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519;
+    public static final int GL_TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A;
+    public static final int GL_TEXTURE_BASE_LEVEL = 0x813C;
+    public static final int GL_TEXTURE_MAG_FILTER = 0x2800;
+    public static final int GL_TEXTURE_MAX_LEVEL = 0x813D;
+    public static final int GL_TEXTURE_MIN_FILTER = 0x2801;
+    public static final int GL_TEXTURE_WRAP_S = 0x2802;
+    public static final int GL_TEXTURE_WRAP_T = 0x2803;
+    public static final int GL_TRIANGLES = 0x4;
+    public static final int GL_TRIANGLE_FAN = 0x6;
+    public static final int GL_TRIANGLE_STRIP = 0x5;
+    public static final int GL_TRUE = 0x1;
+    public static final int GL_UNPACK_ALIGNMENT = 0xCF5;
+    public static final int GL_UNSIGNED_BYTE = 0x1401;
+    public static final int GL_UNSIGNED_INT = 0x1405;
+    public static final int GL_UNSIGNED_SHORT = 0x1403;
+    public static final int GL_UNSIGNED_SHORT_5_6_5 = 0x8363;
+    public static final int GL_UNSIGNED_SHORT_5_5_5_1 = 0x8034;
+    public static final int GL_VENDOR = 0x1F00;
+    public static final int GL_VERSION = 0x1F02;
+    public static final int GL_VERTEX_SHADER = 0x8B31;
+    public static final int GL_ZERO = 0x0;
 
         public void resetStats();
         
@@ -188,6 +195,7 @@ public interface GL {
 	public void glAttachShader(int program, int shader);
 	public void glBindBuffer(int target, int buffer);
 	public void glBindTexture(int target, int texture);
+	public void glBlendEquationSeparate(int colorMode, int alphaMode);
 	public void glBlendFunc(int sfactor, int dfactor);
         public void glBufferData(int target, long data_size, int usage);
 	public void glBufferData(int target, FloatBuffer data, int usage);

+ 5 - 0
jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java

@@ -100,4 +100,9 @@ public class GLDebugDesktop extends GLDebugES implements GL2, GL3, GL4 {
         gl3.glFramebufferTextureLayer(param1, param2, param3, param4, param5);
         checkError();
     }
+
+    public void glBlendEquationSeparate(int colorMode, int alphaMode) {
+        gl.glBlendEquationSeparate(colorMode, alphaMode);
+        checkError();
+    }
 }

+ 5 - 0
jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java

@@ -560,4 +560,9 @@ public class GLDebugES extends GLDebug implements GL, GLFbo, GLExt {
         checkError();
         return sync;
     }
+
+    public void glBlendEquationSeparate(int colorMode, int alphaMode) {
+        gl.glBlendEquationSeparate(colorMode, alphaMode);
+        checkError();
+    }
 }

+ 92 - 21
jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java

@@ -90,6 +90,7 @@ public final class GLRenderer implements Renderer {
     private final Statistics statistics = new Statistics();
     private int vpX, vpY, vpW, vpH;
     private int clipX, clipY, clipW, clipH;
+    private int defaultAnisotropicFilter = 1;
     private boolean linearizeSrgbImages;
     private HashSet<String> extensions;
 
@@ -252,18 +253,14 @@ public final class GLRenderer implements Renderer {
 
         limits.put(Limits.FragmentTextureUnits, getInteger(GL.GL_MAX_TEXTURE_IMAGE_UNITS));
 
-//        gl.glGetInteger(GL.GL_MAX_VERTEX_UNIFORM_COMPONENTS, intBuf16);
-//        vertexUniforms = intBuf16.get(0);
-//        logger.log(Level.FINER, "Vertex Uniforms: {0}", vertexUniforms);
-//
-//        gl.glGetInteger(GL.GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, intBuf16);
-//        fragUniforms = intBuf16.get(0);
-//        logger.log(Level.FINER, "Fragment Uniforms: {0}", fragUniforms);
         if (caps.contains(Caps.OpenGLES20)) {
+            limits.put(Limits.FragmentUniformVectors, getInteger(GL.GL_MAX_FRAGMENT_UNIFORM_VECTORS));
             limits.put(Limits.VertexUniformVectors, getInteger(GL.GL_MAX_VERTEX_UNIFORM_VECTORS));
         } else {
+            limits.put(Limits.FragmentUniformVectors, getInteger(GL.GL_MAX_FRAGMENT_UNIFORM_COMPONENTS) / 4);
             limits.put(Limits.VertexUniformVectors, getInteger(GL.GL_MAX_VERTEX_UNIFORM_COMPONENTS) / 4);
         }
+
         limits.put(Limits.VertexAttributes, getInteger(GL.GL_MAX_VERTEX_ATTRIBS));
         limits.put(Limits.TextureSize, getInteger(GL.GL_MAX_TEXTURE_SIZE));
         limits.put(Limits.CubemapSize, getInteger(GL.GL_MAX_CUBE_MAP_TEXTURE_SIZE));
@@ -474,6 +471,17 @@ public final class GLRenderer implements Renderer {
             {
                 sb.append("\t").append(cap.toString()).append("\n");
             }
+            
+            sb.append("\nHardware limits: \n");
+            for (Limits limit : Limits.values()) {
+                Integer value = limits.get(limit);
+                if (value == null) {
+                    value = 0;
+                }
+                sb.append("\t").append(limit.name()).append(" = ")
+                  .append(value).append("\n");
+            }
+            
             logger.log(Level.FINE, sb.toString());
         }
 
@@ -522,7 +530,6 @@ public final class GLRenderer implements Renderer {
             gl2.glEnable(GL2.GL_VERTEX_PROGRAM_POINT_SIZE);
             if (!caps.contains(Caps.CoreProfile)) {
                 gl2.glEnable(GL2.GL_POINT_SPRITE);
-                context.pointSprite = true;
             }
         }
     }
@@ -594,6 +601,14 @@ public final class GLRenderer implements Renderer {
         }
     }
 
+    @Override
+    public void setDefaultAnisotropicFilter(int level) {
+        if (level < 1) {
+            throw new IllegalArgumentException("level cannot be less than 1");
+        }
+        this.defaultAnisotropicFilter = level;
+    }
+
     public void setAlphaToCoverage(boolean value) {
         if (caps.contains(Caps.Multisample)) {
             if (value) {
@@ -735,6 +750,19 @@ public final class GLRenderer implements Renderer {
                         throw new UnsupportedOperationException("Unrecognized blend mode: "
                                 + state.getBlendMode());
                 }
+                
+                if (state.getBlendEquation() != context.blendEquation || state.getBlendEquationAlpha() != context.blendEquationAlpha) {
+                    int colorMode = convertBlendEquation(state.getBlendEquation());
+                    int alphaMode;
+                    if (state.getBlendEquationAlpha() == RenderState.BlendEquationAlpha.InheritColor) {
+                        alphaMode = colorMode;
+                    } else {
+                        alphaMode = convertBlendEquationAlpha(state.getBlendEquationAlpha());
+                    }
+                    gl.glBlendEquationSeparate(colorMode, alphaMode);
+                    context.blendEquation = state.getBlendEquation();
+                    context.blendEquationAlpha = state.getBlendEquationAlpha();
+                }
             }
 
             context.blendMode = state.getBlendMode();
@@ -785,6 +813,41 @@ public final class GLRenderer implements Renderer {
         }
     }
 
+    private int convertBlendEquation(RenderState.BlendEquation blendEquation) {
+        switch (blendEquation) {
+            case Add:
+                return GL2.GL_FUNC_ADD;
+            case Subtract:
+                return GL2.GL_FUNC_SUBTRACT;
+            case ReverseSubtract:
+                return GL2.GL_FUNC_REVERSE_SUBTRACT;
+            case Min:
+                return GL2.GL_MIN;
+            case Max:
+                return GL2.GL_MAX;
+            default:
+                throw new UnsupportedOperationException("Unrecognized blend operation: " + blendEquation);
+        }
+    }
+    
+    private int convertBlendEquationAlpha(RenderState.BlendEquationAlpha blendEquationAlpha) {
+        //Note: InheritColor mode should already be handled, that is why it does not belong the the switch case.
+        switch (blendEquationAlpha) {
+            case Add:
+                return GL2.GL_FUNC_ADD;
+            case Subtract:
+                return GL2.GL_FUNC_SUBTRACT;
+            case ReverseSubtract:
+                return GL2.GL_FUNC_REVERSE_SUBTRACT;
+            case Min:
+                return GL2.GL_MIN;
+            case Max:
+                return GL2.GL_MAX;
+            default:
+                throw new UnsupportedOperationException("Unrecognized alpha blend operation: " + blendEquationAlpha);
+        }
+    }
+
     private int convertStencilOperation(StencilOperation stencilOp) {
         switch (stencilOp) {
             case Keep:
@@ -964,12 +1027,12 @@ public final class GLRenderer implements Renderer {
                 gl.glUniform1i(loc, b.booleanValue() ? GL.GL_TRUE : GL.GL_FALSE);
                 break;
             case Matrix3:
-                fb = (FloatBuffer) uniform.getValue();
+                fb = uniform.getMultiData();
                 assert fb.remaining() == 9;
                 gl.glUniformMatrix3(loc, false, fb);
                 break;
             case Matrix4:
-                fb = (FloatBuffer) uniform.getValue();
+                fb = uniform.getMultiData();
                 assert fb.remaining() == 16;
                 gl.glUniformMatrix4(loc, false, fb);
                 break;
@@ -978,23 +1041,23 @@ public final class GLRenderer implements Renderer {
                 gl.glUniform1(loc, ib);
                 break;
             case FloatArray:
-                fb = (FloatBuffer) uniform.getValue();
+                fb = uniform.getMultiData();
                 gl.glUniform1(loc, fb);
                 break;
             case Vector2Array:
-                fb = (FloatBuffer) uniform.getValue();
+                fb = uniform.getMultiData();
                 gl.glUniform2(loc, fb);
                 break;
             case Vector3Array:
-                fb = (FloatBuffer) uniform.getValue();
+                fb = uniform.getMultiData();
                 gl.glUniform3(loc, fb);
                 break;
             case Vector4Array:
-                fb = (FloatBuffer) uniform.getValue();
+                fb = uniform.getMultiData();
                 gl.glUniform4(loc, fb);
                 break;
             case Matrix4Array:
-                fb = (FloatBuffer) uniform.getValue();
+                fb = uniform.getMultiData();
                 gl.glUniformMatrix4(loc, false, fb);
                 break;
             case Int:
@@ -1872,13 +1935,18 @@ public final class GLRenderer implements Renderer {
             gl.glTexParameteri(target, GL.GL_TEXTURE_MIN_FILTER, convertMinFilter(tex.getMinFilter(), haveMips));
             curState.minFilter = tex.getMinFilter();
         }
+
+        int desiredAnisoFilter = tex.getAnisotropicFilter() == 0
+                ? defaultAnisotropicFilter
+                : tex.getAnisotropicFilter();
+
         if (caps.contains(Caps.TextureFilterAnisotropic)
-                && curState.anisoFilter != tex.getAnisotropicFilter()) {
+                && curState.anisoFilter != desiredAnisoFilter) {
             bindTextureAndUnit(target, image, unit);
             gl.glTexParameterf(target,
                     GLExt.GL_TEXTURE_MAX_ANISOTROPY_EXT,
-                    tex.getAnisotropicFilter());
-            curState.anisoFilter = tex.getAnisotropicFilter();
+                    desiredAnisoFilter);
+            curState.anisoFilter = desiredAnisoFilter;
         }
 
         switch (tex.getType()) {
@@ -2689,12 +2757,15 @@ public final class GLRenderer implements Renderer {
     }
 
     public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
-        if (mesh.getVertexCount() == 0) {
+        if (mesh.getVertexCount() == 0 || mesh.getTriangleCount() == 0 || count == 0) {
             return;
         }
 
-        //this is kept for backward compatibility.
-        if (mesh.getLineWidth() != -1 && context.lineWidth != mesh.getLineWidth()) {
+        if (count > 1 && !caps.contains(Caps.MeshInstancing)) {
+            throw new RendererException("Mesh instancing is not supported by the video hardware");
+        }
+
+        if (mesh.getLineWidth() != 1f && context.lineWidth != mesh.getLineWidth()) {
             gl.glLineWidth(mesh.getLineWidth());
             context.lineWidth = mesh.getLineWidth();
         }

+ 10 - 0
jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java

@@ -99,6 +99,16 @@ public class GeometryList implements Iterable<Geometry>{
         return size;
     }
 
+    /**
+     * Sets the element at the given index.
+     * 
+     * @param index The index to set
+     * @param value The value
+     */
+    public void set(int index, Geometry value) {
+        geometries[index] = value;
+    }
+    
     /**
      * Returns the element at the given index.
      *

+ 3 - 2
jme3-core/src/main/java/com/jme3/renderer/queue/OpaqueComparator.java

@@ -69,11 +69,12 @@ public class OpaqueComparator implements GeometryComparator {
         return spat.queueDistance;
     }
 
+    @Override
     public int compare(Geometry o1, Geometry o2) {
         Material m1 = o1.getMaterial();
         Material m2 = o2.getMaterial();
-
-        int compareResult = m2.getSortId() - m1.getSortId();
+        
+        int compareResult = Integer.compare(m1.getSortId(), m2.getSortId());
         if (compareResult == 0){
             // use the same shader.
             // sort front-to-back then.

+ 13 - 12
jme3-core/src/main/java/com/jme3/scene/Mesh.java

@@ -175,7 +175,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
     private IntMap<VertexBuffer> buffers = new IntMap<VertexBuffer>();
     private VertexBuffer[] lodLevels;
     private float pointSize = 1;
-    private float lineWidth = -1;
+    private float lineWidth = 1;
 
     private transient int vertexArrayID = -1;
 
@@ -589,28 +589,26 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
     }
 
     /**
-     * Returns the size of points for point meshes
+     * @deprecated Always returns <code>1.0</code> since point size is
+     * determined in the vertex shader.
      *
-     * @return the size of points
+     * @return <code>1.0</code>
      *
      * @see #setPointSize(float)
      */
+    @Deprecated
     public float getPointSize() {
-        return pointSize;
+        return 1.0f;
     }
 
     /**
-     * Set the size of points for meshes of mode {@link Mode#Points}.
-     * The point size is specified as on-screen pixels, the default
-     * value is 1.0. The point size
-     * does nothing if {@link RenderState#setPointSprite(boolean) point sprite}
-     * render state is enabled, in that case, the vertex shader must specify the
-     * point size by writing to <code>gl_PointSize</code>.
+     * @deprecated Does nothing, since the size of {@link Mode#Points points} is
+     * determined via the vertex shader's <code>gl_PointSize</code> output.
      *
-     * @param pointSize The size of points
+     * @param pointSize ignored
      */
+    @Deprecated
     public void setPointSize(float pointSize) {
-        this.pointSize = pointSize;
     }
 
     /**
@@ -634,6 +632,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
      */
     @Deprecated
     public void setLineWidth(float lineWidth) {
+        if (lineWidth < 1f) {
+            throw new IllegalArgumentException("lineWidth must be greater than or equal to 1.0");
+        }
         this.lineWidth = lineWidth;
     }
 

+ 18 - 26
jme3-core/src/main/java/com/jme3/scene/Node.java

@@ -75,7 +75,6 @@ public class Node extends Spatial {
      * requiresUpdate() method.
      */
     private SafeArrayList<Spatial> updateList = null;
-
     /**
      * False if the update list requires rebuilding.  This is Node.class
      * specific and therefore not included as part of the Spatial update flags.
@@ -100,7 +99,6 @@ public class Node extends Spatial {
      */
     public Node(String name) {
         super(name);
-
         // For backwards compatibility, only clear the "requires
         // update" flag if we are not a subclass of Node.
         // This prevents subclass from silently failing to receive
@@ -141,10 +139,21 @@ public class Node extends Spatial {
         }
     }
 
+    @Override
+    protected void setMatParamOverrideRefresh() {
+        super.setMatParamOverrideRefresh();
+        for (Spatial child : children.getArray()) {
+            if ((child.refreshFlags & RF_MATPARAM_OVERRIDE) != 0) {
+                continue;
+            }
+
+            child.setMatParamOverrideRefresh();
+        }
+    }
+
     @Override
     protected void updateWorldBound(){
         super.updateWorldBound();
-
         // for a node, the world bound is a combination of all it's children
         // bounds
         BoundingVolume resultBound = null;
@@ -239,19 +248,19 @@ public class Node extends Spatial {
             // This branch has no geometric state that requires updates.
             return;
         }
-
         if ((refreshFlags & RF_LIGHTLIST) != 0){
             updateWorldLightList();
         }
-
         if ((refreshFlags & RF_TRANSFORM) != 0){
             // combine with parent transforms- same for all spatial
             // subclasses.
             updateWorldTransforms();
         }
+        if ((refreshFlags & RF_MATPARAM_OVERRIDE) != 0) {
+            updateMatParamOverrides();
+        }
 
         refreshFlags &= ~RF_CHILD_LIGHTLIST;
-
         if (!children.isEmpty()) {
             // the important part- make sure child geometric state is refreshed
             // first before updating own world bound. This saves
@@ -287,7 +296,6 @@ public class Node extends Spatial {
 
         return count;
     }
-
     /**
      * <code>getVertexCount</code> returns the number of vertices contained
      * in all sub-branches of this node that contain geometry.
@@ -321,7 +329,6 @@ public class Node extends Spatial {
     public int attachChild(Spatial child) {
         return attachChildAt(child, children.size());
     }
-
     /**
      *
      * <code>attachChildAt</code> attaches a child to this node at an index. This node
@@ -345,20 +352,18 @@ public class Node extends Spatial {
             }
             child.setParent(this);
             children.add(index, child);
-
             // XXX: Not entirely correct? Forces bound update up the
             // tree stemming from the attached child. Also forces
             // transform update down the tree-
             child.setTransformRefresh();
             child.setLightListRefresh();
+            child.setMatParamOverrideRefresh();
             if (logger.isLoggable(Level.FINE)) {
                 logger.log(Level.FINE,"Child ({0}) attached to this node ({1})",
                         new Object[]{child.getName(), getName()});
             }
-
             invalidateUpdateList();
         }
-
         return children.size();
     }
 
@@ -433,7 +438,8 @@ public class Node extends Spatial {
             child.setTransformRefresh();
             // lights are also inherited from parent
             child.setLightListRefresh();
-
+            child.setMatParamOverrideRefresh();
+            
             invalidateUpdateList();
         }
         return child;
@@ -519,7 +525,6 @@ public class Node extends Spatial {
         }
         return null;
     }
-
     /**
      * determines if the provided Spatial is contained in the children list of
      * this node.
@@ -567,39 +572,32 @@ public class Node extends Spatial {
 
     public int collideWith(Collidable other, CollisionResults results){
         int total = 0;
-
         // optimization: try collideWith BoundingVolume to avoid possibly redundant tests on children
         // number 4 in condition is somewhat arbitrary. When there is only one child, the boundingVolume test is redundant at all.
         // The idea is when there are few children, it can be too expensive to test boundingVolume first.
         /*
         I'm removing this change until some issues can be addressed and I really
         think it needs to be implemented a better way anyway.
-
         First, it causes issues for anyone doing collideWith() with BoundingVolumes
         and expecting it to trickle down to the children.  For example, children
         with BoundingSphere bounding volumes and collideWith(BoundingSphere).  Doing
         a collision check at the parent level then has to do a BoundingSphere to BoundingBox
         collision which isn't resolved.  (Having to come up with a collision point in that
         case is tricky and the first sign that this is the wrong approach.)
-
         Second, the rippling changes this caused to 'optimize' collideWith() for this
         special use-case are another sign that this approach was a bit dodgy.  The whole
         idea of calculating a full collision just to see if the two shapes collide at all
         is very wasteful.
-
         A proper implementation should support a simpler boolean check that doesn't do
         all of that calculation.  For example, if 'other' is also a BoundingVolume (ie: 99.9%
         of all non-Ray cases) then a direct BV to BV intersects() test can be done.  So much
         faster.  And if 'other' _is_ a Ray then the BV.intersects(Ray) call can be done.
-
         I don't have time to do it right now but I'll at least un-break a bunch of peoples'
         code until it can be 'optimized' properly.  Hopefully it's not too late to back out
         the other dodgy ripples this caused.  -pspeed (hindsight-expert ;))
-
         Note: the code itself is relatively simple to implement but I don't have time to
         a) test it, and b) see if '> 4' is still a decent check for it.  Could be it's fast
         enough to do all the time for > 1.
-
         if (children.size() > 4)
         {
           BoundingVolume bv = this.getWorldBound();
@@ -692,7 +690,6 @@ public class Node extends Spatial {
         // Reset the fields of the clone that should be in a 'new' state.
         nodeClone.updateList = null;
         nodeClone.updateListValid = false; // safe because parent is nulled out in super.clone()
-
         return nodeClone;
     }
 
@@ -732,7 +729,6 @@ public class Node extends Spatial {
         // cloning this list is fine.
         this.updateList = cloner.clone(updateList);
     }
-
     @Override
     public void write(JmeExporter e) throws IOException {
         super.write(e);
@@ -744,7 +740,6 @@ public class Node extends Spatial {
         // XXX: Load children before loading itself!!
         // This prevents empty children list if controls query
         // it in Control.setSpatial().
-
         children = new SafeArrayList( Spatial.class,
                                       e.getCapsule(this).readSavableArrayList("children", null) );
 
@@ -754,7 +749,6 @@ public class Node extends Spatial {
                 child.parent = this;
             }
         }
-
         super.read(e);
     }
 
@@ -775,7 +769,6 @@ public class Node extends Spatial {
             }
         }
     }
-
     @Override
     public void depthFirstTraversal(SceneGraphVisitor visitor) {
         for (Spatial child : children.getArray()) {
@@ -783,7 +776,6 @@ public class Node extends Spatial {
         }
         visitor.visit(this);
     }
-
     @Override
     protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
         queue.addAll(children);

+ 125 - 35
jme3-core/src/main/java/com/jme3/scene/Spatial.java

@@ -38,6 +38,7 @@ import com.jme3.collision.Collidable;
 import com.jme3.export.*;
 import com.jme3.light.Light;
 import com.jme3.light.LightList;
+import com.jme3.material.MatParamOverride;
 import com.jme3.material.Material;
 import com.jme3.math.*;
 import com.jme3.renderer.Camera;
@@ -121,9 +122,10 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
      */
     protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms
                                RF_BOUND = 0x02,
-                               RF_LIGHTLIST = 0x04, // changes in light lists
-                               RF_CHILD_LIGHTLIST = 0x08; // some child need geometry update
-
+                               RF_LIGHTLIST = 0x04, // changes in light lists 
+                               RF_CHILD_LIGHTLIST = 0x08, // some child need geometry update
+                               RF_MATPARAM_OVERRIDE = 0x10;
+    
     protected CullHint cullHint = CullHint.Inherit;
     protected BatchHint batchHint = BatchHint.Inherit;
     /**
@@ -135,7 +137,11 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
      */
     protected LightList localLights;
     protected transient LightList worldLights;
-    /**
+
+    protected ArrayList<MatParamOverride> localOverrides;
+    protected ArrayList<MatParamOverride> worldOverrides;
+
+    /** 
      * This spatial's name.
      */
     protected String name;
@@ -195,13 +201,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
      */
     protected Spatial(String name) {
         this.name = name;
-
         localTransform = new Transform();
         worldTransform = new Transform();
 
         localLights = new LightList(this);
         worldLights = new LightList(this);
 
+        localOverrides = new ArrayList<>();
+        worldOverrides = new ArrayList<>();
         refreshFlags |= RF_BOUND;
     }
 
@@ -222,7 +229,6 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
     boolean requiresUpdates() {
         return requiresUpdates | !controls.isEmpty();
     }
-
     /**
      * Subclasses can call this with true to denote that they require
      * updateLogicalState() to be called even if they contain no controls.
@@ -272,35 +278,33 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
 
     protected void setLightListRefresh() {
         refreshFlags |= RF_LIGHTLIST;
-
         // Make sure next updateGeometricState() visits this branch
         // to update lights.
         Spatial p = parent;
         while (p != null) {
-            //if (p.refreshFlags != 0) {
-                // any refresh flag is sufficient,
-                // as each propagates to the root Node
-
-                // 2015/2/8:
-                // This is not true, because using e.g. getWorldBound()
-                // or getWorldTransform() activates a "partial refresh"
-                // which does not update the lights but does clear
-                // the refresh flags on the ancestors!
-
-            //    return;
-            //}
-
             if ((p.refreshFlags & RF_CHILD_LIGHTLIST) != 0) {
                 // The parent already has this flag,
                 // so must all ancestors.
                 return;
             }
-
             p.refreshFlags |= RF_CHILD_LIGHTLIST;
             p = p.parent;
         }
     }
 
+    protected void setMatParamOverrideRefresh() {
+        refreshFlags |= RF_MATPARAM_OVERRIDE;
+        Spatial p = parent;
+        while (p != null) {
+            if ((p.refreshFlags & RF_MATPARAM_OVERRIDE) != 0) {
+                return;
+            }
+
+            p.refreshFlags |= RF_MATPARAM_OVERRIDE;
+            p = p.parent;
+        }
+    }
+
     /**
      * Indicate that the bounding of this spatial has changed and that
      * a refresh is required.
@@ -318,7 +322,6 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
             p = p.parent;
         }
     }
-
     /**
      * (Internal use only) Forces a refresh of the given types of data.
      *
@@ -424,6 +427,29 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
         return worldLights;
     }
 
+    /**
+     * Get the local material parameter overrides.
+     *
+     * @return The list of local material parameter overrides.
+     */
+    public List<MatParamOverride> getLocalMatParamOverrides() {
+        return localOverrides;
+    }
+
+    /**
+     * Get the world material parameter overrides.
+     *
+     * Note that this list is only updated on a call to
+     * {@link #updateGeometricState()}. After update, the world overrides list
+     * will contain the {@link #getParent() parent's} world overrides combined
+     * with this spatial's {@link #getLocalMatParamOverrides() local overrides}.
+     *
+     * @return The list of world material parameter overrides.
+     */
+    public List<MatParamOverride> getWorldMatParamOverrides() {
+        return worldOverrides;
+    }
+
     /**
      * <code>getWorldRotation</code> retrieves the absolute rotation of the
      * Spatial.
@@ -525,10 +551,8 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
         TempVars vars = TempVars.get();
 
         Vector3f compVecA = vars.vect4;
-
         compVecA.set(position).subtractLocal(worldTranslation);
         getLocalRotation().lookAt(compVecA, upVector);
-
         if ( getParent() != null ) {
             Quaternion rot=vars.quat1;
             rot =  rot.set(parent.getWorldRotation()).inverseLocal().multLocal(getLocalRotation());
@@ -555,15 +579,63 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
             worldLights.update(localLights, null);
             refreshFlags &= ~RF_LIGHTLIST;
         } else {
-            if ((parent.refreshFlags & RF_LIGHTLIST) == 0) {
-                worldLights.update(localLights, parent.worldLights);
-                refreshFlags &= ~RF_LIGHTLIST;
-            } else {
-                assert false;
-            }
+            assert (parent.refreshFlags & RF_LIGHTLIST) == 0;
+            worldLights.update(localLights, parent.worldLights);
+            refreshFlags &= ~RF_LIGHTLIST;
+        }
+    }
+
+    protected void updateMatParamOverrides() {
+        refreshFlags &= ~RF_MATPARAM_OVERRIDE;
+
+        worldOverrides.clear();
+        if (parent == null) {
+            worldOverrides.addAll(localOverrides);
+        } else {
+            assert (parent.refreshFlags & RF_MATPARAM_OVERRIDE) == 0;
+            worldOverrides.addAll(parent.worldOverrides);
+            worldOverrides.addAll(localOverrides);
+        }
+    }
+
+    /**
+     * Adds a local material parameter override.
+     *
+     * @param override The override to add.
+     * @see MatParamOverride
+     */
+    public void addMatParamOverride(MatParamOverride override) {
+        if (override == null) {
+            throw new IllegalArgumentException("override cannot be null");
+        }
+        localOverrides.add(override);
+        setMatParamOverrideRefresh();
+    }
+
+    /**
+     * Remove a local material parameter override if it exists.
+     *
+     * @param override The override to remove.
+     * @see MatParamOverride
+     */
+    public void removeMatParamOverride(MatParamOverride override) {
+        if (localOverrides.remove(override)) {
+            setMatParamOverrideRefresh();
         }
     }
 
+    /**
+     * Remove all local material parameter overrides.
+     *
+     * @see #addMatParamOverride(com.jme3.material.MatParamOverride)
+     */
+    public void clearMatParamOverrides() {
+        if (!localOverrides.isEmpty()) {
+            setMatParamOverrideRefresh();
+        }
+        localOverrides.clear();
+    }
+
     /**
      * Should only be called from updateGeometricState().
      * In most cases should not be subclassed.
@@ -696,7 +768,6 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
         controls.add(control);
         control.setSpatial(this);
         boolean after = requiresUpdates();
-
         // If the requirement to be updated has changed
         // then we need to let the parent node know so it
         // can rebuild its update list.
@@ -720,7 +791,6 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
             }
         }
         boolean after = requiresUpdates();
-
         // If the requirement to be updated has changed
         // then we need to let the parent node know so it
         // can rebuild its update list.
@@ -746,14 +816,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
         }
 
         boolean after = requiresUpdates();
-
         // If the requirement to be updated has changed
         // then we need to let the parent node know so it
         // can rebuild its update list.
         if( parent != null && before != after ) {
             parent.invalidateUpdateList();
         }
-
         return result;
     }
 
@@ -838,7 +906,10 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
         if ((refreshFlags & RF_BOUND) != 0) {
             updateWorldBound();
         }
-
+        if ((refreshFlags & RF_MATPARAM_OVERRIDE) != 0) {
+            updateMatParamOverrides();
+        }
+        
         assert refreshFlags == 0;
     }
 
@@ -1292,6 +1363,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
         // the transforms and stuff get refreshed.
         clone.setTransformRefresh();
         clone.setLightListRefresh();
+        clone.setMatParamOverrideRefresh();
 
         return clone;
     }
@@ -1312,6 +1384,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
             clone.localLights.setOwner(clone);
             clone.worldLights.setOwner(clone);
 
+            clone.worldOverrides = new ArrayList<MatParamOverride>();
+            clone.localOverrides = new ArrayList<MatParamOverride>();
+
+            for (MatParamOverride override : localOverrides) {
+                clone.localOverrides.add((MatParamOverride) override.clone());
+            }
+
             // No need to force cloned to update.
             // This node already has the refresh flags
             // set below so it will have to update anyway.
@@ -1333,6 +1412,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
             clone.setBoundRefresh();
             clone.setTransformRefresh();
             clone.setLightListRefresh();
+            clone.setMatParamOverrideRefresh();
 
             clone.controls = new SafeArrayList<Control>(Control.class);
             for (int i = 0; i < controls.size(); i++) {
@@ -1388,6 +1468,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
         // the transforms and stuff get refreshed.
         clone.setTransformRefresh();
         clone.setLightListRefresh();
+        clone.setMatParamOverrideRefresh();
 
         return clone;
     }
@@ -1419,6 +1500,8 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
         this.localLights = cloner.clone(localLights);
         this.worldTransform = cloner.clone(worldTransform);
         this.localTransform = cloner.clone(localTransform);
+        this.worldOverrides = cloner.clone(worldOverrides);
+        this.localOverrides = cloner.clone(localOverrides);
         this.controls = cloner.clone(controls);
 
         // Cloner doesn't handle maps on its own just yet.
@@ -1515,6 +1598,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
         capsule.write(shadowMode, "shadow_mode", ShadowMode.Inherit);
         capsule.write(localTransform, "transform", Transform.IDENTITY);
         capsule.write(localLights, "lights", null);
+        capsule.writeSavableArrayList(localOverrides, "overrides", null);
 
         // Shallow clone the controls array to convert its type.
         capsule.writeSavableArrayList(new ArrayList(controls), "controlsList", null);
@@ -1538,6 +1622,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
         localLights = (LightList) ic.readSavable("lights", null);
         localLights.setOwner(this);
 
+        localOverrides = ic.readSavableArrayList("overrides", null);
+        if (localOverrides == null) {
+            localOverrides = new ArrayList<>();
+        }
+        worldOverrides = new ArrayList<>();
+
         //changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split
         //the AnimControl creates the SkeletonControl for old files and add it to the spatial.
         //The SkeletonControl must be the last in the stack so we add the list of all other control before it.

+ 2 - 2
jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java

@@ -108,8 +108,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
          * Do not use.
          */
         @Deprecated
-        MiscAttrib,
-
+        Reserved0,
         /**
          * Specifies the index buffer, must contain integer data
          * (ubyte, ushort, or uint).
@@ -522,6 +521,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
 //            throw new UnsupportedOperationException("Data has already been sent. Cannot set usage.");
 
         this.usage = usage;
+        this.setUpdateNeeded();
     }
 
     /**

+ 0 - 8
jme3-core/src/main/java/com/jme3/scene/debug/WireBox.java

@@ -101,14 +101,6 @@ public class WireBox extends Mesh {
         );
         updateBound();
     }
-
-    /**
-     * Old method retained for compatibility: use makeGeometry instead.
-     */
-    @Deprecated
-    public void fromBoundingBox(BoundingBox bbox) {
-        updatePositions(bbox.getXExtent(), bbox.getYExtent(), bbox.getZExtent());
-    }
     
     /**
      * Create a geometry suitable for visualizing the specified bounding box.

+ 179 - 286
jme3-core/src/main/java/com/jme3/shader/DefineList.java

@@ -1,286 +1,179 @@
-/*
- * Copyright (c) 2009-2012 jMonkeyEngine
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * * Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- *
- * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
- *   may be used to endorse or promote products derived from this software
- *   without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.jme3.shader;
-
-import com.jme3.export.*;
-import com.jme3.material.MatParam;
-import com.jme3.material.TechniqueDef;
-import com.jme3.util.ListMap;
-
-import java.io.IOException;
-import java.util.Map;
-import java.util.TreeMap;
-
-public final class DefineList implements Savable, Cloneable {
-
-    private static final String ONE = "1";
-    
-    private TreeMap<String, String> defines = new TreeMap<String, String>();
-    private String compiled = null;
-    private int cachedHashCode = 0;
-
-    public void write(JmeExporter ex) throws IOException{
-        OutputCapsule oc = ex.getCapsule(this);
-
-        String[] keys = new String[defines.size()];
-        String[] vals = new String[defines.size()];
-
-        int i = 0;
-        for (Map.Entry<String, String> define : defines.entrySet()){
-            keys[i] = define.getKey();
-            vals[i] = define.getValue();
-            i++;
-        }
-
-        oc.write(keys, "keys", null);
-        oc.write(vals, "vals", null);
-    }
-
-    public void read(JmeImporter im) throws IOException{
-        InputCapsule ic = im.getCapsule(this);
-
-        String[] keys = ic.readStringArray("keys", null);
-        String[] vals = ic.readStringArray("vals", null);
-        for (int i = 0; i < keys.length; i++){
-            defines.put(keys[i], vals[i]);
-        }
-    }
-
-    public void clear() {
-        defines.clear();
-        compiled = "";
-        cachedHashCode = 0;
-    }
-
-    public String get(String key){
-        return defines.get(key);
-    }
-    
-    @Override
-    public DefineList clone() {
-        try {
-            DefineList clone = (DefineList) super.clone();
-            clone.cachedHashCode = 0;
-            clone.compiled = null;
-            clone.defines = (TreeMap<String, String>) defines.clone();
-            return clone;
-        } catch (CloneNotSupportedException ex) {
-            throw new AssertionError();
-        }
-    }
-
-    public boolean set(String key, VarType type, Object val){    
-        if (val == null){
-            defines.remove(key);
-            compiled = null;
-            cachedHashCode = 0;
-            return true;
-        }
-
-        switch (type){
-            case Boolean:
-                if (((Boolean) val).booleanValue()) {
-                    // same literal, != will work
-                    if (defines.put(key, ONE) != ONE) {
-                        compiled = null;
-                        cachedHashCode = 0;
-                        return true;
-                    }
-                } else if (defines.containsKey(key)) {
-                    defines.remove(key);
-                    compiled = null;
-                    cachedHashCode = 0;
-                    return true;
-                }
-                
-                break;
-            case Float:
-            case Int:
-                String newValue = val.toString();
-                String original = defines.put(key, newValue);
-                if (!val.equals(original)) {
-                    compiled = null;
-                    cachedHashCode = 0;
-                    return true;            
-                }
-                break;
-            default:
-                // same literal, != will work
-                if (defines.put(key, ONE) != ONE) {  
-                    compiled = null;
-                    cachedHashCode = 0;
-                    return true;            
-                }
-                break;
-        }
-        
-        return false;
-    }
-
-    public boolean remove(String key){   
-        if (defines.remove(key) != null) {
-            compiled = null;
-            cachedHashCode = 0;
-            return true;
-        }
-        return false;
-    }
-
-    public void addFrom(DefineList other){    
-        if (other == null) {
-            return;
-        }
-        compiled = null;
-        cachedHashCode = 0;
-        defines.putAll(other.defines);
-    }
-
-    public String getCompiled(){
-        if (compiled == null){
-            StringBuilder sb = new StringBuilder();
-            for (Map.Entry<String, String> entry : defines.entrySet()){
-                sb.append("#define ").append(entry.getKey()).append(" ");
-                sb.append(entry.getValue()).append('\n');
-            }
-            compiled = sb.toString();
-        }
-        return compiled;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        final DefineList other = (DefineList) obj;
-        return defines.equals(other.defines);
-    }
-    
-    /**
-     * Update defines if the define list changed based on material parameters.
-     * @param params
-     * @param def
-     * @return true if defines was updated
-     */
-    public boolean update(ListMap params, TechniqueDef def){
-        if(equalsParams(params, def)){
-            return false;
-        }
-         // Defines were changed, update define list
-        clear();
-        for(int i=0;i<params.size();i++) {
-            MatParam param = (MatParam)params.getValue(i);
-            String defineName = def.getShaderParamDefine(param.getName());
-            if (defineName != null) {
-                set(defineName, param.getVarType(), param.getValue());
-            }
-        }
-        return true;
-    }
-    
-    private boolean equalsParams(ListMap params, TechniqueDef def) {
-        
-        int size = 0;
-
-        for(int i = 0; i < params.size() ; i++ ) {
-            MatParam param = (MatParam)params.getValue(i);
-            String key = def.getShaderParamDefine(param.getName());
-            if (key != null) {
-                Object val = param.getValue();
-                if (val != null) {
-
-                    switch (param.getVarType()) {
-                    case Boolean: {
-                        String current = defines.get(key);
-                        if (((Boolean) val).booleanValue()) {
-                            if (current == null || current != ONE) {
-                                return false;
-                            }
-                            size++;
-                        } else {
-                            if (current != null) {
-                                return false;
-                            }
-                        }
-                    }
-                        break;
-                    case Float:
-                    case Int: {
-                        String newValue = val.toString();
-                        String current = defines.get(key);
-                        if (!newValue.equals(current)) {
-                            return false;
-                        }
-                        size++;
-                    }
-                        break;
-                    default: {
-                        if (!defines.containsKey(key)) {
-                            return false;
-                        }
-                        size++;
-                    }
-                        break;
-                    }
-
-                }
-
-            }
-        }
-
-        if (size != defines.size()) {
-            return false;
-        }
-
-        return true;
-    }
-    
-    @Override
-    public int hashCode() {
-        if (cachedHashCode == 0) {
-            cachedHashCode = defines.hashCode();
-        }
-        return cachedHashCode;
-    }
-
-    @Override
-    public String toString(){
-        StringBuilder sb = new StringBuilder();
-        int i = 0;
-        for (Map.Entry<String, String> entry : defines.entrySet()) {
-            sb.append(entry.getKey()).append("=").append(entry.getValue());
-            if (i != defines.size() - 1) {
-                sb.append(", ");
-            }
-            i++;
-        }
-        return sb.toString();
-    }
-
-}
+/*
+ * Copyright (c) 2009-2015 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.shader;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The new define list.
+ * 
+ * @author Kirill Vainer
+ */
+public final class DefineList {
+
+    public static final int MAX_DEFINES = 64;
+
+    private long hash;
+    private final int[] vals;
+
+    public DefineList(int numValues) {
+        if (numValues < 0 || numValues > MAX_DEFINES) {
+            throw new IllegalArgumentException("numValues must be between 0 and 64");
+        }
+        vals = new int[numValues];
+    }
+    
+    private DefineList(DefineList original) {
+        this.hash = original.hash;
+        this.vals = new int[original.vals.length];
+        System.arraycopy(original.vals, 0, vals, 0, vals.length);
+    }
+
+    public void set(int id, int val) {
+        assert 0 <= id && id < 64;
+        if (val != 0) {
+            hash |=  (1L << id);
+        } else {
+            hash &= ~(1L << id);
+        }
+        vals[id] = val;
+    }
+    
+    public void set(int id, float val) {
+        set(id, Float.floatToIntBits(val));
+    }
+    
+    public void set(int id, boolean val) {
+        set(id, val ? 1 : 0);
+    }
+
+    public void set(int id, VarType type, Object value) {
+        if (value == null) {
+            set(id, 0);
+            return;
+        }
+
+        switch (type) {
+            case Int:
+                set(id, (Integer) value);
+                break;
+            case Float:
+                set(id, (Float) value);
+                break;
+            case Boolean:
+                set(id, ((Boolean) value));
+                break;
+            default:
+                set(id, 1);
+                break;
+        }
+    }
+
+    public void setAll(DefineList other) {
+        for (int i = 0; i < other.vals.length; i++) {
+            if (other.vals[i] != 0) {
+                vals[i] = other.vals[i];
+            }
+        }
+    }
+
+    public void clear() {
+        hash = 0;
+        Arrays.fill(vals, 0);
+    }
+
+    public boolean getBoolean(int id) {
+        return vals[id] != 0;
+    }
+    
+    public float getFloat(int id) {
+        return Float.intBitsToFloat(vals[id]);
+    }
+    
+    public int getInt(int id) {
+        return vals[id];
+    }
+    
+    @Override
+    public int hashCode() {
+        return (int)((hash >> 32) ^ hash);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+         DefineList o = (DefineList) other;
+         if (hash == o.hash) {
+             for (int i = 0; i < vals.length; i++) {
+                  if (vals[i] != o.vals[i]) return false;
+             }
+             return true;
+         }
+         return false;
+    }
+
+    public DefineList deepClone() {
+         return new DefineList(this);
+    }
+    
+    public void generateSource(StringBuilder sb, List<String> defineNames, List<VarType> defineTypes) {
+        for (int i = 0; i < vals.length; i++) {
+            if (vals[i] != 0) {
+                String defineName = defineNames.get(i);
+                
+                sb.append("#define ");
+                sb.append(defineName);
+                sb.append(" ");
+                
+                if (defineTypes != null && defineTypes.get(i) == VarType.Float) {
+                    float val = Float.intBitsToFloat(vals[i]);
+                    if (Float.isInfinite(val) || Float.isNaN(val)) {
+                        throw new IllegalArgumentException(
+                                "GLSL does not support NaN "
+                                + "or Infinite float literals");
+                    }
+                    sb.append(val);
+                } else {
+                    sb.append(vals[i]);
+                }
+                
+                sb.append("\n");
+            }
+        }
+    }
+    
+    public String generateSource(List<String> defineNames, List<VarType> defineTypes) {
+        StringBuilder sb = new StringBuilder();
+        generateSource(sb, defineNames, defineTypes);
+        return sb.toString();
+    }
+}

+ 56 - 23
jme3-core/src/main/java/com/jme3/shader/Shader.java

@@ -45,44 +45,63 @@ public final class Shader extends NativeObject {
     /**
      * A list of all shader sources currently attached.
      */
-    private ArrayList<ShaderSource> shaderSourceList;
+    private final ArrayList<ShaderSource> shaderSourceList;
 
     /**
      * Maps uniform name to the uniform variable.
      */
-    private ListMap<String, Uniform> uniforms;
+    private final ListMap<String, Uniform> uniforms;
+    
+    /**
+     * Uniforms bound to {@link UniformBinding}s.
+     * 
+     * Managed by the {@link UniformBindingManager}.
+     */
+    private final ArrayList<Uniform> boundUniforms;
 
     /**
      * Maps attribute name to the location of the attribute in the shader.
      */
-    private IntMap<Attribute> attribs;
+    private final IntMap<Attribute> attribs;
 
     /**
      * Type of shader. The shader will control the pipeline of it's type.
      */
     public static enum ShaderType {
+
         /**
          * Control fragment rasterization. (e.g color of pixel).
          */
-        Fragment,
-
+        Fragment("frag"),
         /**
          * Control vertex processing. (e.g transform of model to clip space)
          */
-        Vertex,
-
+        Vertex("vert"),
         /**
-         * Control geometry assembly. (e.g compile a triangle list from input data)
+         * Control geometry assembly. (e.g compile a triangle list from input
+         * data)
          */
-        Geometry,
+        Geometry("geom"),
         /**
-         * Controls tesselation factor (e.g how often a input patch should be subdivided)
+         * Controls tesselation factor (e.g how often a input patch should be
+         * subdivided)
          */
-        TessellationControl,
+        TessellationControl("tsctrl"),
         /**
-         * Controls tesselation transform (e.g similar to the vertex shader, but required to mix inputs manual)
+         * Controls tesselation transform (e.g similar to the vertex shader, but
+         * required to mix inputs manual)
          */
-        TessellationEvaluation;
+        TessellationEvaluation("tseval");
+
+        private String extension;
+        
+        public String getExtension() {
+            return extension;
+        }
+        
+        private ShaderType(String extension) {
+            this.extension = extension;
+        }
     }
 
     /**
@@ -195,22 +214,16 @@ public final class Shader extends NativeObject {
         }
     }
 
-    /**
-     * Initializes the shader for use, must be called after the 
-     * constructor without arguments is used.
-     */
-    public void initialize() {
-        shaderSourceList = new ArrayList<ShaderSource>();
-        uniforms = new ListMap<String, Uniform>();
-        attribs = new IntMap<Attribute>();
-    }
-    
     /**
      * Creates a new shader, {@link #initialize() } must be called
      * after this constructor for the shader to be usable.
      */
     public Shader(){
         super();
+        shaderSourceList = new ArrayList<ShaderSource>();
+        uniforms = new ListMap<String, Uniform>();
+        attribs = new IntMap<Attribute>();
+        boundUniforms = new ArrayList<Uniform>();
     }
 
     /**
@@ -225,6 +238,10 @@ public final class Shader extends NativeObject {
         for (ShaderSource source : s.shaderSourceList){
             shaderSourceList.add( (ShaderSource)source.createDestructableClone() );
         }
+        
+        uniforms = null;
+        boundUniforms = null;
+        attribs = null;
     }
 
     /**
@@ -248,6 +265,18 @@ public final class Shader extends NativeObject {
         setUpdateNeeded();
     }
 
+    public void addUniformBinding(UniformBinding binding){
+        String uniformName = "g_" + binding.name();
+        Uniform uniform = uniforms.get(uniformName);
+        if (uniform == null) {
+            uniform = new Uniform();
+            uniform.name = uniformName;
+            uniform.binding = binding;
+            uniforms.put(uniformName, uniform);
+            boundUniforms.add(uniform);
+        }
+    }
+    
     public Uniform getUniform(String name){
         assert name.startsWith("m_") || name.startsWith("g_");
         Uniform uniform = uniforms.get(name);
@@ -277,6 +306,10 @@ public final class Shader extends NativeObject {
     public ListMap<String, Uniform> getUniformMap(){
         return uniforms;
     }
+    
+    public ArrayList<Uniform> getBoundUniforms() {
+        return boundUniforms;
+    }
 
     public Collection<ShaderSource> getSources(){
         return shaderSourceList;

+ 30 - 17
jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java

@@ -57,9 +57,9 @@ public abstract class ShaderGenerator {
      */
     protected int indent;
     /**
-     * the technique to use for the shader generation
+     * the technique def to use for the shader generation
      */
-    protected Technique technique = null;    
+    protected TechniqueDef techniqueDef = null;    
 
     /**
      * Build a shaderGenerator
@@ -70,8 +70,8 @@ public abstract class ShaderGenerator {
         this.assetManager = assetManager;        
     }
     
-    public void initialize(Technique technique){
-        this.technique = technique;
+    public void initialize(TechniqueDef techniqueDef){
+        this.techniqueDef = techniqueDef;
     }
     
     /**
@@ -79,24 +79,29 @@ public abstract class ShaderGenerator {
      *
      * @return a Shader program
      */
-    public Shader generateShader() {
-        if(technique == null){
-            throw new UnsupportedOperationException("The shaderGenerator was not properly initialized, call initialize(Technique) before any generation");
+    public Shader generateShader(String definesSourceCode) {
+        if (techniqueDef == null) {
+            throw new UnsupportedOperationException("The shaderGenerator was not "
+                    + "properly initialized, call "
+                    + "initialize(TechniqueDef) before any generation");
         }
 
-        DefineList defines = technique.getAllDefines();
-        TechniqueDef def = technique.getDef();
-        ShaderGenerationInfo info = def.getShaderGenerationInfo();
-
-        String vertexSource = buildShader(def.getShaderNodes(), info, ShaderType.Vertex);
-        String fragmentSource = buildShader(def.getShaderNodes(), info, ShaderType.Fragment);
+        String techniqueName = techniqueDef.getName();
+        ShaderGenerationInfo info = techniqueDef.getShaderGenerationInfo();
 
         Shader shader = new Shader();
-        shader.initialize();
-        shader.addSource(Shader.ShaderType.Vertex, technique.getDef().getName() + ".vert", vertexSource, defines.getCompiled(), getLanguageAndVersion(ShaderType.Vertex));
-        shader.addSource(Shader.ShaderType.Fragment, technique.getDef().getName() + ".frag", fragmentSource, defines.getCompiled(), getLanguageAndVersion(ShaderType.Fragment));
+        for (ShaderType type : ShaderType.values()) {
+            String extension = type.getExtension();
+            String language = getLanguageAndVersion(type);
+            String shaderSourceCode = buildShader(techniqueDef.getShaderNodes(), info, type);
+            
+            if (shaderSourceCode != null) {
+                String shaderSourceAssetName = techniqueName + "." + extension;
+                shader.addSource(type, shaderSourceAssetName, shaderSourceCode, definesSourceCode, language);
+            }
+        }
         
-        technique = null;
+        techniqueDef = null;
         return shader;
     }
 
@@ -109,6 +114,14 @@ public abstract class ShaderGenerator {
      * @return the code of the generated vertex shader
      */
     protected String buildShader(List<ShaderNode> shaderNodes, ShaderGenerationInfo info, ShaderType type) {
+        if (type == ShaderType.TessellationControl ||
+            type == ShaderType.TessellationEvaluation || 
+            type == ShaderType.Geometry) {
+            // TODO: Those are not supported.
+            // Too much code assumes that type is either Vertex or Fragment
+            return null;
+        }
+        
         indent = 0;
 
         StringBuilder sourceDeclaration = new StringBuilder();

+ 0 - 201
jme3-core/src/main/java/com/jme3/shader/ShaderKey.java

@@ -1,201 +0,0 @@
-/*
- * Copyright (c) 2009-2012 jMonkeyEngine
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * * Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- *
- * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
- *   may be used to endorse or promote products derived from this software
- *   without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.jme3.shader;
-
-import com.jme3.asset.AssetKey;
-import com.jme3.export.InputCapsule;
-import com.jme3.export.JmeExporter;
-import com.jme3.export.JmeImporter;
-import com.jme3.export.OutputCapsule;
-import java.io.IOException;
-import java.util.EnumMap;
-import java.util.Set;
-
-public class ShaderKey extends AssetKey<Shader> {
-
-    protected EnumMap<Shader.ShaderType,String> shaderLanguage;
-    protected EnumMap<Shader.ShaderType,String> shaderName;
-    protected DefineList defines;
-    protected int cachedHashedCode = 0;
-    protected boolean usesShaderNodes = false;
-
-    public ShaderKey(){
-        shaderLanguage=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
-        shaderName=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
-    }
-
-    public ShaderKey(DefineList defines, EnumMap<Shader.ShaderType,String> shaderLanguage,EnumMap<Shader.ShaderType,String> shaderName){
-        super("");
-        this.name = reducePath(getShaderName(Shader.ShaderType.Vertex));
-        this.shaderLanguage=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
-        this.shaderName=new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
-        this.defines = defines;
-        for (Shader.ShaderType shaderType : shaderName.keySet()) {
-            this.shaderName.put(shaderType,shaderName.get(shaderType));
-            this.shaderLanguage.put(shaderType,shaderLanguage.get(shaderType));
-        }
-    }
-    
-    @Override
-    public ShaderKey clone() {
-        ShaderKey clone = (ShaderKey) super.clone();
-        clone.cachedHashedCode = 0;
-        clone.defines = defines.clone();
-        return clone;
-    }
-    
-    @Override
-    public String toString(){
-        //todo:
-        return "V="+name+";";
-    }
-    
-    private final String getShaderName(Shader.ShaderType type) {
-        if (shaderName == null) {
-            return "";
-        }
-        String shName = shaderName.get(type);
-        return shName != null ? shName : "";
-    }
-
-    //todo: make equals and hashCode work
-    @Override
-    public boolean equals(Object obj) {
-        final ShaderKey other = (ShaderKey) obj;
-        if (name.equals(other.name) && getShaderName(Shader.ShaderType.Fragment).equals(other.getShaderName(Shader.ShaderType.Fragment))){
-            if (defines != null && other.defines != null) {
-                return defines.equals(other.defines);
-            } else if (defines != null || other.defines != null) {
-                return false;
-            } else {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public int hashCode() {
-        if (cachedHashedCode == 0) {
-            int hash = 7;
-            hash = 41 * hash + name.hashCode();
-            hash = 41 * hash + getShaderName(Shader.ShaderType.Fragment).hashCode();
-            hash = getShaderName(Shader.ShaderType.Geometry) == null ? hash : 41 * hash + getShaderName(Shader.ShaderType.Geometry).hashCode();
-            hash = getShaderName(Shader.ShaderType.TessellationControl) == null ? hash : 41 * hash + getShaderName(Shader.ShaderType.TessellationControl).hashCode();
-            hash = getShaderName(Shader.ShaderType.TessellationEvaluation) == null ? hash : 41 * hash + getShaderName(Shader.ShaderType.TessellationEvaluation).hashCode();
-            hash = 41 * hash + (defines != null ? defines.hashCode() : 0);
-            cachedHashedCode = hash;
-        }
-        return cachedHashedCode;
-    }
-
-    public DefineList getDefines() {
-        return defines;
-    }
-
-    public String getVertName(){
-        return getShaderName(Shader.ShaderType.Vertex);
-    }
-
-    public String getFragName() {
-        return getShaderName(Shader.ShaderType.Fragment);
-    }
-
-    /**
-     * @deprecated Use {@link #getVertexShaderLanguage() } instead.
-     */
-    @Deprecated
-    public String getLanguage() {
-        return shaderLanguage.get(Shader.ShaderType.Vertex);
-    }
-    
-    public String getVertexShaderLanguage() { 
-        return shaderLanguage.get(Shader.ShaderType.Vertex);
-    }
-    
-    public String getFragmentShaderLanguage() {
-        return shaderLanguage.get(Shader.ShaderType.Vertex);
-    }
-
-    public boolean isUsesShaderNodes() {
-        return usesShaderNodes;
-    }
-
-    public void setUsesShaderNodes(boolean usesShaderNodes) {
-        this.usesShaderNodes = usesShaderNodes;
-    }
-
-    public Set<Shader.ShaderType> getUsedShaderPrograms(){
-        return shaderName.keySet();
-    }
-
-    public String getShaderProgramLanguage(Shader.ShaderType shaderType){
-        return shaderLanguage.get(shaderType);
-    }
-
-    public String getShaderProgramName(Shader.ShaderType shaderType){
-        return getShaderName(shaderType);
-    }
-
-    @Override
-    public void write(JmeExporter ex) throws IOException{
-        super.write(ex);
-        OutputCapsule oc = ex.getCapsule(this);
-        oc.write(shaderName.get(Shader.ShaderType.Fragment), "fragment_name", null);
-        oc.write(shaderName.get(Shader.ShaderType.Geometry), "geometry_name", null);
-        oc.write(shaderName.get(Shader.ShaderType.TessellationControl), "tessControl_name", null);
-        oc.write(shaderName.get(Shader.ShaderType.TessellationEvaluation), "tessEval_name", null);
-        oc.write(shaderLanguage.get(Shader.ShaderType.Vertex), "language", null);
-        oc.write(shaderLanguage.get(Shader.ShaderType.Fragment), "frag_language", null);
-        oc.write(shaderLanguage.get(Shader.ShaderType.Geometry), "geom_language", null);
-        oc.write(shaderLanguage.get(Shader.ShaderType.TessellationControl), "tsctrl_language", null);
-        oc.write(shaderLanguage.get(Shader.ShaderType.TessellationEvaluation), "tseval_language", null);
-
-    }
-
-    @Override
-    public void read(JmeImporter im) throws IOException{
-        super.read(im);
-        InputCapsule ic = im.getCapsule(this);
-        shaderName.put(Shader.ShaderType.Vertex,name);
-        shaderName.put(Shader.ShaderType.Fragment,ic.readString("fragment_name", null));
-        shaderName.put(Shader.ShaderType.Geometry,ic.readString("geometry_name", null));
-        shaderName.put(Shader.ShaderType.TessellationControl,ic.readString("tessControl_name", null));
-        shaderName.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tessEval_name", null));
-        shaderLanguage.put(Shader.ShaderType.Vertex,ic.readString("language", null));
-        shaderLanguage.put(Shader.ShaderType.Fragment,ic.readString("frag_language", null));
-        shaderLanguage.put(Shader.ShaderType.Geometry,ic.readString("geom_language", null));
-        shaderLanguage.put(Shader.ShaderType.TessellationControl,ic.readString("tsctrl_language", null));
-        shaderLanguage.put(Shader.ShaderType.TessellationEvaluation,ic.readString("tseval_language", null));
-    }
-
-}

+ 82 - 33
jme3-core/src/main/java/com/jme3/shader/Uniform.java

@@ -70,6 +70,30 @@ public class Uniform extends ShaderVariable {
      */
     protected boolean setByCurrentMaterial = false;
 
+    @Override
+    public int hashCode() {
+        int hash = 5;
+        hash = 31 * hash + (this.value != null ? this.value.hashCode() : 0);
+        hash = 31 * hash + (this.varType != null ? this.varType.hashCode() : 0);
+        hash = 31 * hash + (this.binding != null ? this.binding.hashCode() : 0);
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        final Uniform other = (Uniform) obj;
+        if (this.value != other.value && (this.value == null || !this.value.equals(other.value))) {
+            return false;
+        }
+        return this.binding == other.binding && this.varType == other.varType;
+    }
+
     @Override
     public String toString(){
         StringBuilder sb = new StringBuilder();
@@ -102,6 +126,10 @@ public class Uniform extends ShaderVariable {
     public Object getValue(){
         return value;
     }
+    
+    public FloatBuffer getMultiData() {
+        return multiData;
+    }
 
     public boolean isSetByCurrentMaterial() {
         return setByCurrentMaterial;
@@ -111,21 +139,6 @@ public class Uniform extends ShaderVariable {
         setByCurrentMaterial = false;
     }
 
-    private static void setVector4(Vector4f vec, Object value) {
-        if (value instanceof ColorRGBA) {
-            ColorRGBA color = (ColorRGBA) value;
-            vec.set(color.r, color.g, color.b, color.a);
-        } else if (value instanceof Quaternion) {
-            Quaternion quat = (Quaternion) value;
-            vec.set(quat.getX(), quat.getY(), quat.getZ(), quat.getW());
-        } else if (value instanceof Vector4f) {
-            Vector4f vec4 = (Vector4f) value;
-            vec.set(vec4);
-        } else{
-            throw new IllegalArgumentException();
-        }
-    }
-    
     public void clearValue(){
         updateNeeded = true;
 
@@ -182,27 +195,43 @@ public class Uniform extends ShaderVariable {
         }
 
         if (value == null) {
-            throw new NullPointerException();
+            throw new IllegalArgumentException("for uniform " + name + ": value cannot be null");
         }
 
         setByCurrentMaterial = true;
 
         switch (type){
             case Matrix3:
+                if (value.equals(this.value)) {
+                    return;
+                }
                 Matrix3f m3 = (Matrix3f) value;
                 if (multiData == null) {
                     multiData = BufferUtils.createFloatBuffer(9);
                 }
                 m3.fillFloatBuffer(multiData, true);
                 multiData.clear();
+                if (this.value == null) {
+                    this.value = new Matrix3f(m3);
+                } else {
+                    ((Matrix3f)this.value).set(m3);
+                }
                 break;
             case Matrix4:
+                if (value.equals(this.value)) {
+                    return;
+                }
                 Matrix4f m4 = (Matrix4f) value;
                 if (multiData == null) {
                     multiData = BufferUtils.createFloatBuffer(16);
                 }
                 m4.fillFloatBuffer(multiData, true);
                 multiData.clear();
+                if (this.value == null) {
+                    this.value = new Matrix4f(m4);
+                } else {
+                    ((Matrix4f)this.value).copy(m4);
+                }
                 break;
             case IntArray:
                 int[] ia = (int[]) value;
@@ -283,11 +312,32 @@ public class Uniform extends ShaderVariable {
                 }
                 multiData.clear();
                 break;
+            case Vector4:
+                if (value.equals(this.value)) {
+                    return;
+                }
+                if (value instanceof ColorRGBA) {
+                    if (this.value == null) {
+                        this.value = new ColorRGBA();
+                    }
+                    ((ColorRGBA) this.value).set((ColorRGBA) value);
+                } else if (value instanceof Vector4f) {
+                    if (this.value == null) {
+                        this.value = new Vector4f();
+                    }
+                    ((Vector4f) this.value).set((Vector4f) value);
+                } else {
+                    if (this.value == null) {
+                        this.value = new Quaternion();
+                    }
+                    ((Quaternion) this.value).set((Quaternion) value);
+                }
+                break;
                 // Only use check if equals optimization for primitive values
             case Int:
             case Float:
             case Boolean:
-                if (this.value != null && this.value.equals(value)) {
+                if (value.equals(this.value)) {
                     return;
                 }
                 this.value = value;
@@ -297,39 +347,38 @@ public class Uniform extends ShaderVariable {
                 break;
         }
 
-        if (multiData != null) {
-            this.value = multiData;
-        }
+//        if (multiData != null) {
+//            this.value = multiData;
+//        }
 
         varType = type;
         updateNeeded = true;
     }
 
     public void setVector4Length(int length){
-        if (location == -1)
+        if (location == -1) {
             return;
-
-        FloatBuffer fb = (FloatBuffer) value;
-        if (fb == null || fb.capacity() < length * 4) {
-            value = BufferUtils.createFloatBuffer(length * 4);
         }
-
+        
+        multiData = BufferUtils.ensureLargeEnough(multiData, length * 4);
+        value = multiData;
         varType = VarType.Vector4Array;
         updateNeeded = true;
         setByCurrentMaterial = true;
     }
 
     public void setVector4InArray(float x, float y, float z, float w, int index){
-        if (location == -1)
+        if (location == -1) {
             return;
+        }
 
-        if (varType != null && varType != VarType.Vector4Array)
-            throw new IllegalArgumentException("Expected a "+varType.name()+" value!");
+        if (varType != null && varType != VarType.Vector4Array) {
+            throw new IllegalArgumentException("Expected a " + varType.name() + " value!");
+        }
 
-        FloatBuffer fb = (FloatBuffer) value;
-        fb.position(index * 4);
-        fb.put(x).put(y).put(z).put(w);
-        fb.rewind();
+        multiData.position(index * 4);
+        multiData.put(x).put(y).put(z).put(w);
+        multiData.rewind();
         updateNeeded = true;
         setByCurrentMaterial = true;
     }

+ 3 - 2
jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java

@@ -36,7 +36,7 @@ import com.jme3.math.*;
 import com.jme3.renderer.Camera;
 import com.jme3.renderer.RenderManager;
 import com.jme3.system.Timer;
-import java.util.List;
+import java.util.ArrayList;
 
 /**
  * <code>UniformBindingManager</code> helps {@link RenderManager} to manage
@@ -84,7 +84,8 @@ public class UniformBindingManager {
      * Updates the given list of uniforms with {@link UniformBinding uniform bindings}
      * based on the current world state.
      */
-    public void updateUniformBindings(List<Uniform> params) {
+    public void updateUniformBindings(Shader shader) {
+        ArrayList<Uniform> params = shader.getBoundUniforms();
         for (int i = 0; i < params.size(); i++) {
             Uniform u = params.get(i);
             switch (u.getBinding()) {

+ 2 - 7
jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java

@@ -330,12 +330,7 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
     public void initialize(RenderManager rm, ViewPort vp) {
         renderManager = rm;
         viewPort = vp;
-        //checking for caps to chosse the appropriate post material technique
-        if (renderManager.getRenderer().getCaps().contains(Caps.GLSL150)) {
-            postTechniqueName = "PostShadow15";
-        } else {
-            postTechniqueName = "PostShadow";
-        }
+        postTechniqueName = "PostShadow";
         if(zFarOverride>0 && frustumCam == null){
             initFrustumCam();
         }
@@ -587,7 +582,7 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable
         for (int i = 0; i < l.size(); i++) {
             Material mat = l.get(i).getMaterial();
             //checking if the material has the post technique and adding it to the material cache
-            if (mat.getMaterialDef().getTechniqueDef(postTechniqueName) != null) {
+            if (mat.getMaterialDef().getTechniqueDefs(postTechniqueName) != null) {
                 if (!matCache.contains(mat)) {
                     matCache.add(mat);
                 }

+ 2 - 7
jme3-core/src/main/java/com/jme3/shadow/PssmShadowRenderer.java

@@ -355,12 +355,7 @@ public class PssmShadowRenderer implements SceneProcessor {
     public void initialize(RenderManager rm, ViewPort vp) {
         renderManager = rm;
         viewPort = vp;
-        //checking for caps to chosse the appropriate post material technique
-        if (renderManager.getRenderer().getCaps().contains(Caps.GLSL150)) {
-            postTechniqueName = "PostShadow15";
-        } else {
-            postTechniqueName = "PostShadow";
-        }
+        postTechniqueName = "PostShadow";
     }
 
     public boolean isInitialized() {
@@ -533,7 +528,7 @@ public class PssmShadowRenderer implements SceneProcessor {
         for (int i = 0; i < l.size(); i++) {
             Material mat = l.get(i).getMaterial();
             //checking if the material has the post technique and adding it to the material cache
-            if (mat.getMaterialDef().getTechniqueDef(postTechniqueName) != null) {
+            if (mat.getMaterialDef().getTechniqueDefs(postTechniqueName) != null) {
                 if (!matCache.contains(mat)) {
                     matCache.add(mat);
                 }

+ 4 - 3
jme3-core/src/main/java/com/jme3/system/NullContext.java

@@ -128,12 +128,13 @@ public class NullContext implements JmeContext, Runnable {
     public void run(){
         initInThread();
 
-        while (!needClose.get()){
+        do {
             listener.update();
 
-            if (frameRate > 0)
+            if (frameRate > 0) {
                 sync(frameRate);
-        }
+            }
+        } while (!needClose.get());
 
         deinitInThread();
 

+ 17 - 3
jme3-core/src/main/java/com/jme3/system/NullRenderer.java

@@ -39,6 +39,7 @@ import com.jme3.material.RenderState;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.Matrix4f;
 import com.jme3.renderer.Caps;
+import com.jme3.renderer.Limits;
 import com.jme3.renderer.Renderer;
 import com.jme3.renderer.Statistics;
 import com.jme3.scene.Mesh;
@@ -48,15 +49,25 @@ import com.jme3.shader.Shader.ShaderSource;
 import com.jme3.texture.FrameBuffer;
 import com.jme3.texture.Image;
 import com.jme3.texture.Texture;
+import java.util.EnumMap;
 
 public class NullRenderer implements Renderer {
 
-    private static final EnumSet<Caps> caps = EnumSet.noneOf(Caps.class);
-    private static final Statistics stats = new Statistics();
+    private final EnumSet<Caps> caps = EnumSet.allOf(Caps.class);
+    private final EnumMap<Limits, Integer> limits = new EnumMap<>(Limits.class);
+    private final Statistics stats = new Statistics();
 
     public void initialize() {
+        for (Limits limit : Limits.values()) {
+            limits.put(limit, Integer.MAX_VALUE);
+        }
     }
-    
+
+    @Override
+    public EnumMap<Limits, Integer> getLimits() {
+        return limits;
+    }
+
     public EnumSet<Caps> getCaps() {
         return caps;
     }
@@ -164,4 +175,7 @@ public class NullRenderer implements Renderer {
     public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) {        
     }
 
+    @Override
+    public void setDefaultAnisotropicFilter(int level) {
+    }
 }

+ 1 - 1
jme3-core/src/main/java/com/jme3/texture/image/LastTextureState.java

@@ -58,7 +58,7 @@ public final class LastTextureState {
         rWrap = null;
         magFilter = null;
         minFilter = null;
-        anisoFilter = 0;
+        anisoFilter = 1;
         
         // The default in OpenGL is OFF, so we avoid setting this per texture
         // if its not used.

+ 2 - 2
jme3-core/src/main/java/com/jme3/util/clone/Cloner.java

@@ -207,10 +207,10 @@ public class Cloner {
 
         // Check the index to see if we already have it
         Object clone = index.get(object);
-        if( clone != null ) {
+        if( clone != null || index.containsKey(object) ) {
             if( log.isLoggable(Level.FINER) ) {
                 log.finer("cloned:" + object.getClass() + "@" + System.identityHashCode(object)
-                            + " as cached:" + clone.getClass() + "@" + System.identityHashCode(clone));
+                            + " as cached:" + (clone == null ? "null" : (clone.getClass() + "@" + System.identityHashCode(clone))));
             }
             return type.cast(clone);
         }

+ 7 - 14
jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md

@@ -114,10 +114,10 @@ MaterialDef Phong Lighting {
         //For instancing
         Boolean UseInstancing
 
-        Boolean BackfaceShadows: false
+        Boolean BackfaceShadows : false
     }
 
- Technique {
+    Technique {
         LightMode SinglePass
         
         VertexShader GLSL100:   Common/MatDefs/Light/SPLighting.vert
@@ -149,7 +149,7 @@ MaterialDef Phong Lighting {
             SEPARATE_TEXCOORD : SeparateTexCoord
             DISCARD_ALPHA : AlphaDiscardThreshold
             USE_REFLECTION : EnvMap
-            SPHERE_MAP : SphereMap  
+            SPHERE_MAP : EnvMapAsSphereMap  
             NUM_BONES : NumberOfBones                        
             INSTANCING : UseInstancing
         }
@@ -188,7 +188,7 @@ MaterialDef Phong Lighting {
             SEPARATE_TEXCOORD : SeparateTexCoord
             DISCARD_ALPHA : AlphaDiscardThreshold
             USE_REFLECTION : EnvMap
-            SPHERE_MAP : SphereMap  
+            SPHERE_MAP : EnvMapAsSphereMap  
             NUM_BONES : NumberOfBones                        
             INSTANCING : UseInstancing
         }
@@ -209,7 +209,6 @@ MaterialDef Phong Lighting {
         }
 
         Defines {
-            COLOR_MAP : ColorMap
             DISCARD_ALPHA : AlphaDiscardThreshold
             NUM_BONES : NumberOfBones
             INSTANCING : UseInstancing
@@ -218,7 +217,7 @@ MaterialDef Phong Lighting {
     }
 
 
-    Technique PostShadow15{
+    Technique PostShadow {
         VertexShader GLSL150:   Common/MatDefs/Shadow/PostShadow.vert
         FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag
 
@@ -234,8 +233,7 @@ MaterialDef Phong Lighting {
             HARDWARE_SHADOWS : HardwareShadows
             FILTER_MODE : FilterMode
             PCFEDGE : PCFEdge
-            DISCARD_ALPHA : AlphaDiscardThreshold           
-            COLOR_MAP : ColorMap
+            DISCARD_ALPHA : AlphaDiscardThreshold
             SHADOWMAP_SIZE : ShadowMapSize
             FADE : FadeInfo
             PSSM : Splits
@@ -268,8 +266,7 @@ MaterialDef Phong Lighting {
             HARDWARE_SHADOWS : HardwareShadows
             FILTER_MODE : FilterMode
             PCFEDGE : PCFEdge
-            DISCARD_ALPHA : AlphaDiscardThreshold           
-            COLOR_MAP : ColorMap
+            DISCARD_ALPHA : AlphaDiscardThreshold
             SHADOWMAP_SIZE : ShadowMapSize
             FADE : FadeInfo
             PSSM : Splits
@@ -343,10 +340,6 @@ MaterialDef Phong Lighting {
         Defines {
             VERTEX_COLOR : UseVertexColor
             MATERIAL_COLORS : UseMaterialColors
-            V_TANGENT : VTangent
-            MINNAERT  : Minnaert
-            WARDISO   : WardIso
-
             DIFFUSEMAP : DiffuseMap
             NORMALMAP : NormalMap
             SPECULARMAP : SpecularMap

+ 2 - 2
jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md

@@ -148,7 +148,7 @@ MaterialDef Unshaded {
     }
 
 
-    Technique PostShadow15{
+    Technique PostShadow {
         VertexShader GLSL150:   Common/MatDefs/Shadow/PostShadow.vert
         FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag
 
@@ -181,7 +181,7 @@ MaterialDef Unshaded {
         }
     }
 
-    Technique PostShadow{
+    Technique PostShadow {
         VertexShader GLSL100:   Common/MatDefs/Shadow/PostShadow.vert
         FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadow.frag
 

+ 1 - 2
jme3-core/src/main/resources/com/jme3/asset/General.cfg

@@ -22,5 +22,4 @@ LOADER com.jme3.scene.plugins.ogre.MaterialLoader : material
 LOADER com.jme3.scene.plugins.ogre.SceneLoader : scene
 LOADER com.jme3.scene.plugins.blender.BlenderModelLoader : blend
 LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, geom, tsctrl, tseval, glsl, glsllib
-LOADER com.jme3.scene.plugins.fbx.SceneLoader : fbx
-LOADER com.jme3.scene.plugins.fbx.SceneWithAnimationLoader : fba
+LOADER com.jme3.scene.plugins.fbx.FbxLoader : fbx

+ 86 - 22
jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java

@@ -31,8 +31,12 @@
  */
 package com.jme3.material.plugins;
 
+import com.jme3.material.logic.MultiPassLightingLogic;
+import com.jme3.material.logic.SinglePassLightingLogic;
+import com.jme3.material.logic.DefaultTechniqueDefLogic;
 import com.jme3.asset.*;
 import com.jme3.material.*;
+import com.jme3.material.RenderState.BlendEquation;
 import com.jme3.material.RenderState.BlendMode;
 import com.jme3.material.RenderState.FaceCullMode;
 import com.jme3.material.TechniqueDef.LightMode;
@@ -40,6 +44,7 @@ import com.jme3.material.TechniqueDef.ShadowMode;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.Vector2f;
 import com.jme3.math.Vector3f;
+import com.jme3.shader.DefineList;
 import com.jme3.shader.Shader;
 import com.jme3.shader.VarType;
 import com.jme3.texture.Texture;
@@ -73,15 +78,16 @@ public class J3MLoader implements AssetLoader {
     private Material material;
     private TechniqueDef technique;
     private RenderState renderState;
+    private ArrayList<String> presetDefines = new ArrayList<String>();
 
-    private EnumMap<Shader.ShaderType, String> shaderLanguage;
-    private EnumMap<Shader.ShaderType, String> shaderName;
+    private EnumMap<Shader.ShaderType, String> shaderLanguages;
+    private EnumMap<Shader.ShaderType, String> shaderNames;
 
     private static final String whitespacePattern = "\\p{javaWhitespace}+";
 
     public J3MLoader() {
-        shaderLanguage = new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
-        shaderName = new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class);
+        shaderLanguages = new EnumMap<>(Shader.ShaderType.class);
+        shaderNames = new EnumMap<>(Shader.ShaderType.class);
     }
 
 
@@ -104,8 +110,8 @@ public class J3MLoader implements AssetLoader {
     }
 
     private void readShaderDefinition(Shader.ShaderType shaderType, String name, String language) {
-        shaderName.put(shaderType, name);
-        shaderLanguage.put(shaderType, language);
+        shaderNames.put(shaderType, name);
+        shaderLanguages.put(shaderType, language);
     }
 
     // LightMode <MODE>
@@ -443,9 +449,12 @@ public class J3MLoader implements AssetLoader {
             renderState.setDepthTest(parseBoolean(split[1]));
         }else if (split[0].equals("Blend")){
             renderState.setBlendMode(BlendMode.valueOf(split[1]));
+        }else if (split[0].equals("BlendEquation")){
+            renderState.setBlendEquation(BlendEquation.valueOf(split[1]));
+        }else if (split[0].equals("BlendEquationAlpha")){
+            renderState.setBlendEquationAlpha(RenderState.BlendEquationAlpha.valueOf(split[1]));
         }else if (split[0].equals("AlphaTestFalloff")){
-            renderState.setAlphaTest(true);
-            renderState.setAlphaFallOff(Float.parseFloat(split[1]));
+            // Ignore for backwards compatbility
         }else if (split[0].equals("PolyOffset")){
             float factor = Float.parseFloat(split[1]);
             float units = Float.parseFloat(split[2]);
@@ -453,7 +462,7 @@ public class J3MLoader implements AssetLoader {
         }else if (split[0].equals("ColorWrite")){
             renderState.setColorWrite(parseBoolean(split[1]));
         }else if (split[0].equals("PointSprite")){
-            renderState.setPointSprite(parseBoolean(split[1]));
+            // Ignore for backwards compatbility
         }else if (split[0].equals("DepthFunc")){
             renderState.setDepthFunc(RenderState.TestFunction.valueOf(split[1]));
         }else if (split[0].equals("AlphaFunc")){
@@ -495,10 +504,22 @@ public class J3MLoader implements AssetLoader {
     private void readDefine(String statement) throws IOException{
         String[] split = statement.split(":");
         if (split.length == 1){
-            // add preset define
-            technique.addShaderPresetDefine(split[0].trim(), VarType.Boolean, true);
+            String defineName = split[0].trim();
+            presetDefines.add(defineName);
         }else if (split.length == 2){
-            technique.addShaderParamDefine(split[1].trim(), split[0].trim());
+            String defineName = split[0].trim();
+            String paramName = split[1].trim();
+            MatParam param = materialDef.getMaterialParam(paramName);
+            if (param == null) {
+                logger.log(Level.WARNING, "In technique ''{0}'':\n"
+                        + "Define ''{1}'' mapped to non-existent"
+                        + " material parameter ''{2}'', ignoring.",
+                        new Object[]{technique.getName(), defineName, paramName});
+                return;
+            }
+            
+            VarType paramType = param.getVarType();
+            technique.addShaderParamDefine(paramName, paramType, defineName);
         }else{
             throw new IOException("Define syntax incorrect");
         }
@@ -560,37 +581,80 @@ public class J3MLoader implements AssetLoader {
         }
         material.setTransparent(parseBoolean(split[1]));
     }
+    
+    private static String createShaderPrologue(List<String> presetDefines) {
+        DefineList dl = new DefineList(presetDefines.size());
+        for (int i = 0; i < presetDefines.size(); i++) {
+            dl.set(i, 1);
+        }
+        StringBuilder sb = new StringBuilder();
+        dl.generateSource(sb, presetDefines, null);
+        return sb.toString();
+    }
 
     private void readTechnique(Statement techStat) throws IOException{
         isUseNodes = false;
         String[] split = techStat.getLine().split(whitespacePattern);
+
+        String name;
         if (split.length == 1) {
-            technique = new TechniqueDef(null);
+            name = TechniqueDef.DEFAULT_TECHNIQUE_NAME;
         } else if (split.length == 2) {
-            String techName = split[1];
-            technique = new TechniqueDef(techName);
+            name = split[1];
         } else {
             throw new IOException("Technique statement syntax incorrect");
         }
 
+        String techniqueUniqueName = materialDef.getAssetName() + "@" + name;
+        technique = new TechniqueDef(name, techniqueUniqueName.hashCode());
+
         for (Statement statement : techStat.getContents()){
             readTechniqueStatement(statement);
         }
 
         if(isUseNodes){
             nodesLoaderDelegate.computeConditions();
+            
             //used for caching later, the shader here is not a file.
+            
+            // KIRILL 9/19/2015
+            // Not sure if this is needed anymore, since shader caching
+            // is now done by TechniqueDef.
             technique.setShaderFile(technique.hashCode() + "", technique.hashCode() + "", "GLSL100", "GLSL100");
-        }
-
-        if (shaderName.containsKey(Shader.ShaderType.Vertex) && shaderName.containsKey(Shader.ShaderType.Fragment)) {
-            technique.setShaderFile(shaderName, shaderLanguage);
+        }else if (shaderNames.containsKey(Shader.ShaderType.Vertex) && shaderNames.containsKey(Shader.ShaderType.Fragment)) {
+            technique.setShaderFile(shaderNames, shaderLanguages);
+        } else {
+            technique = null;
+            shaderLanguages.clear();
+            shaderNames.clear();
+            presetDefines.clear();
+            logger.log(Level.WARNING, "Fixed function technique was ignored");
+            logger.log(Level.WARNING, "Fixed function technique ''{0}'' was ignored for material {1}",
+                    new Object[]{name, key});
+            return;
+        }
+        
+        technique.setShaderPrologue(createShaderPrologue(presetDefines));
+        
+        switch (technique.getLightMode()) {
+            case Disable:
+                technique.setLogic(new DefaultTechniqueDefLogic(technique));
+                break;
+            case MultiPass:
+                technique.setLogic(new MultiPassLightingLogic(technique));
+                break;
+            case SinglePass:
+                technique.setLogic(new SinglePassLightingLogic(technique));
+                break;
+            default:
+                throw new UnsupportedOperationException();
         }
 
         materialDef.addTechniqueDef(technique);
         technique = null;
-        shaderLanguage.clear();
-        shaderName.clear();
+        shaderLanguages.clear();
+        shaderNames.clear();
+        presetDefines.clear();
     }
 
     private void loadFromRoot(List<Statement> roots) throws IOException{
@@ -711,7 +775,7 @@ public class J3MLoader implements AssetLoader {
 
     protected void initNodesLoader() {
         if (!isUseNodes) {
-            isUseNodes = shaderName.get(Shader.ShaderType.Vertex) == null && shaderName.get(Shader.ShaderType.Fragment) == null;
+            isUseNodes = shaderNames.get(Shader.ShaderType.Vertex) == null && shaderNames.get(Shader.ShaderType.Fragment) == null;
             if (isUseNodes) {
                 if (nodesLoaderDelegate == null) {
                     nodesLoaderDelegate = new ShaderNodeLoaderDelegate();

+ 5 - 4
jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java

@@ -44,6 +44,7 @@ import com.jme3.shader.ShaderNodeDefinition;
 import com.jme3.shader.ShaderNodeVariable;
 import com.jme3.shader.ShaderUtils;
 import com.jme3.shader.UniformBinding;
+import com.jme3.shader.VarType;
 import com.jme3.shader.VariableMapping;
 import com.jme3.util.blockparser.Statement;
 import java.io.IOException;
@@ -583,7 +584,7 @@ public class ShaderNodeLoaderDelegate {
                     //multiplicity is not an int attempting to find for a material parameter.
                     MatParam mp = findMatParam(multiplicity);
                     if (mp != null) {
-                        addDefine(multiplicity);
+                        addDefine(multiplicity, VarType.Int);
                         multiplicity = multiplicity.toUpperCase();
                     } else {
                         throw new MatParseException("Wrong multiplicity for variable" + mapping.getLeftVariable().getName() + ". " + multiplicity + " should be an int or a declared material parameter.", statement);
@@ -625,9 +626,9 @@ public class ShaderNodeLoaderDelegate {
      *
      * @param paramName
      */
-    public void addDefine(String paramName) {
+    public void addDefine(String paramName, VarType paramType) {
         if (techniqueDef.getShaderParamDefine(paramName) == null) {
-            techniqueDef.addShaderParamDefine(paramName, paramName.toUpperCase());
+            techniqueDef.addShaderParamDefine(paramName, paramType, paramName.toUpperCase());
         }
     }
 
@@ -660,7 +661,7 @@ public class ShaderNodeLoaderDelegate {
         for (String string : defines) {
             MatParam param = findMatParam(string);
             if (param != null) {
-                addDefine(param.getName());
+                addDefine(param.getName(), param.getVarType());
             } else {
                 throw new MatParseException("Invalid condition, condition must match a Material Parameter named " + cond, statement);
             }

+ 1 - 2
jme3-core/src/plugins/java/com/jme3/scene/plugins/MTLLoader.java

@@ -149,8 +149,7 @@ public class MTLLoader implements AssetLoader {
         if (transparent){
             material.setTransparent(true);
             material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
-            material.getAdditionalRenderState().setAlphaTest(true);
-            material.getAdditionalRenderState().setAlphaFallOff(0.01f);
+            material.setFloat("AlphaDiscardThreshold", 0.01f);
         }
         
         matList.put(matName, material);

+ 18 - 10
jme3-core/src/main/java/com/jme3/animation/BoneAnimation.java → jme3-core/src/test/java/com/jme3/asset/LoadShaderSourceTest.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2015 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -29,16 +29,24 @@
  * 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.animation;
+package com.jme3.asset;
 
-/**
- * @deprecated use Animation instead with tracks of selected type (ie. BoneTrack, SpatialTrack, MeshTrack)
- */
-@Deprecated
-public final class BoneAnimation extends Animation {
+import com.jme3.asset.plugins.ClasspathLocator;
+import com.jme3.shader.plugins.GLSLLoader;
+import com.jme3.system.JmeSystem;
+import com.jme3.system.MockJmeSystemDelegate;
+import org.junit.Test;
+
+public class LoadShaderSourceTest {
 
-    @Deprecated
-    public BoneAnimation(String name, float length) {
-        super(name, length);
+    @Test
+    public void testLoadShaderSource() {
+        JmeSystem.setSystemDelegate(new MockJmeSystemDelegate());
+        AssetManager assetManager = new DesktopAssetManager();
+        assetManager.registerLocator(null, ClasspathLocator.class);
+        assetManager.registerLoader(GLSLLoader.class, "frag");
+        String showNormals = (String) assetManager.loadAsset("Common/MatDefs/Misc/ShowNormals.frag");
+        System.out.println(showNormals);
     }
+    
 }

+ 600 - 0
jme3-core/src/test/java/com/jme3/material/MaterialMatParamOverrideTest.java

@@ -0,0 +1,600 @@
+/*
+ * Copyright (c) 2009-2016 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.material;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.light.LightList;
+import com.jme3.math.Matrix4f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.shader.Shader;
+import com.jme3.shader.Uniform;
+import com.jme3.shader.VarType;
+import java.util.Arrays;
+import java.util.HashSet;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static com.jme3.scene.MPOTestUtils.*;
+import com.jme3.scene.Node;
+import com.jme3.shader.DefineList;
+import com.jme3.system.NullRenderer;
+import com.jme3.system.TestUtil;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture2D;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Before;
+
+/**
+ * Validates how {@link MatParamOverride MPOs} work on the material level.
+ *
+ * @author Kirill Vainer
+ */
+public class MaterialMatParamOverrideTest {
+
+    private static final HashSet<String> IGNORED_UNIFORMS = new HashSet<String>(
+            Arrays.asList(new String[]{"m_ParallaxHeight", "m_Shininess", "m_BackfaceShadows"}));
+
+    @Test
+    public void testBoolMpoOnly() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMpo(mpoBool("UseMaterialColors", true));
+        outDefines(def("MATERIAL_COLORS", VarType.Boolean, true));
+        outUniforms(uniform("UseMaterialColors", VarType.Boolean, true));
+    }
+
+    @Test
+    public void testBoolMpOnly() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMp(mpoBool("UseMaterialColors", true));
+        outDefines(def("MATERIAL_COLORS", VarType.Boolean, true));
+        outUniforms(uniform("UseMaterialColors", VarType.Boolean, true));
+    }
+
+    @Test
+    public void testBoolOverrideFalse() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMp(mpoBool("UseMaterialColors", true));
+        inputMpo(mpoBool("UseMaterialColors", false));
+        outDefines();
+        outUniforms(uniform("UseMaterialColors", VarType.Boolean, false));
+    }
+
+    @Test
+    public void testBoolOverrideTrue() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMp(mpoBool("UseMaterialColors", false));
+        inputMpo(mpoBool("UseMaterialColors", true));
+        outDefines(def("MATERIAL_COLORS", VarType.Boolean, true));
+        outUniforms(uniform("UseMaterialColors", VarType.Boolean, true));
+    }
+
+    @Test
+    public void testFloatMpoOnly() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMpo(mpoFloat("AlphaDiscardThreshold", 3.12f));
+        outDefines(def("DISCARD_ALPHA", VarType.Float, 3.12f));
+        outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 3.12f));
+    }
+
+    @Test
+    public void testFloatMpOnly() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMp(mpoFloat("AlphaDiscardThreshold", 3.12f));
+        outDefines(def("DISCARD_ALPHA", VarType.Float, 3.12f));
+        outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 3.12f));
+    }
+
+    @Test
+    public void testFloatOverride() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMp(mpoFloat("AlphaDiscardThreshold", 3.12f));
+        inputMpo(mpoFloat("AlphaDiscardThreshold", 2.79f));
+        outDefines(def("DISCARD_ALPHA", VarType.Float, 2.79f));
+        outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 2.79f));
+    }
+
+    @Test
+    public void testForcedOverride() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMp(mpoFloat("AlphaDiscardThreshold", 3.12f));
+        inputMpo(mpoFloat("AlphaDiscardThreshold", 2.79f));
+        inputFpo(mpoFloat("AlphaDiscardThreshold", 1.23f));
+        outDefines(def("DISCARD_ALPHA", VarType.Float, 1.23f));
+        outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 1.23f));
+
+        reset();
+        root.clearMatParamOverrides();
+        root.updateGeometricState();
+        outDefines(def("DISCARD_ALPHA", VarType.Float, 2.79f));
+        outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 2.79f));
+    }
+
+    @Test
+    public void testChildOverridesParent() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+
+        inputParentMpo(mpoFloat("AlphaDiscardThreshold", 3.12f));
+        inputMpo(mpoFloat("AlphaDiscardThreshold", 2.79f));
+
+        outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 2.79f));
+        outDefines(def("DISCARD_ALPHA", VarType.Float, 2.79f));
+    }
+
+    @Test
+    public void testMpoDisable() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMp(mpoFloat("AlphaDiscardThreshold", 3.12f));
+
+        MatParamOverride override = mpoFloat("AlphaDiscardThreshold", 2.79f);
+        inputMpo(override);
+        outDefines(def("DISCARD_ALPHA", VarType.Float, 2.79f));
+        outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 2.79f));
+
+        reset();
+        override.setEnabled(false);
+        outDefines(def("DISCARD_ALPHA", VarType.Float, 3.12f));
+        outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 3.12f));
+
+        reset();
+        override.setEnabled(true);
+        outDefines(def("DISCARD_ALPHA", VarType.Float, 2.79f));
+        outUniforms(uniform("AlphaDiscardThreshold", VarType.Float, 2.79f));
+    }
+
+    @Test
+    public void testIntMpoOnly() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMpo(mpoInt("NumberOfBones", 1234));
+        outDefines(def("NUM_BONES", VarType.Int, 1234));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 1234));
+    }
+
+    @Test
+    public void testIntMpOnly() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMp(mpoInt("NumberOfBones", 1234));
+        outDefines(def("NUM_BONES", VarType.Int, 1234));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 1234));
+    }
+
+    @Test
+    public void testIntOverride() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMp(mpoInt("NumberOfBones", 1234));
+        inputMpo(mpoInt("NumberOfBones", 4321));
+        outDefines(def("NUM_BONES", VarType.Int, 4321));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 4321));
+    }
+
+    @Test
+    public void testMatrixArray() {
+        Matrix4f[] matrices = new Matrix4f[]{
+            new Matrix4f()
+        };
+
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMpo(mpoMatrix4Array("BoneMatrices", matrices));
+        outDefines();
+        outUniforms(uniform("BoneMatrices", VarType.Matrix4Array, matrices));
+    }
+
+    @Test
+    public void testNonExistentParameter() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMpo(mpoInt("NonExistent", 4321));
+        outDefines();
+        outUniforms();
+    }
+
+    @Test
+    public void testWrongType() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMpo(mpoInt("UseMaterialColors", 4321));
+        outDefines();
+        outUniforms();
+    }
+
+    @Test
+    public void testParamOnly() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        inputMpo(mpoFloat("ShadowMapSize", 3.12f));
+        outDefines();
+        outUniforms(uniform("ShadowMapSize", VarType.Float, 3.12f));
+    }
+
+    @Test
+    public void testRemove() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+
+        reset();
+        inputMp(mpoInt("NumberOfBones", 1234));
+        outDefines(def("NUM_BONES", VarType.Int, 1234));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 1234));
+
+        reset();
+        inputMpo(mpoInt("NumberOfBones", 4321));
+        outDefines(def("NUM_BONES", VarType.Int, 4321));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 4321));
+
+        reset();
+        geometry.clearMatParamOverrides();
+        root.updateGeometricState();
+        outDefines(def("NUM_BONES", VarType.Int, 1234));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 1234));
+
+        reset();
+        geometry.getMaterial().clearParam("NumberOfBones");
+        outDefines();
+        outUniforms();
+
+        reset();
+        inputMpo(mpoInt("NumberOfBones", 4321));
+        outDefines(def("NUM_BONES", VarType.Int, 4321));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 4321));
+
+        reset();
+        inputMp(mpoInt("NumberOfBones", 1234));
+        outDefines(def("NUM_BONES", VarType.Int, 4321));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 4321));
+    }
+
+    public void testRemoveOverride() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+
+        reset();
+        inputMp(mpoInt("NumberOfBones", 1234));
+        outDefines(def("NUM_BONES", VarType.Int, 1234));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 1234));
+
+        reset();
+        inputMpo(mpoInt("NumberOfBones", 4321));
+        outDefines(def("NUM_BONES", VarType.Int, 4321));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 4321));
+
+        reset();
+        geometry.clearMatParamOverrides();
+        outDefines(def("NUM_BONES", VarType.Int, 1234));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 1234));
+    }
+
+    @Test
+    public void testRemoveMpoOnly() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+
+        reset();
+        inputMpo(mpoInt("NumberOfBones", 4321));
+        outDefines(def("NUM_BONES", VarType.Int, 4321));
+        outUniforms(uniform("NumberOfBones", VarType.Int, 4321));
+
+        reset();
+        geometry.clearMatParamOverrides();
+        root.updateGeometricState();
+        outDefines();
+        outUniforms();
+    }
+
+    @Test
+    public void testTextureMpoOnly() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        Texture2D tex = new Texture2D(128, 128, Format.RGBA8);
+
+        inputMpo(mpoTexture2D("DiffuseMap", tex));
+        outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex));
+        outUniforms(uniform("DiffuseMap", VarType.Int, 0));
+        outTextures(tex);
+    }
+
+    @Test
+    public void testTextureOverride() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        Texture2D tex1 = new Texture2D(128, 128, Format.RGBA8);
+        Texture2D tex2 = new Texture2D(128, 128, Format.RGBA8);
+
+        inputMp(mpoTexture2D("DiffuseMap", tex1));
+        inputMpo(mpoTexture2D("DiffuseMap", tex2));
+
+        outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex2));
+        outUniforms(uniform("DiffuseMap", VarType.Int, 0));
+        outTextures(tex2);
+    }
+
+    @Test
+    public void testRemoveTexture() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        Texture2D tex = new Texture2D(128, 128, Format.RGBA8);
+
+        reset();
+        inputMpo(mpoTexture2D("DiffuseMap", tex));
+        outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex));
+        outUniforms(uniform("DiffuseMap", VarType.Int, 0));
+        outTextures(tex);
+
+        reset();
+        geometry.clearMatParamOverrides();
+        root.updateGeometricState();
+        outDefines();
+        outUniforms();
+        outTextures();
+    }
+
+    @Test
+    public void testRemoveTextureOverride() {
+        material("Common/MatDefs/Light/Lighting.j3md");
+        Texture2D tex1 = new Texture2D(128, 128, Format.RGBA8);
+        Texture2D tex2 = new Texture2D(128, 128, Format.RGBA8);
+
+        reset();
+        inputMp(mpoTexture2D("DiffuseMap", tex1));
+        outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex1));
+        outUniforms(uniform("DiffuseMap", VarType.Int, 0));
+        outTextures(tex1);
+
+        reset();
+        inputMpo(mpoTexture2D("DiffuseMap", tex2));
+        outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex2));
+        outUniforms(uniform("DiffuseMap", VarType.Int, 0));
+        outTextures(tex2);
+
+        reset();
+        geometry.clearMatParamOverrides();
+        root.updateGeometricState();
+        outDefines(def("DIFFUSEMAP", VarType.Texture2D, tex1));
+        outUniforms(uniform("DiffuseMap", VarType.Int, 0));
+        outTextures(tex1);
+    }
+
+    private static final class Define {
+
+        public String name;
+        public VarType type;
+        public Object value;
+
+        @Override
+        public int hashCode() {
+            int hash = 3;
+            hash = 89 * hash + this.name.hashCode();
+            hash = 89 * hash + this.type.hashCode();
+            hash = 89 * hash + this.value.hashCode();
+            return hash;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            final Define other = (Define) obj;
+            return this.name.equals(other.name) && this.type.equals(other.type) && this.value.equals(other.value);
+        }
+    }
+
+    private final Geometry geometry = new Geometry("Geometry", new Box(1, 1, 1));
+    private final Node root = new Node("Root Node");
+    private final LightList lightList = new LightList(geometry);
+
+    @Before
+    public void setUp() {
+        root.attachChild(geometry);
+    }
+
+    private final NullRenderer renderer = new NullRenderer() {
+        @Override
+        public void setShader(Shader shader) {
+            MaterialMatParamOverrideTest.this.usedShader = shader;
+            evaluated = true;
+        }
+
+        @Override
+        public void setTexture(int unit, Texture texture) {
+            MaterialMatParamOverrideTest.this.usedTextures[unit] = texture;
+        }
+    };
+    private final RenderManager renderManager = new RenderManager(renderer);
+
+    private boolean evaluated = false;
+    private Shader usedShader = null;
+    private final Texture[] usedTextures = new Texture[32];
+
+    private void inputMp(MatParam... params) {
+        if (evaluated) {
+            throw new IllegalStateException();
+        }
+        Material mat = geometry.getMaterial();
+        for (MatParam param : params) {
+            mat.setParam(param.getName(), param.getVarType(), param.getValue());
+        }
+    }
+
+    private void inputMpo(MatParamOverride... overrides) {
+        if (evaluated) {
+            throw new IllegalStateException();
+        }
+        for (MatParamOverride override : overrides) {
+            geometry.addMatParamOverride(override);
+        }
+        root.updateGeometricState();
+    }
+
+    private void inputParentMpo(MatParamOverride... overrides) {
+        if (evaluated) {
+            throw new IllegalStateException();
+        }
+        for (MatParamOverride override : overrides) {
+            root.addMatParamOverride(override);
+        }
+        root.updateGeometricState();
+    }
+
+    private void inputFpo(MatParamOverride... overrides) {
+        if (evaluated) {
+            throw new IllegalStateException();
+        }
+        for (MatParamOverride override : overrides) {
+            renderManager.addForcedMatParam(override);
+        }
+    }
+
+    private void reset() {
+        evaluated = false;
+        usedShader = null;
+        Arrays.fill(usedTextures, null);
+        for (MatParamOverride override : new ArrayList<>(renderManager.getForcedMatParams())) {
+            renderManager.removeForcedMatParam(override);
+        }
+    }
+
+    private Define def(String name, VarType type, Object value) {
+        Define d = new Define();
+        d.name = name;
+        d.type = type;
+        d.value = value;
+        return d;
+    }
+
+    private Uniform uniform(String name, VarType type, Object value) {
+        Uniform u = new Uniform();
+        u.setName("m_" + name);
+        u.setValue(type, value);
+        return u;
+    }
+
+    private void material(String path) {
+        AssetManager assetManager = TestUtil.createAssetManager();
+        geometry.setMaterial(new Material(assetManager, path));
+    }
+
+    private void evaluateTechniqueDef() {
+        Assert.assertFalse(evaluated);
+        Material mat = geometry.getMaterial();
+        mat.render(geometry, lightList, renderManager);
+        Assert.assertTrue(evaluated);
+    }
+
+    private void outTextures(Texture... textures) {
+        for (int i = 0; i < usedTextures.length; i++) {
+            if (i < textures.length) {
+                Assert.assertSame(textures[i], usedTextures[i]);
+            } else {
+                Assert.assertNull(usedTextures[i]);
+            }
+        }
+    }
+
+    private void outDefines(Define... expectedDefinesArray) {
+        Map<String, Define> nameToDefineMap = new HashMap<String, Define>();
+        for (Define define : expectedDefinesArray) {
+            nameToDefineMap.put(define.name, define);
+        }
+
+        if (!evaluated) {
+            evaluateTechniqueDef();
+        }
+
+        Material mat = geometry.getMaterial();
+        Technique tech = mat.getActiveTechnique();
+        TechniqueDef def = tech.getDef();
+        DefineList actualDefines = tech.getDynamicDefines();
+
+        String[] defineNames = def.getDefineNames();
+        VarType[] defineTypes = def.getDefineTypes();
+
+        Assert.assertEquals(defineNames.length, defineTypes.length);
+
+        for (int index = 0; index < defineNames.length; index++) {
+            String name = defineNames[index];
+            VarType type = defineTypes[index];
+            Define expectedDefine = nameToDefineMap.remove(name);
+            Object expectedValue = null;
+
+            if (expectedDefine != null) {
+                Assert.assertEquals(expectedDefine.type, type);
+                expectedValue = expectedDefine.value;
+            }
+
+            switch (type) {
+                case Boolean:
+                    if (expectedValue != null) {
+                        Assert.assertEquals((boolean) (Boolean) expectedValue, actualDefines.getBoolean(index));
+                    } else {
+                        Assert.assertEquals(false, actualDefines.getBoolean(index));
+                    }
+                    break;
+                case Int:
+                    if (expectedValue != null) {
+                        Assert.assertEquals((int) (Integer) expectedValue, actualDefines.getInt(index));
+                    } else {
+                        Assert.assertEquals(0, actualDefines.getInt(index));
+                    }
+                    break;
+                case Float:
+                    if (expectedValue != null) {
+                        Assert.assertEquals((float) (Float) expectedValue, actualDefines.getFloat(index), 0f);
+                    } else {
+                        Assert.assertEquals(0f, actualDefines.getFloat(index), 0f);
+                    }
+                    break;
+                default:
+                    if (expectedValue != null) {
+                        Assert.assertEquals(1, actualDefines.getInt(index));
+                    } else {
+                        Assert.assertEquals(0, actualDefines.getInt(index));
+                    }
+                    break;
+            }
+        }
+
+        Assert.assertTrue(nameToDefineMap.isEmpty());
+    }
+
+    private void outUniforms(Uniform... uniforms) {
+        if (!evaluated) {
+            evaluateTechniqueDef();
+        }
+
+        HashSet<Uniform> actualUniforms = new HashSet<>();
+
+        for (Uniform uniform : usedShader.getUniformMap().values()) {
+            if (uniform.getName().startsWith("m_")
+                    && !IGNORED_UNIFORMS.contains(uniform.getName())) {
+                actualUniforms.add(uniform);
+            }
+        }
+
+        HashSet<Uniform> expectedUniforms = new HashSet<>(Arrays.asList(uniforms));
+
+        if (!expectedUniforms.equals(actualUniforms)) {
+            Assert.fail("Uniform lists must match: " + expectedUniforms + " != " + actualUniforms);
+        }
+    }
+}

+ 171 - 0
jme3-core/src/test/java/com/jme3/material/MaterialTest.java

@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2009-2016 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.material;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.light.LightList;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.system.NullRenderer;
+import com.jme3.system.TestUtil;
+import java.util.Arrays;
+import java.util.EnumSet;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class MaterialTest {
+
+    private Material material;
+    private final Geometry geometry = new Geometry("Geometry", new Box(1, 1, 1));
+    private final EnumSet<Caps> myCaps = EnumSet.noneOf(Caps.class);
+    private final RenderManager renderManager = new RenderManager(new NullRenderer() {
+        @Override
+        public EnumSet<Caps> getCaps() {
+            return MaterialTest.this.myCaps;
+        }
+    });
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSelectNonExistentTechnique() {
+        material("Common/MatDefs/Gui/Gui.j3md");
+        material.selectTechnique("Doesn't Exist", renderManager);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testSelectDefaultTechnique_NoCaps() {
+        material("Common/MatDefs/Gui/Gui.j3md");
+        material.selectTechnique("Default", renderManager);
+    }
+
+    @Test
+    public void testSelectDefaultTechnique_GLSL100Cap() {
+        supportGlsl(100);
+        material("Common/MatDefs/Gui/Gui.j3md");
+
+        material.selectTechnique("Default", renderManager);
+
+        checkRequiredCaps(Caps.GLSL100);
+    }
+
+    @Test
+    public void testSelectDefaultTechnique_GLSL150Cap() {
+        supportGlsl(150);
+        material("Common/MatDefs/Gui/Gui.j3md");
+
+        material.selectTechnique("Default", renderManager);
+
+        checkRequiredCaps(Caps.GLSL150);
+    }
+
+    @Test
+    public void testSelectDefaultTechnique_GLSL120Cap_MultipleLangs() {
+        supportGlsl(120);
+        material("Common/MatDefs/Misc/Particle.j3md");
+
+        material.selectTechnique("Default", renderManager);
+
+        checkRequiredCaps(Caps.GLSL100, Caps.GLSL120);
+    }
+
+    @Test
+    public void testSelectDefaultTechnique_GLSL100Cap_MultipleLangs() {
+        supportGlsl(100);
+        material("Common/MatDefs/Misc/Particle.j3md");
+
+        material.selectTechnique("Default", renderManager);
+
+        checkRequiredCaps(Caps.GLSL100);
+    }
+
+    @Test
+    public void testSelectNamedTechnique_GLSL150Cap() {
+        supportGlsl(150);
+        material("Common/MatDefs/Light/Lighting.j3md");
+
+        material.selectTechnique("PostShadow", renderManager);
+
+        checkRequiredCaps(Caps.GLSL150);
+    }
+
+    @Test
+    public void testSelectNamedTechnique_GLSL100Cap() {
+        supportGlsl(100);
+        material("Common/MatDefs/Light/Lighting.j3md");
+
+        material.selectTechnique("PostShadow", renderManager);
+
+        checkRequiredCaps(Caps.GLSL100);
+    }
+
+    private void checkRequiredCaps(Caps... caps) {
+        EnumSet<Caps> expected = EnumSet.noneOf(Caps.class);
+        expected.addAll(Arrays.asList(caps));
+
+        Technique tech = material.getActiveTechnique();
+
+        assertEquals(expected, tech.getDef().getRequiredCaps());
+    }
+
+    private void supportGlsl(int version) {
+        switch (version) {
+            case 150:
+                myCaps.add(Caps.GLSL150);
+            case 140:
+                myCaps.add(Caps.GLSL140);
+            case 130:
+                myCaps.add(Caps.GLSL130);
+            case 120:
+                myCaps.add(Caps.GLSL120);
+            case 110:
+                myCaps.add(Caps.GLSL110);
+            case 100:
+                myCaps.add(Caps.GLSL100);
+                break;
+        }
+    }
+
+    private void caps(Caps... caps) {
+        myCaps.addAll(Arrays.asList(caps));
+    }
+
+    private void material(String path) {
+        AssetManager assetManager = TestUtil.createAssetManager();
+        material = new Material(assetManager, path);
+        geometry.setMaterial(material);
+    }
+
+}

+ 29 - 1
jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java

@@ -7,8 +7,11 @@ import com.jme3.asset.TextureKey;
 import com.jme3.material.MatParamTexture;
 import com.jme3.material.Material;
 import com.jme3.material.MaterialDef;
+import com.jme3.renderer.Caps;
 import com.jme3.shader.VarType;
 import com.jme3.texture.Texture;
+import java.io.IOException;
+import java.util.EnumSet;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -18,6 +21,7 @@ import org.mockito.runners.MockitoJUnitRunner;
 
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.verify;
+import static org.junit.Assert.*;
 import static org.mockito.Mockito.when;
 
 /**
@@ -51,6 +55,30 @@ public class J3MLoaderTest {
         j3MLoader = new J3MLoader();
     }
 
+    @Test
+    public void noDefaultTechnique_shouldBeSupported() throws IOException {
+        when(assetInfo.openStream()).thenReturn(J3MLoader.class.getResourceAsStream("/no-default-technique.j3md"));
+        MaterialDef def = (MaterialDef) j3MLoader.load(assetInfo);
+        assertEquals(1, def.getTechniqueDefs("Test").size());
+    }
+
+    @Test
+    public void fixedPipelineTechnique_shouldBeIgnored() throws IOException {
+        when(assetInfo.openStream()).thenReturn(J3MLoader.class.getResourceAsStream("/no-shader-specified.j3md"));
+        MaterialDef def = (MaterialDef) j3MLoader.load(assetInfo);
+        assertEquals(null, def.getTechniqueDefs("A"));
+        assertEquals(1, def.getTechniqueDefs("B").size());
+    }
+
+    @Test
+    public void multipleSameNamedTechniques_shouldBeSupported() throws IOException {
+        when(assetInfo.openStream()).thenReturn(J3MLoader.class.getResourceAsStream("/same-name-technique.j3md"));
+        MaterialDef def = (MaterialDef) j3MLoader.load(assetInfo);
+        assertEquals(2, def.getTechniqueDefs("Test").size());
+        assertEquals(EnumSet.of(Caps.GLSL150), def.getTechniqueDefs("Test").get(0).getRequiredCaps());
+        assertEquals(EnumSet.of(Caps.GLSL100), def.getTechniqueDefs("Test").get(1).getRequiredCaps());
+    }
+
     @Test
     public void oldStyleTextureParameters_shouldBeSupported() throws Exception {
         when(assetInfo.openStream()).thenReturn(J3MLoader.class.getResourceAsStream("/texture-parameters-oldstyle.j3m"));
@@ -107,7 +135,7 @@ public class J3MLoaderTest {
     }
 
     private TextureKey setupMockForTexture(final String paramName, final String path, final boolean flipY, final Texture texture) {
-        when(materialDef.getMaterialParam(paramName)).thenReturn(new MatParamTexture(VarType.Texture2D, paramName, texture, 0, null));
+        when(materialDef.getMaterialParam(paramName)).thenReturn(new MatParamTexture(VarType.Texture2D, paramName, texture, null));
 
         final TextureKey textureKey = new TextureKey(path, flipY);
         textureKey.setGenerateMips(true);

+ 100 - 0
jme3-core/src/test/java/com/jme3/material/plugins/LoadJ3mdTest.java

@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2009-2016 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.material.plugins;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.*;
+import com.jme3.renderer.*;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.shader.Shader;
+import com.jme3.system.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.util.*;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(MockitoJUnitRunner.class)
+public class LoadJ3mdTest {
+
+    private Material material;
+    private final Geometry geometry = new Geometry("Geometry", new Box(1, 1, 1));
+    private final EnumSet<Caps> myCaps = EnumSet.noneOf(Caps.class);
+    private final RenderManager renderManager = new RenderManager(new NullRenderer() {
+        @Override
+        public EnumSet<Caps> getCaps() {
+            return LoadJ3mdTest.this.myCaps;
+        }
+    });
+
+    @Test
+    public void testShaderNodesMaterialDefLoading() {
+        supportGlsl(100);
+        material("testMatDef.j3md");
+        material.selectTechnique("Default", renderManager);
+
+        assertEquals(material.getActiveTechnique().getDef().getShaderNodes().size(), 2);
+        Shader s = material.getActiveTechnique().getDef().getShader(TestUtil.createAssetManager(), myCaps,  material.getActiveTechnique().getDynamicDefines());
+        assertEquals(s.getSources().size(), 2);
+    }
+
+    private void supportGlsl(int version) {
+        switch (version) {
+            case 150:
+                myCaps.add(Caps.GLSL150);
+            case 140:
+                myCaps.add(Caps.GLSL140);
+            case 130:
+                myCaps.add(Caps.GLSL130);
+            case 120:
+                myCaps.add(Caps.GLSL120);
+            case 110:
+                myCaps.add(Caps.GLSL110);
+            case 100:
+                myCaps.add(Caps.GLSL100);
+                break;
+        }
+    }
+    private void caps(Caps... caps) {
+        myCaps.addAll(Arrays.asList(caps));
+    }
+
+    private void material(String path) {
+        AssetManager assetManager = TestUtil.createAssetManager();
+        material = new Material(assetManager, path);
+        geometry.setMaterial(material);
+    }
+
+}

+ 38 - 0
jme3-core/src/test/java/com/jme3/math/FastMathTest.java

@@ -33,6 +33,9 @@ package com.jme3.math;
 
 import org.junit.Test;
 
+import static org.junit.Assert.assertEquals;
+import org.junit.Ignore;
+
 /**
  * Verifies that algorithms in {@link FastMath} are working correctly.
  * 
@@ -56,4 +59,39 @@ public class FastMathTest {
             assert nextPowerOf2 == nearestPowerOfTwoSlow(i);
         }
     }
+    
+    private static int fastCounterClockwise(Vector2f p0, Vector2f p1, Vector2f p2) {
+        float result = (p1.x - p0.x) * (p2.y - p1.y) - (p1.y - p0.y) * (p2.x - p1.x);
+        return (int) Math.signum(result);
+    }
+    
+    private static Vector2f randomVector() {
+        return new Vector2f(FastMath.nextRandomFloat(),
+                            FastMath.nextRandomFloat());
+    }
+    
+    @Ignore
+    @Test
+    public void testCounterClockwise() {
+        for (int i = 0; i < 100; i++) {
+            Vector2f p0 = randomVector();
+            Vector2f p1 = randomVector();
+            Vector2f p2 = randomVector();
+
+            int fastResult = fastCounterClockwise(p0, p1, p2);
+            int slowResult = FastMath.counterClockwise(p0, p1, p2);
+            
+            assert fastResult == slowResult;
+        }
+        
+        // duplicate test
+        Vector2f p0 = new Vector2f(0,0);
+        Vector2f p1 = new Vector2f(0,0);
+        Vector2f p2 = new Vector2f(0,1);
+        
+        int fastResult = fastCounterClockwise(p0, p1, p2);
+        int slowResult = FastMath.counterClockwise(p0, p1, p2);
+        
+        assertEquals(slowResult, fastResult);
+    }
 }

+ 343 - 0
jme3-core/src/test/java/com/jme3/renderer/OpaqueComparatorTest.java

@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2009-2015 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.renderer;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.material.TechniqueDef;
+import com.jme3.math.ColorRGBA;
+import com.jme3.renderer.queue.GeometryList;
+import com.jme3.renderer.queue.OpaqueComparator;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.shape.Box;
+import com.jme3.system.TestUtil;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture2D;
+import com.jme3.texture.image.ColorSpace;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.util.HashSet;
+import java.util.Set;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class OpaqueComparatorTest {
+    
+    private final Mesh mesh = new Box(1,1,1);
+    private Camera cam = new Camera(1, 1);
+    private RenderManager renderManager;
+    private AssetManager assetManager;
+    private OpaqueComparator comparator = new OpaqueComparator();
+    
+    @Before
+    public void setUp() {
+        assetManager = TestUtil.createAssetManager();
+        renderManager = TestUtil.createRenderManager();
+        comparator.setCamera(cam);
+    }
+    
+    /**
+     * Given a correctly sorted list of materials, check if the 
+     * opaque comparator can sort a reversed list of them.
+     * 
+     * Each material will be cloned so that none of them will be equal to each other
+     * in reference, forcing the comparator to compare the material sort ID.
+     * 
+     * E.g. for a list of materials A, B, C, the following list will be generated:
+     * <pre>C, B, A, C, B, A, C, B, A</pre>, it should result in
+     * <pre>A, A, A, B, B, B, C, C, C</pre>.
+     * 
+     * @param materials The pre-sorted list of materials to check sorting for.
+     */
+    private void testSort(Material ... materials) {
+        GeometryList gl = new GeometryList(comparator);
+        for (int g = 0; g < 5; g++) {
+            for (int i = materials.length - 1; i >= 0; i--) {
+                Geometry geom = new Geometry("geom", mesh);
+                Material clonedMaterial = materials[i].clone();
+                
+                if (materials[i].getActiveTechnique() != null) {
+                    String techniqueName = materials[i].getActiveTechnique().getDef().getName();
+                    clonedMaterial.selectTechnique(techniqueName, renderManager);
+                } else {
+                    clonedMaterial.selectTechnique(TechniqueDef.DEFAULT_TECHNIQUE_NAME, renderManager);
+                }
+                
+                geom.setMaterial(clonedMaterial);
+                gl.add(geom);
+            }
+        }
+        gl.sort();
+        
+        for (int i = 0; i < gl.size(); i++) {
+            Material mat = gl.get(i).getMaterial();
+            String sortId = Integer.toHexString(mat.getSortId()).toUpperCase();
+            System.out.print(sortId + "\t");
+            System.out.println(mat);
+        }
+        
+        Set<String> alreadySeen = new HashSet<String>();
+        Material current = null;
+        for (int i = 0; i < gl.size(); i++) {
+            Material mat = gl.get(i).getMaterial();
+            if (current == null) {
+                current = mat;
+            } else if (!current.getName().equals(mat.getName())) {
+                assert !alreadySeen.contains(mat.getName());
+                alreadySeen.add(current.getName());
+                current = mat;
+            }
+        }
+        
+        for (int i = 0; i < materials.length; i++) {
+            for (int g = 0; g < 5; g++) {
+                int index = i * 5 + g;
+                Material mat1 = gl.get(index).getMaterial();
+                Material mat2 = materials[i];
+                assert mat1.getName().equals(mat2.getName()) : 
+                       mat1.getName() + " != " + mat2.getName() + " for index " + index;
+            }
+        }
+    }
+    
+    @Test
+    public void testSortByMaterialDef() {
+        Material lightingMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material particleMat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
+        Material unshadedMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        Material skyMat      = new Material(assetManager, "Common/MatDefs/Misc/Sky.j3md");
+        
+        lightingMat.setName("MatLight");
+        particleMat.setName("MatParticle");
+        unshadedMat.setName("MatUnshaded");
+        skyMat.setName("MatSky");
+        testSort(skyMat, lightingMat, particleMat, unshadedMat);
+    }
+    
+    @Test
+    public void testSortByTechnique() {
+        Material lightingMatDefault = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material lightingPreShadow = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material lightingPostShadow = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material lightingMatPreNormalPass = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material lightingMatGBuf = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material lightingMatGlow = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        
+        lightingMatDefault.setName("TechDefault");
+        lightingMatDefault.selectTechnique(TechniqueDef.DEFAULT_TECHNIQUE_NAME, renderManager);
+        
+        lightingPostShadow.setName("TechPostShad");
+        lightingPostShadow.selectTechnique("PostShadow", renderManager);
+        
+        lightingPreShadow.setName("TechPreShad");
+        lightingPreShadow.selectTechnique("PreShadow", renderManager);
+        
+        lightingMatPreNormalPass.setName("TechNorm");
+        lightingMatPreNormalPass.selectTechnique("PreNormalPass", renderManager);
+        
+        lightingMatGBuf.setName("TechGBuf");
+        lightingMatGBuf.selectTechnique("GBuf", renderManager);
+        
+        lightingMatGlow.setName("TechGlow");
+        lightingMatGlow.selectTechnique("Glow", renderManager);
+        
+        testSort(lightingMatGlow, lightingPreShadow, lightingMatPreNormalPass,
+                 lightingMatDefault, lightingPostShadow, lightingMatGBuf);
+    }
+    
+    @Test(expected = AssertionError.class)
+    public void testNoSortByParam() {
+        Material sameMat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        Material sameMat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        
+        sameMat1.setName("MatRed");
+        sameMat1.setColor("Color", ColorRGBA.Red);
+        
+        sameMat2.setName("MatBlue");
+        sameMat2.setColor("Color", ColorRGBA.Blue);
+        
+        testSort(sameMat1, sameMat2);
+    }
+    
+    private Texture createTexture(String name) {
+        ByteBuffer bb = BufferUtils.createByteBuffer(3);
+        Image image = new Image(Format.RGB8, 1, 1, bb, ColorSpace.sRGB);
+        Texture2D texture = new Texture2D(image);
+        texture.setName(name);
+        return texture;
+    }
+    
+    @Test
+    public void testSortByTexture() {
+        Material texture1Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        Material texture2Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        Material texture3Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        
+        Texture tex1 = createTexture("A");
+        tex1.getImage().setId(1);
+        
+        Texture tex2 = createTexture("B");
+        tex2.getImage().setId(2);
+        
+        Texture tex3 = createTexture("C");
+        tex3.getImage().setId(3);
+        
+        texture1Mat.setName("TexA");
+        texture1Mat.setTexture("ColorMap", tex1);
+        
+        texture2Mat.setName("TexB");
+        texture2Mat.setTexture("ColorMap", tex2);
+        
+        texture3Mat.setName("TexC");
+        texture3Mat.setTexture("ColorMap", tex3);
+        
+        testSort(texture1Mat, texture2Mat, texture3Mat);
+    }
+    
+    @Test
+    public void testSortByShaderDefines() {
+        Material lightingMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material lightingMatVColor = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material lightingMatVLight = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material lightingMatTC = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material lightingMatVColorLight = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material lightingMatTCVColorLight = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        
+        lightingMat.setName("DefNone");
+        
+        lightingMatVColor.setName("DefVC");
+        lightingMatVColor.setBoolean("UseVertexColor", true);
+        
+        lightingMatVLight.setName("DefVL");
+        lightingMatVLight.setBoolean("VertexLighting", true);
+        
+        lightingMatTC.setName("DefTC");
+        lightingMatTC.setBoolean("SeparateTexCoord", true);
+        
+        lightingMatVColorLight.setName("DefVCVL");
+        lightingMatVColorLight.setBoolean("UseVertexColor", true);
+        lightingMatVColorLight.setBoolean("VertexLighting", true);
+        
+        lightingMatTCVColorLight.setName("DefVCVLTC");
+        lightingMatTCVColorLight.setBoolean("UseVertexColor", true);
+        lightingMatTCVColorLight.setBoolean("VertexLighting", true);
+        lightingMatTCVColorLight.setBoolean("SeparateTexCoord", true);
+        
+        testSort(lightingMat, lightingMatVColor, lightingMatVLight,
+                 lightingMatVColorLight, lightingMatTC, lightingMatTCVColorLight);
+    }
+    
+    @Test
+    public void testSortByAll() {
+        Material matBase1 = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        Material matBase2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        
+        Texture texBase = createTexture("BASE");
+        texBase.getImage().setId(1);
+        Texture tex1 = createTexture("1");
+        tex1.getImage().setId(2);
+        Texture tex2 = createTexture("2");
+        tex2.getImage().setId(3);
+        
+        matBase1.setName("BASE");
+        matBase1.selectTechnique(TechniqueDef.DEFAULT_TECHNIQUE_NAME, renderManager);
+        matBase1.setBoolean("UseVertexColor", true);
+        matBase1.setTexture("DiffuseMap", texBase);
+        
+        Material mat1100 = matBase1.clone();
+        mat1100.setName("1100");
+        mat1100.selectTechnique("PreShadow", renderManager);
+        
+        Material mat1101 = matBase1.clone();
+        mat1101.setName("1101");
+        mat1101.selectTechnique("PreShadow", renderManager);
+        mat1101.setTexture("DiffuseMap", tex1);
+        
+        Material mat1102 = matBase1.clone();
+        mat1102.setName("1102");
+        mat1102.selectTechnique("PreShadow", renderManager);
+        mat1102.setTexture("DiffuseMap", tex2);
+        
+        Material mat1110 = matBase1.clone();
+        mat1110.setName("1110");
+        mat1110.selectTechnique("PreShadow", renderManager);
+        mat1110.setFloat("AlphaDiscardThreshold", 2f);
+        
+        Material mat1120 = matBase1.clone();
+        mat1120.setName("1120");
+        mat1120.selectTechnique("PreShadow", renderManager);
+        mat1120.setBoolean("UseInstancing", true);
+        
+        Material mat1121 = matBase1.clone();
+        mat1121.setName("1121");
+        mat1121.selectTechnique("PreShadow", renderManager);
+        mat1121.setBoolean("UseInstancing", true);
+        mat1121.setTexture("DiffuseMap", tex1);
+        
+        Material mat1122 = matBase1.clone();
+        mat1122.setName("1122");
+        mat1122.selectTechnique("PreShadow", renderManager);
+        mat1122.setBoolean("UseInstancing", true);
+        mat1122.setTexture("DiffuseMap", tex2);
+        
+        Material mat1140 = matBase1.clone();
+        mat1140.setName("1140");
+        mat1140.selectTechnique("PreShadow", renderManager);
+        mat1140.setFloat("AlphaDiscardThreshold", 2f);
+        mat1140.setBoolean("UseInstancing", true);
+        
+        Material mat1200 = matBase1.clone();
+        mat1200.setName("1200");
+        mat1200.selectTechnique("PostShadow", renderManager);
+        
+        Material mat1210 = matBase1.clone();
+        mat1210.setName("1210");
+        mat1210.selectTechnique("PostShadow", renderManager);
+        mat1210.setFloat("AlphaDiscardThreshold", 2f);
+        
+        Material mat1220 = matBase1.clone();
+        mat1220.setName("1220");
+        mat1220.selectTechnique("PostShadow", renderManager);
+        mat1220.setBoolean("UseInstancing", true);
+        
+        Material mat2000 = matBase2.clone();
+        mat2000.setName("2000");
+        
+        testSort(mat1100, mat1101, mat1102, mat1110, 
+                 mat1120, mat1121, mat1122, mat1140, 
+                 mat1200, mat1210, mat1220, mat2000);
+    }
+}

+ 173 - 0
jme3-core/src/test/java/com/jme3/scene/MPOTestUtils.java

@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2009-2016 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.scene;
+
+import com.jme3.material.MatParamOverride;
+import com.jme3.math.Matrix4f;
+import com.jme3.renderer.Camera;
+import com.jme3.shader.VarType;
+import com.jme3.texture.Texture2D;
+import java.lang.reflect.Field;
+import java.util.HashSet;
+import java.util.Set;
+import static org.junit.Assert.assertEquals;
+
+public class MPOTestUtils {
+
+    private static final Camera DUMMY_CAM = new Camera(640, 480);
+
+    private static final SceneGraphVisitor VISITOR = new SceneGraphVisitor() {
+        @Override
+        public void visit(Spatial spatial) {
+            validateSubScene(spatial);
+        }
+    };
+
+    private static void validateSubScene(Spatial scene) {
+        scene.checkCulling(DUMMY_CAM);
+
+        Set<MatParamOverride> actualOverrides = new HashSet<MatParamOverride>();
+        for (MatParamOverride override : scene.getWorldMatParamOverrides()) {
+            actualOverrides.add(override);
+        }
+
+        Set<MatParamOverride> expectedOverrides = new HashSet<MatParamOverride>();
+        Spatial current = scene;
+        while (current != null) {
+            for (MatParamOverride override : current.getLocalMatParamOverrides()) {
+                expectedOverrides.add(override);
+            }
+            current = current.getParent();
+        }
+
+        assertEquals("For " + scene, expectedOverrides, actualOverrides);
+    }
+    
+    public static void validateScene(Spatial scene) {
+        scene.updateGeometricState();
+        scene.depthFirstTraversal(VISITOR);
+    }
+
+    public static MatParamOverride mpoInt(String name, int value) {
+        return new MatParamOverride(VarType.Int, name, value);
+    }
+
+    public static MatParamOverride mpoBool(String name, boolean value) {
+        return new MatParamOverride(VarType.Boolean, name, value);
+    }
+
+    public static MatParamOverride mpoFloat(String name, float value) {
+        return new MatParamOverride(VarType.Float, name, value);
+    }
+
+    public static MatParamOverride mpoMatrix4Array(String name, Matrix4f[] value) {
+        return new MatParamOverride(VarType.Matrix4Array, name, value);
+    }
+
+    public static MatParamOverride mpoTexture2D(String name, Texture2D texture) {
+        return new MatParamOverride(VarType.Texture2D, name, texture);
+    }
+
+    private static int getRefreshFlags(Spatial scene) {
+        try {
+            Field refreshFlagsField = Spatial.class.getDeclaredField("refreshFlags");
+            refreshFlagsField.setAccessible(true);
+            return (Integer) refreshFlagsField.get(scene);
+        } catch (NoSuchFieldException ex) {
+            throw new AssertionError(ex);
+        } catch (SecurityException ex) {
+            throw new AssertionError(ex);
+        } catch (IllegalArgumentException ex) {
+            throw new AssertionError(ex);
+        } catch (IllegalAccessException ex) {
+            throw new AssertionError(ex);
+        }
+    }
+
+    private static void dumpSceneRF(Spatial scene, String indent, boolean last, int refreshFlagsMask) {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append(indent);
+        if (last) {
+            if (!indent.isEmpty()) {
+                sb.append("└─");
+            } else {
+                sb.append("  ");
+            }
+            indent += "  ";
+        } else {
+            sb.append("├─");
+            indent += "│ ";
+        }
+        sb.append(scene.getName());
+        int rf = getRefreshFlags(scene) & refreshFlagsMask;
+        if (rf != 0) {
+            sb.append("(");
+            if ((rf & 0x1) != 0) {
+                sb.append("T");
+            }
+            if ((rf & 0x2) != 0) {
+                sb.append("B");
+            }
+            if ((rf & 0x4) != 0) {
+                sb.append("L");
+            }
+            if ((rf & 0x8) != 0) {
+                sb.append("l");
+            }
+            if ((rf & 0x10) != 0) {
+                sb.append("O");
+            }
+            sb.append(")");
+        }
+
+        if (!scene.getLocalMatParamOverrides().isEmpty()) {
+            sb.append(" [MPO]");
+        }
+
+        System.out.println(sb);
+
+        if (scene instanceof Node) {
+            Node node = (Node) scene;
+            int childIndex = 0;
+            for (Spatial child : node.getChildren()) {
+                boolean childLast = childIndex == node.getQuantity() - 1;
+                dumpSceneRF(child, indent, childLast, refreshFlagsMask);
+                childIndex++;
+            }
+        }
+    }
+
+    public static void dumpSceneRF(Spatial scene, int refreshFlagsMask) {
+        dumpSceneRF(scene, "", true, refreshFlagsMask);
+    }
+}

+ 278 - 0
jme3-core/src/test/java/com/jme3/scene/SceneMatParamOverrideTest.java

@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2009-2016 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.scene;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.binary.BinaryExporter;
+import com.jme3.material.MatParamOverride;
+import org.junit.Test;
+
+import static com.jme3.scene.MPOTestUtils.*;
+import static org.junit.Assert.*;
+
+import com.jme3.system.TestUtil;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Validates how {@link MatParamOverride MPOs} work on the scene level.
+ *
+ * @author Kirill Vainer
+ */
+public class SceneMatParamOverrideTest {
+
+    private static Node createDummyScene() {
+        Node scene = new Node("Scene Node");
+
+        Node a = new Node("A");
+        Node b = new Node("B");
+
+        Node c = new Node("C");
+        Node d = new Node("D");
+
+        Node e = new Node("E");
+        Node f = new Node("F");
+
+        Node g = new Node("G");
+        Node h = new Node("H");
+        Node j = new Node("J");
+
+        scene.attachChild(a);
+        scene.attachChild(b);
+
+        a.attachChild(c);
+        a.attachChild(d);
+
+        b.attachChild(e);
+        b.attachChild(f);
+
+        c.attachChild(g);
+        c.attachChild(h);
+        c.attachChild(j);
+
+        return scene;
+    }
+
+    @Test
+    public void testOverrides_Empty() {
+        Node n = new Node("Node");
+        assertTrue(n.getLocalMatParamOverrides().isEmpty());
+        assertTrue(n.getWorldMatParamOverrides().isEmpty());
+
+        n.updateGeometricState();
+        assertTrue(n.getLocalMatParamOverrides().isEmpty());
+        assertTrue(n.getWorldMatParamOverrides().isEmpty());
+    }
+
+    @Test
+    public void testOverrides_AddRemove() {
+        MatParamOverride override = mpoBool("Test", true);
+        Node n = new Node("Node");
+
+        n.removeMatParamOverride(override);
+        assertTrue(n.getLocalMatParamOverrides().isEmpty());
+        assertTrue(n.getWorldMatParamOverrides().isEmpty());
+
+        n.addMatParamOverride(override);
+
+        assertSame(n.getLocalMatParamOverrides().get(0), override);
+        assertTrue(n.getWorldMatParamOverrides().isEmpty());
+        n.updateGeometricState();
+
+        assertSame(n.getLocalMatParamOverrides().get(0), override);
+        assertSame(n.getWorldMatParamOverrides().get(0), override);
+
+        n.removeMatParamOverride(override);
+        assertTrue(n.getLocalMatParamOverrides().isEmpty());
+        assertSame(n.getWorldMatParamOverrides().get(0), override);
+
+        n.updateGeometricState();
+        assertTrue(n.getLocalMatParamOverrides().isEmpty());
+        assertTrue(n.getWorldMatParamOverrides().isEmpty());
+    }
+
+    @Test
+    public void testOverrides_Clear() {
+        MatParamOverride override = mpoBool("Test", true);
+        Node n = new Node("Node");
+
+        n.clearMatParamOverrides();
+        assertTrue(n.getLocalMatParamOverrides().isEmpty());
+        assertTrue(n.getWorldMatParamOverrides().isEmpty());
+
+        n.addMatParamOverride(override);
+        n.clearMatParamOverrides();
+        assertTrue(n.getLocalMatParamOverrides().isEmpty());
+        assertTrue(n.getWorldMatParamOverrides().isEmpty());
+
+        n.addMatParamOverride(override);
+        n.updateGeometricState();
+        n.clearMatParamOverrides();
+        assertTrue(n.getLocalMatParamOverrides().isEmpty());
+        assertSame(n.getWorldMatParamOverrides().get(0), override);
+
+        n.updateGeometricState();
+        assertTrue(n.getLocalMatParamOverrides().isEmpty());
+        assertTrue(n.getWorldMatParamOverrides().isEmpty());
+
+        n.addMatParamOverride(override);
+        n.clearMatParamOverrides();
+        n.updateGeometricState();
+        assertTrue(n.getLocalMatParamOverrides().isEmpty());
+        assertTrue(n.getWorldMatParamOverrides().isEmpty());
+    }
+
+    @Test
+    public void testOverrides_AddAfterAttach() {
+        Node scene = createDummyScene();
+        scene.updateGeometricState();
+
+        Node root = new Node("Root Node");
+        root.updateGeometricState();
+
+        root.attachChild(scene);
+        scene.getChild("A").addMatParamOverride(mpoInt("val", 5));
+
+        validateScene(root);
+    }
+
+    @Test
+    public void testOverrides_AddBeforeAttach() {
+        Node scene = createDummyScene();
+        scene.getChild("A").addMatParamOverride(mpoInt("val", 5));
+        scene.updateGeometricState();
+
+        Node root = new Node("Root Node");
+        root.updateGeometricState();
+
+        root.attachChild(scene);
+
+        validateScene(root);
+    }
+
+    @Test
+    public void testOverrides_RemoveBeforeAttach() {
+        Node scene = createDummyScene();
+        scene.updateGeometricState();
+
+        Node root = new Node("Root Node");
+        root.updateGeometricState();
+
+        scene.getChild("A").addMatParamOverride(mpoInt("val", 5));
+        validateScene(scene);
+
+        scene.getChild("A").clearMatParamOverrides();
+        validateScene(scene);
+
+        root.attachChild(scene);
+        validateScene(root);
+    }
+
+    @Test
+    public void testOverrides_RemoveAfterAttach() {
+        Node scene = createDummyScene();
+        scene.updateGeometricState();
+
+        Node root = new Node("Root Node");
+        root.updateGeometricState();
+
+        scene.getChild("A").addMatParamOverride(mpoInt("val", 5));
+
+        root.attachChild(scene);
+        validateScene(root);
+
+        scene.getChild("A").clearMatParamOverrides();
+        validateScene(root);
+    }
+
+    @Test
+    public void testOverrides_IdenticalNames() {
+        Node scene = createDummyScene();
+
+        scene.getChild("A").addMatParamOverride(mpoInt("val", 5));
+        scene.getChild("C").addMatParamOverride(mpoInt("val", 7));
+
+        validateScene(scene);
+    }
+
+    @Test
+    public void testOverrides_CloningScene_DoesntCloneMPO() {
+        Node originalScene = createDummyScene();
+
+        originalScene.getChild("A").addMatParamOverride(mpoInt("int", 5));
+        originalScene.getChild("A").addMatParamOverride(mpoBool("bool", true));
+        originalScene.getChild("A").addMatParamOverride(mpoFloat("float", 3.12f));
+
+        Node clonedScene = originalScene.clone(false);
+
+        validateScene(clonedScene);
+        validateScene(originalScene);
+
+        List<MatParamOverride> clonedOverrides = clonedScene.getChild("A").getLocalMatParamOverrides();
+        List<MatParamOverride> originalOverrides = originalScene.getChild("A").getLocalMatParamOverrides();
+
+        assertNotSame(clonedOverrides, originalOverrides);
+        assertEquals(clonedOverrides, originalOverrides);
+
+        for (int i = 0; i < clonedOverrides.size(); i++) {
+            assertNotSame(clonedOverrides.get(i), originalOverrides.get(i));
+            assertEquals(clonedOverrides.get(i), originalOverrides.get(i));
+        }
+    }
+
+    @Test
+    public void testOverrides_SaveAndLoad_KeepsMPOs() {
+        MatParamOverride override = mpoInt("val", 5);
+        Node scene = createDummyScene();
+        scene.getChild("A").addMatParamOverride(override);
+
+        AssetManager assetManager = TestUtil.createAssetManager();
+        Node loadedScene = BinaryExporter.saveAndLoad(assetManager, scene);
+
+        Node root = new Node("Root Node");
+        root.attachChild(loadedScene);
+        validateScene(root);
+        validateScene(scene);
+
+        assertNotSame(override, loadedScene.getChild("A").getLocalMatParamOverrides().get(0));
+        assertEquals(override, loadedScene.getChild("A").getLocalMatParamOverrides().get(0));
+    }
+
+    @Test
+    public void testEquals() {
+        assertEquals(mpoInt("val", 5), mpoInt("val", 5));
+        assertEquals(mpoBool("val", true), mpoBool("val", true));
+        assertNotEquals(mpoInt("val", 5), mpoInt("val", 6));
+        assertNotEquals(mpoInt("val1", 5), mpoInt("val2", 5));
+        assertNotEquals(mpoBool("val", true), mpoInt("val", 1));
+    }
+}

+ 300 - 0
jme3-core/src/test/java/com/jme3/shader/DefineListTest.java

@@ -0,0 +1,300 @@
+/*
+ * Copyright (c) 2009-2015 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.shader;
+
+import com.jme3.math.FastMath;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class DefineListTest {
+    
+    private static final List<String> DEFINE_NAMES = Arrays.asList("BOOL_VAR", "INT_VAR", "FLOAT_VAR");
+    private static final List<VarType> DEFINE_TYPES = Arrays.asList(VarType.Boolean, VarType.Int, VarType.Float);
+    private static final int NUM_DEFINES = DEFINE_NAMES.size();
+    private static final int BOOL_VAR = 0;
+    private static final int INT_VAR = 1;
+    private static final int FLOAT_VAR = 2;
+    private static final DefineList EMPTY = new DefineList(NUM_DEFINES);
+
+    @Test
+    public void testHashCollision() {
+        DefineList dl1 = new DefineList(64);
+        DefineList dl2 = new DefineList(64);
+        
+        // Try to cause a hash collision
+        // (since bit #32 is aliased to bit #1 in 32-bit ints)
+        dl1.set(0, 123);
+        dl1.set(32, 0);
+        
+        dl2.set(32, 0);
+        dl2.set(0, 123);
+        
+        assert dl1.hashCode() == dl2.hashCode();
+        assert dl1.equals(dl2);
+    }
+    
+    @Test
+    public void testGetSet() {
+        DefineList dl = new DefineList(NUM_DEFINES);
+        
+        assertFalse(dl.getBoolean(BOOL_VAR));
+        assertEquals(dl.getInt(INT_VAR), 0);
+        assertEquals(dl.getFloat(FLOAT_VAR), 0f, 0f);
+        
+        dl.set(BOOL_VAR, true);
+        dl.set(INT_VAR, -1);
+        dl.set(FLOAT_VAR, Float.NaN);
+        
+        assertTrue(dl.getBoolean(BOOL_VAR));
+        assertEquals(dl.getInt(INT_VAR), -1);
+        assertTrue(Float.isNaN(dl.getFloat(FLOAT_VAR)));
+    }
+    
+    private String generateSource(DefineList dl) {
+        StringBuilder sb = new StringBuilder();
+        dl.generateSource(sb, DEFINE_NAMES, DEFINE_TYPES);
+        return sb.toString();
+    }
+    
+    @Test
+    public void testSourceInitial() {
+        DefineList dl = new DefineList(NUM_DEFINES);
+        assert dl.hashCode() == 0;
+        assert generateSource(dl).equals("");
+    }
+    
+    @Test
+    public void testSourceBooleanDefine() {
+        DefineList dl = new DefineList(NUM_DEFINES);
+
+        dl.set(BOOL_VAR, true);
+        assert dl.hashCode() == 1;
+        assert generateSource(dl).equals("#define BOOL_VAR 1\n");
+        
+        dl.set(BOOL_VAR, false);
+        assert dl.hashCode() == 0;
+        assert generateSource(dl).equals("");
+    }
+    
+    @Test
+    public void testSourceIntDefine() {
+        DefineList dl = new DefineList(NUM_DEFINES);
+
+        int hashCodeWithInt = 1 << INT_VAR;
+        
+        dl.set(INT_VAR, 123);
+        assert dl.hashCode() == hashCodeWithInt;
+        assert generateSource(dl).equals("#define INT_VAR 123\n");
+        
+        dl.set(INT_VAR, 0);
+        assert dl.hashCode() == 0;
+        assert generateSource(dl).equals("");
+        
+        dl.set(INT_VAR, -99);
+        assert dl.hashCode() == hashCodeWithInt;
+        assert generateSource(dl).equals("#define INT_VAR -99\n");
+        
+        dl.set(INT_VAR, Integer.MAX_VALUE);
+        assert dl.hashCode() == hashCodeWithInt;
+        assert generateSource(dl).equals("#define INT_VAR 2147483647\n");
+    }
+    
+    @Test
+    public void testSourceFloatDefine() {
+        DefineList dl = new DefineList(NUM_DEFINES);
+
+        dl.set(FLOAT_VAR, 1f);
+        assert dl.hashCode() == (1 << FLOAT_VAR);
+        assert generateSource(dl).equals("#define FLOAT_VAR 1.0\n");
+        
+        dl.set(FLOAT_VAR, 0f);
+        assert dl.hashCode() == 0;
+        assert generateSource(dl).equals("");
+        
+        dl.set(FLOAT_VAR, -1f);
+        assert generateSource(dl).equals("#define FLOAT_VAR -1.0\n");
+        
+        dl.set(FLOAT_VAR, FastMath.FLT_EPSILON);
+        assert generateSource(dl).equals("#define FLOAT_VAR 1.1920929E-7\n");
+        
+        dl.set(FLOAT_VAR, FastMath.PI);
+        assert generateSource(dl).equals("#define FLOAT_VAR 3.1415927\n");
+        
+        try {
+            dl.set(FLOAT_VAR, Float.NaN);
+            generateSource(dl);
+            assert false;
+        } catch (IllegalArgumentException ex) { }
+        
+        try {
+            dl.set(FLOAT_VAR, Float.POSITIVE_INFINITY);
+            generateSource(dl);
+            assert false;
+        } catch (IllegalArgumentException ex) { }
+        
+        try {
+            dl.set(FLOAT_VAR, Float.NEGATIVE_INFINITY);
+            generateSource(dl);
+            assert false;
+        } catch (IllegalArgumentException ex) { }
+    }
+    
+    @Test
+    public void testEqualsAndHashCode() {
+        DefineList dl1 = new DefineList(NUM_DEFINES);
+        DefineList dl2 = new DefineList(NUM_DEFINES);
+        
+        assertTrue(dl1.hashCode() == 0);
+        assertEquals(dl1, dl2);
+        
+        dl1.set(BOOL_VAR, true);
+        
+        assertTrue(dl1.hashCode() == 1);
+        assertNotSame(dl1, dl2);
+        
+        dl2.set(BOOL_VAR, true);
+        
+        assertEquals(dl1, dl2);
+        
+        dl1.set(INT_VAR, 2);
+        
+        assertTrue(dl1.hashCode() == (1|2));
+        assertNotSame(dl1, dl2);
+        
+        dl2.set(INT_VAR, 2);
+        
+        assertEquals(dl1, dl2);
+        
+        dl1.set(BOOL_VAR, false);
+        
+        assertTrue(dl1.hashCode() == 2);
+        assertNotSame(dl1, dl2);
+    }
+    
+    @Test
+    public void testDeepClone() {
+        DefineList dl1 = new DefineList(NUM_DEFINES);
+        DefineList dl2 = dl1.deepClone();
+        
+        assertFalse(dl1 == dl2);
+        assertTrue(dl1.equals(dl2));
+        assertTrue(dl1.hashCode() == dl2.hashCode());
+        
+        dl1.set(BOOL_VAR, true);
+        dl2 = dl1.deepClone();
+        
+        assertTrue(dl1.equals(dl2));
+        assertTrue(dl1.hashCode() == dl2.hashCode());
+        
+        dl1.set(INT_VAR, 123);
+        
+        assertFalse(dl1.equals(dl2));
+        assertFalse(dl1.hashCode() == dl2.hashCode());
+        
+        dl2 = dl1.deepClone();
+        
+        assertTrue(dl1.equals(dl2));
+        assertTrue(dl1.hashCode() == dl2.hashCode());
+    }
+    
+    @Test
+    public void testGenerateSource() {
+        DefineList dl = new DefineList(NUM_DEFINES);
+        
+        assertEquals("", generateSource(dl));
+        
+        dl.set(BOOL_VAR, true);
+        
+        assertEquals("#define BOOL_VAR 1\n", generateSource(dl));
+        
+        dl.set(INT_VAR, 123);
+        
+        assertEquals("#define BOOL_VAR 1\n" + 
+                     "#define INT_VAR 123\n", generateSource(dl));
+        
+        dl.set(BOOL_VAR, false);
+        
+        assertEquals("#define INT_VAR 123\n", generateSource(dl));
+        
+        dl.set(BOOL_VAR, true);
+        
+        // should have predictable ordering based on defineId
+        assertEquals("#define BOOL_VAR 1\n" + 
+                     "#define INT_VAR 123\n", generateSource(dl));
+    }
+    
+    private static String doLookup(HashMap<DefineList, String> map, boolean boolVal, int intVal, float floatVal) {
+        DefineList dl = new DefineList(NUM_DEFINES);
+        dl.set(BOOL_VAR, boolVal);
+        dl.set(INT_VAR, intVal);
+        dl.set(FLOAT_VAR, floatVal);
+        return map.get(dl);
+    }
+    
+    @Test
+    public void testHashLookup() {
+        String STR_EMPTY          = "This is an empty define list";
+        String STR_INT            = "This define list has an int value";
+        String STR_BOOL           = "This define list just has boolean value set";
+        String STR_BOOL_INT       = "This define list has both a boolean and int value";
+        String STR_BOOL_INT_FLOAT = "This define list has a boolean, int, and float value";
+        
+        HashMap<DefineList, String> map = new HashMap<DefineList, String>();
+        
+        DefineList lookup = new DefineList(NUM_DEFINES);
+        
+        map.put(lookup.deepClone(), STR_EMPTY);
+        
+        lookup.set(BOOL_VAR, true);
+        map.put(lookup.deepClone(), STR_BOOL);
+        
+        lookup.set(BOOL_VAR, false);
+        lookup.set(INT_VAR, 123);
+        map.put(lookup.deepClone(), STR_INT);
+        
+        lookup.set(BOOL_VAR, true);
+        map.put(lookup.deepClone(), STR_BOOL_INT);
+        
+        lookup.set(FLOAT_VAR, FastMath.PI);
+        map.put(lookup.deepClone(), STR_BOOL_INT_FLOAT);
+        
+        assertEquals(doLookup(map, false, 0, 0f), STR_EMPTY);
+        assertEquals(doLookup(map, false, 123, 0f), STR_INT);
+        assertEquals(doLookup(map, true, 0, 0f), STR_BOOL);
+        assertEquals(doLookup(map, true, 123, 0f), STR_BOOL_INT);
+        assertEquals(doLookup(map, true, 123, FastMath.PI), STR_BOOL_INT_FLOAT);
+    }
+}

+ 37 - 35
jme3-core/src/main/java/com/jme3/effect/ParticleComparator.java → jme3-core/src/test/java/com/jme3/system/MockJmeSystemDelegate.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2015 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -29,48 +29,50 @@
  * 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.effect;
+package com.jme3.system;
 
-import com.jme3.renderer.Camera;
-import java.util.Comparator;
+import com.jme3.audio.AudioRenderer;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URL;
+import java.nio.ByteBuffer;
 
-@Deprecated
-class ParticleComparator implements Comparator<Particle> {
+public class MockJmeSystemDelegate extends JmeSystemDelegate {
 
-    private Camera cam;
+    @Override
+    public void writeImageFile(OutputStream outStream, String format, ByteBuffer imageData, int width, int height) throws IOException {
+    }
 
-    public void setCamera(Camera cam){
-        this.cam = cam;
+    @Override
+    public void showErrorDialog(String message) {
     }
 
-    public int compare(Particle p1, Particle p2) {
-        return 0; // unused
-        /*
-        if (p1.life <= 0 || p2.life <= 0)
-            return 0;
+    @Override
+    public boolean showSettingsDialog(AppSettings sourceSettings, boolean loadFromRegistry) {
+        return false;
+    }
 
-//        if (p1.life <= 0)
-//            return 1;
-//        else if (p2.life <= 0)
-//            return -1;
+    @Override
+    public URL getPlatformAssetConfigURL() {
+        return Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/General.cfg");
+    }
 
-        float d1 = p1.distToCam, d2 = p2.distToCam;
+    @Override
+    public JmeContext newContext(AppSettings settings, JmeContext.Type contextType) {
+        return null;
+    }
 
-        if (d1 == -1){
-            d1 = cam.distanceToNearPlane(p1.position);
-            p1.distToCam = d1;
-        }
-        if (d2 == -1){
-            d2 = cam.distanceToNearPlane(p2.position);
-            p2.distToCam = d2;
-        }
+    @Override
+    public AudioRenderer newAudioRenderer(AppSettings settings) {
+        return null;
+    }
+
+    @Override
+    public void initialize(AppSettings settings) {
+    }
 
-        if (d1 < d2)
-            return 1;
-        else if (d1 > d2)
-            return -1;
-        else
-            return 0;
-        */
+    @Override
+    public void showSoftKeyboard(boolean show) {
     }
-}
+    
+}

+ 21 - 21
jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ImageHeightmap.java → jme3-core/src/test/java/com/jme3/system/TestUtil.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2015 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -29,27 +29,27 @@
  * 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.terrain.heightmap;
+package com.jme3.system;
 
-/**
- * A heightmap that is built off an image.
- * If you want to be able to supply different Image types to 
- * ImageBaseHeightMapGrid, you need to implement this interface,
- * and have that class extend Abstract heightmap.
- * 
- * @author bowens
- * @deprecated
- */
-public interface ImageHeightmap {
+import com.jme3.asset.AssetConfig;
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.DesktopAssetManager;
+import com.jme3.renderer.RenderManager;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class TestUtil {
+    
+    static {
+        JmeSystem.setSystemDelegate(new MockJmeSystemDelegate());
+    }
     
-    /**
-     * Set the image to use for this heightmap
-     */
-    //public void setImage(Image image);
+    public static AssetManager createAssetManager() {
+        Logger.getLogger(AssetConfig.class.getName()).setLevel(Level.OFF);
+        return new DesktopAssetManager(true);
+    }
     
-    /**
-     * The BufferedImage.TYPE_ that is supported
-     * by this ImageHeightmap
-     */
-    //public int getSupportedImageType();
+    public static RenderManager createRenderManager() {
+        return new RenderManager(new NullRenderer());
+    }
 }

+ 6 - 0
jme3-core/src/test/resources/no-default-technique.j3md

@@ -0,0 +1,6 @@
+MaterialDef Test Material {
+    Technique Test {
+        VertexShader GLSL100 : test.vert
+        FragmentShader GLSL100 : test.frag
+    }
+}

+ 8 - 0
jme3-core/src/test/resources/no-shader-specified.j3md

@@ -0,0 +1,8 @@
+MaterialDef Test Material {
+    Technique A {
+    }
+    Technique B {
+        VertexShader GLSL100 : test.vert
+        FragmentShader GLSL100 : test.frag
+    }
+}

+ 10 - 0
jme3-core/src/test/resources/same-name-technique.j3md

@@ -0,0 +1,10 @@
+MaterialDef Test Material {
+    Technique Test {
+        VertexShader GLSL150 : test150.vert
+        FragmentShader GLSL150 : test150.frag
+    }
+    Technique Test {
+        VertexShader GLSL100 : test.vert
+        FragmentShader GLSL100 : test.frag
+    }
+}

+ 34 - 0
jme3-core/src/test/resources/testMatDef.j3md

@@ -0,0 +1,34 @@
+MaterialDef Simple {
+    MaterialParameters {
+        Color Color
+    }
+    Technique {
+        WorldParameters {
+            WorldViewProjectionMatrix
+        }
+        VertexShaderNodes {
+            ShaderNode CommonVert {
+                Definition : CommonVert : Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn
+                InputMappings {
+                    worldViewProjectionMatrix = WorldParam.WorldViewProjectionMatrix
+                    modelPosition = Global.position.xyz
+                }
+                OutputMappings {
+                    Global.position = projPosition
+                }
+            }
+        }
+        FragmentShaderNodes {
+            ShaderNode ColorMult {
+                Definition : ColorMult : Common/MatDefs/ShaderNodes/Basic/ColorMult.j3sn
+                InputMappings {
+                    color1 = MatParam.Color
+                    color2 = Global.color
+                }
+                OutputMappings {
+                    Global.color = outColor
+                }
+            }
+        }
+    }
+}

+ 0 - 1
jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java

@@ -504,7 +504,6 @@ public class TextureAtlas {
         geom.setMesh(mesh);
 
         Material mat = new Material(mgr, "Common/MatDefs/Light/Lighting.j3md");
-        mat.getAdditionalRenderState().setAlphaTest(true);
         Texture diffuseMap = atlas.getAtlasTexture("DiffuseMap");
         Texture normalMap = atlas.getAtlasTexture("NormalMap");
         Texture specularMap = atlas.getAtlasTexture("SpecularMap");

+ 12 - 12
jme3-core/src/tools/java/jme3tools/shadercheck/ShaderCheck.java

@@ -6,11 +6,12 @@ import com.jme3.asset.plugins.FileLocator;
 import com.jme3.material.MaterialDef;
 import com.jme3.material.TechniqueDef;
 import com.jme3.material.plugins.J3MLoader;
+import com.jme3.renderer.Caps;
 import com.jme3.shader.DefineList;
 import com.jme3.shader.Shader;
-import com.jme3.shader.ShaderKey;
 import com.jme3.shader.plugins.GLSLLoader;
 import com.jme3.system.JmeSystem;
+import java.util.EnumSet;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -33,23 +34,22 @@ public class ShaderCheck {
         assetManager.registerLoader(GLSLLoader.class, "vert", "frag","geom","tsctrl","tseval","glsllib");
     }
     
-    private static void checkMatDef(String matdefName){
+    private static void checkMatDef(String matdefName) {
         MaterialDef def = (MaterialDef) assetManager.loadAsset(matdefName);
-        for (TechniqueDef techDef : def.getDefaultTechniques()){
-            DefineList dl = new DefineList();
-            dl.addFrom(techDef.getShaderPresetDefines());
-            ShaderKey shaderKey = new ShaderKey(dl,techDef.getShaderProgramLanguages(),techDef.getShaderProgramNames());
-
-            Shader shader = assetManager.loadShader(shaderKey);
-
-            for (Validator validator : validators){
+        EnumSet<Caps> rendererCaps = EnumSet.noneOf(Caps.class);
+        rendererCaps.add(Caps.GLSL100);
+        for (TechniqueDef techDef : def.getTechniqueDefs(TechniqueDef.DEFAULT_TECHNIQUE_NAME)) {
+            DefineList defines = techDef.createDefineList();
+            Shader shader = techDef.getShader(assetManager, rendererCaps, defines);
+            for (Validator validator : validators) {
                 StringBuilder sb = new StringBuilder();
                 validator.validate(shader, sb);
-                System.out.println("==== Validator: " + validator.getName() + " " + 
-                                        validator.getInstalledVersion() + " ====");
+                System.out.println("==== Validator: " + validator.getName() + " "
+                        + validator.getInstalledVersion() + " ====");
                 System.out.println(sb.toString());
             }
         }
+        throw new UnsupportedOperationException();
     }
           
     public static void main(String[] args){

+ 18 - 24
jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java

@@ -43,6 +43,7 @@ import com.jme3.system.JmeContext.Type;
 import com.jme3.util.Screenshots;
 import java.awt.EventQueue;
 import java.awt.Graphics2D;
+import java.awt.GraphicsEnvironment;
 import java.awt.RenderingHints;
 import java.awt.geom.AffineTransform;
 import java.awt.image.AffineTransformOp;
@@ -116,12 +117,16 @@ public class JmeDesktopSystem extends JmeSystemDelegate {
 
     @Override
     public void showErrorDialog(String message) {
-        final String msg = message;
-        EventQueue.invokeLater(new Runnable() {
-            public void run() {
-                ErrorDialog.showDialog(msg);
-            }
-        });
+        if (!GraphicsEnvironment.isHeadless()) {
+            final String msg = message;
+            EventQueue.invokeLater(new Runnable() {
+                public void run() {
+                    ErrorDialog.showDialog(msg);
+                }
+            });
+        } else {
+            System.err.println("[JME ERROR] " + message);
+        }
     }
 
     @Override
@@ -129,6 +134,9 @@ public class JmeDesktopSystem extends JmeSystemDelegate {
         if (SwingUtilities.isEventDispatchThread()) {
             throw new IllegalStateException("Cannot run from EDT");
         }
+        if (GraphicsEnvironment.isHeadless()) {
+            throw new IllegalStateException("Cannot show dialog in headless environment");
+        }
 
         final AppSettings settings = new AppSettings(false);
         settings.copyFrom(sourceSettings);
@@ -333,27 +341,13 @@ public class JmeDesktopSystem extends JmeSystemDelegate {
         if (initialized) {
             return;
         }
-
         initialized = true;
-        try {
-            if (!lowPermissions) {
-                // can only modify logging settings
-                // if permissions are available
-//                JmeFormatter formatter = new JmeFormatter();
-//                Handler fileHandler = new FileHandler("jme.log");
-//                fileHandler.setFormatter(formatter);
-//                Logger.getLogger("").addHandler(fileHandler);
-//                Handler consoleHandler = new ConsoleHandler();
-//                consoleHandler.setFormatter(formatter);
-//                Logger.getLogger("").removeHandler(Logger.getLogger("").getHandlers()[0]);
-//                Logger.getLogger("").addHandler(consoleHandler);
+        logger.log(Level.INFO, getBuildInfo());
+        if (!lowPermissions) {
+            if (NativeLibraryLoader.isUsingNativeBullet()) {
+                NativeLibraryLoader.loadNativeLibrary("bulletjme", true);
             }
-//        } catch (IOException ex){
-//            logger.log(Level.SEVERE, "I/O Error while creating log file", ex);
-        } catch (SecurityException ex) {
-            logger.log(Level.SEVERE, "Security error in creating log file", ex);
         }
-        logger.log(Level.INFO, getBuildInfo());
     }
 
     @Override

+ 0 - 359
jme3-desktop/src/main/java/com/jme3/system/Natives.java

@@ -1,359 +0,0 @@
-/*
- * Copyright (c) 2009-2012 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.system;
-
-import java.io.*;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Helper class for extracting the natives (dll, so) from the jars.
- * This class should only be used internally.
- * 
- * @deprecated Use {@link NativeLibraryLoader} instead.
- */
-@Deprecated
-public final class Natives {
-
-    private static final Logger logger = Logger.getLogger(Natives.class.getName());
-    private static final byte[] buf = new byte[1024 * 100];
-    private static File extractionDirOverride = null;
-    private static File extractionDir = null;
-
-    public static void setExtractionDir(String name) {
-        extractionDirOverride = new File(name).getAbsoluteFile();
-    }
-
-    public static File getExtractionDir() {
-        if (extractionDirOverride != null) {
-            return extractionDirOverride;
-        }
-        if (extractionDir == null) {
-            File workingFolder = new File("").getAbsoluteFile();
-            if (!workingFolder.canWrite()) {
-                setStorageExtractionDir();
-            } else {
-                try {
-                    File file = new File(workingFolder.getAbsolutePath() + File.separator + ".jmetestwrite");
-                    file.createNewFile();
-                    file.delete();
-                    extractionDir = workingFolder;
-                } catch (Exception e) {
-                    setStorageExtractionDir();
-                }
-            }
-        }
-        return extractionDir;
-    }
-
-    private static void setStorageExtractionDir() {
-        logger.log(Level.WARNING, "Working directory is not writable. Using home directory instead.");
-        extractionDir = new File(JmeSystem.getStorageFolder(),
-                "natives_" + Integer.toHexString(computeNativesHash()));
-        if (!extractionDir.exists()) {
-            extractionDir.mkdir();
-        }
-    }
-
-    private static int computeNativesHash() {
-        URLConnection conn = null;
-        try {
-            String classpath = System.getProperty("java.class.path");
-            URL url = Thread.currentThread().getContextClassLoader().getResource("com/jme3/system/Natives.class");
-
-            StringBuilder sb = new StringBuilder(url.toString());
-            if (sb.indexOf("jar:") == 0) {
-                sb.delete(0, 4);
-                sb.delete(sb.indexOf("!"), sb.length());
-                sb.delete(sb.lastIndexOf("/") + 1, sb.length());
-            }
-            try {
-                url = new URL(sb.toString());
-            } catch (MalformedURLException ex) {
-                throw new UnsupportedOperationException(ex);
-            }
-
-            conn = url.openConnection();
-            int hash = classpath.hashCode() ^ (int) conn.getLastModified();
-            return hash;
-        } catch (IOException ex) {
-            throw new UnsupportedOperationException(ex);
-        } finally {
-            if (conn != null) {
-                try {
-                    conn.getInputStream().close();
-                    conn.getOutputStream().close();
-                } catch (IOException ex) { }
-            }
-        }
-    }
-
-    public static void extractNativeLib(String sysName, String name) throws IOException {
-        extractNativeLib(sysName, name, false, true);
-    }
-
-    public static void extractNativeLib(String sysName, String name, boolean load) throws IOException {
-        extractNativeLib(sysName, name, load, true);
-    }
-
-    public static void extractNativeLib(String sysName, String name, boolean load, boolean warning) throws IOException {
-        String fullname;
-        String path;
-        //XXX: hack to allow specifying the extension via supplying an extension in the name (e.g. "blah.dylib")
-        //     this is needed on osx where the openal.dylib always needs to be extracted as dylib
-        //     and never as jnilib, even if that is the platform JNI lib suffix (OpenAL is no JNI library)
-        if(!name.contains(".")){
-            // automatic name mapping
-            fullname = System.mapLibraryName(name);
-            path = "native/" + sysName + "/" + fullname;
-            //XXX: Hack to extract jnilib to dylib on OSX Java 1.7+
-            //     This assumes all jni libs for osx are stored as "jnilib" in the jar file.
-            //     It will be extracted with the name required for the platform.
-            //     At a later stage this should probably inverted so that dylib is the default name.
-            if(sysName.equals("macosx")){
-                path = path.replaceAll("dylib","jnilib");
-            }
-        } else{
-            fullname = name;
-            path = "native/" + sysName + "/" + fullname;
-        }
-
-        URL url = Thread.currentThread().getContextClassLoader().getResource(path);
-
-        // Also check for binaries that are not packed in folders by jme team, e.g. maven artifacts
-        if(url == null){
-            path = fullname;
-            if(sysName.equals("macosx") && !name.contains(".")){
-                path = path.replaceAll("dylib","jnilib");
-            }
-            url = Thread.currentThread().getContextClassLoader().getResource(path);
-        }
-        
-        if(url == null){
-            if (!warning) {
-                logger.log(Level.WARNING, "Cannot locate native library in classpath: {0}/{1}",
-                        new String[]{sysName, fullname});
-            }
-            // Still try loading the library without a filename, maybe it is
-            // accessible otherwise
-            try{
-                System.loadLibrary(name);
-            } catch(UnsatisfiedLinkError e){
-                if (!warning) {
-                    logger.log(Level.WARNING, "Cannot load native library: {0}/{1}",
-                            new String[]{sysName, fullname});
-                }
-            }
-            return;
-        }
-
-        URLConnection conn = url.openConnection();
-        InputStream in = conn.getInputStream();
-        File targetFile = new File(getExtractionDir(), fullname);
-        OutputStream out = null;
-        try {
-            if (targetFile.exists()) {
-                // OK, compare last modified date of this file to 
-                // file in jar
-                long targetLastModified = targetFile.lastModified();
-                long sourceLastModified = conn.getLastModified();
-
-                // Allow ~1 second range for OSes that only support low precision
-                if (targetLastModified + 1000 > sourceLastModified) {
-                    logger.log(Level.FINE, "Not copying library {0}. Latest already extracted.", fullname);
-                    return;
-                }
-            }
-
-            out = new FileOutputStream(targetFile);
-            int len;
-            while ((len = in.read(buf)) > 0) {
-                out.write(buf, 0, len);
-            }
-            in.close();
-            in = null;
-            out.close();
-            out = null;
-
-            // NOTE: On OSes that support "Date Created" property, 
-            // this will cause the last modified date to be lower than
-            // date created which makes no sense
-            targetFile.setLastModified(conn.getLastModified());
-        } catch (FileNotFoundException ex) {
-            if (ex.getMessage().contains("used by another process")) {
-                return;
-            }
-
-            throw ex;
-        } finally {
-            if (load) {
-                System.load(targetFile.getAbsolutePath());
-            }
-            if(in != null){
-                in.close();
-            }
-            if(out != null){
-                out.close();
-            }
-        }
-        logger.log(Level.FINE, "Copied {0} to {1}", new Object[]{fullname, targetFile});
-    }
-
-    protected static boolean isUsingNativeBullet() {
-        try {
-            Class clazz = Class.forName("com.jme3.bullet.util.NativeMeshUtil");
-            return clazz != null;
-        } catch (ClassNotFoundException ex) {
-            return false;
-        }
-    }
-
-    public static void extractNativeLibs(Platform platform, AppSettings settings) throws IOException {
-        if (true) {
-            throw new UnsupportedEncodingException("Now, why would you EVER want to do that?");
-        }
-        
-        String renderer = settings.getRenderer();
-        String audioRenderer = settings.getAudioRenderer();
-        boolean needLWJGL = false;
-        boolean needOAL = false;
-        boolean needJInput = false;
-        boolean needNativeBullet = isUsingNativeBullet();
-        
-        if (renderer != null) {
-            if (renderer.startsWith("LWJGL")) {
-                needLWJGL = true;
-            }
-        }
-        if (audioRenderer != null) {
-            if (audioRenderer.equals("LWJGL")) {
-                needLWJGL = true;
-                needOAL = true;
-            }
-        }
-        needJInput = settings.useJoysticks();
-
-        String libraryPath = getExtractionDir().toString();
-        if (needLWJGL) {
-            logger.log(Level.INFO, "Extraction Directory: {0}", getExtractionDir().toString());
-
-            // LWJGL supports this feature where
-            // it can load libraries from this path.
-            System.setProperty("org.lwjgl.librarypath", libraryPath);
-        }
-        if (needJInput) {
-            // AND Luckily enough JInput supports the same feature.
-            System.setProperty("net.java.games.input.librarypath", libraryPath);
-        }
-
-        switch (platform) {
-            case Windows64:
-                if (needLWJGL) {
-                    extractNativeLib("windows", "lwjgl64");
-                }
-                if (needOAL) {
-                    extractNativeLib("windows", "OpenAL64", true, false);
-                }
-                if (needJInput) {
-                    extractNativeLib("windows", "jinput-dx8_64");
-                    extractNativeLib("windows", "jinput-raw_64");
-                }
-                if (needNativeBullet) {
-                    extractNativeLib("windows", "bulletjme64", true, false);
-                }
-                break;
-            case Windows32:
-                if (needLWJGL) {
-                    extractNativeLib("windows", "lwjgl");
-                }
-                if (needOAL) {
-                    extractNativeLib("windows", "OpenAL32", true, false);
-                }
-                if (needJInput) {
-                    extractNativeLib("windows", "jinput-dx8");
-                    extractNativeLib("windows", "jinput-raw");
-                }
-                if (needNativeBullet) {
-                    extractNativeLib("windows", "bulletjme", true, false);
-                }
-                break;
-            case Linux64:
-                if (needLWJGL) {
-                    extractNativeLib("linux", "lwjgl64");
-                }
-                if (needJInput) {
-                    extractNativeLib("linux", "jinput-linux64");
-                }
-                if (needOAL) {
-                    extractNativeLib("linux", "openal64");
-                }
-                if (needNativeBullet) {
-                    extractNativeLib("linux", "bulletjme64", true, false);
-                }
-                break;
-            case Linux32:
-                if (needLWJGL) {
-                    extractNativeLib("linux", "lwjgl");
-                }
-                if (needJInput) {
-                    extractNativeLib("linux", "jinput-linux");
-                }
-                if (needOAL) {
-                    extractNativeLib("linux", "openal");
-                }
-                if (needNativeBullet) {
-                    extractNativeLib("linux", "bulletjme", true, false);
-                }
-                break;
-            case MacOSX_PPC32:
-            case MacOSX32:
-            case MacOSX_PPC64:
-            case MacOSX64:
-                if (needLWJGL) {
-                    extractNativeLib("macosx", "lwjgl");
-                }
-                if (needOAL){
-                    extractNativeLib("macosx", "openal.dylib");
-                }
-                if (needJInput) {
-                    extractNativeLib("macosx", "jinput-osx");
-                }
-                if (needNativeBullet) {
-                    extractNativeLib("macosx", "bulletjme", true, false);
-                }
-                break;
-        }
-    }
-}

+ 3 - 1
jme3-examples/build.gradle

@@ -12,7 +12,9 @@ task run(dependsOn: 'build', type:JavaExec) {
         jvmArgs "-Djava.awt.headless=true"
     }
 
-    systemProperty "java.util.logging.config.file", System.getProperty("java.util.logging.config.file")
+    if (System.properties['java.util.logging.config.file'] != null) {
+        systemProperty "java.util.logging.config.file", System.properties['java.util.logging.config.file']
+    }
 
     if( assertions  == "true" ){
         enableAssertions = true;

+ 2 - 1
jme3-examples/src/main/java/jme3test/animation/TestCameraMotionPath.java

@@ -139,7 +139,8 @@ public class TestCameraMotionPath extends SimpleApplication {
 
 
         rootNode.attachChild(teapot);
-        Geometry soil = new Geometry("soil", new Box(new Vector3f(0, -1.0f, 0), 50, 1, 50));
+        Geometry soil = new Geometry("soil", new Box(50, 1, 50));
+        soil.setLocalTranslation(0, -1, 0);
         soil.setMaterial(matSoil);
         rootNode.attachChild(soil);
         DirectionalLight light = new DirectionalLight();

+ 2 - 1
jme3-examples/src/main/java/jme3test/animation/TestCinematic.java

@@ -222,7 +222,8 @@ public class TestCinematic extends SimpleApplication {
         matSoil.setColor("Diffuse", ColorRGBA.Green);
         matSoil.setColor("Specular", ColorRGBA.Black);
 
-        Geometry soil = new Geometry("soil", new Box(new Vector3f(0, -6.0f, 0), 50, 1, 50));
+        Geometry soil = new Geometry("soil", new Box(50, 1, 50));
+        soil.setLocalTranslation(0, -6, 0);
         soil.setMaterial(matSoil);
         soil.setShadowMode(ShadowMode.Receive);
         rootNode.attachChild(soil);

+ 2 - 1
jme3-examples/src/main/java/jme3test/animation/TestMotionPath.java

@@ -136,7 +136,8 @@ public class TestMotionPath extends SimpleApplication {
 
 
         rootNode.attachChild(teapot);
-        Geometry soil = new Geometry("soil", new Box(new Vector3f(0, -1.0f, 0), 50, 1, 50));
+        Geometry soil = new Geometry("soil", new Box(50, 1, 50));
+        soil.setLocalTranslation(0, -1, 0);
         soil.setMaterial(matSoil);
 
         rootNode.attachChild(soil);

+ 1 - 1
jme3-examples/src/main/java/jme3test/app/TestAppStateLifeCycle.java

@@ -57,7 +57,7 @@ public class TestAppStateLifeCycle extends SimpleApplication {
 
     @Override
     public void simpleInitApp() {
-        Box b = new Box(Vector3f.ZERO, 1, 1, 1);
+        Box b = new Box(1, 1, 1);
         Geometry geom = new Geometry("Box", b);
         Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
         mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));

+ 1 - 1
jme3-examples/src/main/java/jme3test/app/TestBareBonesApp.java

@@ -56,7 +56,7 @@ public class TestBareBonesApp extends LegacyApplication {
         System.out.println("Initialize");
 
         // create a box
-        boxGeom = new Geometry("Box", new Box(Vector3f.ZERO, 2, 2, 2));
+        boxGeom = new Geometry("Box", new Box(2, 2, 2));
 
         // load some default material
         boxGeom.setMaterial(assetManager.loadMaterial("Interface/Logo/Logo.j3m"));

+ 0 - 156
jme3-examples/src/main/java/jme3test/app/TestNativeLoader.java

@@ -1,156 +0,0 @@
-/*
- * Copyright (c) 2009-2012 jMonkeyEngine
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * * Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- *
- * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
- *   may be used to endorse or promote products derived from this software
- *   without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package jme3test.app;
-
-import com.jme3.system.NativeLibraryLoader;
-import java.io.File;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Try to load some natives.
- * 
- * @author Kirill Vainer
- */
-public class TestNativeLoader {
-    
-    private static final File WORKING_FOLDER = new File(System.getProperty("user.dir"));
-    
-    private static void tryLoadLwjgl() {
-        NativeLibraryLoader.loadNativeLibrary("lwjgl", true);
-        System.out.println("Succeeded in loading LWJGL.\n\tVersion: " + 
-                           org.lwjgl.Sys.getVersion());
-    }
-    
-    private static void tryLoadJinput() {
-        NativeLibraryLoader.loadNativeLibrary("jinput", true);
-        NativeLibraryLoader.loadNativeLibrary("jinput-dx8", true);
-        
-        net.java.games.input.ControllerEnvironment ce =
-            net.java.games.input.ControllerEnvironment.getDefaultEnvironment();
-        if (ce.isSupported()) {
-            net.java.games.input.Controller[] c =
-                    ce.getControllers();
-            
-            System.out.println("Succeeded in loading JInput.\n\tVersion: " + 
-                            net.java.games.util.Version.getVersion());
-        }
-    }
-    
-    private static void tryLoadOpenAL() {
-        NativeLibraryLoader.loadNativeLibrary("openal", true);
-        
-        try {
-            org.lwjgl.openal.AL.create();
-            String renderer = org.lwjgl.openal.AL10.alGetString(org.lwjgl.openal.AL10.AL_RENDERER);
-            String vendor = org.lwjgl.openal.AL10.alGetString(org.lwjgl.openal.AL10.AL_VENDOR);
-            String version = org.lwjgl.openal.AL10.alGetString(org.lwjgl.openal.AL10.AL_VERSION);
-            System.out.println("Succeeded in loading OpenAL.");
-            System.out.println("\tVersion: " + version);
-        } catch (org.lwjgl.LWJGLException ex) {
-            throw new RuntimeException(ex);
-        } finally {
-            if (org.lwjgl.openal.AL.isCreated()) {
-                org.lwjgl.openal.AL.destroy();
-            }
-        }
-    }
-    
-    private static void tryLoadOpenGL() {
-        org.lwjgl.opengl.Pbuffer pb = null;
-        try {
-            pb = new org.lwjgl.opengl.Pbuffer(1, 1, new org.lwjgl.opengl.PixelFormat(0, 0, 0), null);
-            pb.makeCurrent();
-            String version = org.lwjgl.opengl.GL11.glGetString(org.lwjgl.opengl.GL11.GL_VERSION);
-            System.out.println("Succeeded in loading OpenGL.\n\tVersion: " + version);
-        } catch (org.lwjgl.LWJGLException ex) {
-            throw new RuntimeException(ex);
-        } finally {
-            if (pb != null) {
-                pb.destroy();
-            }
-        }
-    }
-    
-    private static void tryLoadBulletJme() {
-        if (NativeLibraryLoader.isUsingNativeBullet()) {
-            NativeLibraryLoader.loadNativeLibrary("bulletjme", true);
-
-            com.jme3.bullet.PhysicsSpace physSpace = new com.jme3.bullet.PhysicsSpace();
-
-            System.out.println("Succeeded in loading BulletJme.");
-        } else {
-            System.out.println("Native bullet not included. Cannot test loading.");
-        }
-    }
-    
-    private static void cleanupNativesFolder(File folder) {
-        for (File file : folder.listFiles()) {
-            String lowerCaseName = file.getName().toLowerCase();
-            if (lowerCaseName.contains("lwjgl") ||
-                lowerCaseName.contains("jinput") ||
-                lowerCaseName.contains("openal") ||
-                lowerCaseName.contains("bulletjme")) {
-                file.delete();
-            }
-        }
-    }
-    
-    public static void main(String[] args) {
-        Logger.getLogger("").getHandlers()[0].setLevel(Level.WARNING);
-        Logger.getLogger(NativeLibraryLoader.class.getName()).setLevel(Level.ALL);
-        
-        // Get a bit more output from LWJGL about issues.
-        // System.setProperty("org.lwjgl.util.Debug", "true");
-        
-        // Extracting to working folder is no brainer. 
-        // Choose some random path, then load LWJGL.
-        File customNativesFolder = new File("CustomNativesFolder");
-        customNativesFolder.mkdirs();
-        
-        if (!customNativesFolder.isDirectory()) {
-            throw new IllegalStateException("Failed to make custom natives folder");
-        }
-        
-        // Let's cleanup our folders first.
-        cleanupNativesFolder(WORKING_FOLDER);
-        cleanupNativesFolder(customNativesFolder);
-        
-        NativeLibraryLoader.setCustomExtractionFolder(customNativesFolder.getAbsolutePath());
-        
-        tryLoadLwjgl();
-        tryLoadOpenGL();
-        tryLoadOpenAL();
-        tryLoadJinput();
-        tryLoadBulletJme();
-    }
-}

+ 1 - 1
jme3-examples/src/main/java/jme3test/app/TestReleaseDirectMemory.java

@@ -50,7 +50,7 @@ public class TestReleaseDirectMemory extends SimpleApplication {
 
     @Override
     public void simpleInitApp() {
-        Box b = new Box(Vector3f.ZERO, 1, 1, 1);
+        Box b = new Box(1, 1, 1);
         Geometry geom = new Geometry("Box", b);
         Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
         mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));

+ 1 - 1
jme3-examples/src/main/java/jme3test/audio/TestAmbient.java

@@ -69,7 +69,7 @@ public class TestAmbient extends SimpleApplication {
     nature.play();
     
     // just a blue box to mark the spot
-    Box box1 = new Box(Vector3f.ZERO, .5f, .5f, .5f);
+    Box box1 = new Box(.5f, .5f, .5f);
     Geometry player = new Geometry("Player", box1);
     Material mat1 = new Material(assetManager,
             "Common/MatDefs/Misc/Unshaded.j3md");

Some files were not shown because too many files changed in this diff